From: Josef Wolf Date: 2009-09-09T04:20:12+09:00 Subject: Re: Zoomable TkCanvas? On Wed, Sep 09, 2009 at 02:05:34AM +0900, Joel VanderWerf wrote: > Josef Wolf wrote: >> On Tue, Sep 08, 2009 at 07:35:31AM +0900, Joel VanderWerf wrote: >>> Josef Wolf wrote: >>>> - maintain original coordinates at all zoom factors. Thus even after >>>> zoom operations, events as well as querying/moving/adding items are >>>> done as if the canvas is at zoom factor 1.0. So zoom is handled >>>> completely transparent to the user of the module.. >>> Yep, tkar does that too--object coordinates are independent of zoom >>> level. TkCanvas coordinates are hidden in the abstraction. >> I see zoom_by does the scaling, adjusts the scrollregion and updates the >> view. IMHO, to keep coordinates independent from zoom level, you would need >> to intercept query/movement/creation of the items. Without that >> >> TkcRectangle.new(canvas, [100,100], [300, 200]) >> canvas.zoom_by(2.0) >> TkcRectangle.new(canvas, [100,100], [300, 200]) >> >> would result in two rectangles with different sizes. > [ ... ] > I think a ruby port of the perl Tk::AbstractCanvas would be useful, but in > a different way from tkar. [ ... ] So OK. I thought, although I'm a complete newbie to ruby, I'd try to roll my own. At least, that would result in a good exercise. So I started by stealing the basics for a scrolled canvas from http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-talk/122597?122482-123428 and applying this patch: --- lib/scrolledcanvas.rb.orig +++ lib/scrolledcanvas.rb @@ -6,6 +6,8 @@ include TkComposite def initialize_composite(keys={}) + @zoom = 1.0 # need this for the zoom_by method + @h_scr = TkScrollbar.new(@frame) @v_scr = TkScrollbar.new(@frame) @@ -23,7 +25,7 @@ @v_scr.grid(:row=>0, :column=>1, :sticky=>'ns') delegate('DEFAULT', @canvas) - delegate('background', @text, @h_scr, @v_scr) + delegate('background', @frame, @h_scr, @v_scr) # looked like a typo delegate('activeforeground', @h_scr, @v_scr) delegate('troughcolor', @h_scr, @v_scr) delegate('repeatdelay', @h_scr, @v_scr) Then, I copied the zoon_by, xview and yview methods from your tkar package and commented the call to adjust_scrollregion to avoid access to the uninitialized @bounds array. So at this stage, I have a canvas that can be scrolled and zoomed. Fine. But how do I override the methods to create the items? In Perl/Tk, that would be easy, since item creation is done via canvas methods. But in Ruby/Tk, items are created via their own classes (e.g. TkcLine.new(args) or something). There don't seem to exist methods in the Canvas class to create items, which could easily be overridden. Any hints? PS: here's the current state of affairs: #!/usr/bin/env ruby require 'tk' class TkScrolledCanvas < TkCanvas include TkComposite 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', @text, @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 adjust_scrollregion xview "moveto", xf yview "moveto", yf end def adjust_scrollregion # configure :scrollregion => @bounds.map {|u|u*@zoom} ## if all of canvas can be shown, hide the scroll bars end def xview(mode=nil, *args) if mode and mode == "scroll" and @follow_xdelta number, what = args x_pre, = xview r = super(mode, *args) x_post, = xview x0,y0,x1,y1 = @bounds @follow_xdelta += (x_post - x_pre) * (x1-x0) r elsif not mode super() else super(mode, *args) end end def yview(mode=nil, *args) if mode and mode == "scroll" and @follow_ydelta number, what = args y_pre, = yview r = super(mode, *args) y_post, = yview x0,y0,x1,y1 = @bounds @follow_ydelta += (y_post - y_pre) * (y1-y0) r elsif not mode super() else super(mode, *args) end end end root = TkRoot.new { title "zoomcanvas" } c = TkScrolledCanvas.new(:scrollregion=>[0,0,500,400], :relief=>"sunken").pack(:expand=>1,:fill=>"both") TkcRectangle.new(c, [100,100], [300, 200]) c.bind("1", proc{|e| TkcRectangle.new(c, [100,100], [300, 200]) }) root.bind("z") { c.zoom_by(1.5) } root.bind("Z") { c.zoom_by(1/1.5) } Tk.mainloop