1# Manager widget for menus. 2 3import string 4import types 5import tkinter 6import Pmw 7 8class 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 list(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 list(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 'tearoff' in kw: 107 menukw['tearoff'] = kw['tearoff'] 108 del kw['tearoff'] 109 else: 110 menukw['tearoff'] = 0 111 112 if textKey not in kw: 113 kw[textKey] = menuName 114 115 self._addHotkeyToOptions(parentMenuName, kw, textKey, traverseSpec) 116 117 if parentMenuName is None: 118 button = 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 parentMenu.add_cascade(*(), **kw) 129 self._menuInfo[parentMenuName][1].append(statusHelp) 130 131 menu = 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 command(*(), **kw) 172 173 def _addHotkeyToOptions(self, menuName, kw, textKey, traverseSpec): 174 175 if (not self['hotkeys'] or 'underline' in kw or 176 textKey not in kw): 177 return 178 179 if type(traverseSpec) == int: 180 kw['underline'] = traverseSpec 181 return 182 183 hotkeyList = [] 184 if menuName is None: 185 for menuName in list(self._menuInfo.keys()): 186 if self._menuInfo[menuName][0] is None: 187 menubutton = self.component(menuName + '-button') 188 #Python 3 conversion 189 #underline = string.atoi(str(menubutton.cget('underline'))) 190 underline = int(str(menubutton.cget('underline'))) 191 if underline != -1: 192 label = str(menubutton.cget(textKey)) 193 if underline < len(label): 194 hotkey = label[underline].lower() 195 if hotkey not in hotkeyList: 196 hotkeyList.append(hotkey) 197 else: 198 menu = self.component(menuName + '-menu') 199 end = menu.index('end') 200 if end is not None: 201 for item in range(end + 1): 202 if menu.type(item) not in ('separator', 'tearoff'): 203 #Python 3 conversion 204 #underline = string.atoi( 205 # str(menu.entrycget(item, 'underline'))) 206 underline = int( 207 str(menu.entrycget(item, 'underline'))) 208 if underline != -1: 209 label = str(menu.entrycget(item, textKey)) 210 if underline < len(label): 211 hotkey = label[underline].lower() 212 if hotkey not in hotkeyList: 213 hotkeyList.append(hotkey) 214 215 name = kw[textKey] 216 217 if type(traverseSpec) is str: 218 lowerLetter = traverseSpec.lower() 219 if traverseSpec in name and lowerLetter not in hotkeyList: 220 kw['underline'] = name.index(traverseSpec) 221 else: 222 targets = string.digits + string.ascii_letters 223 lowerName = name.lower() 224 for letter_index in range(len(name)): 225 letter = lowerName[letter_index] 226 if letter in targets and letter not in hotkeyList: 227 kw['underline'] = letter_index 228 break 229 230 def _menuHelp(self, menuName): 231 menu = self.component(menuName + '-menu') 232 index = menu.index('active') 233 234 balloon = self['balloon'] 235 if balloon is not None: 236 if index is None: 237 balloon.showstatus('') 238 else: 239 if str(menu.cget('tearoff')) == '1': 240 index = index - 1 241 if index >= 0: 242 help = self._menuInfo[menuName][1][index] 243 balloon.showstatus(help) 244 245 def _resetHelpmessage(self, event=None): 246 balloon = self['balloon'] 247 if balloon is not None: 248 balloon.clearstatus() 249