"""MultiStateButton class@copyright: Copyright (c) 2006 Open Source Applications Foundation@license: http://osafoundation.org/Chandler_0.1_license_terms.htm"""import typesimport osimport sysimport wxfrom wx.lib.buttons import GenBitmapButtonclass BitmapInfo(object): __slots__ = ["normal", "normalBitmap", "rollover", "rolloverBitmap", "disabled", "disabledBitmap", "focus", "focusBitmap", "selected", "selectedBitmap", "stateName"]class MultiStateButton(GenBitmapButton): """ A MultiStateButton can have multiple bitmaps in its default state. These bitmaps are passed in as a list of names, which are also the names of the PNG/GIF/JPG/TIFF/PSD bitmaps. The name of a "state" is the same as the base name of the image. So for "/var/image/foo.png", the state name would be "foo" mailButton = MultiStateButton(parent_view, 100, multibitmaps=["/var/image/NoMail", "/var/image/Mail"]) will create a button with the two bitmaps Mail.png and NoMail.png. The default initial value will be the first named state; in this case "NoMail". When desired (presumably when mail comes in), the state of the button can be changed thusly: mailButton.SetState("/var/image/Mail") and the button's bitmap will change to Mail.png. Later, after all the mail has been read, the button can be changed back: mailButton.SetState("/var/image/NoMail") Roll-over bitmaps can be passed in through the use of tuples: mailButton = MultiStateButton(parent_view, 100, multibitmaps=[("/var/image/NoMail", "/var/image/NoMailRollover"), ("/var/image/Mail", "/var/image/MailRollover")]) When the mouse rolls over the button in a particular state, it will display the named rollover bitmap. Even more control can be exercised through the use of the BitmapInfo class. You can specify the normally auto-generated bitmaps for disabled, selected and focus states, as well as a separate state name not derived from the bitmap name(s). no_mail_bitmaps = MultiStateButton.BitmapInfo() no_mail_bitmaps.normal = "/var/image/NoMail" no_mail_bitmaps.rollover = "/var/image/NoMailRollover" no_mail_bitmaps.disabled = "/var/image/NoMailDisabled" no_mail_bitmaps.focus = "/var/image/NoMailFocus" no_mail_bitmaps.selected = "/var/image/NoMailSelected" no_mail_bitmaps.stateName = "ThereIsNoMail" mail_bitmaps = MultiStateButton.BitmapInfo() mail_bitmaps.normal = "/var/image/Mail" mail_bitmaps.rollover = "/var/image/MailRollover" mail_bitmaps.disabled = "/var/image/MailDisabled" mail_bitmaps.focus = "/var/image/MailFocus" mail_bitmaps.selected = "/var/image/MailSelected" mail_bitmaps.stateName = "ThereIsSomeMail" mailButton = MultiStateButton(parent_view, 100, multibitmaps=[no_mail_bitmaps, mail_bitmaps]) mailButton.SetState("ThereIsSomeMail") Obviously this latter method is a lot more verbose """ def __init__(self, parent, ID=-1, multibitmaps=(), *args, **kwds): """ Initialize the button to the usual button states, plus a list of button bitmap filenames and their optional rollover bitmap filenames @param multibitmaps: a list containing strings/tuples/BitmapInfo's. Each item in the list represents a button state, the name of which is the base root name of the "normal" bitmap file. e.g. /var/share/NoMail.png would become the state "NoMail", and could thus be passed in to SetState() as SetState("NoMail"). If an item in the list is a tuple of two strings, the first string is the normal button bitmap filename, while the second is the rollover button bitmap filename. The rollover bitmap is displayed when the mouse just moves over the button. If a BitmapInfo is passed in, it can specify separate bitmaps for each possible situation (pressed, rollover, selected, etc) as well as the name of the state itself """ self.stateBitmaps = {} self.currentState = None if kwds.get("multibitmaps"): multibitmaps = kwds["multibitmaps"] del kwds["multibitmaps"] else: multibitmaps = {} super(MultiStateButton, self).__init__(parent, ID, None, *args, **kwds) firstStateName = self.AddStates(multibitmaps) assert firstStateName is not None self.SetState(firstStateName) # calls to Bind must come after call to super's __init__ self.Bind(wx.EVT_ENTER_WINDOW, self._RolloverStart) self.Bind(wx.EVT_LEAVE_WINDOW, self._RolloverFinish) def AddStates(self, multibitmaps): """ Add more state bitmaps to the button. @param multibitmaps: a list of strings/tuples/NitmpaInfo's. See __init__ for a description of this list. More bitmap states can be added at any time to the button. """ firstFoundState = None found = False for entry in multibitmaps: stateName = None paths = {} bitmaps = {} # try/except method of type detection suggested by Heikki # did not work because strings can be accessed via [] just # as well as tuples # alecf suggest actually using types if type(entry) == types.TupleType: # assume it's a tuple with normal and rollover bitmap names paths["normal"] = entry[0] paths["rollover"] = entry[1] elif type(entry) == types.StringType: paths["normal"] = entry paths["rollover"] = None elif type(entry) == type(BitmapInfo): stateName = entry.get("stateName") for variation in ["normal", "rollover", "disabled", "focus", "selected"]: bitmaps[variation] = entry.get(variation + "Bitmap") paths[variation] = entry.get(variation) else: # this will fail, throwing an exception that will help # to explain what's wrong; needs to be more elegant; how # do you pass an error string up the chain of exception? assert type(entry) == type(BitmapInfo) if stateName is None: assert paths["normal"] is not None # The name of the state is the same as the base name of the # bitmap stateName = os.path.basename(paths["normal"]) assert len(stateName) > 0 self.stateBitmaps[stateName] = BitmapInfo() for variation in ["normal", "rollover", "disabled", "focus", "selected"]: sys.stderr.write("Checking '" + stateName + "'[" + variation + "]") if bitmaps.get(variation) is not None: setattr(self.stateBitmaps[stateName], variation, bitmaps[variation]) assert getattr(self.stateBitmaps[stateName], variation).GetWidth() > 0# self.stateBitmaps[stateName][variation] = bitmaps[variation] elif paths.get(variation) is not None: setattr(self.stateBitmaps[stateName], variation, self._GetBitmapFor(paths[variation]))# self.stateBitmaps[stateName][variation] = self._GetBitmapFor(paths[variation]) assert self.stateBitmaps[stateName].normal is not None if firstFoundState is None: firstFoundState = stateName assert firstFoundState is not None return firstFoundState def SetState(self, inStateName): """ Set the current state name of the button. The appropriate bitmap will be used. @param multibitmaps: a list of strings or tuples of two strings. See __init__ for a description of this list. More bitmap states can be added at any time to the button. """ if inStateName != self.currentState: stateBitmaps = self.stateBitmaps.get(inStateName) assert stateBitmaps is not None, "invalid state name '" + inStateName + "'" assert getattr(stateBitmaps, "normal", None) is not None, "invalid state '" + inStateName + "' is missing 'normal' bitmap" import pdb;pdb.set_trace() self.SetBitmapLabel(stateBitmaps.normal) #rae the following seems a bit awkward; is there a better way? if getattr(stateBitmaps, "disabled", None) is not None: self.SetBitmapDisabled(stateBitmaps.disabled) if getattr(stateBitmaps, "focus", None) is not None: self.SetBitmapFocus(stateBitmaps.focus) if getattr(stateBitmaps, "selected", None) is not None: self.SetBitmapSelected(stateBitmaps.selected) self.currentState = inStateName self.Refresh() self.Update() def _RolloverStart(self, event): """ Change the state of the button to its possible rollover state. """ stateBitmaps = self.stateBitmaps[self.currentState] assert stateBitmaps is not None rolloverBitmap = getattr(stateBitmaps, "rollover", None) if rolloverBitmap is not None: self.SetBitmapLabel(rolloverBitmap) self.Refresh() # will the app need this call to Update? self.Update() def _RolloverFinish(self, event): """ Return the button to its non-rollover state. """ stateBitmaps = self.stateBitmaps[self.currentState] # only do all this if there was actually a rollover if getattr(stateBitmaps, "rollover", None) is not None: assert getattr(stateBitmaps, "normal", None) is not None, "invalid state '" + inStateName + "' is missing 'normal' bitmap" self.SetBitmapLabel(stateBitmaps.normal) self.Refresh() # will the app need this call to Update? self.Update() def _GetBitmapFor(self, bitmapName): """ Find the named bitmap, checking various file type extensions (are these available inside wx.Image somewhere?) The design decision here is that the speicifc type of the image is not as important as its name. Indeed, the type of the image can change over time to accomodate new technologies. By leaving the type unspecified, code does not have to change whenever the file format changes. """ bitmap = None for ext in ["png", "gif", "jpg", "tiff", "psd"]: try: #img = wx.Image("%s.%s" % (bitmapName, ext)) img = wx.GetApp().GetImage("%s.%s" % (bitmapName, ext)) bitmap = img.ConvertToBitmap() assert bitmap.GetWidth() > 0 break except IOError: # file was not found pass assert bitmap is not None return bitmap# execute with execfile("/Users/rae/work/rae-button/MultiStateButton.py", { "__name__" :"__main__" })# or similarif __name__ == "__main__": # # Change this to where you keep your bitmaps; remember the # trailing path separator - it prevents needing os.path code dir = "/Users/rae/work/rae-button/" # these lines assume you have bitmaps available; you will need to # change the absolute paths to bitmaps you have. theWindow = wx.Frame(parent=None, id=-1, title="MultiButton testing") b = MultiStateButton(theWindow, style=wx.BORDER_NONE, multibitmaps = [ (dir + "button1", dir + "button4"), (dir + "button2", dir + "button3") ]) b2 = MultiStateButton(theWindow, style=wx.BORDER_NONE, multibitmaps=[ (dir + "button5", dir + "button4"), (dir + "button2", dir + "button3"), ]) b3 = MultiStateButton(theWindow, style=wx.BORDER_NONE, multibitmaps=[ (dir + "button1", dir + "button4"), (dir + "button2", dir + "button3"), ]) box = wx.BoxSizer(wx.HORIZONTAL) box.Add(b) box.Add(b2) box.Add(b3) sb = MultiStateButton(theWindow, 1010, style=wx.BORDER_NONE, multibitmaps=[ (dir + "button1-small", dir + "button4-small"), (dir + "button2-small", dir + "button3-small"), ]) sb2 = MultiStateButton(theWindow, 1011, style=wx.BORDER_NONE, multibitmaps=[ (dir + "button5-small", dir + "button4-small"), (dir + "button2-small", dir + "button3-small"), ]) sb3 = MultiStateButton(theWindow, 1010, style=wx.BORDER_NONE, multibitmaps=[ (dir + "button1-small", dir + "button4-small"), (dir + "button2-small", dir + "button3-small"), ]) box2 = wx.BoxSizer(wx.HORIZONTAL) box2.Add(sb) box2.Add(sb2) box2.Add(sb3) box.Add(box2)# bmi = BitmapInfo()# bmi.normal = dir + "button1"# bmi.rollover = dir + "button2"# bmi.focus = dir + "button3"# bmi.disabled = dir + "button4"# bmi.selected = dir + "button5"# bmi.stateName = "zimbabwe"# mmb = MultiStateButton(theWindow, 1010, multibitmaps=(bmi))# box3 = wx.BoxSizer(wx.HORIZONTAL)# box3.Add(mmb)# box.Add(box3) theWindow.SetSizer(box) theWindow.Show()