On Mon, Sep 14, 2009 at 03:41:34AM +0900, Joel VanderWerf wrote: > Josef Wolf wrote: >> 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. 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