Shift function key accelerators down by one (from F5 and up to F4 and up).
[hooke.git] / hooke / ui / gui / menu.py
1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of Hooke.
4 #
5 # Hooke is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 #
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
13 # Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with Hooke.  If not, see
17 # <http://www.gnu.org/licenses/>.
18
19 """Menu bar for Hooke.
20 """
21
22 import wx
23
24 from ...util.callback import callback, in_callback
25
26
27 class Menu (wx.Menu):
28     """A `Bind`able version of :class:`wx.Menu`.
29
30     From the `wxPython Style Guide`_, you can't do
31     wx.Menu().Bind(...), so we hack around it by bubbling the Bind up
32     to the closest parent :class:`wx.Frame`.
33
34     .. _wxPython Style Guide:
35       http://wiki.wxpython.org/wxPython%20Style%20Guide#line-101
36     """
37     def __init__(self, parent=None, **kwargs):
38         self._parent = parent
39         self._bindings = []
40         super(Menu, self).__init__(**kwargs)
41
42     def Bind(self, **kwargs):
43         assert 'id' in kwargs, kwargs
44         obj = self
45         while not isinstance(obj, wx.Frame):
46             obj = obj._parent
47         obj.Bind(**kwargs)
48         self._bindings.append(kwargs)
49
50     def Unbind(self, **kwargs):
51         assert 'id' in kwargs, kwargs
52         try:
53             self._bindings.remove(kwargs)
54         except ValueError:
55             pass
56         kwargs.pop('handler', None)
57         obj = self
58         while not isinstance(obj, wx.Frame):
59             obj = obj._parent
60         obj.Unbind(**kwargs)
61
62     def _unbind_all_items(self):
63         for kwargs in self._bindings:
64             self.Unbind(**kwargs)
65         self._bindings = []
66
67
68 class MenuBar (wx.MenuBar):
69     """A `Bind`able version of :class:`wx.MenuBar`.
70
71     See :class:`Menu` for the motivation.
72     """
73     def __init__(self, parent=None, **kwargs):
74         self._parent = parent
75         super(MenuBar, self).__init__(**kwargs)
76
77     def Append(self, menu, title):
78         menu._parent = self
79         super(MenuBar, self).Append(menu, title)
80
81
82 class FileMenu (Menu):
83     def __init__(self, callbacks=None, **kwargs):
84         super(FileMenu, self).__init__(**kwargs)
85         if callbacks == None:
86             callbacks = {}
87         self._callbacks = callbacks
88         self._c = {'exit': self.Append(wx.ID_EXIT)}
89         self.Bind(event=wx.EVT_MENU, handler=self.close, id=wx.ID_EXIT)
90
91     @callback
92     def close(self, event):
93         pass
94
95
96 class ViewMenu (Menu):
97     def __init__(self, panels, callbacks=None, **kwargs):
98         super(ViewMenu, self).__init__(**kwargs)
99         if callbacks == None:
100             callbacks = {}
101         self._callbacks = callbacks
102         self._c = {}
103         for i,panelname in enumerate(sorted([p.managed_name for p in panels])):
104             text = '%s\tF%d' % (panelname, i+4)
105             self._c[panelname] = self.AppendCheckItem(id=wx.ID_ANY, text=text)
106         for item in self._c.values():
107             item.Check()
108             self.Bind(event=wx.EVT_MENU, handler=self.view_panel, id=item.GetId())
109
110     def view_panel(self, event):
111         _id = event.GetId()
112         item = self.FindItemById(_id)
113         label = item.GetLabel()
114         selected = item.IsChecked()
115         in_callback(self, panel_name=label, visible=selected)
116
117
118 class PerspectiveMenu (Menu):
119     def __init__(self, callbacks=None, **kwargs):
120         super(PerspectiveMenu, self).__init__(**kwargs)
121         if callbacks == None:
122             callbacks = {}
123         self._callbacks = callbacks
124         self._c = {}
125
126     def update(self, perspectives, selected):
127         """Rebuild the perspectives menu.
128         """
129         self._unbind_all_items()
130         for item in self.GetMenuItems():
131             self.DeleteItem(item)
132         self._c = {
133             'save': self.Append(id=wx.ID_ANY, text='Save Perspective'),
134             'delete': self.Append(id=wx.ID_ANY, text='Delete Perspective'),
135             }
136         self.Bind(event=wx.EVT_MENU, handler=self.save_perspective,
137                   id=self._c['save'].GetId())
138         self.Bind(event=wx.EVT_MENU, handler=self.delete_perspective,
139                   id=self._c['delete'].GetId())
140         self.AppendSeparator()
141         for label in perspectives:
142             self._c[label] = self.AppendRadioItem(id=wx.ID_ANY, text=label)
143             self.Bind(event=wx.EVT_MENU, handler=self.select_perspective,
144                       id=self._c[label].GetId())
145             if label == selected:
146                 self._c[label].Check(True)
147
148     @callback
149     def save_perspective(self, event):
150         pass
151
152     @callback
153     def delete_perspective(self, event):
154         pass
155
156     def select_perspective(self, event):
157         _id = event.GetId()
158         item = self.FindItemById(_id)
159         label = item.GetLabel()
160         selected = item.IsChecked()
161         assert selected == True, label
162         in_callback(self, name=label)
163
164
165 class HelpMenu (Menu):
166     def __init__(self, callbacks=None, **kwargs):
167         super(HelpMenu, self).__init__(**kwargs)
168         if callbacks == None:
169             callbacks = {}
170         self._callbacks = callbacks
171         self._c = {'about': self.Append(id=wx.ID_ABOUT)}
172         self.Bind(event=wx.EVT_MENU, handler=self.about, id=wx.ID_ABOUT)
173
174     @callback
175     def about(self, event):
176         pass
177
178
179 class HookeMenuBar (MenuBar):
180     def __init__(self, panels, callbacks=None, **kwargs):
181         super(HookeMenuBar, self).__init__(**kwargs)
182         if callbacks == None:
183             callbacks = {}
184         self._callbacks = callbacks
185         self._c = {}
186
187         # Attach *Menu() instances
188         for key in ['file', 'view', 'perspective', 'help']:
189             cap_key = key.capitalize()
190             hot_key = '&' + cap_key
191             _class = globals()['%sMenu' % cap_key]
192             kwargs = {}
193             if key == 'view':
194                 kwargs['panels'] = panels
195             self._c[key] = _class(parent=self, callbacks=callbacks, **kwargs)
196             self.Append(self._c[key], hot_key)