How to properly bind context menus to canvas in Ruby/Tk?

Discussion in 'Ruby' started by Josef Wolf, Sep 3, 2006.

  1. Josef Wolf

    Josef Wolf Guest

    Hello!

    I am trying to bind context-menus to a Tk canvas. The canvas itself should
    have a context menu, giving the ability to create new objects. In addition,
    every object should have its own context menu so that the object can be
    modified/deleted. This is what I'm doing:

    #! /usr/bin/ruby
    require 'tk'
    $-w = 1

    root = TkRoot.new
    canvas = TkCanvas.new.pack

    # This is the context menu for the canvas to create new objects
    #
    menu=TkMenu.new
    menu.add('command', 'label'=>'New Foo', 'command'=>proc{p "new foo"})
    menu.add('command', 'label'=>'New Bar', 'command'=>proc{p "new bar"})

    # canvas binding
    canvas.bind("Button-3") { |e| evt=e; menu.popup(e.x_root, e.y_root) }

    # Now create a new object and bind to it a context menu to modify/delete
    # the object
    #
    rect = TkcRectangle.new(canvas, 0, 0, 50, 50, "fill"=>"white")
    menu=TkMenu.new
    menu.add('command', 'label'=>'Edit', 'command'=>proc{p "edit"})
    menu.add('command', 'label'=>'Delete', 'command'=>proc{p "del"})

    # object binding
    canvas.itembind(rect, "Button-3") { |e| menu.popup(e.x_root, e.y_root) }

    Tk.mainloop

    Unfortunately, this don't work as desired. As soon as the binding to the
    object is done, the canvas binding seems to be overridden. Even when
    right-klicking on empty space in the canvas, the object's menu is invoked.

    Any ideas what I am doing wrong here?
     
    Josef Wolf, Sep 3, 2006
    #1
    1. Advertising

  2. Josef Wolf

    Josef Wolf Guest

    On Mon, Sep 04, 2006 at 12:45:34AM +0900, Paul Lutus wrote:

    Thanks for your reply, Paul!

    > > Unfortunately, this don't work as desired. As soon as the binding to the
    > > object is done, the canvas binding seems to be overridden. Even when
    > > right-klicking on empty space in the canvas, the object's menu is invoked.
    > >
    > > Any ideas what I am doing wrong here?

    >
    > First, rename the second menu something other than "menu" You have already
    > used that name, and this multiple use is causing you a lot of confusion.


    I admit that this is bad style :) I've made copy/paste. In the real
    program, the creation/binding of the menus is done in different methods,
    thus they are actually two different variables (beacuse they are local
    to their method)

    > Second, after a bit of experimentation, I offer this educated guess. The
    > rectangle object cannot process mouse events, so it cannot invoke a context
    > menu different from that bound to the canvas.


    AFAIK, the rectangle is no real object in its own right. The canvas
    handles everything for it. That's the reason why its binding is done
    with canvas.itembind() instead of rect.bind(). IMHO, it is very
    important to understand this difference.

    > It is apparent that the canvas is receiving all mouse events, not the
    > rectangle,


    Exactly

    > and in order for the rectangle to launch its own context menu,
    > it would have to process mouse events independently. AFAICS it can't.


    The canvas handles those events. But for some reason the canvas fails
    to differentiate.

    > I want to emphasize this is just a guess, and I don't use the 'Tk' library
    > because it is too poorly documented.


    It's poorly documented, but it works great once you figure out how to
    use it :-(

    Especially the canvas widget is pure black magic. The concept of
    attaching arbitrary tags to items combined to the ability to attach
    bindings to the tags is very powerfull.

    > Have you considered using something other than 'Tk'?


    I'm not aware of any usable alternatives. What would you suggest?
     
    Josef Wolf, Sep 4, 2006
    #2
    1. Advertising

  3. On Sep 3, 2006, at 10:40 AM, Josef Wolf wrote:

    > Hello!
    >
    > I am trying to bind context-menus to a Tk canvas. The canvas
    > itself should
    > have a context menu, giving the ability to create new objects. In
    > addition,
    > every object should have its own context menu so that the object
    > can be
    > modified/deleted. This is what I'm doing:
    >
    > #! /usr/bin/ruby
    > require 'tk'
    > $-w = 1
    >
    > root = TkRoot.new
    > canvas = TkCanvas.new.pack
    >
    > # This is the context menu for the canvas to create new objects
    > #
    > menu=TkMenu.new
    > menu.add('command', 'label'=>'New Foo', 'command'=>proc{p "new
    > foo"})
    > menu.add('command', 'label'=>'New Bar', 'command'=>proc{p "new
    > bar"})
    >
    > # canvas binding
    > canvas.bind("Button-3") { |e| evt=e; menu.popup(e.x_root,
    > e.y_root) }
    >
    > # Now create a new object and bind to it a context menu to modify/
    > delete
    > # the object
    > #
    > rect = TkcRectangle.new(canvas, 0, 0, 50, 50, "fill"=>"white")
    > menu=TkMenu.new
    > menu.add('command', 'label'=>'Edit', 'command'=>proc{p "edit"})
    > menu.add('command', 'label'=>'Delete', 'command'=>proc{p "del"})
    >
    > # object binding
    > canvas.itembind(rect, "Button-3") { |e| menu.popup(e.x_root,
    > e.y_root) }
    >
    > Tk.mainloop
    >
    > Unfortunately, this don't work as desired. As soon as the binding
    > to the
    > object is done, the canvas binding seems to be overridden. Even when
    > right-klicking on empty space in the canvas, the object's menu is
    > invoked.
    >
    > Any ideas what I am doing wrong here?


    I tried running your code on my system which is OS X 10.4.7 running
    Ruby 1.8.2, and I don't see what you report. What I see is following:

    * Right-clicking away from the the rectangle, the canvas' contextual
    menu pops-up.
    * Right-clicking on the rectangle.
    ** The rectangle's contextual menu pops-up.
    ** When the rectangle's menu is dismissed, the canvas'
    contextual menu pops-up.

    With a small tweak, I was able to keep the canvas' contextual menu
    from popping up when the the rectangle is right-clicked. So what I
    have now running on my system works as you expected.

    Regards, Morton

    P.S. Just in case you want to compare what I am running with your own
    code.
    <code>
    #! /usr/bin/ruby -w
    # Date: September 5, 2006
    #
    # Contextual menus on a canvas

    require 'tk'

    DEBUG = []
    RIGHT_BUTTON = "Button-2" # On OS X right button is Button-2.

    begin
    root = TkRoot.new {title 'Ruby Tk'}
    canvas = TkCanvas.new.pack
    handled_by_rect = false

    # This is the context menu for the canvas to create new objects
    menu1=TkMenu.new
    menu1.add('command', 'label'=>'New Foo', 'command'=>proc{p "new
    foo"})
    menu1.add('command', 'label'=>'New Bar', 'command'=>proc{p "new
    bar"})

    # Canvas binding
    canvas.bind(RIGHT_BUTTON) do |e|
    if handled_by_rect
    handled_by_rect = false
    else
    menu1.popup(e.x_root, e.y_root)
    end
    end

    # Now create a new object and bind to it a context menu to modify/
    delete
    # the object
    rect = TkcRectangle.new(canvas, 50, 50, 100, 100, "fill"=>"white")
    menu2=TkMenu.new
    menu2.add('command', 'label'=>'Edit', 'command'=>proc{p "edit"})
    menu2.add('command', 'label'=>'Delete', 'command'=>proc{p "del"})

    # Rectangle binding
    canvas.itembind(rect, RIGHT_BUTTON) do |e|
    menu2.popup(e.x_root, e.y_root)
    handled_by_rect = true
    end

    # Set initial window geometry; i.e., size and placement.
    win_w, win_h = 300, 200
    # root.minsize(win_w, win_h)
    win_lf = (root.winfo_screenwidth - win_w) / 2
    root.geometry("#{win_w}x#{win_h}+#{win_lf}+50")

    # Make Cmnd+Q work as expected on OS X.
    root.bind('Command-q') {Tk.root.destroy}

    Tk.mainloop
    ensure
    puts DEBUG unless DEBUG.empty?
    end
    </code>
     
    Morton Goldberg, Sep 5, 2006
    #3
  4. Josef Wolf

    Josef Wolf Guest

    On Tue, Sep 05, 2006 at 08:43:01PM +0900, Morton Goldberg wrote:

    Thanks for the reply, Morton!

    > I tried running your code on my system which is OS X 10.4.7 running
    > Ruby 1.8.2, and I don't see what you report. What I see is following:
    >
    > * Right-clicking away from the the rectangle, the canvas' contextual
    > menu pops-up.
    > * Right-clicking on the rectangle.
    > ** The rectangle's contextual menu pops-up.
    > ** When the rectangle's menu is dismissed, the canvas'
    > contextual menu pops-up.


    How do you "dismiss" the menu? Whatever I do, I never get the second menu.

    > With a small tweak, I was able to keep the canvas' contextual menu
    > from popping up when the the rectangle is right-clicked. So what I
    > have now running on my system works as you expected.


    Your handled_by_rect flag seems to do the trick. But this would be a
    global flag in my case since the menus are created from different
    classes. :-( I would like to avoid such a global flag.

    The Tk::Bind manpage mentions that processing of matching callbacks can
    be controlled by "return" or calling "Tk->break" inside the callback.
    That is, the rectangle's callback should call Tk->break. But none of
    the involved classes seem to have a break method. And returning
    false/true/nil/1/0 don't have any effect, too. Any ideas?
     
    Josef Wolf, Sep 5, 2006
    #4
  5. On Sep 5, 2006, at 2:00 PM, Josef Wolf wrote:

    > On Tue, Sep 05, 2006 at 08:43:01PM +0900, Morton Goldberg wrote:
    >
    > Thanks for the reply, Morton!
    >
    >> I tried running your code on my system which is OS X 10.4.7 running
    >> Ruby 1.8.2, and I don't see what you report. What I see is following:
    >>
    >> * Right-clicking away from the the rectangle, the canvas' contextual
    >> menu pops-up.
    >> * Right-clicking on the rectangle.
    >> ** The rectangle's contextual menu pops-up.
    >> ** When the rectangle's menu is dismissed, the canvas'
    >> contextual menu pops-up.

    >
    > How do you "dismiss" the menu? Whatever I do, I never get the
    > second menu.


    By left clicking -- either on or off the menu. Which menu don't you
    get, the canvas' or the rect's?

    >> With a small tweak, I was able to keep the canvas' contextual menu
    >> from popping up when the the rectangle is right-clicked. So what I
    >> have now running on my system works as you expected.

    >
    > Your handled_by_rect flag seems to do the trick. But this would be a
    > global flag in my case since the menus are created from different
    > classes. :-( I would like to avoid such a global flag.


    WeLL, I try to avoid globals, too, but sometimes a global is the
    simplest solution. On the other hand you could create a class, say
    CanvasFlag, to manage the flag -- creating a class will solve most
    Ruby design problems ;)

    > The Tk::Bind manpage mentions that processing of matching callbacks
    > can
    > be controlled by "return" or calling "Tk->break" inside the callback.
    > That is, the rectangle's callback should call Tk->break. But none of
    > the involved classes seem to have a break method. And returning
    > false/true/nil/1/0 don't have any effect, too. Any ideas?


    I believe the Ruby/Tk equivalent is Tk.callback_break, but I don't
    think it will help here where we're dealing with an interaction
    between a bind and an itembind.

    Regards, Morton
     
    Morton Goldberg, Sep 5, 2006
    #5
  6. Josef Wolf

    Josef Wolf Guest

    On Wed, Sep 06, 2006 at 04:00:45AM +0900, Morton Goldberg wrote:
    > On Sep 5, 2006, at 2:00 PM, Josef Wolf wrote:


    > >How do you "dismiss" the menu? Whatever I do, I never get the
    > >second menu.

    > By left clicking -- either on or off the menu.


    Don't work here.

    > Which menu don't you get, the canvas' or the rect's?


    I always get the canvas menu and never see the rectangle's menu.

    > >The Tk::Bind manpage mentions that processing of matching callbacks
    > >can
    > >be controlled by "return" or calling "Tk->break" inside the callback.
    > >That is, the rectangle's callback should call Tk->break. But none of
    > >the involved classes seem to have a break method. And returning
    > >false/true/nil/1/0 don't have any effect, too. Any ideas?

    > I believe the Ruby/Tk equivalent is Tk.callback_break, but I don't
    > think it will help here where we're dealing with an interaction
    > between a bind and an itembind.


    I tried Tk.callback_break too, and it didn't work. I just forgot to
    mention this in my last mail.
     
    Josef Wolf, Sep 6, 2006
    #6
  7. On Sep 6, 2006, at 12:50 PM, Josef Wolf wrote:

    > On Wed, Sep 06, 2006 at 04:00:45AM +0900, Morton Goldberg wrote:
    >> On Sep 5, 2006, at 2:00 PM, Josef Wolf wrote:

    >
    >>> How do you "dismiss" the menu? Whatever I do, I never get the
    >>> second menu.

    >> By left clicking -- either on or off the menu.

    >
    > Don't work here.
    >
    >> Which menu don't you get, the canvas' or the rect's?

    >
    > I always get the canvas menu and never see the rectangle's menu.


    I think the reason my code works differently from yours is because I
    use separate variable names for the two contextual menus while you
    use the same one for both. It really does make a difference.

    Regards, Morton
     
    Morton Goldberg, Sep 7, 2006
    #7
  8. Josef Wolf

    Josef Wolf Guest

    On Thu, Sep 07, 2006 at 10:31:00AM +0900, Morton Goldberg wrote:
    > On Sep 6, 2006, at 12:50 PM, Josef Wolf wrote:
    > >On Wed, Sep 06, 2006 at 04:00:45AM +0900, Morton Goldberg wrote:


    > >>Which menu don't you get, the canvas' or the rect's?

    > >I always get the canvas menu and never see the rectangle's menu.

    > I think the reason my code works differently from yours is because I
    > use separate variable names for the two contextual menus while you
    > use the same one for both. It really does make a difference.


    It _does_ make a difference, but in another way:

    - If I use the same variable, always the last allocated menu wins.
    Thus with the version I posted, always rectangle's menu wins.

    - If I use different variables (as in my original code because they
    are in different methods), always the canvas menu wins.

    I _never_ see two different menus unless I introduce the handled_by_root
    flag.
     
    Josef Wolf, Sep 7, 2006
    #8
  9. On Sep 7, 2006, at 1:40 AM, Josef Wolf wrote:

    > On Thu, Sep 07, 2006 at 10:31:00AM +0900, Morton Goldberg wrote:
    >> On Sep 6, 2006, at 12:50 PM, Josef Wolf wrote:
    >>> On Wed, Sep 06, 2006 at 04:00:45AM +0900, Morton Goldberg wrote:

    >
    >>>> Which menu don't you get, the canvas' or the rect's?
    >>> I always get the canvas menu and never see the rectangle's menu.

    >> I think the reason my code works differently from yours is because I
    >> use separate variable names for the two contextual menus while you
    >> use the same one for both. It really does make a difference.

    >
    > It _does_ make a difference, but in another way:
    >
    > - If I use the same variable, always the last allocated menu wins.
    > Thus with the version I posted, always rectangle's menu wins.
    >
    > - If I use different variables (as in my original code because they
    > are in different methods), always the canvas menu wins.
    >
    > I _never_ see two different menus unless I introduce the
    > handled_by_root
    > flag.


    You _can_ see different menus if you change the posted version of
    your code to assign each menu to a different variable. I can't
    comment on your original code -- I've never seen it. But your posted
    code will work much better if you use different variables for each
    menu. Please try it yourself and see what happens.

    Regards, Morton
     
    Morton Goldberg, Sep 7, 2006
    #9
  10. Josef Wolf

    Josef Wolf Guest

    On Thu, Sep 07, 2006 at 11:41:55PM +0900, Morton Goldberg wrote:
    > On Sep 7, 2006, at 1:40 AM, Josef Wolf wrote:
    > >On Thu, Sep 07, 2006 at 10:31:00AM +0900, Morton Goldberg wrote:
    > >>On Sep 6, 2006, at 12:50 PM, Josef Wolf wrote:
    > >>>On Wed, Sep 06, 2006 at 04:00:45AM +0900, Morton Goldberg wrote:


    > >I _never_ see two different menus unless I introduce the
    > >handled_by_root flag.

    >
    > You _can_ see different menus if you change the posted version of
    > your code to assign each menu to a different variable. I can't
    > comment on your original code -- I've never seen it. But your posted
    > code will work much better if you use different variables for each
    > menu. Please try it yourself and see what happens.


    Well, I tried and I see different menus only when I use different
    variables _and_ modify the code to use the handled_by_root flag. This
    is probably because "dismissing a menu" has different effects in OS-X
    than on KDE.
     
    Josef Wolf, Sep 7, 2006
    #10
  11. On Sep 7, 2006, at 1:30 PM, Josef Wolf wrote:

    > On Thu, Sep 07, 2006 at 11:41:55PM +0900, Morton Goldberg wrote:
    >> On Sep 7, 2006, at 1:40 AM, Josef Wolf wrote:
    >>> On Thu, Sep 07, 2006 at 10:31:00AM +0900, Morton Goldberg wrote:

    >
    >>> I _never_ see two different menus unless I introduce the
    >>> handled_by_root flag.

    >>
    >> You _can_ see different menus if you change the posted version of
    >> your code to assign each menu to a different variable. I can't
    >> comment on your original code -- I've never seen it. But your posted
    >> code will work much better if you use different variables for each
    >> menu. Please try it yourself and see what happens.

    >
    > Well, I tried and I see different menus only when I use different
    > variables _and_ modify the code to use the handled_by_root flag. This
    > is probably because "dismissing a menu" has different effects in OS-X
    > than on KDE.


    I find the behavior of your code as I see it under OS X to be what I
    expect, but the behavior of it under KDE, as you describe it,
    mystifies me. I'm afraid I'm out of ideas on this matter. I'm truly
    sorry that I haven't been able to help you.

    Regards, Morton
     
    Morton Goldberg, Sep 7, 2006
    #11
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. news.west.cox.net

    Select menus above flyout menus? help?

    news.west.cox.net, Dec 8, 2004, in forum: HTML
    Replies:
    8
    Views:
    805
  2. Askari
    Replies:
    2
    Views:
    717
    Askari
    Aug 30, 2004
  3. PhilC
    Replies:
    2
    Views:
    899
    PhilC
    Oct 25, 2004
  4. Davy
    Replies:
    1
    Views:
    496
  5. Replies:
    10
    Views:
    273
    Rick Johnson
    Mar 15, 2013
Loading...

Share This Page