Ruby/Tk (X,Y) Coordinate Question


#1

How do I get the same (x,y) coordinate for a given relative location in
the Ruby/tk application regardless of which widget I’m clicking on?

Background:

This has to do with a sudoku program I’m writing wherein I use the (x,y)
coordinates from mouse clicks to determine which sudoku cell I am in.
The cells are TkcRectangles and the numbers in (actually ‘on’) them are
TkLabels. Note that both the TkcRectangles and the TkLabels have the
same TkCanvas as their parent widget.

When I click in a cell (TkcRectangle) with no number (TkLabel) displayed
within it, I get an (x,y) coordinate relative to the canvas (TkCanvas),
but
when I click in the same rectangle after I display a number within it
(placed
over the TkcRectangle as a TkLabel) - and I click on that number, then
the (x,y) coordinate reported back to me is relative to the TkLabel’s
origin, not the TkCanvas’.

This breaks my cell lookup scheme!

Again, since both the rectangles and labels have the canvas a parent, I
don’t
see why the (x,y) coordinates shouldn’t be relative to the canvas in
both
cases. That is what I want anyway. Is there a way to make this this
happen?

Does Ruby/Tk implement Tk’s Virtual Desktop Coordinates (vrootx and
vrooty) that give coordinates relative to the virtual desktop?

Or does Ruby/Tk implement accessing the rootx and rooty coordinates
which would be the same regardless of which widget is under the mouse
click?

Or can I use a ‘lower’ method on the the TkLabel widget to put it below
the
TkcRectangles (without, of course, covering it up)?

Alas, and alack, I’ve browsed around the docs quite a bit. I also tried
a bunch
of stuff, but I just keep breaking the code that I had running fine!

I’m stuck!!!

The relevant code is straightforward.

Iâ??ve got a root and a canvas:
@root = TkRoot.new(:title=>“David’s Sudoku Treasury”,
:geometry=>“650x650”)
@canvas = TkCanvas.new(@root, :width=> @canvasWidth, :height=>
@canvasHeight)

A callback proc bind’ed to the mouse button:
@root.bind(‘Button’, proc{|b,x,y| mainCellClickedProc(b, x, y)},
“%b %x %y”)

