Changed 'Dismiss' to 'Close' (Chris Shaffer).
[rox-lib.git] / python / rox / Menu.py
blobee0fa213bef7a7eaf3d4467d4f28a63940ca2678
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.
5 Example:
7 from rox.Menu import Menu, set_save_name
9 set_save_name('Edit')
11 menu = Menu('main', [
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),
29 """
31 import rox
32 from rox import g
33 import choices
35 _save_name = None
36 def set_save_name(prog, leaf = 'menus'):
37 """Set the directory/leafname (see choices) used to save the menu keys.
38 Call this before creating any menus."""
39 global _save_name
40 _save_name = (prog, leaf)
42 class Menu:
43 def __init__(self, name, items):
44 """names should be unique (eg, 'popup', 'main', etc).
45 items is a list of menu items:
46 [(name, callback_name, type, key), ...].
47 'name' is the item's path.
48 'callback_name' is the NAME of a method to call.
49 'type' is as for g.ItemFactory.
50 'key' is only used if no bindings are in Choices."""
51 if not _save_name:
52 raise Exception('Call rox.Menu.set_save_name() first!')
54 ag = g.AccelGroup()
55 self.accel_group = ag
56 factory = g.ItemFactory(g.Menu, '<%s>' % name, ag)
58 program, save_leaf = _save_name
59 path = choices.load(program, save_leaf)
61 out = []
62 self.fns = []
63 for item in items:
64 stock = None
65 key = None
66 if len(item) == 3:
67 (label, fn, type) = item
68 elif len(item) == 4:
69 (label, fn, type, key) = item
70 else:
71 (label, fn, type, key, stock) = item
72 if fn:
73 self.fns.append(fn)
74 cb = self._activate
75 else:
76 cb = None
77 if stock:
78 out.append((label, key, cb, len(self.fns) - 1, type, stock))
79 else:
80 out.append((label, key, cb, len(self.fns) - 1, type))
82 factory.create_items(out)
83 self.factory = factory
85 if path:
86 g.accel_map_load(path)
88 self.caller = None # Caller of currently open menu
89 self.menu = factory.get_widget('<%s>' % name)
91 def keys_changed(*unused):
92 program, name = _save_name
93 path = choices.save(program, name)
94 if path:
95 try:
96 g.accel_map_save(path)
97 except AttributeError:
98 print "Error saving keybindings to", path
99 ag.connect('accel_changed', keys_changed)
101 def attach(self, window, object):
102 """Keypresses on this window will be treated as menu shortcuts
103 for this object, calling 'object.<callback_name>' when used."""
104 def kev(w, k):
105 self.caller = object
106 return 0
107 window.connect('key-press-event', kev)
108 window.add_accel_group(self.accel_group)
110 def _position(self, menu):
111 x, y, mods = g.gdk.get_default_root_window().get_pointer()
112 width, height = menu.size_request()
113 return (x - width * 3 / 4, y - 16, True)
115 def popup(self, caller, event, position_fn = None):
116 """Display the menu. Call 'caller.<callback_name>' when an item is chosen.
117 For applets, position_fn should be my_applet.position_menu)."""
118 self.caller = caller
119 if event:
120 self.menu.popup(None, None, position_fn or self._position, event.button, event.time)
121 else:
122 self.menu.popup(None, None, position_fn or self._position, 0, 0)
124 def _activate(self, action, widget):
125 if self.caller:
126 try:
127 getattr(self.caller, self.fns[action])()
128 except:
129 rox.report_exception()
130 else:
131 raise Exception("No caller for menu!")