comparison orpg/tools/metamenus.py @ 0:4385a7d0efd1 grumpy-goblin

Deleted and repushed it with the 'grumpy-goblin' branch. I forgot a y
author sirebral
date Tue, 14 Jul 2009 16:41:58 -0500
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4385a7d0efd1
1 __author__ = "E. A. Tacao <e.a.tacao |at| estadao.com.br>"
2 __date__ = "21 Apr 2006, 13:15 GMT-03:00"
3 __version__ = "0.07"
4 __doc__ = """
5 metamenus: classes that aim to simplify the use of menus in wxPython.
6
7 MenuBarEx is a wx.MenuBar derived class for wxPython;
8 MenuEx is a wx.Menu derived class for wxPython.
9
10 Some features:
11
12 - Menus are created based on the indentation of items on a list. (See 'Usage'
13 below.)
14
15 - Each menu item will trigger a method on the parent. The methods names may
16 be explicitly defined on the constructor or generated automatically. It's
17 also possible to define some names and let metamenus create the remaining.
18
19 - Allows the user to enable or disable a menu item or an entire menu given
20 its label.
21
22 - Supplies EVT_BEFOREMENU and EVT_AFTERMENU, events that are triggered right
23 before and after, respectively, the triggering of a EVT_MENU-bound method
24 on selection of some menu item.
25
26 - MenuBarEx handles accelerators for numpad keys and also supports 'invisible
27 menu items'.
28
29 - If your app is already i18n'd, menu items may be translated on the fly.
30 All you need to do is to write somewhere a .mo file containing the menu
31 translations.
32
33
34 MenuEx Usage:
35
36 The MenuEx usage is similar to MenuBarEx (please see below), except that it
37 has an optional kwarg named show_title (boolean; controls whether the menu
38 title will be shown) and doesn't have the MenuBarEx's xaccel kwarg:
39
40 MenuEx(self, menus, margin=wx.DEFAULT, show_title=True,
41 font=wx.NullFont, custfunc={}, i18n=True, style=0)
42
43
44 MenuBarEx Usage:
45
46 In order to put a MenuBarEx inside a frame it's enough to do this:
47 MenuBarEx(self, menus)
48
49 or you can also use some few optional keyword arguments:
50 MenuBarEx(self, menus, margin=wx.DEFAULT, font=wx.NullFont,
51 xaccel=None, custfunc={}, i18n=True, style=0)
52
53 Arguments:
54 - self: The frame in question.
55
56 - menus: A python list of 'menus', which are python lists of
57 'menu_entries'. Each 'menu_entry' is a python list that needs to
58 be in one of the following formats:
59
60 [label]
61 or [label, args]
62 or [label, kwargs]
63 or [label, args, kwargs]
64 or [label, kwargs, args] (but please don't do this one).
65
66 . label: (string) The text for the menu item.
67
68 Leading whitespaces at the beginning of a label are used to
69 compute the indentation level of the item, which in turn is
70 used to determine the grouping of items. MenuBarEx determines
71 one indentation level for every group of two whitespaces.
72
73 If you want this item to be a sub-item, increase its
74 indentation. Top-level items must have no indentation.
75
76 Separators are items labeled with a "-" and may not have args
77 and kwargs.
78
79 Menu breaks (please see the wx.MenuItem.Break docs) are items
80 labeled with a "/" and may not have args and kwargs.
81
82 Accelerators are handled as usual; MenuBarEx also supports
83 numpad accelerators (e.g, " &Forward\tCtrl+Num 8").
84
85 Please refer to the wxPython docs for wx.Menu.Append for more
86 information about them.
87
88 . args: (tuple) (helpString, wxItemKind)
89
90 - helpString is an optional help string that will be shown on
91 the parent's status bar. If don't pass it, no help string
92 for this item will appear on the statusbar.
93
94 - wxItemKind may be one of wx.ITEM_CHECK, "check",
95 wx.ITEM_RADIO or "radio". It is also optional; if don't pass
96 one, a default wx.ITEM_NORMAL will be used.
97
98 Note that if you have to pass only one argument, you can do
99 either:
100
101 args=("", wxItemKind)
102 or args=(helpString,)
103 or helpString
104 or wxItemKind
105 or (helpString)
106 or (wxItemKind)
107
108 When you pass only one item, Metamenus will check if the
109 thing passed can be translated as an item kind (either
110 wx.RADIO, "radio", etc.) or not, and so will try to guess
111 what to do with the thing. So that if you want a status bar
112 showing something that could be translated as an item kind,
113 say, "radio", you'll have to pass both arguments: ("radio",).
114
115
116 . kwargs: (dict) wxBitmap bmpChecked, wxBitmap bmpUnchecked,
117 wxFont font, int width,
118 wxColour fgcolour, wxColour bgcolour
119
120 These options access wx.MenuItem methods in order to change
121 its appearance, and might not be present on all platforms.
122 They are internally handled as follows:
123
124 key: item method:
125
126 "bmpChecked" and "bmpUnchecked" : SetBitmaps
127 "font" : SetFont
128 "margin", : SetMarginWidth
129 "fgColour", : SetTextColour
130 "bgColour", : SetBackgroundColour
131
132 The "bmpChecked" and "bmpUnchecked" options accept a bitmap or
133 a callable that returns a bitmap when called. This is useful
134 if you created your bitmaps with encode_bitmaps.py and want to
135 pass something like {"bmpChecked": my_images.getSmilesBitmap}.
136
137 Please refer to the wxPython docs for wx.MenuItem for more
138 information about the item methods.
139
140 - margin: (int) a value that will be used to do a SetMargin() for each
141 menubar item. Please refer to the wxPython docs for
142 wx.MenuItem.SetMargin for more information about this.
143
144 - font: (wx.Font) a value that will be used to do a SetFont() for
145 each menu item. Please refer to the wxPython docs for
146 wx.MenuItem.SetFont for more information about this.
147
148 - xaccel: (MenuBarEx only) allows one to bind events to 'items' that
149 are not actually menu items, rather methods or functions that
150 are triggered when some key or combination of keys is
151 pressed.
152
153 xaccel is a list of tuples (accel, function), where accel is
154 a string following the accelerator syntax described in the
155 wx.Menu.Append docs and function is the function/method to be
156 executed when the accelerator is triggered.
157
158 The events are managed in the same way as MenuBarEx events.
159
160 - custfunc: (dict) allows one to define explicitly what will be the
161 parent's method called on a menu event.
162
163 By default, all parent's methods have to start with "OnMB_"
164 (for menubars) or "OnM_" (for menus) plus the full menu
165 'path'. For a 'Save' menu item inside a 'File' top menu, e.g:
166
167 def OnMB_FileSave(self):
168 self.file.save()
169
170 However, the custfunc arg allows you to pass a dict of
171
172 {menupath: method, menupath: method, ...}
173
174 so that if you want your File > Save menu triggering a
175 'onSave' method instead, you may pass
176
177 {"FileSave": "onSave"}
178 or {"FileSave": self.onSave}
179
180 as custfunc. This way, your parent's method should look like
181 this instead:
182
183 def onSave(self):
184 self.file.save()
185
186 You don't have to put all menu items inside custfunc. The
187 menupaths not found there will still trigger automatically
188 an OnMB_/OnM_-prefixed method.
189
190 - i18n: (bool) Controls whether you want the items to be translated
191 or not. Default is True. For more info on i18n, please see
192 'More about i18n' below.
193
194 - style: Please refer to the wxPython docs for wx.MenuBar/wx.Menu for
195 more information about this.
196
197
198 The public methods:
199
200 The 'menu_string' arg on some of the public methods is a string that
201 refers to a menu item. For a File > Save menu, e. g., it may be
202 "OnMB_FileSave", "FileSave" or the string you passed via the custfunc
203 parameter (i. e., if you passed {"FileSave": "onSave"} as custfunc, the
204 string may also be "onSave").
205
206 The 'menu_string_list' arg on some of the public methods is a python list
207 of 'menu_string' strings described above. Please refer to the methods
208 themselves for more details.
209
210
211 More about i18n:
212 If you want to get your menu items automatically translated, you'll need
213 to:
214
215 1. Create a directory named 'locale' under your app's directory, and under
216 the 'locale', create subdirectories named after the canonical names of
217 the languages you're going to use (e. g., 'pt_BR', 'es_ES', etc.)
218
219 2. Inside each of the subdirectories, write a gettext compiled catalog file
220 (e. g., "my_messages.mo") containing all of the menu labels translated
221 to the language represented by the subdirectory.
222
223 4. The language can be changed on the fly. Whenever you want to change the
224 menu language, execute these lines somewhere in your app:
225
226 l = wx.Locale(wx.LANGUAGE_PORTUGUESE_BRAZILIAN)
227 l.AddCatalogLookupPathPrefix("locale")
228 l.AddCatalog("my_messages.mo")
229 self.my_menu.UpdateMenus()
230
231 Unless you want your menus showing up in pt_BR, replace the
232 wx.LANGUAGE_PORTUGUESE_BRAZILIAN above by the proper language identifier.
233 For a list of supported identifiers please see the wxPython docs, under the
234 'Constants\Language identifiers' section.
235
236 Some items may show up in the selected language even though you didn't
237 create a .mo file for the translations. That's because wxPython looks for
238 them in the wxstd.mo file placed somewhere under the wxPython tree, and
239 maybe wxPython already uses some of the string you are using.
240
241 Note that if you're to distribute a standalone app the wxPython tree may
242 not be present, so it's a good idea to include a specific .mo file in your
243 package. On the other hand, if by any reason you _don't_ want the menu
244 items to be translated, you may pass a i18n=False kwarg to the constructor.
245
246 You can use metamenus itself directly from a command line to help on
247 creating a gettext-parseable file based on the menus you wrote. For more
248 info about this, please see the docs for the _mmprep class.
249
250 For more info about i18n, .mo files and gettext, please see
251 <http://wiki.wxpython.org/index.cgi/Internationalization>.
252
253
254 Menu bar example:
255
256 a = [["File"],
257 [" New", "Creates a new file"],
258 [" Save"],
259 [" -"],
260 [" Preview", "Preview Document",
261 {"bmpChecked": images.getSmilesBitmap(),
262 "fgColour": wx.RED}],
263 [" -"],
264 [" Exit"]]
265
266 b = [["Edit"],
267 [" Cut"],
268 [" Copy"],
269 [" Foo", "check"],
270 [" Bar", "check"],
271 [" Paste"]]
272
273 myMenuBar = MenuBarEx(self, [a, b])
274
275
276 Context menu example:
277
278 a = [["Edit"], # A 'top-level' menu item is used as title;
279 [" Cut"],
280 [" Copy"],
281 [" Foo", "radio"],
282 [" Bar", "radio"],
283 [" Paste"]]
284
285 myContextMenu = MenuEx(self, a)
286
287
288 If you don't want to show the title for the context menu:
289
290 myContextMenu = MenuEx(self, a, show_title=False)
291
292
293 A very default 'File' menu example:
294
295 [
296 ['&File'],
297 [' &New\tCtrl+N'],
298 [' &Open...\tCtrl+O'],
299 [' &Save\tCtrl+S'],
300 [' Save &As...\tCtrl+Shift+S'],
301 [' -'],
302 [' Publis&h\tCtrl+Shift+P'],
303 [' -'],
304 [' &Close\tCtrl+W'],
305 [' C&lose All'],
306 [' -'],
307 [' E&xit\tAlt+X']
308 ]
309
310
311 Known Issues:
312
313 These are wx.Menu issues, and since metamenus doesn't/can't work around them
314 it's advisable to stay away from custom menus:
315
316 - If you try to customize an item changing either its font, margin or
317 colours, the following issues arise:
318
319 1. The item will appear shifted to the right when compared to default menu
320 items, although a GetMarginWidth() will return a default value;
321 2. wx.ITEM_RADIO items won't show their bullets.
322
323 - If you try to change the bitmaps for wx.ITEM_RADIO items, the items will
324 ignore the 2nd bitmap passed and will always show the checked bitmap,
325 regardless of their state.
326
327
328 About:
329
330 metamenus is distributed under the wxWidgets license.
331
332 This code should meet the wxPython Coding Guidelines
333 <http://www.wxpython.org/codeguidelines.php> and the wxPython Style Guide
334 <http://wiki.wxpython.org/index.cgi/wxPython_20Style_20Guide>.
335
336 For all kind of problems, requests, enhancements, bug reports, etc,
337 please drop me an e-mail.
338
339 For updates please visit <http://j.domaindlx.com/elements28/wxpython/>.
340 """
341
342 # History:
343 #
344 # Version 0.07:
345 # - Applied a patch from Michele Petrazzo which now allows the values
346 # passed via the custfunc dictionary to be either callable objects
347 # or strings representing the callable objects.
348 #
349 # Version 0.06:
350 # - Added i18n capabilities. Running metamenus.py from the command line
351 # also creates a gettext-parseable file than can in turn be used to
352 # create .po files.
353 #
354 # - Some minor syntax fixes so that this code hopefully should meet the
355 # wxPython Coding Guidelines and the wxPython Style Guide.
356 #
357 # - Changed EVT_BEFOREMENU_EVENT to EVT_BEFOREMENU and EVT_AFTERMENU_EVENT
358 # to EVT_AFTERMENU. If your app was using them, please update it.
359 #
360 # - Fixed a test into OnMB_ that would raise an error on unicode systems;
361 # thanks to Michele Petrazzo for pointing this out.
362 #
363 # - Fixed the EVT_MENU binding so that the accelerators now should work
364 # on Linux; thanks to Michele Petrazzo for pointing this out.
365 #
366 # - Fixed a couple of bad names in the public methods (EnableMenuTop to
367 # EnableTopMenu, etc.) that would prevent the methods to work.
368 #
369 # - Fixed a bug that would prevent checkable items to be created when
370 # only a tupleless wxItemKind was passed within a menu item.
371 #
372 # - Fixed a couple of potential unicode bugs in _adjust that could arise
373 # if unicode objects were passed as menu items or help strings.
374 #
375 # - Changes in _sItem: _adjust now is a method of _sItem; GetPath
376 # substituted _walkMenu/_walkMenuBar; _sItem now finds a translated
377 # label when using 18n, etc.
378 #
379 # - All of the menu item strings passed to the public methods now may be
380 # in one of the following forms: (1) The full menu 'path' (e. g.,
381 # "FileSave"), (2) The prefix + the full menu 'path' (e. g.,
382 # "OnMB_FileSave"), (3) The method name passed as custfunc (e. g., if
383 # you passed {"FileSave": "onSave"} as custfunc, the string may also
384 # be "onSave").
385 #
386 # - "bmpChecked" and "bmpUnchecked" options now may accept a bitmap or
387 # a callable that returns a bitmap when called. This is useful if your
388 # menu 'tree' is in another file and you import it _before_ your app is
389 # created, since BitmapFromImage can only be used if the app is already
390 # out there.
391 #
392 # Version 0.05:
393 # - Fixed the popup menu position on MenuEx.
394 #
395 # - Applied a patch from Michele Petrazzo which implemented the custfunc
396 # funcionality, allowing one to choose arbitrary names for methods
397 # called on menu events.
398 #
399 # Version 0.04:
400 # - Changed the OnMB_, OnM_ code so that they won't shadow AttributeErrors
401 # raised on parent's code.
402 #
403 # - Add the show_title kwarg to the MenuEx constructor.
404 #
405 # Version 0.03:
406 # - Added support for numpad accelerators; they must be passed as "Num x",
407 # where x may be into a [0-9] range.
408 #
409 # - Added support for wx.MenuItem.Break(); if you want a menu break,
410 # now you can pass a "/" on a menu entry label.
411 #
412 # - Added the EVT_BEFOREMENU_EVENT, which will be triggered right before
413 # the menu event.
414 #
415 # - Those who believe that wx.UPPERCASE_STUFF_IS_UGLY 8^) now can pass
416 # "radio" instead of wx.ITEM_RADIO, "check" instead of wx.ITEM_CHECK, and
417 # "normal" (or "", or even nothing at all) instead of wx.ITEM_NORMAL.
418 #
419 # - The args syntax has been extended. The previous version allowed one to
420 # pass either:
421 #
422 # (helpString, wxItemKind)
423 # or ("", wxItemKind)
424 # or (helpString,)
425 #
426 # Now its also possible to pass:
427 #
428 # helpString
429 # or wxItemKind
430 # or (helpString)
431 # or (wxItemKind)
432 #
433 # When you use this new style, Metamenus will check if the thing passed
434 # can be translated as an item kind (either wx.RADIO, "radio", etc.) or
435 # not, and so will try to guess what to do with the thing. Note that if
436 # you want a status bar showing something like "radio", you'll not be
437 # able to use this new style, but ("radio",) will still work for such
438 # purposes, though.
439 #
440 # - xaccel, a new kwarg available in MenuBarEx, allows one to bind events
441 # to 'items' that are not actually menu items, rather methods or
442 # functions that are triggered when some key or combination of keys is
443 # pressed.
444 #
445 # xaccel is a list of tuples (accel, function), where accel is a string
446 # following the accelerator syntax described in wx.Menu.Append docs and
447 # function is the function/method to be executed when the accelerator is
448 # triggered.
449 #
450 # The events will be managed in the same way as MenuBarEx events. IOW,
451 # xaccel accelerators will provide some sort of 'invisible menu items'.
452 #
453 # Version 0.02: severe code clean-up; accelerators for submenus now work.
454 #
455 # Version 0.01: initial release.
456
457 #----------------------------------------------------------------------------
458
459 import wx
460 from wx.lib.newevent import NewEvent
461
462 # Events --------------------------------------------------------------------
463
464 (MenuExBeforeEvent, EVT_BEFOREMENU) = NewEvent()
465 (MenuExAfterEvent, EVT_AFTERMENU) = NewEvent()
466
467 # Constants -----------------------------------------------------------------
468
469 # If you're to use a different indentation level for menus, change
470 # _ind here.
471 _ind = 2 * " "
472
473 # _sep is used internally only and is a substring that _cannot_
474 # appear on any of the regular menu labels.
475 _sep = " @@@ "
476
477 # If you want to use different prefixes for methods called by this
478 # menubar/menu, change them here.
479 _prefixMB = "OnMB_"
480 _prefixM = "OnM_"
481
482 #----------------------------------------------------------------------------
483
484 class _sItem:
485 """
486 Internal use only. This provides a structure for parsing the 'trees'
487 supplied in a sane way.
488 """
489
490 def __init__(self, params):
491 self.parent = None
492 self.Id = wx.NewId()
493 self.params = self._adjust(params)
494 self.children = []
495
496 self.Update()
497
498
499 def _adjust(self, params):
500 """
501 This is responsible for formatting the args and kwargs for items
502 supplied within the 'tree'.
503 """
504
505 args = (); kwargs = {}
506 params = params + [None] * (3 - len(params))
507
508 if type(params[1]) == tuple:
509 args = params[1]
510 elif type(params[1]) in [str, unicode, int]:
511 args = (params[1],)
512 elif type(params[1]) == dict:
513 kwargs = params[1]
514
515 if type(params[2]) == tuple:
516 args = params[2]
517 elif type(params[2]) in [str, unicode, int]:
518 args = (params[2],)
519 elif type(params[2]) == dict:
520 kwargs = params[2]
521
522 args = list(args) + [""] * (2 - len(args))
523
524 # For those who believe wx.UPPERCASE_STUFF_IS_UGLY... 8^)
525 kind_conv = {"radio": wx.ITEM_RADIO,
526 "check": wx.ITEM_CHECK,
527 "normal": wx.ITEM_NORMAL}
528
529 if args[0] in kind_conv.keys() + kind_conv.values():
530 args = (args[1], args[0])
531
532 kind_conv.update({"normal": None, "": None})
533
534 if type(args[1]) in [str, unicode]:
535 kind = kind_conv.get(args[1])
536 if kind is not None:
537 args = (args[0], kind)
538 else:
539 args = (args[0],)
540
541 return (params[0], tuple(args), kwargs)
542
543
544 def Update(self):
545 # Members created/updated here:
546 #
547 # label: "&New\tCtrl+N"
548 # label_text: "&New"
549 # tlabel: "&Novo\tCtrl+N"
550 # tlabel_text: "&Novo"
551 # acc: "Ctrl+N"
552 #
553 # I'm not actually using all of them right now, but maybe I will...
554
555 self.label = self.params[0].strip()
556 self.label_text = self.label.split("\t")[0].strip()
557 label, acc = (self.label.split("\t") + [''])[:2]
558 self.tlabel_text = wx.GetTranslation(label.strip())
559 self.acc = acc.strip()
560 if self.acc:
561 self.tlabel = "\t".join([self.tlabel_text, self.acc])
562 else:
563 self.tlabel = self.tlabel_text
564
565
566 def AddChild(self, Item):
567 Item.parent = self
568 self.children.append(Item)
569 return Item
570
571
572 def GetRealLabel(self, i18n):
573 if i18n:
574 label = self.GetLabelTranslation()
575 else:
576 label = self.GetLabel()
577 return label
578
579
580 def GetLabel(self):
581 return self.label
582
583
584 def GetLabelText(self):
585 return self.label_text
586
587
588 def GetLabelTranslation(self):
589 return self.tlabel
590
591
592 def GetLabelTextTranslation(self):
593 return self.tlabel_text
594
595
596 def GetAccelerator(self):
597 return self.acc
598
599
600 def GetId(self):
601 return self.Id
602
603
604 def GetParams(self):
605 return self.params
606
607
608 def GetParent(self):
609 return self.parent
610
611
612 def GetChildren(self, recursive=False):
613 def _walk(Item, r):
614 for child in Item.GetChildren():
615 r.append(child)
616 if child.HasChildren():
617 _walk(child, r)
618 return r
619
620 if not recursive:
621 return self.children
622 else:
623 return _walk(self, [])
624
625
626 def HasChildren(self):
627 return bool(self.children)
628
629
630 def GetChildWithChildren(self):
631 def _walk(Item, r):
632 for child in Item.GetChildren():
633 if child.HasChildren():
634 r.insert(0, child); _walk(child, r)
635 return r
636
637 return _walk(self, [])
638
639
640 def GetChildWithId(self, Id):
641 r = None
642 for child in self.GetChildren(True):
643 if child.GetId() == Id:
644 r = child; break
645 return r
646
647
648 def GetPath(self):
649 this = self; path = this.GetLabelText()
650
651 while this.GetParent() is not None:
652 this = this.GetParent()
653 path = "%s %s %s" % (this.GetLabelText(), _sep, path)
654
655 return path
656
657
658 def SetMethod(self, prefix, custfunc):
659 menuName = _clean(self.GetPath())
660
661 method_custom = custfunc.get(menuName)
662 method_default = prefix + menuName
663
664 # If a custfunc was passed here, use it; otherwise we'll use a
665 # default method name when this menu item is selected.
666 self.method = method_custom or method_default
667
668 # We also store a reference to all method names that the public
669 # methods can address.
670 self.all_methods = {method_custom: self.GetId(),
671 method_default: self.GetId(),
672 menuName: self.GetId()}
673
674
675 def GetMethod(self):
676 return self.method
677
678
679 def GetAllMethods(self):
680 return self.all_methods
681
682 #----------------------------------------------------------------------------
683
684 class _acceleratorTable:
685 """
686 Internal use only.
687
688 The main purposes here are to provide MenuBarEx support for accelerators
689 unhandled by the original wxMenu implementation (currently we only handle
690 numpad accelerators here) and to allow user to define accelerators
691 (passing the kwarg xaccel on MenuBarEx.__init__) that work even though
692 they're not associated to a menu item.
693 """
694
695 def __init__(self, xaccel=None):
696 """
697 Constructor.
698
699 xaccel is a list of tuples (accel, function), where accel is a string
700 following the accelerator syntax described in wx.Menu.Append docs and
701 function is the function/method to be executed when the
702 accelerator is triggered.
703 """
704
705 self.entries = []
706
707 self.flag_conv = {"alt" : wx.ACCEL_ALT,
708 "shift": wx.ACCEL_SHIFT,
709 "ctrl" : wx.ACCEL_CTRL,
710 "" : wx.ACCEL_NORMAL}
711
712 xaccel = xaccel or (); n = []
713 for acc, fctn in xaccel:
714 flags, keyCode = self._parseEntry(acc)
715 if flags != None and keyCode != None:
716 n.append((flags, keyCode, fctn))
717 self.xaccel = n
718
719
720 def _parseEntry(self, acc):
721 """Support for unhandled accelerators."""
722
723 lacc = acc.lower()
724 flags, keyCode = None, None
725
726 # Process numpad keys...
727 if "num" in lacc:
728
729 # flags...
730 if "+" in lacc:
731 flag = lacc.split("+")[:-1]
732 elif "-" in acc:
733 flag = lacc.split("-")[:-1]
734 else:
735 flag = [""]
736
737 flags = 0
738 for rflag in flag:
739 flags |= self.flag_conv[rflag.strip()]
740
741 # keycode...
742 exec("keyCode = wx.WXK_NUMPAD%s" % lacc.split("num")[1].strip())
743
744 return flags, keyCode
745
746
747 def Convert(self, cmd, accel):
748 """
749 Converts id and accelerator supplied into wx.AcceleratorEntry
750 objects.
751 """
752
753 flags, keyCode = self._parseEntry(accel)
754 if flags != None and keyCode != None:
755 self.entries.append(wx.AcceleratorEntry(flags, keyCode, cmd))
756
757
758 def Assemble(self, MBIds):
759 """Assembles the wx.AcceleratorTable."""
760
761 for flags, keyCode, fctn in self.xaccel:
762 _id = wx.NewId(); MBIds[_id] = fctn
763 self.entries.append(wx.AcceleratorEntry(flags, keyCode, _id))
764
765 return MBIds, wx.AcceleratorTable(self.entries)
766
767 #----------------------------------------------------------------------------
768
769 def _process_kwargs(item, kwargs, margin, font):
770 """
771 Internal use only. This is responsible for setting font, margin and
772 colour for menu items.
773 """
774
775 if kwargs.has_key("bmpChecked"):
776 checked = kwargs["bmpChecked"]
777 unchecked = kwargs.get("bmpUnchecked", wx.NullBitmap)
778
779 if callable(checked):
780 checked = checked()
781 if callable(unchecked):
782 unchecked = unchecked()
783
784 item.SetBitmaps(checked, unchecked)
785
786 kwlist = [("font", "SetFont"),
787 ("margin", "SetMarginWidth"),
788 ("fgColour", "SetTextColour"),
789 ("bgColour", "SetBackgroundColour")]
790
791 for kw, m in kwlist:
792 if kwargs.has_key(kw):
793 getattr(item, m)(kwargs[kw])
794
795 if margin != wx.DEFAULT:
796 item.SetMarginWidth(margin)
797
798 if font != wx.NullFont:
799 item.SetFont(font)
800
801 return item
802
803 #----------------------------------------------------------------------------
804
805 def _evolve(a):
806 """Internal use only. This will parse the menu 'tree' supplied."""
807
808 top = _sItem(a[0]); il = 0; cur = {il: top}
809
810 for i in range(1, len(a)):
811 params = a[i]
812 level = params[0].count(_ind) - 1
813
814 if level > il:
815 il += 1; cur[il] = new_sItem
816 elif level < il:
817 il = level
818
819 new_sItem = cur[il].AddChild(_sItem(params))
820
821 return top
822
823 #----------------------------------------------------------------------------
824
825 def _clean(s):
826 """Internal use only. Removes all non-alfanumeric chars from a string."""
827
828 return "".join([x for x in s if x.isalnum()])
829
830 #----------------------------------------------------------------------------
831
832 def _makeMenus(wxmenus, saccel, h, k, margin, font, i18n):
833 """Internal use only. Creates menu items."""
834
835 label = h.GetRealLabel(i18n); Id = h.GetId()
836 args, kwargs = h.GetParams()[1:]
837
838 if h.HasChildren():
839 args = (wxmenus[h], Id, label) + args
840 item = wx.MenuItem(*args, **{"subMenu": wxmenus[h]})
841 item = _process_kwargs(item, kwargs, margin, font)
842 wxmenus[k].AppendItem(item)
843 if saccel is not None:
844 saccel.Convert(item.GetId(), h.GetAccelerator())
845
846 else:
847 if label == "-":
848 wxmenus[k].AppendSeparator()
849
850 elif label == "/":
851 wxmenus[k].Break()
852
853 else:
854 args = (wxmenus[k], Id, label) + args
855 item = wx.MenuItem(*args)
856 item = _process_kwargs(item, kwargs, margin, font)
857 wxmenus[k].AppendItem(item)
858 if saccel is not None:
859 saccel.Convert(item.GetId(), h.GetAccelerator())
860
861 #----------------------------------------------------------------------------
862
863 class _mmprep:
864 """
865 Generates a temporary file that can be read by gettext utilities in order
866 to create a .po file with strings to be translated. This class is called
867 when you run metamenus from the command line.
868
869 Usage:
870 1. Make sure your menus are in a separate file and that the separate
871 file in question contain only your menus;
872
873 2. From a command line, type:
874 metamenus.py separate_file outputfile
875
876 where 'separate_file' is the python file containing the menu 'trees',
877 and 'outputfile' is the python-like file generated that can be parsed
878 by gettext utilities.
879
880 To get a .po file containing the translatable strings, put the
881 'outputfile' in the app.fil list of translatable files and run the
882 mki18n.py script. For more info please see
883 <http://wiki.wxpython.org/index.cgi/Internationalization>.
884 """
885
886 def __init__(self, filename, outputfile):
887 """Constructor."""
888
889 print "Parsing %s.py..." % filename
890
891 exec("import %s" % filename)
892 mod = eval(filename)
893
894 objs = []
895 for obj in dir(mod):
896 if type(getattr(mod, obj)) == list:
897 objs.append(obj)
898
899 all_lines = []
900 for obj in objs:
901 gerr = False; header = ["\n# Strings for '%s':\n" % obj]
902 err, lines = self.parseMenu(mod, obj)
903 if not err:
904 print "OK: parsed '%s'" % obj
905 all_lines += header + lines
906 else:
907 err, lines = self.parseMenuBar(mod, obj)
908 if not err:
909 print "OK: parsed '%s'" % obj
910 all_lines += header + lines
911 else:
912 gerr = True
913 if gerr:
914 print "Warning: couldn't parse '%s'" % obj
915
916 try:
917 f = file("%s.py" % outputfile, "w")
918 f.writelines(all_lines)
919 f.close()
920 print "File %s.py succesfully written." % outputfile
921
922 except:
923 print "ERROR: File %s.py was NOT written." % outputfile
924 raise
925
926
927 def form(self, lines):
928 """Removes separators and breaks and adds gettext stuff."""
929
930 new_lines = []
931 for line in lines:
932 if line not in ["-", "/"]:
933 new_lines.append("_(" + `line` + ")\n")
934 return new_lines
935
936
937 def parseMenuBar(self, mod, obj):
938 """Tries to parse a MenuBarEx object."""
939
940 err = False; lines = []
941 try:
942 for menu in getattr(mod, obj):
943 top = _evolve(menu)
944 lines.append(top.GetLabelText())
945 for child in top.GetChildren(True):
946 lines.append(child.GetLabelText())
947 except:
948 err = True
949
950 return err, self.form(lines)
951
952
953 def parseMenu(self, mod, obj):
954 """Tries to parse a MenuEx object."""
955
956 err = False; lines = []
957 try:
958 top = _evolve(getattr(mod, obj))
959 lines.append(top.GetLabelText())
960 for child in top.GetChildren(True):
961 lines.append(child.GetLabelText())
962 except:
963 err = True
964
965 return err, self.form(lines)
966
967
968 # MenuBarEx Main stuff ------------------------------------------------------
969
970 class MenuBarEx(wx.MenuBar):
971 def __init__(self, *args, **kwargs):
972 """
973 Constructor.
974 MenuBarEx(parent, menus, margin=wx.DEFAULT, font=wx.NullFont,
975 xaccel=None, custfunc={}, i18n=True, style=0)
976 """
977
978 # Initializing...
979 self.parent, menus = args
980 margin = kwargs.pop("margin", wx.DEFAULT)
981 font = kwargs.pop("font", wx.NullFont)
982 xaccel = kwargs.pop("xaccel", None)
983 custfunc = kwargs.pop("custfunc", {})
984 i18n = self.i18n = kwargs.pop("i18n", True)
985
986 wx.MenuBar.__init__(self, **kwargs)
987
988 # An object to handle accelerators.
989 self.accel = _acceleratorTable(xaccel)
990
991 # A reference to all of the sItems involved.
992 tops = []
993
994 # For each menu...
995 for a in menus:
996 # Parse the menu 'tree' supplied.
997 top = _evolve(a)
998
999 # Create these menus first...
1000 wxmenus = {top: wx.Menu()}
1001 for k in top.GetChildWithChildren():
1002 wxmenus[k] = wx.Menu()
1003
1004 # ...and append their respective children.
1005 for h in k.GetChildren():
1006 _makeMenus(wxmenus, self.accel, h, k, margin, font, i18n)
1007
1008 # Now append these items to the top level menu.
1009 for h in top.GetChildren():
1010 _makeMenus(wxmenus, self.accel, h, top, margin, font, i18n)
1011
1012 # Now append the top menu to the menubar.
1013 self.Append(wxmenus[top], top.GetRealLabel(i18n))
1014
1015 # Store a reference of this sItem.
1016 tops.append(top)
1017
1018 # Now find out what are the methods that should be called upon
1019 # menu items selection.
1020 MBIds = {}; self.MBStrings = {}
1021 for top in tops:
1022 for child in top.GetChildren(True):
1023 child.SetMethod(_prefixMB, custfunc)
1024 MBIds[child.GetId()] = child
1025 self.MBStrings.update(child.GetAllMethods())
1026
1027 # It won't hurt if we get rid of a None key, if any.
1028 bogus = self.MBStrings.pop(None, None)
1029
1030 # We store the position of top-level menus rather than ids because
1031 # wx.Menu.EnableTop uses positions...
1032 for i, top in enumerate(tops):
1033 self.MBStrings[_clean(top.GetLabelText())] = i
1034 MBIds[i] = top
1035
1036 # Nice class. 8^) Will take care of this automatically.
1037 self.parent.SetMenuBar(self)
1038 self.parent.Bind(wx.EVT_MENU, self.OnMB_)
1039
1040 # Now do something about the accelerators...
1041 self.MBIds, at = self.accel.Assemble(MBIds)
1042 self.parent.SetAcceleratorTable(at)
1043
1044
1045 def OnMB_(self, evt):
1046 """
1047 Called on all menu events for this menu. It will in turn call
1048 the related method on parent, if any.
1049 """
1050
1051 try:
1052 attr = self.MBIds[evt.GetId()]
1053
1054 self.OnMB_before()
1055
1056 # Trigger everything except stuff passed via xaccel.
1057 if isinstance(attr, _sItem):
1058 attr_name = attr.GetMethod()
1059
1060 if callable(attr_name):
1061 attr_name()
1062 elif hasattr(self.parent, attr_name) and \
1063 callable(getattr(self.parent, attr_name)):
1064 getattr(self.parent, attr_name)()
1065 else:
1066 print "%s not found in parent." % attr_name
1067
1068 # Trigger something passed via xaccel.
1069 elif callable(attr):
1070 attr()
1071
1072 self.OnMB_after()
1073
1074 except KeyError:
1075 # Maybe another menu was triggered elsewhere in parent.
1076 pass
1077
1078 #evt.Skip() - removed. see http://www.archivum.info/comp.soft-sys.wxwindows/2008-07/msg00027.html
1079
1080
1081 def OnMB_before(self):
1082 """
1083 If you need to execute something right before a menu event is
1084 triggered, you can bind the EVT_BEFOREMENU.
1085 """
1086
1087 evt = MenuExBeforeEvent(obj=self)
1088 wx.PostEvent(self, evt)
1089
1090
1091 def OnMB_after(self):
1092 """
1093 If you need to execute something right after a menu event is
1094 triggered, you can bind the EVT_AFTERMENU.
1095 """
1096
1097 evt = MenuExAfterEvent(obj=self)
1098 wx.PostEvent(self, evt)
1099
1100
1101 # Public methods --------------------------------------------------------
1102
1103 def UpdateMenus(self):
1104 """
1105 Call this to update menu labels whenever the current locale
1106 changes.
1107 """
1108
1109 if not self.i18n:
1110 return
1111
1112 for k, v in self.MBIds.items():
1113 # Update top-level menus
1114 if not v.GetParent():
1115 v.Update()
1116 self.SetLabelTop(k, v.GetRealLabel(self.i18n))
1117 # Update other menu items
1118 else:
1119 item = self.FindItemById(k)
1120 if item is not None: # Skip separators
1121 v.Update()
1122 self.SetLabel(k, v.GetRealLabel(self.i18n))
1123
1124
1125 def GetMenuState(self, menu_string):
1126 """Returns True if a checkable menu item is checked."""
1127
1128 this = self.MBStrings[menu_string]
1129 return self.IsChecked(this)
1130
1131
1132 def SetMenuState(self, menu_string, check=True):
1133 """Toggles a checkable menu item checked or unchecked."""
1134
1135 this = self.MBStrings[menu_string]
1136 self.Check(this, check)
1137
1138
1139 def EnableItem(self, menu_string, enable=True):
1140 """Enables or disables a menu item via its label."""
1141
1142 this = self.MBStrings[menu_string]
1143 self.Enable(this, enable)
1144
1145
1146 def EnableItems(self, menu_string_list, enable=True):
1147 """Enables or disables menu items via a list of labels."""
1148
1149 for menu_string in menu_string_list:
1150 self.EnableItem(menu_string, enable)
1151
1152
1153 def EnableTopMenu(self, menu_string, enable=True):
1154 """Enables or disables a top level menu via its label."""
1155
1156 this = self.MBStrings[menu_string]
1157 self.EnableTop(this, enable)
1158
1159
1160 def EnableTopMenus(self, menu_string_list, enable=True):
1161 """Enables or disables top level menus via a list of labels."""
1162
1163 for menu_string in menu_string_list:
1164 self.EnableTopMenu(menu_string, enable)
1165
1166
1167 # MenuEx Main stuff ---------------------------------------------------------
1168
1169 class MenuEx(wx.Menu):
1170 def __init__(self, *args, **kwargs):
1171 """
1172 Constructor.
1173
1174 MenuEx(parent, menu, margin=wx.DEFAULT, font=wx.NullFont,
1175 show_title=True, custfunc={}, i18n=True, style=0)
1176 """
1177
1178 # Initializing...
1179 self.parent, menu = args
1180 margin = kwargs.pop("margin", wx.DEFAULT)
1181 font = kwargs.pop("font", wx.NullFont)
1182 show_title = kwargs.pop("show_title", True)
1183 custfunc = kwargs.pop("custfunc", {})
1184 i18n = self.i18n = kwargs.pop("i18n", True)
1185
1186 wx.Menu.__init__(self, **kwargs)
1187
1188 self._title = menu[0][0]
1189 if show_title:
1190 if i18n:
1191 self.SetTitle(wx.GetTranslation(self._title))
1192 else:
1193 self.SetTitle(self._title)
1194
1195 # Parse the menu 'tree' supplied.
1196 top = _evolve(menu)
1197
1198 # Create these menus first...
1199 wxmenus = {top: self}
1200 for k in top.GetChildWithChildren():
1201 wxmenus[k] = wx.Menu()
1202
1203 # ...and append their respective children.
1204 for h in k.GetChildren():
1205 _makeMenus(wxmenus, None, h, k, margin, font, i18n)
1206
1207 # Now append these items to the top level menu.
1208 for h in top.GetChildren():
1209 _makeMenus(wxmenus, None, h, top, margin, font, i18n)
1210
1211 # Now find out what are the methods that should be called upon
1212 # menu items selection.
1213 self.MenuIds = {}; self.MenuStrings = {}; self.MenuList = []
1214 for child in top.GetChildren(True):
1215 Id = child.GetId(); item = self.FindItemById(Id)
1216 if item:
1217 child.SetMethod(_prefixM, custfunc)
1218 self.MenuIds[Id] = child
1219 self.MenuStrings.update(child.GetAllMethods())
1220 self.MenuList.append([Id, child.GetPath()])
1221
1222 # Initialize menu states.
1223 self.MenuState = {}
1224 for Id in self.MenuIds.keys():
1225 if self.FindItemById(Id).IsCheckable():
1226 is_checked = self.IsChecked(Id)
1227 else:
1228 is_checked = False
1229 self.MenuState[Id] = is_checked
1230
1231 # Nice class. 8^) Will take care of this automatically.
1232 self.parent.Bind(wx.EVT_MENU, self.OnM_)
1233
1234
1235 def _update(self, i):
1236 def _resetRadioGroup(i):
1237 g = []; n = []
1238
1239 for Id, s in self.MenuList:
1240 item = self.FindItemById(Id)
1241 if item.GetKind() == wx.ITEM_RADIO:
1242 g.append(Id)
1243 else:
1244 g.append(None)
1245
1246 for x in range(g.index(i), 0, -1):
1247 if g[x] != None:
1248 n.append(g[x])
1249 else:
1250 break
1251
1252 for x in range(g.index(i) + 1, len(g)):
1253 if g[x] != None:
1254 n.append(g[x])
1255 else:
1256 break
1257
1258 for i in n:
1259 self.MenuState[i] = False
1260
1261 kind = self.FindItemById(i).GetKind()
1262
1263 if kind == wx.ITEM_CHECK:
1264 self.MenuState[i] = not self.IsChecked(i)
1265
1266 elif kind == wx.ITEM_RADIO:
1267 _resetRadioGroup(i)
1268 self.MenuState[i] = True
1269
1270
1271 def OnM_(self, evt):
1272 """
1273 Called on all menu events for this menu. It will in turn call
1274 the related method on parent, if any.
1275 """
1276
1277 try:
1278 attr = self.MenuIds[evt.GetId()]
1279
1280 self.OnM_before()
1281
1282 if isinstance(attr, _sItem):
1283 attr_name = attr.GetMethod()
1284
1285 if callable(attr_name):
1286 attr_name()
1287 elif hasattr(self.parent, attr_name) and \
1288 callable(getattr(self.parent, attr_name)):
1289 getattr(self.parent, attr_name)()
1290 else:
1291 print "%s not found in parent." % attr_name
1292
1293 self.OnM_after()
1294
1295 except KeyError:
1296 # Maybe another menu was triggered elsewhere in parent.
1297 pass
1298
1299 #evt.Skip() - removed. see http://www.archivum.info/comp.soft-sys.wxwindows/2008-07/msg00027.html
1300
1301
1302 def OnM_before(self):
1303 """
1304 If you need to execute something right before a menu event is
1305 triggered, you can bind the EVT_BEFOREMENU.
1306 """
1307
1308 evt = MenuExBeforeEvent(obj=self)
1309 wx.PostEvent(self, evt)
1310
1311
1312 def OnM_after(self):
1313 """
1314 If you need to execute something right after a menu event is
1315 triggered, you can bind the EVT_AFTERMENU.
1316 """
1317
1318 evt = MenuExAfterEvent(obj=self)
1319 wx.PostEvent(self, evt)
1320
1321
1322 # Public methods --------------------------------------------------------
1323
1324 def UpdateMenus(self):
1325 """
1326 Call this to update menu labels whenever the current locale
1327 changes.
1328 """
1329
1330 if not self.i18n:
1331 return
1332
1333 for k, v in MenuIds.items():
1334 item = self.FindItemById(k)
1335 if item is not None: # Skip separators
1336 v.Update()
1337 self.SetLabel(k, v.GetRealLabel(self.i18n))
1338
1339
1340 def Popup(self, evt):
1341 """Pops this menu up."""
1342
1343 [self.Check(i, v) for i, v in self.MenuState.items() \
1344 if self.FindItemById(i).IsCheckable()]
1345
1346 obj = evt.GetEventObject(); pos = evt.GetPosition()
1347 obj.PopupMenu(self, pos)
1348
1349
1350 def GetItemState(self, menu_string):
1351 """Returns True if the item is checked."""
1352
1353 this = self.MenuStrings[menu_string]
1354 return self.IsChecked(this)
1355
1356
1357 def SetItemState(self, menu_string, check):
1358 """Toggles a checkable menu item checked or unchecked."""
1359
1360 this = self.MenuStrings[menu_string]
1361 self.MenuState[this] = check
1362
1363
1364 def EnableItem(self, menu_string, enable=True):
1365 """Enables or disables a menu item via its label."""
1366
1367 this = self.MenuStrings[menu_string]
1368 self.Enable(this, enable)
1369
1370
1371 def EnableItems(self, menu_string_list, enable=True):
1372 """Enables or disables menu items via a list of labels."""
1373
1374 for menu_string in menu_string_list:
1375 self.EnableItem(menu_string, enable)
1376
1377
1378 def EnableAllItems(self, enable=True):
1379 """Enables or disables all menu items."""
1380
1381 for Id in self.MenuIds.keys():
1382 self.Enable(Id, enable)
1383
1384 #----------------------------------------------------------------------------
1385
1386 if __name__ == "__main__":
1387 import sys, os.path
1388 args = sys.argv[1:]
1389 if len(args) == 2:
1390 _mmprep(*[os.path.splitext(arg)[0] for arg in args])
1391 else:
1392 print """
1393 -----------------------------------------------------------------------------
1394 metamenus %s
1395
1396 %s
1397 %s
1398 Distributed under the wxWidgets license.
1399 -----------------------------------------------------------------------------
1400
1401 Usage:
1402 ------
1403
1404 metamenus.py separate_file outputfile
1405
1406 - 'separate_file' is the python file containing the menu 'trees';
1407 - 'outputfile' is the output file generated that can be parsed by the gettext
1408 utilities.
1409
1410 Please see metamenus.__doc__ (under the 'More about i18n' section) and
1411 metamenus._mmprep.__doc__ for more details.
1412 -----------------------------------------------------------------------------
1413 """ % (__version__, __author__, __date__)
1414
1415
1416 #
1417 ##
1418 ### eof