A method that is called inside of the bind callback proc to report the
button clicked and the (x,y) coordinate of the clicked location:
def tell_it(btn, x, y)
puts “tell_it: Mouse button #{btn} clicked at coordinates
(#{x},#{y})”
end

A set of 81 rectangles:
def createAndDrawCells()
# Sizing and Location Variables
@cellSize = 40 if @cellSize == 0 # Cell size
@xr1c1 = 10 if @xr1c1 == 0 # Cell R1C1 X Upper Left Position
@yr1c1 = 10 if @yr1c1 == 0 # Cell R1C1 Y Upper Left Position
@color = @bgColor
# Cell Rows
9.times do |i|
xul = @xr1c1
yul = @yr1c1 + (i * @cellSize)
xlr = xul + @cellSize
ylr = yul + @cellSize
# Cell Columns
9.times do |j|
@boardRects [ (i * 9) + j ] = TkcRectangle.new(@canvas, xul,
yul, xlr, ylr,
:fill=>@color)
xul = xul + @cellSize
xlr = xlr + @cellSize
end
end
end

A set of 81 labels:
def createEntryNumberLabels()
81.times do |i|
@entryNumberLabels[i] = TkLabel.new(@canvas)
end
end


#2

You could try “%X %Y” to get coordinates relative to the top left corner
of the program.

There is also a “%W” that might be useful. If I remember correctly, it
returns the id of the widget the mouse clicked, but it might return the
reference.


#3

Actually, what I think you are looking for is the translation from
“real” coordinates to canvas coordinates for which you want:

canvas.canvasx and canvas.canvasy

also note that you can bind mouse clicks to Tkc objects, though be
careful that if the fill is none then TkcRect’s won’t register clicks
on the interior.

So, in summary, see TkCanvas.canvasx, TkCanvas.canvasy, and
TkcRectangle.bind
(actually part of the TkcTagAccess mixin).

Another command you may find useful is TkCanvas.find_overlapping which
can translate a mouse click to a TkcObject. Thus code such as:

obj_at_xy =
@canvas.find_overlapping(@canvas.canvasx(x),@canvas.canvasy(y),
@canvas.canvasx(x),@canvas.canvasy(y))

Here I believe x,y should be from %x,%y, that is, relative to the canvas
widget.

c.


#4

unknown wrote:

You could try “%X %Y” to get coordinates relative to the top left corner
of the program.

There is also a “%W” that might be useful. If I remember correctly, it
returns the id of the widget the mouse clicked, but it might return the
reference.

Um, just to try out these %X and %Y objects real quick, I added X, Y, %X
and %Y to my argument list in the callback proc in the same form as the
x, y, %x and %y objects to see what would happen, but got the following
“dynamic constant assignment” and “formal argument” errors:

C:\Documents and Settings\dbailey1\Desktop\Ruby My Dear>ruby
sudoku_treasury.rb
sudoku_treasury.rb:15: dynamic constant assignment
@root.bind(‘Button’, proc{|b,x,y,X,Y| mainCellClickedProc(b, x, y,
X, Y)}, “%b %x %y %X %Y”)
^
sudoku_treasury.rb:15: dynamic constant assignment
@root.bind(‘Button’, proc{|b,x,y,X,Y| mainCellClickedProc(b, x, y,
X, Y)}, “%b %x %y %X %Y”)
^
sudoku_treasury.rb:68: formal argument cannot be a constant
def mainCellClickedProc(btn,x,y,X,Y)
^
sudoku_treasury.rb:68: formal argument cannot be a constant
def mainCellClickedProc(btn,x,y,X,Y)
^
sudoku_treasury.rb:116: formal argument cannot be a constant
def tell_it(btn, x, y, X, Y)
^
sudoku_treasury.rb:116: formal argument cannot be a constant
def tell_it(btn, x, y, X, Y)
^
C:\Documents and Settings\dbailey1\Desktop\Ruby My Dear>

What are these %X and %Y objects, and where can I find some
documentation on them? But, more to the point, what does the “%”
leading character on these object names mean? I am looking through the
pickaxe book, but I’m not finding any reference to “%” outside of the
modulo operator. Please excuse my ignorance of Ruby syntax and
semantics!


#5

David B. wrote:

How do I get the same (x,y) coordinate for a given relative location in
the Ruby/tk application regardless of which widget I’m clicking on?

Background:

This has to do with a sudoku program I’m writing wherein I use the (x,y)
coordinates from mouse clicks to determine which sudoku cell I am in.
The cells are TkcRectangles and the numbers in (actually ‘on’) them are
TkLabels. Note that both the TkcRectangles and the TkLabels have the
same TkCanvas as their parent widget.

When I click in a cell (TkcRectangle) with no number (TkLabel) displayed
within it, I get an (x,y) coordinate relative to the canvas (TkCanvas),
but
when I click in the same rectangle after I display a number within it
(placed
over the TkcRectangle as a TkLabel) - and I click on that number, then
the (x,y) coordinate reported back to me is relative to the TkLabel’s
origin, not the TkCanvas’.

This breaks my cell lookup scheme!

Again, since both the rectangles and labels have the canvas a parent, I
don’t
see why the (x,y) coordinates shouldn’t be relative to the canvas in
both
cases. That is what I want anyway. Is there a way to make this this
happen?

Does Ruby/Tk implement Tk’s Virtual Desktop Coordinates (vrootx and
vrooty) that give coordinates relative to the virtual desktop?

Or does Ruby/Tk implement accessing the rootx and rooty coordinates
which would be the same regardless of which widget is under the mouse
click?

Or can I use a ‘lower’ method on the the TkLabel widget to put it below
the
TkcRectangles (without, of course, covering it up)?

Alas, and alack, I’ve browsed around the docs quite a bit. I also tried
a bunch
of stuff, but I just keep breaking the code that I had running fine!

I’m stuck!!!

The relevant code is straightforward.

Iâ??ve got a root and a canvas:
@root = TkRoot.new(:title=>“David’s Sudoku Treasury”,
:geometry=>“650x650”)
@canvas = TkCanvas.new(@root, :width=> @canvasWidth, :height=>
@canvasHeight)

A callback proc bind’ed to the mouse button:
@root.bind(‘Button’, proc{|b,x,y| mainCellClickedProc(b, x, y)},
“%b %x %y”)

A method that is called inside of the bind callback proc to report the
button clicked and the (x,y) coordinate of the clicked location:
def tell_it(btn, x, y)
puts “tell_it: Mouse button #{btn} clicked at coordinates
(#{x},#{y})”
end

A set of 81 rectangles:
def createAndDrawCells()
# Sizing and Location Variables
@cellSize = 40 if @cellSize == 0 # Cell size
@xr1c1 = 10 if @xr1c1 == 0 # Cell R1C1 X Upper Left Position
@yr1c1 = 10 if @yr1c1 == 0 # Cell R1C1 Y Upper Left Position
@color = @bgColor
# Cell Rows
9.times do |i|
xul = @xr1c1
yul = @yr1c1 + (i * @cellSize)
xlr = xul + @cellSize
ylr = yul + @cellSize
# Cell Columns
9.times do |j|
@boardRects [ (i * 9) + j ] = TkcRectangle.new(@canvas, xul,
yul, xlr, ylr,
:fill=>@color)
xul = xul + @cellSize
xlr = xlr + @cellSize
end
end
end

A set of 81 labels:
def createEntryNumberLabels()
81.times do |i|
@entryNumberLabels[i] = TkLabel.new(@canvas)
end
end

And the method to write the numbers (TkLabels) is:

def writeEntryNumber(num, x, y, fgCol, bgCol)
if @cellValues[calcCellFromXYCoords(x,y)] == nil
entryNumText = " "
else
entryNumText = num.to_s
end
@entryNumberLabels[calcCellFromXYCoords(x, y)].configure(
:text=> entryNumText,
:font=> ‘arial 16 bold’,
:foreground=> fgCol,
:background=> bgCol
).place(
:x=>x + 15,
:y=>y + 25,
:width=>20,
:height=>20
)
end


#6

unknown wrote:

You could try “%X %Y” to get coordinates relative to the top left corner
of the program.

There is also a “%W” that might be useful. If I remember correctly, it
returns the id of the widget the mouse clicked, but it might return the
reference.

Um, just to try out these %X and %Y objects real quick, I added X, Y, %X
and %Y to my argument list in the callback proc in the same form as the
x, y, %x and %y objects to see what would happen, but got the following
“dynamic constant assignment” and “formal argument” errors:

C:\Documents and Settings\dbailey1\Desktop\Ruby My Dear>ruby
sudoku_treasury.rb
sudoku_treasury.rb:15: dynamic constant assignment
@root.bind(‘Button’, proc{|b,x,y,X,Y| mainCellClickedProc(b, x, y,
X, Y)}, “%b %x %y %X %Y”)
^
sudoku_treasury.rb:15: dynamic constant assignment
@root.bind(‘Button’, proc{|b,x,y,X,Y| mainCellClickedProc(b, x, y,
X, Y)}, “%b %x %y %X %Y”)
^
sudoku_treasury.rb:68: formal argument cannot be a constant
def mainCellClickedProc(btn,x,y,X,Y)
^
sudoku_treasury.rb:68: formal argument cannot be a constant
def mainCellClickedProc(btn,x,y,X,Y)
^
sudoku_treasury.rb:116: formal argument cannot be a constant
def tell_it(btn, x, y, X, Y)
^
sudoku_treasury.rb:116: formal argument cannot be a constant
def tell_it(btn, x, y, X, Y)
^
C:\Documents and Settings\dbailey1\Desktop\Ruby My Dear>

What are these %X and %Y objects, and where can I find some
documentation on them? But, more to the point, what does the “%”
leading character on these object names mean? I am looking through the
pickaxe book, but I’m not finding any reference to “%” outside of the
modulo operator. Please excuse my “less than ideal” knowledge of Ruby
syntax and semantics!


#7

From: David B. removed_email_address@domain.invalid
Subject: Re: Ruby/Tk (X,Y) Coordinate Question
Date: Sat, 1 Apr 2006 23:12:00 +0900
Message-ID: removed_email_address@domain.invalid

Um, just to try out these %X and %Y objects real quick, I added X, Y, %X
and %Y to my argument list in the callback proc in the same form as the
x, y, %x and %y objects to see what would happen, but got the following
“dynamic constant assignment” and “formal argument” errors:

X and Y are constans. You must use local variables.
e.g.
@root.bind(‘Button’,
proc{|b,x,y,root_x,root_y|
mainCellClickedProc(b, x, y, root_x, root_y)
}, “%b %x %y %X %Y”)


#8

Chris A. wrote:

Actually, what I think you are looking for is the translation from
“real” coordinates to canvas coordinates for which you want:

canvas.canvasx and canvas.canvasy

also note that you can bind mouse clicks to Tkc objects, though be
careful that if the fill is none then TkcRect’s won’t register clicks
on the interior.

So, in summary, see TkCanvas.canvasx, TkCanvas.canvasy, and
TkcRectangle.bind
(actually part of the TkcTagAccess mixin).

Another command you may find useful is TkCanvas.find_overlapping which
can translate a mouse click to a TkcObject. Thus code such as:

obj_at_xy =
@canvas.find_overlapping(@canvas.canvasx(x),@canvas.canvasy(y),
@canvas.canvasx(x),@canvas.canvasy(y))

Here I believe x,y should be from %x,%y, that is, relative to the canvas
widget.

c.

Chris,

I’m researching these canvasx and canvasy methods now. I’ll let you
know how I make out. Thank you for your help.

David


#9

Um, just to try out these %X and %Y objects real quick, I added X, Y, %X
and %Y to my argument list in the callback proc in the same form as the
x, y, %x and %y objects to see what would happen, but got the following
“dynamic constant assignment” and “formal argument” errors:

Anything that begins with a capital letter is a constant, e.g., |b, x,
y,
X, Y|. You could replaces X and Y with xx and yy.

What are these %X and %Y objects, and where can I find some
documentation on them? But, more to the point, what does the “%”
leading character on these object names mean? I am looking through the
pickaxe book, but I’m not finding any reference to “%” outside of the
modulo operator. Please excuse my “less than ideal” knowledge of Ruby
syntax and semantics!

An event can pass parameters to a callback. The % tells Ruby/Tk which
event data you want passed to the callback.

%x %y mouse’s x and y coordinates in widget.
%X %Y mouse’s x and y coordinates in program window.
%W widget mouse is in

There are more variables, but I don’t have my notes with me.

A book on Tk or Perl/Tk should have the complete list.


#10

Hidetoshi NAGAI wrote:

From: David B. removed_email_address@domain.invalid
Subject: Re: Ruby/Tk (X,Y) Coordinate Question
Date: Sat, 1 Apr 2006 23:12:00 +0900
Message-ID: removed_email_address@domain.invalid

Um, just to try out these %X and %Y objects real quick, I added X, Y, %X
and %Y to my argument list in the callback proc in the same form as the
x, y, %x and %y objects to see what would happen, but got the following
“dynamic constant assignment” and “formal argument” errors:

X and Y are constans. You must use local variables.
e.g.
@root.bind(‘Button’,
proc{|b,x,y,root_x,root_y|
mainCellClickedProc(b, x, y, root_x, root_y)
}, “%b %x %y %X %Y”)

Hidetoshi,

At first I jumped with joy thinking that root_x and root_y would solve
my problem. Uh, it still may, but …

When I bind via this:

@root.bind(‘Button’, proc{|b,x,y,root_x,root_y|
mainCellClickedProc(b, x, y,root_x,root_y)}, “%b %x %y %X %Y”)

And report the mouse-click coordinates with this method:

def tell_it(btn, x, y,root_x, root_y)
puts “tell_it: Mouse button #{btn} clicked at coordinates
(#{x},#{y})” +
" and (#{root_x},#{root_y}))"
end

The root_x and root_y coordinates are relative to the top left corner of
the “screen”.

For example, here is the output of a mouse-click on the top left corner
of my application:

tell_it: Mouse button 1 clicked at coordinates (2,1) and (278,32))

Now, if I move the application window on the Windows XP screen and click
in the same relative position of the application’s window, here is the
output of that mouse-click:

tell_it: Mouse button 1 clicked at coordinates (2,1) and (153,80))

See? The (x,y) coordinates are the same, but the (root_x,root_y)
coordinates have changed to reflect that the application’s window has
moved on the Windows XP Desktop. I was hoping that “both” (x,y) and
(root_x,root_y) would be the same, but they are not.

You know, when I use the “place” geometry manager, the :x and :y objects
are relative to the “parent” widget’s origin, but the “event” :x and :y
objects seem to be relative to the “self” widget’s origin. Do you
agree?

In any case, it appears that I’m back to “square one”, so to speak.


#11

David B. wrote:

Chris A. wrote:

Actually, what I think you are looking for is the translation from
“real” coordinates to canvas coordinates for which you want:

canvas.canvasx and canvas.canvasy

also note that you can bind mouse clicks to Tkc objects, though be
careful that if the fill is none then TkcRect’s won’t register clicks
on the interior.

So, in summary, see TkCanvas.canvasx, TkCanvas.canvasy, and
TkcRectangle.bind
(actually part of the TkcTagAccess mixin).

Another command you may find useful is TkCanvas.find_overlapping which
can translate a mouse click to a TkcObject. Thus code such as:

obj_at_xy =
@canvas.find_overlapping(@canvas.canvasx(x),@canvas.canvasy(y),
@canvas.canvasx(x),@canvas.canvasy(y))

Here I believe x,y should be from %x,%y, that is, relative to the canvas
widget.

c.

Chris,

I’m researching these canvasx and canvasy methods now. I’ll let you
know how I make out. Thank you for your help.

David

Hey Chris and/or Fellow Rubyists,

I bought and flipped through “Mastering Perl/Tk” by Nancy Walsh and
Steve Lidie, which was purported to be “the” book on Perk/Tk. There was
scant info on canvasx, canvasy and find_overlapping. I read through
what was there, though.

Both canvasx and canvasy take a single argument, but when I supplied
various x and y arguments, the methods just returned the same argument
as a result! So, I guess I just don’t get it. Do you have a sense of
what the argument is supposed to be and what is supposed to happen to
it?

Also, where can I get some inormation about find_overlapping, real
quick.

I spent all day yesterday fighting with widget placement (see my earlier
post “Placing Sets of Ruby/Tk Widgets”), and that pooped me out. What
week-end brain power I have left, I will apply toward continuing to
explore this problem.

Thanks,

David

P.S. BTW, my use of root_x and root_y coordinates did not work because
they appear to be relative to the desktop and not the application
window. (Unless I did something drastically wrong!) So, if I calculate
an offset but the user then moves the application window, the offset
becomes no longer valid. Rats!


#12

canvasx and canvasy translate from coordinates relative to the canvas
widget to the canvas’s coordinate system. By default this will be the
same but if you have scrollbars or other transformations it can
differ.

I have never found a good use for root_x and root_y coordinates. Try
to avoid using coordinates at all when possible. Tk has a very rich
binding ability which means you can often do everything you want
without every worrying about actual coordinates of mouse clicks. The
exception is canvas work where you use %x %y fed through canvasx and
canvasy to get canvas coordinates.

Writing fully portable Tk code is difficult (though possibly easier
than with certain other GUI toolkits).

I use the packer (.pack) almost exclusively. Only when I’m trying to
do something really unusual do I use the placer (.place). The packer
does have a bit of a learning curve but is very powerful and easy once
learned properly. Don’t try to pack too many widgets into the same
parent. Subdivide into frame’s as needed.

While the perl/tk interface is closer to ruby/tk’s than Tcl/Tk’s the
Tcl/Tk documentation is the ultimate source for how the widgets
actually behave. This is especially true for an implementation like
Ruby/Tk which not only wraps compiled code but also wraps Tcl code.
You should find a copy either only or download (activestate.com has a
nice online copy). Use this documentation is “what does this do?” and
use the Perl/Tk documentation for “how do I use it?”.


#13

From: David B. removed_email_address@domain.invalid
Subject: Re: Ruby/Tk (X,Y) Coordinate Question
Date: Mon, 3 Apr 2006 02:44:50 +0900
Message-ID: removed_email_address@domain.invalid

The root_x and root_y coordinates are relative to the top left corner of
the “screen”.

Yes. You are right.
I talked about error messages which you got.

You know, when I use the “place” geometry manager, the :x and :y objects
are relative to the “parent” widget’s origin, but the “event” :x and :y
objects seem to be relative to the “self” widget’s origin. Do you
agree?

Try this.

require ‘tk’

TkLabel.new(:text=>‘click me!’, :padx=>10, :pady=>5, :relief=>:raised){
place(:x=>30, :y=>50)

bind(‘1’, proc{|x, y|
p info = self.place_info
p [[x, y], [info[‘x’], info[‘y’]], [info[‘x’] + x, info[‘y’] +
y]]
}, ‘%x %y’)
}

Tk.mainloop


#14

Hidetoshi NAGAI wrote:

Try this.

require ‘tk’

TkLabel.new(:text=>‘click me!’, :padx=>10, :pady=>5, :relief=>:raised){
place(:x=>30, :y=>50)

bind(‘1’, proc{|x, y|
p info = self.place_info
p [[x, y], [info[‘x’], info[‘y’]], [info[‘x’] + x, info[‘y’] +
y]]
}, ‘%x %y’)
}

Tk.mainloop

YES!!! YES!!! YES!!! That worked!

When I click in the sudoku cell (TkcRectangle) just above the number
(TkLabel), I get:

tell_it: Mouse button 1 clicked at coordinates (124,71)

When I click the number (TkLabel), I get:

{“relwidth”=>"", “relx”=>0, “x”=>115, “anchor”=>“nw”, “y”=>75,
“relheight”=>"", “rely”=>0, “height”=>20
, “width”=>20}
[[7, 4], [115, 75], [122, 79]]

So, the “[info[‘x’] + x, info[‘y’] + y]” is just what I needed. The
coordinates [122, 79] map to the proper cell! Thank you, Hidetoshi.
Thank you very much!

I now (or will soon, after I integrate this technique) have a playable
sudoku game! (:>)

My development plan was to proceed in three steps. Step 1 was to draw
the board, outline any clicked cell with the left-click, and highlight
the cell’s constraining row, columnn and sub-grid with the the
right-click. Step 2 was to enter numbers constrained by the rules of
sudoku, generate a history of the moves, and to implement backing up
through the history of the moves.

Completion of steps 1 and 2 would give me a “playable” sudoku game. I
am now at this point, thanks to you, my friend!

Step 3 will be to implement inputting a game’s initial configuration
from a file, generating games of varying difficulty, and solving the
game via a set of hints that allow progressively advanced solution
strategies, starting with basic ones such as ‘intersections’ and ‘forced
moves’, and up through the use of advanced techniques such as ‘x-wing’
and ‘unique rectangles’.

I realize that an argument can be made for completing the logic first
and doing the GUI last, but I figured that this way I could acquire more
Ruby syntax and semantics up front. Learning R., after all, was the
reason why I started this this project to begin with.

So, I thank you again. When the code is a bit more robust, I’ll send
you a copy.