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 function property() if you want to perform calculations when
81 getting or setting the value."""
82 MenuItem
.__init
__(self
, label
, property_name
, '<ToggleItem>')
86 def update(self
, menu
, widget
):
87 """Called when then menu is opened."""
89 state
= getattr(menu
.caller
, self
.fn
)
90 widget
.set_active(state
)
93 def activate(self
, caller
):
95 setattr(caller
, self
.fn
, not getattr(caller
, self
.fn
))
97 class SubMenu(MenuItem
):
98 """A branch menu item leading to a submenu."""
99 def __init__(self
, label
, submenu
):
100 MenuItem
.__init
__(self
, label
, None, '<Branch>')
101 self
.submenu
= submenu
103 class Separator(MenuItem
):
104 """A line dividing two parts of the menu."""
106 MenuItem
.__init
__(self
, '', None, '<Separator>')
110 yield "/" + x
.label
, x
111 if isinstance(x
, SubMenu
):
112 for l
, y
in _walk(x
.submenu
):
113 yield "/" + x
.label
+ l
, y
116 def __init__(self
, name
, items
):
117 """names should be unique (eg, 'popup', 'main', etc).
118 items is a list of menu items:
119 [(name, callback_name, type, key), ...].
120 'name' is the item's path.
121 'callback_name' is the NAME of a method to call.
122 'type' is as for g.ItemFactory.
123 'key' is only used if no bindings are in Choices."""
125 raise Exception('Call rox.Menu.set_save_name() first!')
128 self
.accel_group
= ag
129 factory
= g
.ItemFactory(g
.Menu
, '<%s>' % name
, ag
)
131 program
, save_leaf
= _save_name
132 accel_path
= choices
.load(program
, save_leaf
)
137 # Convert old-style list of tuples to new classes
138 if items
and not isinstance(items
[0], MenuItem
):
139 items
= [MenuItem(*t
) for t
in items
]
141 items_with_update
= []
142 for path
, item
in _walk(items
):
144 self
.fns
.append(item
)
149 out
.append((path
, item
.key
, cb
, len(self
.fns
) - 1, item
.type, item
.stock
))
151 out
.append((path
, item
.key
, cb
, len(self
.fns
) - 1, item
.type))
152 if hasattr(item
, 'update'):
153 items_with_update
.append((path
, item
))
155 factory
.create_items(out
)
156 self
.factory
= factory
158 self
.update_callbacks
= []
159 for path
, item
in items_with_update
:
160 widget
= factory
.get_widget(path
)
162 self
.update_callbacks
.append(lambda: fn(self
, widget
))
165 g
.accel_map_load(accel_path
)
167 self
.caller
= None # Caller of currently open menu
168 self
.menu
= factory
.get_widget('<%s>' % name
)
170 def keys_changed(*unused
):
171 program
, name
= _save_name
172 path
= choices
.save(program
, name
)
175 g
.accel_map_save(path
)
176 except AttributeError:
177 print "Error saving keybindings to", path
178 # GtkAccelGroup has its own (unrelated) connect method,
179 # so the obvious approach doesn't work.
180 #ag.connect('accel_changed', keys_changed)
182 gobject
.GObject
.connect(ag
, 'accel_changed', keys_changed
)
184 def attach(self
, window
, object):
185 """Keypresses on this window will be treated as menu shortcuts
186 for this object, calling 'object.<callback_name>' when used."""
190 window
.connect('key-press-event', kev
)
191 window
.add_accel_group(self
.accel_group
)
193 def _position(self
, menu
):
194 x
, y
, mods
= g
.gdk
.get_default_root_window().get_pointer()
195 width
, height
= menu
.size_request()
196 return (x
- width
* 3 / 4, y
- 16, True)
198 def popup(self
, caller
, event
, position_fn
= None):
199 """Display the menu. Call 'caller.<callback_name>' when an item is chosen.
200 For applets, position_fn should be my_applet.position_menu)."""
202 map(apply, self
.update_callbacks
) # Update toggles, etc
204 self
.menu
.popup(None, None, position_fn
or self
._position
, event
.button
, event
.time
)
206 self
.menu
.popup(None, None, position_fn
or self
._position
, 0, 0)
208 def _activate(self
, action
, widget
):
211 self
.fns
[action
].activate(self
.caller
)
213 rox
.report_exception()
215 raise Exception("No caller for menu!")