How to bind canvas events to tags?

J

Josef Wolf

Hello,

In Perl/Tk, I can use

$canv->bind("foobar", '<1>' => sub {
xxxx;
});

to bind a canvas event to all items with the "foobar" tag. This is a very
powerful construct, so I'd like to use it in ruby, too. But ruby's
canvas.bind method seems to accept only two arguments: the event and the
proc to be called. How would I do the same in ruby?

BTW: How do I find out which arguments are accepted by Ruby/Tk methods?
There seem to be no man pages and the "Translating from Perl/Tk
Documentation" in the pragmatic programmers book is not very helpful,
also.
 
J

Joel VanderWerf

Josef said:
$canv->bind("foobar", '<1>' => sub {

canvas.itembind(tag, '1', '%x %y') do |x, y|

In this case, we're binding mouse single click events (I assume that's
the same as perl's '<1>') and we're grabbing the pointer location into
block args x and y.
 
J

Josef Wolf

canvas.itembind(tag, '1', '%x %y') do |x, y|

In this case, we're binding mouse single click events (I assume that's the
same as perl's '<1>') and we're grabbing the pointer location into block
args x and y.

Yeah, that works. Thanks!

BTW: do you have a description of the "%x %y" formatting specification?
 
J

Josef Wolf


Ough. Every time I think I get closer (remember? I am trying to implement
a zoomable canvas), I find there is a lot more work to do :-()

My first attempt to adopt event handling to the zoom operation was like
this:

class TkEvent::Event
# FIXME: need a better name than "nonzoomed". Recommendations?
attr_accessor :x_nonzoomed, :y_nonzoomed
end

class TkZoomCanvas < TkCanvas
def itembind(tag, ev, cb)
super(tag, ev, proc{ |e|
if e.x then e.x_nonzoomed = e.x/@zoom ; end
if e.y then e.y_nonzoomed = e.y/@zoom ; end
cb.call e
})
end

def bind(ev, cb)
# looks similar to itembind
end
end

Now, itembind (and bind?) seem to accept various forms:

1. canvas.itembind("mytag", "1", proc{|e| ... })
This form works fine with above code

2. canvas.itembind("mytag", "1", "%x %y") {|x, y| ... }
In this case, I will have to build the argument list. Seems to be a
tedious task. Maybe there already exist a Tk method I could use to
build that list?

Are this the only two existing forms?
 
J

Joel VanderWerf

Josef said:
Ough. Every time I think I get closer (remember? I am trying to implement
a zoomable canvas), I find there is a lot more work to do :-()

Do you need to exactly replicate the TkCanvas interface in the zoomable
canvas? Why not get creative and develop a class with a new (possibly
better) interface? (And maybe use delegation rather than inheritance.)
The TkCanvas interface is close to Tcl/Tk's canvas, which is great for
documentation and porting, but it's not necessarily very ruby-like.
 
J

Josef Wolf

Do you need to exactly replicate the TkCanvas interface in the zoomable
canvas? Why not get creative and develop a class with a new (possibly
better) interface? (And maybe use delegation rather than inheritance.) The
TkCanvas interface is close to Tcl/Tk's canvas, which is great for
documentation and porting, but it's not necessarily very ruby-like.

Thanks for your suggestion (and sorry for my late reply).

Well, as you might have already guessed, I'm very new to ruby, so I don't
yet have a good feeling about what is "very ruby like". I'd love to see
suggestions.

For the particular topic about the binding, after some thought, I've come
to the conclusion that extending the format string is not the best idea.
The format string consists of one-letter formats. I'd pollute this
one-letter-namespace if I would introduce my own letters. Thus, I decided
to let the base class do the job if a string is passed.

Attached is my current implementation of the zoomable+scrollable canvas.
It works fine, AFAICS. I'd love to hear any suggestions about how to make
it more ruby-like.


#!/usr/bin/ruby

require 'tk'

class TkEvent::Event
attr_accessor :x_nonzoom, :y_nonzoom
end

class TkScrolledCanvas < TkCanvas
include TkComposite
attr_reader :zoom

def initialize_composite(keys={})
@zoom = 1.0

@h_scr = TkScrollbar.new(@frame)
@v_scr = TkScrollbar.new(@frame)

@canvas = TkCanvas.new(@frame)
@path = @canvas.path

@canvas.xscrollbar(@h_scr)
@canvas.yscrollbar(@v_scr)

TkGrid.rowconfigure(@frame, 0, :weight=>1, :minsize=>0)
TkGrid.columnconfigure(@frame, 0, :weight=>1, :minsize=>0)

@canvas.grid:)row=>0, :column=>0, :sticky=>'news')
@h_scr.grid:)row=>1, :column=>0, :sticky=>'ew')
@v_scr.grid:)row=>0, :column=>1, :sticky=>'ns')

delegate('DEFAULT', @canvas)
delegate('background', @frame, @h_scr, @v_scr)
delegate('activeforeground', @h_scr, @v_scr)
delegate('troughcolor', @h_scr, @v_scr)
delegate('repeatdelay', @h_scr, @v_scr)
delegate('repeatinterval', @h_scr, @v_scr)
delegate('borderwidth', @frame)
delegate('relief', @frame)

delegate_alias('canvasborderwidth', 'borderwidth', @canvas)
delegate_alias('canvasrelief', 'relief', @canvas)

delegate_alias('scrollbarborderwidth', 'borderwidth', @h_scr, @v_scr)
delegate_alias('scrollbarrelief', 'relief', @h_scr, @v_scr)

configure(keys) unless keys.empty?
end

def zoom_by zf
zf = Float(zf)
@zoom *= zf

vf = (1 - 1/zf) / 2

x0, x1 = xview ; xf = x0 + vf * (x1-x0)
y0, y1 = yview ; yf = y0 + vf * (y1-y0)

scale 'all', 0, 0, zf, zf
configure :scrollregion => bbox("all")

xview "moveto", xf
yview "moveto", yf
end

def zoom_to z
zoom_by(z/@zoom)
end

def bind(ev, cb)
if cb.class == String
super
else
super(ev, proc{ |e| process_event(e, cb) })
end
end

def itembind(tag, ev, cb)
if cb.class == String
super
else
super(tag, ev, proc{ |e| process_event(e, cb) })
end
end

def coords(tag, *args)
newargs = adjust_coords(@zoom, args)
ret = super(tag, *newargs)
return ret unless ret.class == Array
ret.collect { |v| v / @zoom }
end

def move(tag, x, y)
super(tag, x*@zoom, y*@zoom)
end

def create(type, *args)
newargs = adjust_coords(@zoom, args)
super(type, *newargs)
end

private

def process_event(e, cb)
if e.x then e.x_nonzoom=e.x/@zoom ; end
if e.y then e.y_nonzoom=e.y/@zoom ; end
cb.call e
end

def adjust_coords(mul, args)
args.collect do |arg|
arg.class == Array ? arg.collect { |v| v * mul } : arg
end
end
end

class TkcItem
alias orig_initialize initialize

def initialize(parent, *args)
if parent.class == TkScrolledCanvas
zoom = parent.zoom
newargs = args.collect do |arg|
arg.class == Array ? arg.collect { |v| v * zoom } : arg
end
else
newargs = args
end
orig_initialize parent, *newargs
end

def bind(ev, cb)
super(ev, proc{ |e|
if @parent.class == TkScrolledCanvas
zoom = @parent.zoom
if e.x then e.x_nonzoom=e.x/zoom ; end
if e.y then e.y_nonzoom=e.y/zoom ; end
cb.call e
end
})
end
end
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top