Mercurial > traipse_dev
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/orpg/tools/metamenus.py Tue Jul 14 16:41:58 2009 -0500 @@ -0,0 +1,1418 @@ +__author__ = "E. A. Tacao <e.a.tacao |at| estadao.com.br>" +__date__ = "21 Apr 2006, 13:15 GMT-03:00" +__version__ = "0.07" +__doc__ = """ +metamenus: classes that aim to simplify the use of menus in wxPython. + +MenuBarEx is a wx.MenuBar derived class for wxPython; +MenuEx is a wx.Menu derived class for wxPython. + +Some features: + +- Menus are created based on the indentation of items on a list. (See 'Usage' + below.) + +- Each menu item will trigger a method on the parent. The methods names may + be explicitly defined on the constructor or generated automatically. It's + also possible to define some names and let metamenus create the remaining. + +- Allows the user to enable or disable a menu item or an entire menu given + its label. + +- Supplies EVT_BEFOREMENU and EVT_AFTERMENU, events that are triggered right + before and after, respectively, the triggering of a EVT_MENU-bound method + on selection of some menu item. + +- MenuBarEx handles accelerators for numpad keys and also supports 'invisible + menu items'. + +- If your app is already i18n'd, menu items may be translated on the fly. + All you need to do is to write somewhere a .mo file containing the menu + translations. + + +MenuEx Usage: + +The MenuEx usage is similar to MenuBarEx (please see below), except that it +has an optional kwarg named show_title (boolean; controls whether the menu +title will be shown) and doesn't have the MenuBarEx's xaccel kwarg: + + MenuEx(self, menus, margin=wx.DEFAULT, show_title=True, + font=wx.NullFont, custfunc={}, i18n=True, style=0) + + +MenuBarEx Usage: + +In order to put a MenuBarEx inside a frame it's enough to do this: + MenuBarEx(self, menus) + +or you can also use some few optional keyword arguments: + MenuBarEx(self, menus, margin=wx.DEFAULT, font=wx.NullFont, + xaccel=None, custfunc={}, i18n=True, style=0) + + Arguments: + - self: The frame in question. + + - menus: A python list of 'menus', which are python lists of + 'menu_entries'. Each 'menu_entry' is a python list that needs to + be in one of the following formats: + + [label] + or [label, args] + or [label, kwargs] + or [label, args, kwargs] + or [label, kwargs, args] (but please don't do this one). + + . label: (string) The text for the menu item. + + Leading whitespaces at the beginning of a label are used to + compute the indentation level of the item, which in turn is + used to determine the grouping of items. MenuBarEx determines + one indentation level for every group of two whitespaces. + + If you want this item to be a sub-item, increase its + indentation. Top-level items must have no indentation. + + Separators are items labeled with a "-" and may not have args + and kwargs. + + Menu breaks (please see the wx.MenuItem.Break docs) are items + labeled with a "/" and may not have args and kwargs. + + Accelerators are handled as usual; MenuBarEx also supports + numpad accelerators (e.g, " &Forward\tCtrl+Num 8"). + + Please refer to the wxPython docs for wx.Menu.Append for more + information about them. + + . args: (tuple) (helpString, wxItemKind) + + - helpString is an optional help string that will be shown on + the parent's status bar. If don't pass it, no help string + for this item will appear on the statusbar. + + - wxItemKind may be one of wx.ITEM_CHECK, "check", + wx.ITEM_RADIO or "radio". It is also optional; if don't pass + one, a default wx.ITEM_NORMAL will be used. + + Note that if you have to pass only one argument, you can do + either: + + args=("", wxItemKind) + or args=(helpString,) + or helpString + or wxItemKind + or (helpString) + or (wxItemKind) + + When you pass only one item, Metamenus will check if the + thing passed can be translated as an item kind (either + wx.RADIO, "radio", etc.) or not, and so will try to guess + what to do with the thing. So that if you want a status bar + showing something that could be translated as an item kind, + say, "radio", you'll have to pass both arguments: ("radio",). + + + . kwargs: (dict) wxBitmap bmpChecked, wxBitmap bmpUnchecked, + wxFont font, int width, + wxColour fgcolour, wxColour bgcolour + + These options access wx.MenuItem methods in order to change + its appearance, and might not be present on all platforms. + They are internally handled as follows: + + key: item method: + + "bmpChecked" and "bmpUnchecked" : SetBitmaps + "font" : SetFont + "margin", : SetMarginWidth + "fgColour", : SetTextColour + "bgColour", : SetBackgroundColour + + The "bmpChecked" and "bmpUnchecked" options accept a bitmap or + a callable that returns a bitmap when called. This is useful + if you created your bitmaps with encode_bitmaps.py and want to + pass something like {"bmpChecked": my_images.getSmilesBitmap}. + + Please refer to the wxPython docs for wx.MenuItem for more + information about the item methods. + + - margin: (int) a value that will be used to do a SetMargin() for each + menubar item. Please refer to the wxPython docs for + wx.MenuItem.SetMargin for more information about this. + + - font: (wx.Font) a value that will be used to do a SetFont() for + each menu item. Please refer to the wxPython docs for + wx.MenuItem.SetFont for more information about this. + + - xaccel: (MenuBarEx only) allows one to bind events to 'items' that + are not actually menu items, rather methods or functions that + are triggered when some key or combination of keys is + pressed. + + xaccel is a list of tuples (accel, function), where accel is + a string following the accelerator syntax described in the + wx.Menu.Append docs and function is the function/method to be + executed when the accelerator is triggered. + + The events are managed in the same way as MenuBarEx events. + + - custfunc: (dict) allows one to define explicitly what will be the + parent's method called on a menu event. + + By default, all parent's methods have to start with "OnMB_" + (for menubars) or "OnM_" (for menus) plus the full menu + 'path'. For a 'Save' menu item inside a 'File' top menu, e.g: + + def OnMB_FileSave(self): + self.file.save() + + However, the custfunc arg allows you to pass a dict of + + {menupath: method, menupath: method, ...} + + so that if you want your File > Save menu triggering a + 'onSave' method instead, you may pass + + {"FileSave": "onSave"} + or {"FileSave": self.onSave} + + as custfunc. This way, your parent's method should look like + this instead: + + def onSave(self): + self.file.save() + + You don't have to put all menu items inside custfunc. The + menupaths not found there will still trigger automatically + an OnMB_/OnM_-prefixed method. + + - i18n: (bool) Controls whether you want the items to be translated + or not. Default is True. For more info on i18n, please see + 'More about i18n' below. + + - style: Please refer to the wxPython docs for wx.MenuBar/wx.Menu for + more information about this. + + +The public methods: + + The 'menu_string' arg on some of the public methods is a string that + refers to a menu item. For a File > Save menu, e. g., it may be + "OnMB_FileSave", "FileSave" or the string you passed via the custfunc + parameter (i. e., if you passed {"FileSave": "onSave"} as custfunc, the + string may also be "onSave"). + + The 'menu_string_list' arg on some of the public methods is a python list + of 'menu_string' strings described above. Please refer to the methods + themselves for more details. + + +More about i18n: + If you want to get your menu items automatically translated, you'll need + to: + + 1. Create a directory named 'locale' under your app's directory, and under + the 'locale', create subdirectories named after the canonical names of + the languages you're going to use (e. g., 'pt_BR', 'es_ES', etc.) + + 2. Inside each of the subdirectories, write a gettext compiled catalog file + (e. g., "my_messages.mo") containing all of the menu labels translated + to the language represented by the subdirectory. + + 4. The language can be changed on the fly. Whenever you want to change the + menu language, execute these lines somewhere in your app: + + l = wx.Locale(wx.LANGUAGE_PORTUGUESE_BRAZILIAN) + l.AddCatalogLookupPathPrefix("locale") + l.AddCatalog("my_messages.mo") + self.my_menu.UpdateMenus() + + Unless you want your menus showing up in pt_BR, replace the + wx.LANGUAGE_PORTUGUESE_BRAZILIAN above by the proper language identifier. + For a list of supported identifiers please see the wxPython docs, under the + 'Constants\Language identifiers' section. + + Some items may show up in the selected language even though you didn't + create a .mo file for the translations. That's because wxPython looks for + them in the wxstd.mo file placed somewhere under the wxPython tree, and + maybe wxPython already uses some of the string you are using. + + Note that if you're to distribute a standalone app the wxPython tree may + not be present, so it's a good idea to include a specific .mo file in your + package. On the other hand, if by any reason you _don't_ want the menu + items to be translated, you may pass a i18n=False kwarg to the constructor. + + You can use metamenus itself directly from a command line to help on + creating a gettext-parseable file based on the menus you wrote. For more + info about this, please see the docs for the _mmprep class. + + For more info about i18n, .mo files and gettext, please see + <http://wiki.wxpython.org/index.cgi/Internationalization>. + + +Menu bar example: + + a = [["File"], + [" New", "Creates a new file"], + [" Save"], + [" -"], + [" Preview", "Preview Document", + {"bmpChecked": images.getSmilesBitmap(), + "fgColour": wx.RED}], + [" -"], + [" Exit"]] + + b = [["Edit"], + [" Cut"], + [" Copy"], + [" Foo", "check"], + [" Bar", "check"], + [" Paste"]] + + myMenuBar = MenuBarEx(self, [a, b]) + + +Context menu example: + + a = [["Edit"], # A 'top-level' menu item is used as title; + [" Cut"], + [" Copy"], + [" Foo", "radio"], + [" Bar", "radio"], + [" Paste"]] + + myContextMenu = MenuEx(self, a) + + +If you don't want to show the title for the context menu: + + myContextMenu = MenuEx(self, a, show_title=False) + + +A very default 'File' menu example: + + [ + ['&File'], + [' &New\tCtrl+N'], + [' &Open...\tCtrl+O'], + [' &Save\tCtrl+S'], + [' Save &As...\tCtrl+Shift+S'], + [' -'], + [' Publis&h\tCtrl+Shift+P'], + [' -'], + [' &Close\tCtrl+W'], + [' C&lose All'], + [' -'], + [' E&xit\tAlt+X'] + ] + + +Known Issues: + +These are wx.Menu issues, and since metamenus doesn't/can't work around them +it's advisable to stay away from custom menus: + +- If you try to customize an item changing either its font, margin or +colours, the following issues arise: + + 1. The item will appear shifted to the right when compared to default menu + items, although a GetMarginWidth() will return a default value; + 2. wx.ITEM_RADIO items won't show their bullets. + +- If you try to change the bitmaps for wx.ITEM_RADIO items, the items will +ignore the 2nd bitmap passed and will always show the checked bitmap, +regardless of their state. + + +About: + +metamenus is distributed under the wxWidgets license. + +This code should meet the wxPython Coding Guidelines +<http://www.wxpython.org/codeguidelines.php> and the wxPython Style Guide +<http://wiki.wxpython.org/index.cgi/wxPython_20Style_20Guide>. + +For all kind of problems, requests, enhancements, bug reports, etc, +please drop me an e-mail. + +For updates please visit <http://j.domaindlx.com/elements28/wxpython/>. +""" + +# History: +# +# Version 0.07: +# - Applied a patch from Michele Petrazzo which now allows the values +# passed via the custfunc dictionary to be either callable objects +# or strings representing the callable objects. +# +# Version 0.06: +# - Added i18n capabilities. Running metamenus.py from the command line +# also creates a gettext-parseable file than can in turn be used to +# create .po files. +# +# - Some minor syntax fixes so that this code hopefully should meet the +# wxPython Coding Guidelines and the wxPython Style Guide. +# +# - Changed EVT_BEFOREMENU_EVENT to EVT_BEFOREMENU and EVT_AFTERMENU_EVENT +# to EVT_AFTERMENU. If your app was using them, please update it. +# +# - Fixed a test into OnMB_ that would raise an error on unicode systems; +# thanks to Michele Petrazzo for pointing this out. +# +# - Fixed the EVT_MENU binding so that the accelerators now should work +# on Linux; thanks to Michele Petrazzo for pointing this out. +# +# - Fixed a couple of bad names in the public methods (EnableMenuTop to +# EnableTopMenu, etc.) that would prevent the methods to work. +# +# - Fixed a bug that would prevent checkable items to be created when +# only a tupleless wxItemKind was passed within a menu item. +# +# - Fixed a couple of potential unicode bugs in _adjust that could arise +# if unicode objects were passed as menu items or help strings. +# +# - Changes in _sItem: _adjust now is a method of _sItem; GetPath +# substituted _walkMenu/_walkMenuBar; _sItem now finds a translated +# label when using 18n, etc. +# +# - All of the menu item strings passed to the public methods now may be +# in one of the following forms: (1) The full menu 'path' (e. g., +# "FileSave"), (2) The prefix + the full menu 'path' (e. g., +# "OnMB_FileSave"), (3) The method name passed as custfunc (e. g., if +# you passed {"FileSave": "onSave"} as custfunc, the string may also +# be "onSave"). +# +# - "bmpChecked" and "bmpUnchecked" options now may accept a bitmap or +# a callable that returns a bitmap when called. This is useful if your +# menu 'tree' is in another file and you import it _before_ your app is +# created, since BitmapFromImage can only be used if the app is already +# out there. +# +# Version 0.05: +# - Fixed the popup menu position on MenuEx. +# +# - Applied a patch from Michele Petrazzo which implemented the custfunc +# funcionality, allowing one to choose arbitrary names for methods +# called on menu events. +# +# Version 0.04: +# - Changed the OnMB_, OnM_ code so that they won't shadow AttributeErrors +# raised on parent's code. +# +# - Add the show_title kwarg to the MenuEx constructor. +# +# Version 0.03: +# - Added support for numpad accelerators; they must be passed as "Num x", +# where x may be into a [0-9] range. +# +# - Added support for wx.MenuItem.Break(); if you want a menu break, +# now you can pass a "/" on a menu entry label. +# +# - Added the EVT_BEFOREMENU_EVENT, which will be triggered right before +# the menu event. +# +# - Those who believe that wx.UPPERCASE_STUFF_IS_UGLY 8^) now can pass +# "radio" instead of wx.ITEM_RADIO, "check" instead of wx.ITEM_CHECK, and +# "normal" (or "", or even nothing at all) instead of wx.ITEM_NORMAL. +# +# - The args syntax has been extended. The previous version allowed one to +# pass either: +# +# (helpString, wxItemKind) +# or ("", wxItemKind) +# or (helpString,) +# +# Now its also possible to pass: +# +# helpString +# or wxItemKind +# or (helpString) +# or (wxItemKind) +# +# When you use this new style, Metamenus will check if the thing passed +# can be translated as an item kind (either wx.RADIO, "radio", etc.) or +# not, and so will try to guess what to do with the thing. Note that if +# you want a status bar showing something like "radio", you'll not be +# able to use this new style, but ("radio",) will still work for such +# purposes, though. +# +# - xaccel, a new kwarg available in MenuBarEx, allows one to bind events +# to 'items' that are not actually menu items, rather methods or +# functions that are triggered when some key or combination of keys is +# pressed. +# +# xaccel is a list of tuples (accel, function), where accel is a string +# following the accelerator syntax described in wx.Menu.Append docs and +# function is the function/method to be executed when the accelerator is +# triggered. +# +# The events will be managed in the same way as MenuBarEx events. IOW, +# xaccel accelerators will provide some sort of 'invisible menu items'. +# +# Version 0.02: severe code clean-up; accelerators for submenus now work. +# +# Version 0.01: initial release. + +#---------------------------------------------------------------------------- + +import wx +from wx.lib.newevent import NewEvent + +# Events -------------------------------------------------------------------- + +(MenuExBeforeEvent, EVT_BEFOREMENU) = NewEvent() +(MenuExAfterEvent, EVT_AFTERMENU) = NewEvent() + +# Constants ----------------------------------------------------------------- + +# If you're to use a different indentation level for menus, change +# _ind here. +_ind = 2 * " " + +# _sep is used internally only and is a substring that _cannot_ +# appear on any of the regular menu labels. +_sep = " @@@ " + +# If you want to use different prefixes for methods called by this +# menubar/menu, change them here. +_prefixMB = "OnMB_" +_prefixM = "OnM_" + +#---------------------------------------------------------------------------- + +class _sItem: + """ + Internal use only. This provides a structure for parsing the 'trees' + supplied in a sane way. + """ + + def __init__(self, params): + self.parent = None + self.Id = wx.NewId() + self.params = self._adjust(params) + self.children = [] + + self.Update() + + + def _adjust(self, params): + """ + This is responsible for formatting the args and kwargs for items + supplied within the 'tree'. + """ + + args = (); kwargs = {} + params = params + [None] * (3 - len(params)) + + if type(params[1]) == tuple: + args = params[1] + elif type(params[1]) in [str, unicode, int]: + args = (params[1],) + elif type(params[1]) == dict: + kwargs = params[1] + + if type(params[2]) == tuple: + args = params[2] + elif type(params[2]) in [str, unicode, int]: + args = (params[2],) + elif type(params[2]) == dict: + kwargs = params[2] + + args = list(args) + [""] * (2 - len(args)) + + # For those who believe wx.UPPERCASE_STUFF_IS_UGLY... 8^) + kind_conv = {"radio": wx.ITEM_RADIO, + "check": wx.ITEM_CHECK, + "normal": wx.ITEM_NORMAL} + + if args[0] in kind_conv.keys() + kind_conv.values(): + args = (args[1], args[0]) + + kind_conv.update({"normal": None, "": None}) + + if type(args[1]) in [str, unicode]: + kind = kind_conv.get(args[1]) + if kind is not None: + args = (args[0], kind) + else: + args = (args[0],) + + return (params[0], tuple(args), kwargs) + + + def Update(self): + # Members created/updated here: + # + # label: "&New\tCtrl+N" + # label_text: "&New" + # tlabel: "&Novo\tCtrl+N" + # tlabel_text: "&Novo" + # acc: "Ctrl+N" + # + # I'm not actually using all of them right now, but maybe I will... + + self.label = self.params[0].strip() + self.label_text = self.label.split("\t")[0].strip() + label, acc = (self.label.split("\t") + [''])[:2] + self.tlabel_text = wx.GetTranslation(label.strip()) + self.acc = acc.strip() + if self.acc: + self.tlabel = "\t".join([self.tlabel_text, self.acc]) + else: + self.tlabel = self.tlabel_text + + + def AddChild(self, Item): + Item.parent = self + self.children.append(Item) + return Item + + + def GetRealLabel(self, i18n): + if i18n: + label = self.GetLabelTranslation() + else: + label = self.GetLabel() + return label + + + def GetLabel(self): + return self.label + + + def GetLabelText(self): + return self.label_text + + + def GetLabelTranslation(self): + return self.tlabel + + + def GetLabelTextTranslation(self): + return self.tlabel_text + + + def GetAccelerator(self): + return self.acc + + + def GetId(self): + return self.Id + + + def GetParams(self): + return self.params + + + def GetParent(self): + return self.parent + + + def GetChildren(self, recursive=False): + def _walk(Item, r): + for child in Item.GetChildren(): + r.append(child) + if child.HasChildren(): + _walk(child, r) + return r + + if not recursive: + return self.children + else: + return _walk(self, []) + + + def HasChildren(self): + return bool(self.children) + + + def GetChildWithChildren(self): + def _walk(Item, r): + for child in Item.GetChildren(): + if child.HasChildren(): + r.insert(0, child); _walk(child, r) + return r + + return _walk(self, []) + + + def GetChildWithId(self, Id): + r = None + for child in self.GetChildren(True): + if child.GetId() == Id: + r = child; break + return r + + + def GetPath(self): + this = self; path = this.GetLabelText() + + while this.GetParent() is not None: + this = this.GetParent() + path = "%s %s %s" % (this.GetLabelText(), _sep, path) + + return path + + + def SetMethod(self, prefix, custfunc): + menuName = _clean(self.GetPath()) + + method_custom = custfunc.get(menuName) + method_default = prefix + menuName + + # If a custfunc was passed here, use it; otherwise we'll use a + # default method name when this menu item is selected. + self.method = method_custom or method_default + + # We also store a reference to all method names that the public + # methods can address. + self.all_methods = {method_custom: self.GetId(), + method_default: self.GetId(), + menuName: self.GetId()} + + + def GetMethod(self): + return self.method + + + def GetAllMethods(self): + return self.all_methods + +#---------------------------------------------------------------------------- + +class _acceleratorTable: + """ + Internal use only. + + The main purposes here are to provide MenuBarEx support for accelerators + unhandled by the original wxMenu implementation (currently we only handle + numpad accelerators here) and to allow user to define accelerators + (passing the kwarg xaccel on MenuBarEx.__init__) that work even though + they're not associated to a menu item. + """ + + def __init__(self, xaccel=None): + """ + Constructor. + + xaccel is a list of tuples (accel, function), where accel is a string + following the accelerator syntax described in wx.Menu.Append docs and + function is the function/method to be executed when the + accelerator is triggered. + """ + + self.entries = [] + + self.flag_conv = {"alt" : wx.ACCEL_ALT, + "shift": wx.ACCEL_SHIFT, + "ctrl" : wx.ACCEL_CTRL, + "" : wx.ACCEL_NORMAL} + + xaccel = xaccel or (); n = [] + for acc, fctn in xaccel: + flags, keyCode = self._parseEntry(acc) + if flags != None and keyCode != None: + n.append((flags, keyCode, fctn)) + self.xaccel = n + + + def _parseEntry(self, acc): + """Support for unhandled accelerators.""" + + lacc = acc.lower() + flags, keyCode = None, None + + # Process numpad keys... + if "num" in lacc: + + # flags... + if "+" in lacc: + flag = lacc.split("+")[:-1] + elif "-" in acc: + flag = lacc.split("-")[:-1] + else: + flag = [""] + + flags = 0 + for rflag in flag: + flags |= self.flag_conv[rflag.strip()] + + # keycode... + exec("keyCode = wx.WXK_NUMPAD%s" % lacc.split("num")[1].strip()) + + return flags, keyCode + + + def Convert(self, cmd, accel): + """ + Converts id and accelerator supplied into wx.AcceleratorEntry + objects. + """ + + flags, keyCode = self._parseEntry(accel) + if flags != None and keyCode != None: + self.entries.append(wx.AcceleratorEntry(flags, keyCode, cmd)) + + + def Assemble(self, MBIds): + """Assembles the wx.AcceleratorTable.""" + + for flags, keyCode, fctn in self.xaccel: + _id = wx.NewId(); MBIds[_id] = fctn + self.entries.append(wx.AcceleratorEntry(flags, keyCode, _id)) + + return MBIds, wx.AcceleratorTable(self.entries) + +#---------------------------------------------------------------------------- + +def _process_kwargs(item, kwargs, margin, font): + """ + Internal use only. This is responsible for setting font, margin and + colour for menu items. + """ + + if kwargs.has_key("bmpChecked"): + checked = kwargs["bmpChecked"] + unchecked = kwargs.get("bmpUnchecked", wx.NullBitmap) + + if callable(checked): + checked = checked() + if callable(unchecked): + unchecked = unchecked() + + item.SetBitmaps(checked, unchecked) + + kwlist = [("font", "SetFont"), + ("margin", "SetMarginWidth"), + ("fgColour", "SetTextColour"), + ("bgColour", "SetBackgroundColour")] + + for kw, m in kwlist: + if kwargs.has_key(kw): + getattr(item, m)(kwargs[kw]) + + if margin != wx.DEFAULT: + item.SetMarginWidth(margin) + + if font != wx.NullFont: + item.SetFont(font) + + return item + +#---------------------------------------------------------------------------- + +def _evolve(a): + """Internal use only. This will parse the menu 'tree' supplied.""" + + top = _sItem(a[0]); il = 0; cur = {il: top} + + for i in range(1, len(a)): + params = a[i] + level = params[0].count(_ind) - 1 + + if level > il: + il += 1; cur[il] = new_sItem + elif level < il: + il = level + + new_sItem = cur[il].AddChild(_sItem(params)) + + return top + +#---------------------------------------------------------------------------- + +def _clean(s): + """Internal use only. Removes all non-alfanumeric chars from a string.""" + + return "".join([x for x in s if x.isalnum()]) + +#---------------------------------------------------------------------------- + +def _makeMenus(wxmenus, saccel, h, k, margin, font, i18n): + """Internal use only. Creates menu items.""" + + label = h.GetRealLabel(i18n); Id = h.GetId() + args, kwargs = h.GetParams()[1:] + + if h.HasChildren(): + args = (wxmenus[h], Id, label) + args + item = wx.MenuItem(*args, **{"subMenu": wxmenus[h]}) + item = _process_kwargs(item, kwargs, margin, font) + wxmenus[k].AppendItem(item) + if saccel is not None: + saccel.Convert(item.GetId(), h.GetAccelerator()) + + else: + if label == "-": + wxmenus[k].AppendSeparator() + + elif label == "/": + wxmenus[k].Break() + + else: + args = (wxmenus[k], Id, label) + args + item = wx.MenuItem(*args) + item = _process_kwargs(item, kwargs, margin, font) + wxmenus[k].AppendItem(item) + if saccel is not None: + saccel.Convert(item.GetId(), h.GetAccelerator()) + +#---------------------------------------------------------------------------- + +class _mmprep: + """ + Generates a temporary file that can be read by gettext utilities in order + to create a .po file with strings to be translated. This class is called + when you run metamenus from the command line. + + Usage: + 1. Make sure your menus are in a separate file and that the separate + file in question contain only your menus; + + 2. From a command line, type: + metamenus.py separate_file outputfile + + where 'separate_file' is the python file containing the menu 'trees', + and 'outputfile' is the python-like file generated that can be parsed + by gettext utilities. + + To get a .po file containing the translatable strings, put the + 'outputfile' in the app.fil list of translatable files and run the + mki18n.py script. For more info please see + <http://wiki.wxpython.org/index.cgi/Internationalization>. + """ + + def __init__(self, filename, outputfile): + """Constructor.""" + + print "Parsing %s.py..." % filename + + exec("import %s" % filename) + mod = eval(filename) + + objs = [] + for obj in dir(mod): + if type(getattr(mod, obj)) == list: + objs.append(obj) + + all_lines = [] + for obj in objs: + gerr = False; header = ["\n# Strings for '%s':\n" % obj] + err, lines = self.parseMenu(mod, obj) + if not err: + print "OK: parsed '%s'" % obj + all_lines += header + lines + else: + err, lines = self.parseMenuBar(mod, obj) + if not err: + print "OK: parsed '%s'" % obj + all_lines += header + lines + else: + gerr = True + if gerr: + print "Warning: couldn't parse '%s'" % obj + + try: + f = file("%s.py" % outputfile, "w") + f.writelines(all_lines) + f.close() + print "File %s.py succesfully written." % outputfile + + except: + print "ERROR: File %s.py was NOT written." % outputfile + raise + + + def form(self, lines): + """Removes separators and breaks and adds gettext stuff.""" + + new_lines = [] + for line in lines: + if line not in ["-", "/"]: + new_lines.append("_(" + `line` + ")\n") + return new_lines + + + def parseMenuBar(self, mod, obj): + """Tries to parse a MenuBarEx object.""" + + err = False; lines = [] + try: + for menu in getattr(mod, obj): + top = _evolve(menu) + lines.append(top.GetLabelText()) + for child in top.GetChildren(True): + lines.append(child.GetLabelText()) + except: + err = True + + return err, self.form(lines) + + + def parseMenu(self, mod, obj): + """Tries to parse a MenuEx object.""" + + err = False; lines = [] + try: + top = _evolve(getattr(mod, obj)) + lines.append(top.GetLabelText()) + for child in top.GetChildren(True): + lines.append(child.GetLabelText()) + except: + err = True + + return err, self.form(lines) + + +# MenuBarEx Main stuff ------------------------------------------------------ + +class MenuBarEx(wx.MenuBar): + def __init__(self, *args, **kwargs): + """ + Constructor. + MenuBarEx(parent, menus, margin=wx.DEFAULT, font=wx.NullFont, + xaccel=None, custfunc={}, i18n=True, style=0) + """ + + # Initializing... + self.parent, menus = args + margin = kwargs.pop("margin", wx.DEFAULT) + font = kwargs.pop("font", wx.NullFont) + xaccel = kwargs.pop("xaccel", None) + custfunc = kwargs.pop("custfunc", {}) + i18n = self.i18n = kwargs.pop("i18n", True) + + wx.MenuBar.__init__(self, **kwargs) + + # An object to handle accelerators. + self.accel = _acceleratorTable(xaccel) + + # A reference to all of the sItems involved. + tops = [] + + # For each menu... + for a in menus: + # Parse the menu 'tree' supplied. + top = _evolve(a) + + # Create these menus first... + wxmenus = {top: wx.Menu()} + for k in top.GetChildWithChildren(): + wxmenus[k] = wx.Menu() + + # ...and append their respective children. + for h in k.GetChildren(): + _makeMenus(wxmenus, self.accel, h, k, margin, font, i18n) + + # Now append these items to the top level menu. + for h in top.GetChildren(): + _makeMenus(wxmenus, self.accel, h, top, margin, font, i18n) + + # Now append the top menu to the menubar. + self.Append(wxmenus[top], top.GetRealLabel(i18n)) + + # Store a reference of this sItem. + tops.append(top) + + # Now find out what are the methods that should be called upon + # menu items selection. + MBIds = {}; self.MBStrings = {} + for top in tops: + for child in top.GetChildren(True): + child.SetMethod(_prefixMB, custfunc) + MBIds[child.GetId()] = child + self.MBStrings.update(child.GetAllMethods()) + + # It won't hurt if we get rid of a None key, if any. + bogus = self.MBStrings.pop(None, None) + + # We store the position of top-level menus rather than ids because + # wx.Menu.EnableTop uses positions... + for i, top in enumerate(tops): + self.MBStrings[_clean(top.GetLabelText())] = i + MBIds[i] = top + + # Nice class. 8^) Will take care of this automatically. + self.parent.SetMenuBar(self) + self.parent.Bind(wx.EVT_MENU, self.OnMB_) + + # Now do something about the accelerators... + self.MBIds, at = self.accel.Assemble(MBIds) + self.parent.SetAcceleratorTable(at) + + + def OnMB_(self, evt): + """ + Called on all menu events for this menu. It will in turn call + the related method on parent, if any. + """ + + try: + attr = self.MBIds[evt.GetId()] + + self.OnMB_before() + + # Trigger everything except stuff passed via xaccel. + if isinstance(attr, _sItem): + attr_name = attr.GetMethod() + + if callable(attr_name): + attr_name() + elif hasattr(self.parent, attr_name) and \ + callable(getattr(self.parent, attr_name)): + getattr(self.parent, attr_name)() + else: + print "%s not found in parent." % attr_name + + # Trigger something passed via xaccel. + elif callable(attr): + attr() + + self.OnMB_after() + + except KeyError: + # Maybe another menu was triggered elsewhere in parent. + pass + + #evt.Skip() - removed. see http://www.archivum.info/comp.soft-sys.wxwindows/2008-07/msg00027.html + + + def OnMB_before(self): + """ + If you need to execute something right before a menu event is + triggered, you can bind the EVT_BEFOREMENU. + """ + + evt = MenuExBeforeEvent(obj=self) + wx.PostEvent(self, evt) + + + def OnMB_after(self): + """ + If you need to execute something right after a menu event is + triggered, you can bind the EVT_AFTERMENU. + """ + + evt = MenuExAfterEvent(obj=self) + wx.PostEvent(self, evt) + + + # Public methods -------------------------------------------------------- + + def UpdateMenus(self): + """ + Call this to update menu labels whenever the current locale + changes. + """ + + if not self.i18n: + return + + for k, v in self.MBIds.items(): + # Update top-level menus + if not v.GetParent(): + v.Update() + self.SetLabelTop(k, v.GetRealLabel(self.i18n)) + # Update other menu items + else: + item = self.FindItemById(k) + if item is not None: # Skip separators + v.Update() + self.SetLabel(k, v.GetRealLabel(self.i18n)) + + + def GetMenuState(self, menu_string): + """Returns True if a checkable menu item is checked.""" + + this = self.MBStrings[menu_string] + return self.IsChecked(this) + + + def SetMenuState(self, menu_string, check=True): + """Toggles a checkable menu item checked or unchecked.""" + + this = self.MBStrings[menu_string] + self.Check(this, check) + + + def EnableItem(self, menu_string, enable=True): + """Enables or disables a menu item via its label.""" + + this = self.MBStrings[menu_string] + self.Enable(this, enable) + + + def EnableItems(self, menu_string_list, enable=True): + """Enables or disables menu items via a list of labels.""" + + for menu_string in menu_string_list: + self.EnableItem(menu_string, enable) + + + def EnableTopMenu(self, menu_string, enable=True): + """Enables or disables a top level menu via its label.""" + + this = self.MBStrings[menu_string] + self.EnableTop(this, enable) + + + def EnableTopMenus(self, menu_string_list, enable=True): + """Enables or disables top level menus via a list of labels.""" + + for menu_string in menu_string_list: + self.EnableTopMenu(menu_string, enable) + + +# MenuEx Main stuff --------------------------------------------------------- + +class MenuEx(wx.Menu): + def __init__(self, *args, **kwargs): + """ + Constructor. + + MenuEx(parent, menu, margin=wx.DEFAULT, font=wx.NullFont, + show_title=True, custfunc={}, i18n=True, style=0) + """ + + # Initializing... + self.parent, menu = args + margin = kwargs.pop("margin", wx.DEFAULT) + font = kwargs.pop("font", wx.NullFont) + show_title = kwargs.pop("show_title", True) + custfunc = kwargs.pop("custfunc", {}) + i18n = self.i18n = kwargs.pop("i18n", True) + + wx.Menu.__init__(self, **kwargs) + + self._title = menu[0][0] + if show_title: + if i18n: + self.SetTitle(wx.GetTranslation(self._title)) + else: + self.SetTitle(self._title) + + # Parse the menu 'tree' supplied. + top = _evolve(menu) + + # Create these menus first... + wxmenus = {top: self} + for k in top.GetChildWithChildren(): + wxmenus[k] = wx.Menu() + + # ...and append their respective children. + for h in k.GetChildren(): + _makeMenus(wxmenus, None, h, k, margin, font, i18n) + + # Now append these items to the top level menu. + for h in top.GetChildren(): + _makeMenus(wxmenus, None, h, top, margin, font, i18n) + + # Now find out what are the methods that should be called upon + # menu items selection. + self.MenuIds = {}; self.MenuStrings = {}; self.MenuList = [] + for child in top.GetChildren(True): + Id = child.GetId(); item = self.FindItemById(Id) + if item: + child.SetMethod(_prefixM, custfunc) + self.MenuIds[Id] = child + self.MenuStrings.update(child.GetAllMethods()) + self.MenuList.append([Id, child.GetPath()]) + + # Initialize menu states. + self.MenuState = {} + for Id in self.MenuIds.keys(): + if self.FindItemById(Id).IsCheckable(): + is_checked = self.IsChecked(Id) + else: + is_checked = False + self.MenuState[Id] = is_checked + + # Nice class. 8^) Will take care of this automatically. + self.parent.Bind(wx.EVT_MENU, self.OnM_) + + + def _update(self, i): + def _resetRadioGroup(i): + g = []; n = [] + + for Id, s in self.MenuList: + item = self.FindItemById(Id) + if item.GetKind() == wx.ITEM_RADIO: + g.append(Id) + else: + g.append(None) + + for x in range(g.index(i), 0, -1): + if g[x] != None: + n.append(g[x]) + else: + break + + for x in range(g.index(i) + 1, len(g)): + if g[x] != None: + n.append(g[x]) + else: + break + + for i in n: + self.MenuState[i] = False + + kind = self.FindItemById(i).GetKind() + + if kind == wx.ITEM_CHECK: + self.MenuState[i] = not self.IsChecked(i) + + elif kind == wx.ITEM_RADIO: + _resetRadioGroup(i) + self.MenuState[i] = True + + + def OnM_(self, evt): + """ + Called on all menu events for this menu. It will in turn call + the related method on parent, if any. + """ + + try: + attr = self.MenuIds[evt.GetId()] + + self.OnM_before() + + if isinstance(attr, _sItem): + attr_name = attr.GetMethod() + + if callable(attr_name): + attr_name() + elif hasattr(self.parent, attr_name) and \ + callable(getattr(self.parent, attr_name)): + getattr(self.parent, attr_name)() + else: + print "%s not found in parent." % attr_name + + self.OnM_after() + + except KeyError: + # Maybe another menu was triggered elsewhere in parent. + pass + + #evt.Skip() - removed. see http://www.archivum.info/comp.soft-sys.wxwindows/2008-07/msg00027.html + + + def OnM_before(self): + """ + If you need to execute something right before a menu event is + triggered, you can bind the EVT_BEFOREMENU. + """ + + evt = MenuExBeforeEvent(obj=self) + wx.PostEvent(self, evt) + + + def OnM_after(self): + """ + If you need to execute something right after a menu event is + triggered, you can bind the EVT_AFTERMENU. + """ + + evt = MenuExAfterEvent(obj=self) + wx.PostEvent(self, evt) + + + # Public methods -------------------------------------------------------- + + def UpdateMenus(self): + """ + Call this to update menu labels whenever the current locale + changes. + """ + + if not self.i18n: + return + + for k, v in MenuIds.items(): + item = self.FindItemById(k) + if item is not None: # Skip separators + v.Update() + self.SetLabel(k, v.GetRealLabel(self.i18n)) + + + def Popup(self, evt): + """Pops this menu up.""" + + [self.Check(i, v) for i, v in self.MenuState.items() \ + if self.FindItemById(i).IsCheckable()] + + obj = evt.GetEventObject(); pos = evt.GetPosition() + obj.PopupMenu(self, pos) + + + def GetItemState(self, menu_string): + """Returns True if the item is checked.""" + + this = self.MenuStrings[menu_string] + return self.IsChecked(this) + + + def SetItemState(self, menu_string, check): + """Toggles a checkable menu item checked or unchecked.""" + + this = self.MenuStrings[menu_string] + self.MenuState[this] = check + + + def EnableItem(self, menu_string, enable=True): + """Enables or disables a menu item via its label.""" + + this = self.MenuStrings[menu_string] + self.Enable(this, enable) + + + def EnableItems(self, menu_string_list, enable=True): + """Enables or disables menu items via a list of labels.""" + + for menu_string in menu_string_list: + self.EnableItem(menu_string, enable) + + + def EnableAllItems(self, enable=True): + """Enables or disables all menu items.""" + + for Id in self.MenuIds.keys(): + self.Enable(Id, enable) + +#---------------------------------------------------------------------------- + +if __name__ == "__main__": + import sys, os.path + args = sys.argv[1:] + if len(args) == 2: + _mmprep(*[os.path.splitext(arg)[0] for arg in args]) + else: + print """ +----------------------------------------------------------------------------- +metamenus %s + +%s +%s +Distributed under the wxWidgets license. +----------------------------------------------------------------------------- + +Usage: +------ + +metamenus.py separate_file outputfile + +- 'separate_file' is the python file containing the menu 'trees'; +- 'outputfile' is the output file generated that can be parsed by the gettext + utilities. + +Please see metamenus.__doc__ (under the 'More about i18n' section) and +metamenus._mmprep.__doc__ for more details. +----------------------------------------------------------------------------- +""" % (__version__, __author__, __date__) + + +# +## +### eof