1 """The Menu widget provides an easy way to create menus that allow the user to
2 define keyboard shortcuts, and saves the shortcuts automatically. You only define
3 each Menu once, and attach it to windows as required.
7 from rox.Menu import Menu, set_save_name
12 ('/File', '', '<Branch>'),
13 ('/File/Save', 'save', ''),
14 ('/File/Open Parent', 'up', ''),
15 ('/File/Close', 'close', ''),
16 ('/File/', '', '<Separator>'),
17 ('/File/New', 'new', ''),
18 ('/Edit', '', '<Branch>'),
19 ('/Edit/Undo', 'undo', ''),
20 ('/Edit/Redo', 'redo', ''),
21 ('/Edit/', '', '<Separator>'),
22 ('/Edit/Search...', 'search', ''),
23 ('/Edit/Goto line...', 'goto', ''),
24 ('/Edit/', '', '<Separator>'),
25 ('/Edit/Process...', 'process', ''),
26 ('/Options', 'show_options', ''),
27 ('/Help', 'help', '<StockItem>', 'F1', g.STOCK_HELP),
30 There is also a new syntax, supported from 1.9.13, where you pass instances of MenuItem
31 instead of tuples to the Menu constructor. Be sure to require version 1.9.13 if
35 from __future__
import generators
42 def set_save_name(prog
, leaf
= 'menus'):
43 """Set the directory/leafname (see choices) used to save the menu keys.
44 Call this before creating any menus."""
46 _save_name
= (prog
, leaf
)
49 """Base class for menu items. You should normally use one of the subclasses..."""
50 def __init__(self
, label
, callback_name
, type = '', key
= None, stock
= None):
51 if label
and label
[0] == '/':
52 self
.label
= label
[1:]
55 self
.fn
= callback_name
60 def activate(self
, caller
):
61 getattr(caller
, self
.fn
)()
63 class Action(MenuItem
):
64 """A leaf menu item, possibly with a stock icon, which calls a method when clicked."""
65 def __init__(self
, label
, callback_name
, key
= None, stock
= None, values
= ()):
66 """object.callback(*values) is called when the item is activated."""
68 MenuItem
.__init
__(self
, label
, callback_name
, '<StockItem>', key
, stock
)
70 MenuItem
.__init
__(self
, label
, callback_name
, '', key
)
73 def activate(self
, caller
):
74 getattr(caller
, self
.fn
)(*self
.values
)
76 class ToggleItem(MenuItem
):
77 """A menu item that has a check icon and toggles state each time it is activated."""
78 def __init__(self
, label
, property_name
):
79 """property_name is a boolean property on the caller object. You can use
80 the built-in Python class property() if you want to perform calculations when
81 getting or setting the value."""
82 MenuItem
.__init
__(self
, label
, property_name
, '<ToggleItem>')
85 def update(self
, menu
, widget
):
86 """Called when then menu is opened."""
88 state
= getattr(menu
.caller
, self
.fn
)
89 widget
.set_active(state
)
92 def activate(self
, caller
):
94 setattr(caller
, self
.fn
, not getattr(caller
, self
.fn
))
96 class SubMenu(MenuItem
):
97 """A branch menu item leading to a submenu."""
98 def __init__(self
, label
, submenu
):
99 MenuItem
.__init
__(self
, label
, None, '<Branch>')
100 self
.submenu
= submenu
102 class Separator(MenuItem
):
103 """A line dividing two parts of the menu."""
105 MenuItem
.__init
__(self
, '', None, '<Separator>')
109 yield "/" + x
.label
, x
110 if isinstance(x
, SubMenu
):
111 for l
, y
in _walk(x
.submenu
):
112 yield "/" + x
.label
+ l
, y
115 """A popup menu. This wraps GtkMenu. It handles setting, loading and saving of
116 keyboard-shortcuts, applies translations, and has a simpler API."""
117 fns
= None # List of MenuItem objects which can be activated
118 update_callbacks
= None # List of functions to call just before popping up the menu
120 menu
= None # The actual GtkMenu
122 def __init__(self
, name
, items
):
123 """names should be unique (eg, 'popup', 'main', etc).
124 items is a list of menu items:
125 [(name, callback_name, type, key), ...].
126 'name' is the item's path.
127 'callback_name' is the NAME of a method to call.
128 'type' is as for g.ItemFactory.
129 'key' is only used if no bindings are in Choices."""
131 raise Exception('Call rox.Menu.set_save_name() first!')
134 self
.accel_group
= ag
135 factory
= g
.ItemFactory(g
.Menu
, '<%s>' % name
, ag
)
137 program
, save_leaf
= _save_name
138 accel_path
= choices
.load(program
, save_leaf
)
143 # Convert old-style list of tuples to new classes
144 if items
and not isinstance(items
[0], MenuItem
):
145 items
= [MenuItem(*t
) for t
in items
]
147 items_with_update
= []
148 for path
, item
in _walk(items
):
150 self
.fns
.append(item
)
155 out
.append((path
, item
.key
, cb
, len(self
.fns
) - 1, item
.type, item
.stock
))
157 out
.append((path
, item
.key
, cb
, len(self
.fns
) - 1, item
.type))
158 if hasattr(item
, 'update'):
159 items_with_update
.append((path
, item
))
161 factory
.create_items(out
)
162 self
.factory
= factory
164 self
.update_callbacks
= []
165 for path
, item
in items_with_update
:
166 widget
= factory
.get_widget(path
)
168 self
.update_callbacks
.append(lambda: fn(self
, widget
))
171 g
.accel_map_load(accel_path
)
173 self
.caller
= None # Caller of currently open menu
174 self
.menu
= factory
.get_widget('<%s>' % name
)
176 def keys_changed(*unused
):
177 program
, name
= _save_name
178 path
= choices
.save(program
, name
)
181 g
.accel_map_save(path
)
182 except AttributeError:
183 print "Error saving keybindings to", path
184 # GtkAccelGroup has its own (unrelated) connect method,
185 # so the obvious approach doesn't work.
186 #ag.connect('accel_changed', keys_changed)
188 gobject
.GObject
.connect(ag
, 'accel_changed', keys_changed
)
190 def attach(self
, window
, object):
191 """Keypresses on this window will be treated as menu shortcuts
192 for this object, calling 'object.<callback_name>' when used."""
196 window
.connect('key-press-event', kev
)
197 window
.add_accel_group(self
.accel_group
)
199 def _position(self
, menu
):
200 x
, y
, mods
= g
.gdk
.get_default_root_window().get_pointer()
201 width
, height
= menu
.size_request()
202 return (x
- width
* 3 / 4, y
- 16, True)
204 def popup(self
, caller
, event
, position_fn
= None):
205 """Display the menu. Call 'caller.<callback_name>' when an item is chosen.
206 For applets, position_fn should be my_applet.position_menu)."""
208 map(apply, self
.update_callbacks
) # Update toggles, etc
210 self
.menu
.popup(None, None, position_fn
or self
._position
, event
.button
, event
.time
)
212 self
.menu
.popup(None, None, position_fn
or self
._position
, 0, 0)
214 def _activate(self
, action
, widget
):
217 self
.fns
[action
].activate(self
.caller
)
219 rox
.report_exception()
221 raise Exception("No caller for menu!")