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