Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | # Manager widget for menus. |
2 | ||
3 | import string | |
4 | import types | |
5 | import Tkinter | |
6 | import Pmw | |
7 | ||
8 | class MenuBar(Pmw.MegaWidget): | |
9 | ||
10 | def __init__(self, parent = None, **kw): | |
11 | ||
12 | # Define the megawidget options. | |
13 | INITOPT = Pmw.INITOPT | |
14 | optiondefs = ( | |
15 | ('balloon', None, None), | |
16 | ('hotkeys', 1, INITOPT), | |
17 | ('padx', 0, INITOPT), | |
18 | ) | |
19 | self.defineoptions(kw, optiondefs, dynamicGroups = ('Menu', 'Button')) | |
20 | ||
21 | # Initialise the base class (after defining the options). | |
22 | Pmw.MegaWidget.__init__(self, parent) | |
23 | ||
24 | self._menuInfo = {} | |
25 | # Map from a menu name to a tuple of information about the menu. | |
26 | # The first item in the tuple is the name of the parent menu (for | |
27 | # toplevel menus this is None). The second item in the tuple is | |
28 | # a list of status help messages for each item in the menu. | |
29 | # The third item in the tuple is the id of the binding used | |
30 | # to detect mouse motion to display status help. | |
31 | # Information for the toplevel menubuttons is not stored here. | |
32 | ||
33 | self._mydeletecommand = self.component('hull').tk.deletecommand | |
34 | # Cache this method for use later. | |
35 | ||
36 | # Check keywords and initialise options. | |
37 | self.initialiseoptions() | |
38 | ||
39 | def deletemenuitems(self, menuName, start, end = None): | |
40 | self.component(menuName + '-menu').delete(start, end) | |
41 | if end is None: | |
42 | del self._menuInfo[menuName][1][start] | |
43 | else: | |
44 | self._menuInfo[menuName][1][start:end+1] = [] | |
45 | ||
46 | def deletemenu(self, menuName): | |
47 | """Delete should be called for cascaded menus before main menus. | |
48 | """ | |
49 | ||
50 | # Clean up binding for this menu. | |
51 | parentName = self._menuInfo[menuName][0] | |
52 | bindId = self._menuInfo[menuName][2] | |
53 | _bindtag = 'PmwMenuBar' + str(self) + menuName | |
54 | self.unbind_class(_bindtag, '<Motion>') | |
55 | self._mydeletecommand(bindId) # unbind_class does not clean up | |
56 | del self._menuInfo[menuName] | |
57 | ||
58 | if parentName is None: | |
59 | self.destroycomponent(menuName + '-button') | |
60 | else: | |
61 | parentMenu = self.component(parentName + '-menu') | |
62 | ||
63 | menu = self.component(menuName + '-menu') | |
64 | menuId = str(menu) | |
65 | for item in range(parentMenu.index('end') + 1): | |
66 | if parentMenu.type(item) == 'cascade': | |
67 | itemMenu = str(parentMenu.entrycget(item, 'menu')) | |
68 | if itemMenu == menuId: | |
69 | parentMenu.delete(item) | |
70 | del self._menuInfo[parentName][1][item] | |
71 | break | |
72 | ||
73 | self.destroycomponent(menuName + '-menu') | |
74 | ||
75 | def disableall(self): | |
76 | for menuName in self._menuInfo.keys(): | |
77 | if self._menuInfo[menuName][0] is None: | |
78 | menubutton = self.component(menuName + '-button') | |
79 | menubutton.configure(state = 'disabled') | |
80 | ||
81 | def enableall(self): | |
82 | for menuName in self._menuInfo.keys(): | |
83 | if self._menuInfo[menuName][0] is None: | |
84 | menubutton = self.component(menuName + '-button') | |
85 | menubutton.configure(state = 'normal') | |
86 | ||
87 | def addmenu(self, menuName, balloonHelp, statusHelp = None, | |
88 | side = 'left', traverseSpec = None, **kw): | |
89 | ||
90 | self._addmenu(None, menuName, balloonHelp, statusHelp, | |
91 | traverseSpec, side, 'text', kw) | |
92 | ||
93 | def addcascademenu(self, parentMenuName, menuName, statusHelp = '', | |
94 | traverseSpec = None, **kw): | |
95 | ||
96 | self._addmenu(parentMenuName, menuName, None, statusHelp, | |
97 | traverseSpec, None, 'label', kw) | |
98 | ||
99 | def _addmenu(self, parentMenuName, menuName, balloonHelp, statusHelp, | |
100 | traverseSpec, side, textKey, kw): | |
101 | ||
102 | if (menuName + '-menu') in self.components(): | |
103 | raise ValueError, 'menu "%s" already exists' % menuName | |
104 | ||
105 | menukw = {} | |
106 | if kw.has_key('tearoff'): | |
107 | menukw['tearoff'] = kw['tearoff'] | |
108 | del kw['tearoff'] | |
109 | else: | |
110 | menukw['tearoff'] = 0 | |
111 | ||
112 | if not kw.has_key(textKey): | |
113 | kw[textKey] = menuName | |
114 | ||
115 | self._addHotkeyToOptions(parentMenuName, kw, textKey, traverseSpec) | |
116 | ||
117 | if parentMenuName is None: | |
118 | button = apply(self.createcomponent, (menuName + '-button', | |
119 | (), 'Button', | |
120 | Tkinter.Menubutton, (self.interior(),)), kw) | |
121 | button.pack(side=side, padx = self['padx']) | |
122 | balloon = self['balloon'] | |
123 | if balloon is not None: | |
124 | balloon.bind(button, balloonHelp, statusHelp) | |
125 | parentMenu = button | |
126 | else: | |
127 | parentMenu = self.component(parentMenuName + '-menu') | |
128 | apply(parentMenu.add_cascade, (), kw) | |
129 | self._menuInfo[parentMenuName][1].append(statusHelp) | |
130 | ||
131 | menu = apply(self.createcomponent, (menuName + '-menu', | |
132 | (), 'Menu', | |
133 | Tkinter.Menu, (parentMenu,)), menukw) | |
134 | if parentMenuName is None: | |
135 | button.configure(menu = menu) | |
136 | else: | |
137 | parentMenu.entryconfigure('end', menu = menu) | |
138 | ||
139 | # Need to put this binding after the class bindings so that | |
140 | # menu.index() does not lag behind. | |
141 | _bindtag = 'PmwMenuBar' + str(self) + menuName | |
142 | bindId = self.bind_class(_bindtag, '<Motion>', | |
143 | lambda event=None, self=self, menuName=menuName: | |
144 | self._menuHelp(menuName)) | |
145 | menu.bindtags(menu.bindtags() + (_bindtag,)) | |
146 | menu.bind('<Leave>', self._resetHelpmessage) | |
147 | ||
148 | self._menuInfo[menuName] = (parentMenuName, [], bindId) | |
149 | ||
150 | def addmenuitem(self, menuName, itemType, statusHelp = '', | |
151 | traverseSpec = None, **kw): | |
152 | ||
153 | menu = self.component(menuName + '-menu') | |
154 | if itemType != 'separator': | |
155 | self._addHotkeyToOptions(menuName, kw, 'label', traverseSpec) | |
156 | ||
157 | if itemType == 'command': | |
158 | command = menu.add_command | |
159 | elif itemType == 'separator': | |
160 | command = menu.add_separator | |
161 | elif itemType == 'checkbutton': | |
162 | command = menu.add_checkbutton | |
163 | elif itemType == 'radiobutton': | |
164 | command = menu.add_radiobutton | |
165 | elif itemType == 'cascade': | |
166 | command = menu.add_cascade | |
167 | else: | |
168 | raise ValueError, 'unknown menuitem type "%s"' % itemType | |
169 | ||
170 | self._menuInfo[menuName][1].append(statusHelp) | |
171 | apply(command, (), kw) | |
172 | ||
173 | def _addHotkeyToOptions(self, menuName, kw, textKey, traverseSpec): | |
174 | ||
175 | if (not self['hotkeys'] or kw.has_key('underline') or | |
176 | not kw.has_key(textKey)): | |
177 | return | |
178 | ||
179 | if type(traverseSpec) == types.IntType: | |
180 | kw['underline'] = traverseSpec | |
181 | return | |
182 | ||
183 | hotkeyList = [] | |
184 | if menuName is None: | |
185 | for menuName in self._menuInfo.keys(): | |
186 | if self._menuInfo[menuName][0] is None: | |
187 | menubutton = self.component(menuName + '-button') | |
188 | underline = string.atoi(str(menubutton.cget('underline'))) | |
189 | if underline != -1: | |
190 | label = str(menubutton.cget(textKey)) | |
191 | if underline < len(label): | |
192 | hotkey = string.lower(label[underline]) | |
193 | if hotkey not in hotkeyList: | |
194 | hotkeyList.append(hotkey) | |
195 | else: | |
196 | menu = self.component(menuName + '-menu') | |
197 | end = menu.index('end') | |
198 | if end is not None: | |
199 | for item in range(end + 1): | |
200 | if menu.type(item) not in ('separator', 'tearoff'): | |
201 | underline = string.atoi( | |
202 | str(menu.entrycget(item, 'underline'))) | |
203 | if underline != -1: | |
204 | label = str(menu.entrycget(item, textKey)) | |
205 | if underline < len(label): | |
206 | hotkey = string.lower(label[underline]) | |
207 | if hotkey not in hotkeyList: | |
208 | hotkeyList.append(hotkey) | |
209 | ||
210 | name = kw[textKey] | |
211 | ||
212 | if type(traverseSpec) == types.StringType: | |
213 | lowerLetter = string.lower(traverseSpec) | |
214 | if traverseSpec in name and lowerLetter not in hotkeyList: | |
215 | kw['underline'] = string.index(name, traverseSpec) | |
216 | else: | |
217 | targets = string.digits + string.letters | |
218 | lowerName = string.lower(name) | |
219 | for letter_index in range(len(name)): | |
220 | letter = lowerName[letter_index] | |
221 | if letter in targets and letter not in hotkeyList: | |
222 | kw['underline'] = letter_index | |
223 | break | |
224 | ||
225 | def _menuHelp(self, menuName): | |
226 | menu = self.component(menuName + '-menu') | |
227 | index = menu.index('active') | |
228 | ||
229 | balloon = self['balloon'] | |
230 | if balloon is not None: | |
231 | if index is None: | |
232 | balloon.showstatus('') | |
233 | else: | |
234 | if str(menu.cget('tearoff')) == '1': | |
235 | index = index - 1 | |
236 | if index >= 0: | |
237 | help = self._menuInfo[menuName][1][index] | |
238 | balloon.showstatus(help) | |
239 | ||
240 | def _resetHelpmessage(self, event=None): | |
241 | balloon = self['balloon'] | |
242 | if balloon is not None: | |
243 | balloon.clearstatus() |