1# Main menubar
2
3import string
4import types
5import tkinter
6import Pmw
7
8class MainMenuBar(Pmw.MegaArchetype):
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            ('hull_tearoff', 0,          None),
18        )
19        self.defineoptions(kw, optiondefs, dynamicGroups = ('Menu',))
20
21        # Initialise the base class (after defining the options).
22        Pmw.MegaArchetype.__init__(self, parent, tkinter.Menu)
23
24        self._menuInfo = {}
25        self._menuInfo[None] = (None, [])
26        # Map from a menu name to a tuple of information about the menu.
27        # The first item in the tuple is the name of the parent menu (for
28        # toplevel menus this is None). The second item in the tuple is
29        # a list of status help messages for each item in the menu.
30        # The key for the information for the main menubar is None.
31
32        self._menu = self.interior()
33        self._menu.bind('<Leave>', self._resetHelpmessage)
34        self._menu.bind('<Motion>',
35            lambda event=None, self=self: self._menuHelp(event, None))
36
37        # Check keywords and initialise options.
38        self.initialiseoptions()
39
40    def deletemenuitems(self, menuName, start, end = None):
41        self.component(menuName).delete(start, end)
42        if end is None:
43            del self._menuInfo[menuName][1][start]
44        else:
45            self._menuInfo[menuName][1][start:end+1] = []
46
47    def deletemenu(self, menuName):
48        """Delete should be called for cascaded menus before main menus.
49        """
50
51        parentName = self._menuInfo[menuName][0]
52        del self._menuInfo[menuName]
53        if parentName is None:
54            parentMenu = self._menu
55        else:
56            parentMenu = self.component(parentName)
57
58        menu = self.component(menuName)
59        menuId = str(menu)
60        for item in range(parentMenu.index('end') + 1):
61            if parentMenu.type(item) == 'cascade':
62                itemMenu = str(parentMenu.entrycget(item, 'menu'))
63                if itemMenu == menuId:
64                    parentMenu.delete(item)
65                    del self._menuInfo[parentName][1][item]
66                    break
67
68        self.destroycomponent(menuName)
69
70    def disableall(self):
71        for index in range(len(self._menuInfo[None][1])):
72            self.entryconfigure(index, state = 'disabled')
73
74    def enableall(self):
75        for index in range(len(self._menuInfo[None][1])):
76            self.entryconfigure(index, state = 'normal')
77
78    def addmenu(self, menuName, balloonHelp, statusHelp = None,
79            traverseSpec = None, **kw):
80        if statusHelp is None:
81            statusHelp = balloonHelp
82        self._addmenu(None, menuName, balloonHelp, statusHelp,
83            traverseSpec, kw)
84
85    def addcascademenu(self, parentMenuName, menuName, statusHelp='',
86            traverseSpec = None, **kw):
87        self._addmenu(parentMenuName, menuName, None, statusHelp,
88            traverseSpec, kw)
89
90    def _addmenu(self, parentMenuName, menuName, balloonHelp, statusHelp,
91            traverseSpec, kw):
92
93        if (menuName) in self.components():
94            raise ValueError('menu "%s" already exists' % menuName)
95
96        menukw = {}
97        if 'tearoff' in kw:
98            menukw['tearoff'] = kw['tearoff']
99            del kw['tearoff']
100        else:
101            menukw['tearoff'] = 0
102        if 'name' in kw:
103            menukw['name'] = kw['name']
104            del kw['name']
105
106        if 'label' not in kw:
107            kw['label'] = menuName
108
109        self._addHotkeyToOptions(parentMenuName, kw, traverseSpec)
110
111        if parentMenuName is None:
112            parentMenu = self._menu
113            balloon = self['balloon']
114            # Bug in Tk: balloon help not implemented
115            # if balloon is not None:
116            #     balloon.mainmenubind(parentMenu, balloonHelp, statusHelp)
117        else:
118            parentMenu = self.component(parentMenuName)
119
120        parentMenu.add_cascade(*(), **kw)
121
122        menu = self.createcomponent(*(menuName,
123                (), 'Menu',
124                tkinter.Menu, (parentMenu,)), **menukw)
125        parentMenu.entryconfigure('end', menu = menu)
126
127        self._menuInfo[parentMenuName][1].append(statusHelp)
128        self._menuInfo[menuName] = (parentMenuName, [])
129
130        menu.bind('<Leave>', self._resetHelpmessage)
131        menu.bind('<Motion>',
132            lambda event=None, self=self, menuName=menuName:
133                    self._menuHelp(event, menuName))
134
135    def addmenuitem(self, menuName, itemType, statusHelp = '',
136            traverseSpec = None, **kw):
137
138        menu = self.component(menuName)
139        if itemType != 'separator':
140            self._addHotkeyToOptions(menuName, kw, traverseSpec)
141
142        if itemType == 'command':
143            command = menu.add_command
144        elif itemType == 'separator':
145            command = menu.add_separator
146        elif itemType == 'checkbutton':
147            command = menu.add_checkbutton
148        elif itemType == 'radiobutton':
149            command = menu.add_radiobutton
150        elif itemType == 'cascade':
151            command = menu.add_cascade
152        else:
153            raise ValueError('unknown menuitem type "%s"' % itemType)
154
155        self._menuInfo[menuName][1].append(statusHelp)
156        command(*(), **kw)
157
158    def _addHotkeyToOptions(self, menuName, kw, traverseSpec):
159
160        if (not self['hotkeys'] or 'underline' in kw or
161                'label' not in kw):
162            return
163
164        if type(traverseSpec) == int:
165            kw['underline'] = traverseSpec
166            return
167
168        if menuName is None:
169            menu = self._menu
170        else:
171            menu = self.component(menuName)
172        hotkeyList = []
173        end = menu.index('end')
174        if end is not None:
175            for item in range(end + 1):
176                if menu.type(item) not in ('separator', 'tearoff'):
177                    #Python 3 conversion
178#                    underline = \
179#                            string.atoi(str(menu.entrycget(item, 'underline')))
180                    underline = \
181                            int(str(menu.entrycget(item, 'underline')))
182                    if underline != -1:
183                        label = str(menu.entrycget(item, 'label'))
184                        if underline < len(label):
185                            hotkey = label[underline].lower()
186                            if hotkey not in hotkeyList:
187                                hotkeyList.append(hotkey)
188
189        name = kw['label']
190
191        if type(traverseSpec) is str:
192            lowerLetter = traverseSpec.lower()
193            if traverseSpec in name and lowerLetter not in hotkeyList:
194                kw['underline'] = name.index(traverseSpec)
195        else:
196            targets = string.digits + string.ascii_letters
197            lowerName = name.lower()
198            for letter_index in range(len(name)):
199                letter = lowerName[letter_index]
200                if letter in targets and letter not in hotkeyList:
201                    kw['underline'] = letter_index
202                    break
203
204    def _menuHelp(self, event, menuName):
205        if menuName is None:
206            menu = self._menu
207            index = menu.index('@%d'% event.x)
208        else:
209            menu = self.component(menuName)
210            index = menu.index('@%d'% event.y)
211
212        balloon = self['balloon']
213        if balloon is not None:
214            if index is None:
215                balloon.showstatus('')
216            else:
217                if str(menu.cget('tearoff')) == '1':
218                    index = index - 1
219                if index >= 0:
220                    help = self._menuInfo[menuName][1][index]
221                    balloon.showstatus(help)
222
223    def _resetHelpmessage(self, event=None):
224        balloon = self['balloon']
225        if balloon is not None:
226            balloon.clearstatus()
227
228Pmw.forwardmethods(MainMenuBar, tkinter.Menu, '_hull')
229