class dListBox(dcm.dControlItemMixin, wx.ListBox): """Creates a listbox, allowing the user to choose one or more items.""" def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dListBox self._choices = [] preClass = wx.PreListBox dcm.dControlItemMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, *args, **kwargs) def _initEvents(self): super(dListBox, self)._initEvents() self.Bind(wx.EVT_LISTBOX, self._onWxHit) def clearSelections(self): for elem in self.GetSelections(): self.SetSelection(elem, False) def selectAll(self): if self.MultipleSelect: for ii in xrange(self.Count): self.SetSelection(ii) def unselectAll(self): self.clearSelections() def invertSelections(self): """Switch all the items from False to True, and vice-versa.""" for ii in xrange(self.Count): if self.IsSelected(ii): self.Deselect(ii) else: self.SetSelection(ii) def _getMultipleSelect(self): return self._hasWindowStyleFlag(wx.LB_EXTENDED) def _setMultipleSelect(self, val): if bool(val): self._delWindowStyleFlag(wx.LB_SINGLE) self._addWindowStyleFlag(wx.LB_EXTENDED) else: self._delWindowStyleFlag(wx.LB_EXTENDED) self._addWindowStyleFlag(wx.LB_SINGLE) MultipleSelect = property( _getMultipleSelect, _setMultipleSelect, None, _("Can multiple items be selected at once? (bool)")) DynamicMultipleSelect = makeDynamicProperty(MultipleSelect)
class AlignmentMixin(object): def _getAlignment(self): if self._hasWindowStyleFlag(wx.ALIGN_RIGHT): return "Right" elif self._hasWindowStyleFlag(wx.ALIGN_CENTRE): return "Center" else: return "Left" def _setAlignment(self, value): # Note: Alignment must be set before object created. self._delWindowStyleFlag(wx.ALIGN_LEFT) self._delWindowStyleFlag(wx.ALIGN_CENTRE) self._delWindowStyleFlag(wx.ALIGN_RIGHT) value = ustr(value).lower() if value == "left": self._addWindowStyleFlag(wx.ALIGN_LEFT) elif value == "center": self._addWindowStyleFlag(wx.ALIGN_CENTRE) elif value == "right": self._addWindowStyleFlag(wx.ALIGN_RIGHT) else: raise ValueError("The only possible values are " "'Left', 'Center', and 'Right'.") Alignment = property( _getAlignment, _setAlignment, None, _("""Specifies the alignment of the text. (str) Left (default) Center Right""")) DynamicAlignment = makeDynamicProperty(Alignment)
class dPageFrameNoTabs(dPanel): """ Creates a pageframe with no tabs or other way for the user to select a page. Your code will have to programatically set the page. """ def __init__(self, *args, **kwargs): self._pageClass = dPage self._pageSizerClass = dabo.ui.dSizer self._activePage = None self._pages = [] super(dPageFrameNoTabs, self).__init__(*args, **kwargs) self._baseClass = dPageFrameNoTabs def _afterInit(self): if self.Sizer is None: self.Sizer = dabo.ui.dSizer() super(dPageFrameNoTabs, self)._afterInit() def appendPage(self, pgCls=None, makeActive=False): """ Creates a new page, which should be a subclass of dPage. If makeActive is True, the page is displayed; otherwise, it is added without changing the SelectedPage. """ return self.insertPage(self.PageCount, pgCls=pgCls, makeActive=makeActive) def insertPage(self, pos, pgCls=None, makeActive=False, ignoreOverride=False): """ Inserts the page into the pageframe at the specified position, and makes it the active (displayed) page if makeActive is True. """ # Allow subclasses to potentially override this behavior. This will # enable them to handle page creation in their own way. If overridden, # the method will return the new page. ret = None if not ignoreOverride: ret = self._insertPageOverride(pos, pgCls, makeActive) if ret: return ret if pgCls is None: pgCls = self.PageClass if self.Sizer is None: self.Sizer = dabo.ui.dSizer() if isinstance(pgCls, dPage): pg = pgCls else: # See if the 'pgCls' is either some XML or the path of an XML file if isinstance(pgCls, basestring): xml = pgCls from dabo.lib.DesignerClassConverter import DesignerClassConverter conv = DesignerClassConverter() pgCls = conv.classFromText(xml) pg = pgCls(self) self.Sizer.insert(pos, pg, 1, "x") self._pages.insert(pos, pg) self.layout() if makeActive or (self.PageCount == 1): self.showPage(pg) else: self.showPage(self.SelectedPage) return self.Pages[pos] def _insertPageOverride(self, pos, pgCls, makeActive): pass def removePage(self, pgOrPos, delPage=True): """ Removes the specified page. You can specify a page by either passing the page itself, or a position. If delPage is True (default), the page is released, and None is returned. If delPage is False, the page is returned. """ if isinstance(pgOrPos, int): pg = self.Pages[pgOrPos] else: pg = pgOrPos self._pages.remove(pg) if delPage: pg.release() ret = None else: self.Sizer.remove(pg) ret = pg return ret def layout(self): """Wrap the wx version of the call, if possible.""" for pg in self.Pages: try: pg.layout() except AttributeError: # could be that the page is a single control, not a container pass super(dPageFrameNoTabs, self).layout() def showPage(self, pg): ap = self._activePage if isinstance(pg, int): pg = self.Pages[pg] newPage = (pg is not ap) if pg in self.Pages: if newPage: if ap: dabo.ui.callAfter(ap.raiseEvent, dEvents.PageLeave) apNum = self.getPageNumber(ap) else: apNum = -1 dabo.ui.callAfter(pg.raiseEvent, dEvents.PageEnter) dabo.ui.callAfter(self.raiseEvent, dEvents.PageChanged, oldPageNum=apNum, newPageNum=self.getPageNumber(pg)) self._activePage = pg for ch in self.Pages: self.Sizer.Show(ch, (ch is pg)) self.layout() pg.setFocus() else: raise AttributeError(_("Attempt to show non-member page")) def nextPage(self): """ Selects the next page. If the last page is selected, it will select the first page. """ self.cyclePages(1) def priorPage(self): """ Selects the previous page. If the first page is selected, it will select the last page. """ self.cyclePages(-1) def cyclePages(self, num): """ Moves through the pages by the specified amount, wrapping around the ends. Negative values move to previous pages; positive move through the next pages. """ self.SelectedPageNumber = (self.SelectedPageNumber + num) % self.PageCount def getPageNumber(self, pg): """Given a page, returns its position.""" try: ret = self.Pages.index(pg) except ValueError: ret = None return ret #------------------------------------ # The following methods don't do anything except # make this class compatible with dPage classes, which # expect their parent to have these methods. #------------------------------------ def getPageImage(self, pg): return None def setPageImage(self, pg, img): pass def GetPageText(self, pg): return "" def SetPageText(self, pg, txt): pass #------------------------------------ def _getPageClass(self): return self._pageClass def _setPageClass(self, val): if isinstance(val, basestring): from dabo.lib.DesignerClassConverter import DesignerClassConverter conv = DesignerClassConverter() self._pageClass = conv.classFromText(val) elif issubclass(val, (dPage, dPanel)): self._pageClass = val def _getPageCount(self): return len(self._pages) def _setPageCount(self, val): diff = (val - len(self._pages)) if diff > 0: # Need to add pages while diff: self.appendPage() diff -= 1 elif diff < 0: currPg = self.SelectedPageNumber pagesToKill = self._pages[val:] self._pages = self._pages[:val] # Need to add the check if the page exists since it # may have already been released. [pg.release() for pg in pagesToKill if pg] # Make sure the page we were on isn't one of the deleted pages. # If so, switch to the last page. newPg = min(currPg, val-1) self.SelectedPage = newPg def _getPages(self): return self._pages def _getPageSizerClass(self): return self._pageSizerClass def _setPageSizerClass(self, val): if self._constructed(): self._pageSizerClass = val else: self._properties["PageSizerClass"] = val def _getSelectedPage(self): try: return self._activePage except AttributeError: return None def _setSelectedPage(self, pg): self.showPage(pg) def _getSelectedPageNumber(self): return self.getPageNumber(self._activePage) def _setSelectedPageNumber(self, val): pg = self.Pages[val] self.showPage(pg) PageClass = property(_getPageClass, _setPageClass, None, _("The default class used when adding new pages. (dPage)") ) PageCount = property(_getPageCount, _setPageCount, None, _("Returns the number of pages in this pageframe (int)") ) Pages = property(_getPages, None, None, _("List of all the pages. (list)") ) PageSizerClass = property(_getPageSizerClass, _setPageSizerClass, None, _("""Default sizer class for pages added automatically to this control. Set this to None to prevent sizers from being automatically added to child pages. (dSizer or None)""")) SelectedPage = property(_getSelectedPage, _setSelectedPage, None, _("Returns a reference to the currently displayed page (dPage | dPanel)") ) SelectedPageNumber = property(_getSelectedPageNumber, _setSelectedPageNumber, None, _("Returns a reference to the index of the currently displayed page (int)") ) DynamicPageClass = makeDynamicProperty(PageClass) DynamicPageCount = makeDynamicProperty(PageCount) DynamicSelectedPage = makeDynamicProperty(SelectedPage) DynamicSelectedPageNumber = makeDynamicProperty(SelectedPageNumber)
class dComboBox(dcm.dControlItemMixin, wx.ComboBox): """ Creates a combobox, which combines a dropdown list with a textbox. The user can choose an item in the dropdown, or enter freeform text. """ def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dComboBox self._choices = [] self._userVal = False # Used to force the case of entered text self._forceCase = None self._inForceCase = False self._textLength = None # Flag for appending items when the user presses 'Enter' self._appendOnEnter = False # Holds the text to be appended self._textToAppend = "" preClass = wx.PreComboBox dcm.dControlItemMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, *args, **kwargs) def _preInitUI(self, kwargs): style = kwargs.get("style", 0) style |= wx.PROCESS_ENTER kwargs["style"] = style return kwargs def _initEvents(self): super(dComboBox, self)._initEvents() self.Bind(wx.EVT_COMBOBOX, self.__onComboBox) # self.Bind(wx.EVT_TEXT_ENTER, self.__onTextBox) self.Bind(wx.EVT_KEY_DOWN, self.__onWxKeyDown) def __onComboBox(self, evt): self._userVal = False evt.Skip() self._onWxHit(evt) def __onWxKeyDown(self, evt): """ We need to capture the Enter/Return key in order to implement the AppendOnEnter behavior. However, under Windows this leads to navigation issues, so we also need to capture when Tab is pressed, and handle the navigation ourselves. """ # Shorthand for easy reference dk = dabo.ui.dKeys # Don't call the native Skip() if Tab is pressed; we'll handle it ourselves. callSkip = True enter_codes = (dk.key_Return, dk.key_Numpad_enter) keyCode = evt.GetKeyCode() if keyCode in enter_codes: self._userVal = True if self.AppendOnEnter: txt = self.GetValue() if txt not in self.Choices: self._textToAppend = txt if self.beforeAppendOnEnter() is not False: if self._textToAppend: self.appendItem(self._textToAppend, select=True) self.afterAppendOnEnter() self.raiseEvent(dEvents.Hit, evt) elif keyCode == dk.key_Tab: forward = not evt.ShiftDown() self.Navigate(forward) callSkip = False if callSkip: evt.Skip() def __onKeyChar(self, evt): """This handles KeyChar events when ForceCase is set to a non-empty value.""" if not self: # The control is being destroyed return keyCode = evt.keyCode if keyCode >= dKeys.key_Space: dabo.ui.callAfter(self._checkForceCase) dabo.ui.callAfter(self._checkTextLength) def _checkTextLength(self): """ If the TextLength property is set, checks the current value of the control and truncates it if too long """ if not self: # The control is being destroyed return if not isinstance(self.GetValue(), basestring): #Don't bother if it isn't a string type return length = self.TextLength if not length: return val = self.GetValue() if len(val) > length: dabo.ui.beep() ip = self.GetInsertionPoint() self.SetValue(val[:length]) self.SetInsertionPoint(ip) def _checkForceCase(self): """ If the ForceCase property is set, casts the current value of the control to the specified case. """ if not self: # The control is being destroyed return if not isinstance(self.GetValue(), basestring): # Don't bother if it isn't a string type return case = self.ForceCase if not case: return ip = self.GetInsertionPoint() if case == "upper": self.SetValue(self.GetValue().upper()) elif case == "lower": self.SetValue(self.GetValue().lower()) elif case == "title": self.SetValue(self.GetValue().title()) self.SetInsertionPoint(ip) def beforeAppendOnEnter(self): """ Hook method that is called when user-defined text is entered into the combo box and Enter is pressed (when self.AppendOnEnter is True). This gives the programmer the ability to interact with such events, and optionally prevent them from happening. Returning False will prevent the append from happening. The text value to be appended is stored in self._textToAppend. You may modify this value (e.g., force to upper case), or delete it entirely (e.g., filter out obscenities and such). If you set self._textToAppend to an empty string, nothing will be appended. So this 'before' hook gives you two opportunities to prevent the append: return a non- empty value, or clear out self._textToAppend. """ pass def afterAppendOnEnter(self): """ Hook method that provides a means to interact with the newly- changed list of items after a new item has been added by the user pressing Enter, but before control returns to the program. """ pass # Property get/set/del methods follow. Scroll to bottom to see the property # definitions themselves. def _getAppendOnEnter(self): return self._appendOnEnter def _setAppendOnEnter(self, val): if self._constructed(): self._appendOnEnter = val else: self._properties["AppendOnEnter"] = val def _getForceCase(self): return self._forceCase def _setForceCase(self, val): if self._constructed(): if val is None: valKey = None else: valKey = val[0].upper() self._forceCase = { "U": "upper", "L": "lower", "T": "title", None: None, "None": None }.get(valKey) self._checkForceCase() self.unbindEvent(dEvents.KeyChar, self.__onKeyChar) if self._forceCase or self._textLength: self.bindEvent(dEvents.KeyChar, self.__onKeyChar) else: self._properties["ForceCase"] = val def _getTextLength(self): return self._textLength def _setTextLength(self, val): if self._constructed(): if val == None: self._textLength = None else: val = int(val) if val < 1: raise ValueError('TextLength must be a positve Integer') self._textLength = val self._checkTextLength() self.unbindEvent(dEvents.KeyChar, self.__onKeyChar) if self._forceCase or self._textLength: self.bindEvent(dEvents.KeyChar, self.__onKeyChar) else: self._properties["TextLength"] = val def _getUserValue(self): if self._userVal: return self.GetValue() else: return self.GetStringSelection() def _setUserValue(self, value): if self._constructed(): self.SetValue(value) # don't call _afterValueChanged(), because value tracks the item in the list, # not the displayed value. User code can query UserValue and then decide to # add it to the list, if appropriate. else: self._properties["UserValue"] = value AppendOnEnter = property( _getAppendOnEnter, _setAppendOnEnter, None, _("""Flag to determine if user-entered text is appended when they press 'Enter' (bool)""")) ForceCase = property( _getForceCase, _setForceCase, None, _("""Determines if we change the case of entered text. Possible values are: ============ ===================== None or "" No changes made (default) "Upper" FORCE TO UPPER CASE "Lower" Force to lower case "Title" Force To Title Case ============ ===================== These can be abbreviated to "u", "l" or "t" (str)""")) TextLength = property( _getTextLength, _setTextLength, None, _("""The maximum length the entered text can be. (int)""")) UserValue = property( _getUserValue, _setUserValue, None, _("""Specifies the text displayed in the textbox portion of the ComboBox. String. Read-write at runtime. UserValue can differ from StringValue, which would mean that the user has typed in arbitrary text. Unlike StringValue, PositionValue, and KeyValue, setting UserValue does not change the currently selected item in the list portion of the ComboBox.""")) DynamicUserValue = makeDynamicProperty(UserValue)
class dGauge(cm.dControlMixin, wx.Gauge): """Creates a gauge, which can be used as a progress bar.""" def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dGauge preClass = wx.PreGauge cm.dControlMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, *args, **kwargs) def _initEvents(self): super(dGauge, self)._initEvents() # Property get/set/del methods follow. Scroll to bottom to see the property # definitions themselves. def _getPercentage(self): return round(100 * (float(self.Value) / self.Range), 2) def _setPercentage(self, val): if self._constructed(): self.Value = round(self.Range * (val / 100.0)) else: self._properties["Percentage"] = val def _getOrientation(self): if self.IsVertical(): return "Vertical" else: return "Horizontal" def _setOrientation(self, value): self._delWindowStyleFlag(wx.GA_HORIZONTAL) self._delWindowStyleFlag(wx.GA_VERTICAL) if value.lower()[:1] == "h": self._addWindowStyleFlag(wx.GA_HORIZONTAL) else: self._addWindowStyleFlag(wx.GA_VERTICAL) def _getRange(self): return self.GetRange() def _setRange(self, value): if self._constructed(): self.SetRange(value) else: self._properties["Range"] = value def _getValue(self): return self.GetValue() def _setValue(self, value): if self._constructed(): self.SetValue(value) else: self._properties["Value"] = value # Property definitions: Percentage = property( _getPercentage, _setPercentage, None, _("""Alternate way of setting/getting the Value, using percentage of the Range. (float)""")) Orientation = property( _getOrientation, _setOrientation, None, _("Specifies whether the gauge is displayed as Horizontal or Vertical. (str)" )) Range = property(_getRange, _setRange, None, _("Specifies the maximum value for the gauge. (int)")) Value = property( _getValue, _setValue, None, _("Specifies the state of the gauge, relative to max value.")) DynamicOrientation = makeDynamicProperty(Orientation) DynamicRange = makeDynamicProperty(Range) DynamicValue = makeDynamicProperty(Value)
class dDialog(fm.dFormMixin, wx.Dialog): """ Creates a dialog, which is a lightweight form. Dialogs are like forms, but typically are modal and are requesting a very specific piece of information from the user, and/or offering specific information to the user. """ def __init__(self, parent=None, properties=None, *args, **kwargs): self._baseClass = dDialog self._modal = True self._centered = True self._fit = True self._borderless = self._extractKey((properties, kwargs), "Borderless", False) if self._borderless: defaultStyle = wx.STAY_ON_TOP else: defaultStyle = wx.DEFAULT_DIALOG_STYLE try: kwargs["style"] = kwargs["style"] | defaultStyle except KeyError: kwargs["style"] = defaultStyle preClass = wx.PreDialog fm.dFormMixin.__init__(self, preClass, parent, properties=properties, *args, **kwargs) # Hook method, so that we add the buttons last self._addControls() # Needed starting with wx 2.7, for the first control to have the focus: self.setFocus() def getBizobj(self, *args, **kwargs): ## self.Form resolves to the containing dialog, making calls to 'self.Form.getBizobj()' ## fail since the dialog knows nothing about bizobjs. Let's be helpful and pass the ## request up to the form: return self.Form.getBizobj(*args, **kwargs) def EndModal(self, *args, **kwargs): self.saveSizeAndPosition() self.hide() if self.Modal: try: super(dDialog, self).EndModal(*args, **kwargs) except wx._core.PyAssertionError: # The modal hack is causing problems in some edge cases. pass def _afterInit(self): self.MenuBarClass = None self.Sizer = dabo.ui.dSizer("V") super(dDialog, self)._afterInit() def ShowModal(self): self.restoreSizeAndPositionIfNeeded() # updates were potentially suppressed while the dialog # wasn't visible, so update now. self.update() return super(dDialog, self).ShowModal() def showModal(self): """Show the dialog modally.""" ## pkm: We had to override this, because the default in dPemMixin doesn't ## actually result in a modal dialog. self.Modal = True self.show() def showModeless(self): """Show the dialog non-modally.""" self.Modal = False self.show() def _afterShow(self): if self.AutoSize: self.Fit() if self.Centered: self.Centre() def show(self): # Call _afterShow() once immediately, and then once after the dialog is visible, which # will correct minor mistakes such as the height of wordwrapped labels not being # accounted for. If we only called it after the dialog was already shown, then we # risk the dialog being too jumpy. self._afterShow() dabo.ui.callAfter(self._afterShow) if self.Modal: ret = self.ShowModal() return { wx.ID_OK: kons.DLG_OK, wx.ID_CANCEL: kons.DLG_CANCEL }.get(ret, ret) return self.Show(True) def _addControls(self): """ Any controls that need to be added to the dialog can be added in this method in framework classes, or in addControls() in instances. """ self.beforeAddControls() self.addControls() self.afterAddControls() def beforeAddControls(self): """This is a hook, called at the appropriate time by the framework.""" pass def afterAddControls(self): """This is a hook, called at the appropriate time by the framework.""" pass def addControls(self): """ Add your custom controls to the dialog. This is a hook, called at the appropriate time by the framework. """ pass def release(self): """ Need to augment this to make sure the dialog is removed from the app's forms collection. """ if self.Application is not None: self.Application.uiForms.remove(self) super(dDialog, self).release() def _controlGotFocus(self, ctrl): # Placeholder until we unify dForm and dDialog pass def _getAutoSize(self): return self._fit def _setAutoSize(self, val): self._fit = val def _getBorderless(self): return self._borderless def _setBorderless(self, val): if self._constructed(): raise ValueError( _("Cannot set the Borderless property once the dialog is created." )) else: self._properties["Borderless"] = val def _getCaption(self): return self.GetTitle() def _setCaption(self, val): if self._constructed(): self.SetTitle(val) else: self._properties["Caption"] = val def _getCentered(self): return self._centered def _setCentered(self, val): self._centered = val def _getModal(self): return self._modal def _setModal(self, val): self._modal = val def _getShowStat(self): # Dialogs cannot have status bars. return False _showStatusBar = property(_getShowStat) AutoSize = property( _getAutoSize, _setAutoSize, None, _("When True, the dialog resizes to fit the added controls. (bool)")) Borderless = property( _getBorderless, _setBorderless, None, _("""Must be passed at construction time. When set to True, the dialog displays without a title bar or borders (bool)""")) Caption = property( _getCaption, _setCaption, None, _("The text that appears in the dialog's title bar (str)")) Centered = property( _getCentered, _setCentered, None, _("Determines if the dialog is displayed centered on the screen. (bool)" )) Modal = property( _getModal, _setModal, None, _("Determines if the dialog is shown modal (default) or modeless. (bool)" )) DynamicAutoSize = makeDynamicProperty(AutoSize) DynamicCaption = makeDynamicProperty(Caption) DynamicCentered = makeDynamicProperty(Centered)
class dDockPanel(dabo.ui.dPanel): def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): nmU = self._extractKey((properties, kwargs), "Name", "") nb = self._extractKey((properties, kwargs), "NameBase", "") nmL = self._extractKey((properties, kwargs), "name", "") kwargs["NameBase"] = [txt for txt in (nmU, nb, nmL, "dDockPanel") if txt][0] pcapUp = self._extractKey(kwargs, "Caption", "") pcap = self._extractKey(kwargs, "caption", "") ptype = self._extractKey(kwargs, "typ", "") if pcapUp: kwargs["Caption"] = pcapUp else: kwargs["Caption"] = pcap self._paramType = ptype self._toolbar = self._extractKey(kwargs, "Toolbar", False) # Initialize attributes that underly properties self._bottomDockable = True self._leftDockable = True self._rightDockable = True self._topDockable = True self._floatable = True self._floatingPosition = (0, 0) self._floatingSize = (100, 100) self._gripperPosition = "Left" self._destroyOnClose = False self._movable = True self._resizable = True self._showBorder = True self._showCaption = True self._showCloseButton = True self._showGripper = False self._showMaximizeButton = False self._showMinimizeButton = False self._showPinButton = True super(dDockPanel, self).__init__(parent, properties=properties, attProperties=attProperties, *args, **kwargs) if self.Floating: self._floatingPosition = self.GetParent().GetPosition().Get() self._floatingSize = self.GetParent().GetSize().Get() def _uniqueNameForParent(self, name, parent=None): """ We need to check the AUI manager's PaneInfo name value, too, as that has to be unique there as well as the form. """ changed = True try: mgr = parent._mgr except AttributeError: mgr = self._Manager while changed: i = 0 auiOK = False while not auiOK: auiOK = True candidate = name if i: candidate = "%s%s" % (name, i) mtch = [pi.name for pi in mgr.GetAllPanes() if pi.name == candidate] if mtch: auiOK = False i += 1 changed = changed and (candidate != name) name = candidate candidate = super(dDockPanel, self)._uniqueNameForParent(name, parent) changed = changed and (candidate != name) name = candidate return name def float(self): """Float the panel if it isn't already floating.""" if self.Floating or not self.Floatable: return self._PaneInfo.Float() self._updateAUI() def dock(self, side=None): """ Dock the panel. If side is specified, it is docked on that side of the form. If no side is specified, it is docked in its default location. """ if self.Docked or not self.Dockable: return inf = self._PaneInfo if side is not None: s = side[0].lower() func = {"l": inf.Left, "r": inf.Right, "t": inf.Top, "b": inf.Bottom}.get(s, None) if func: func() else: dabo.log.error(_("Invalid dock position: '%s'.") % side) inf.Dock() self._updateAUI() def _beforeSetProperties(self, props): """ Some properties of Floating panels cannot be set at the usual point in the process, since the panel will still be docked, and you can't change dimensions/location of a docked panel. So extract them now, and then set them afterwards. """ self._propDelayDict = {} props2Delay = ("Bottom", "BottomDockable", "Caption", "DestroyOnClose", "Dockable", "Docked", "DockSide", "Floatable", "Floating", "FloatingBottom", "FloatingHeight", "FloatingLeft", "FloatingPosition", "FloatingRight", "FloatingSize", "FloatingTop", "FloatingWidth", "GripperPosition", "Height", "Left", "LeftDockable", "Movable", "Resizable", "Right", "RightDockable", "ShowBorder", "ShowCaption", "ShowCloseButton", "ShowGripper", "ShowMaximizeButton", "ShowMinimizeButton", "ShowPinButton", "Top", "TopDockable", "Visible", "Width") for delayed in props2Delay: val = self._extractKey(props, delayed, None) if val is not None: self._propDelayDict[delayed] = val return super(dDockPanel, self)._beforeSetProperties(props) def _afterSetProperties(self): nm = self.Name frm = self.Form self._Manager.addPane(self, name=nm, typ=self._paramType, caption=self._propDelayDict.get("Caption", "dDockPanel")) del self._paramType self._PaneInfo.MinSize((50,50)) if self._propDelayDict: self.setProperties(self._propDelayDict) del self._propDelayDict def getState(self): """Returns the local name and a string that can be used to restore the state of this pane.""" inf = self._Manager.SavePaneInfo(self._PaneInfo) try: infPairs = (qq.split("=") for qq in inf.split(";")) nm = dict(infPairs)["name"] except KeyError: # For some reason a name was not returned return "" return (nm, inf.replace("name=%s;" % nm, "")) def _updateAUI(self): frm = self.Form if frm is not None: frm._refreshState() else: try: self._Manager.runUpdate() except AttributeError: pass def __getPosition(self): if self.Floating: obj = self.GetParent() else: obj = self return obj.GetPosition().Get() def __getSize(self): if self.Floating: obj = self.GetParent() else: obj = self return obj.GetSize().Get() # Property get/set/del methods follow. Scroll to bottom to see the property # definitions themselves. def _getBottom(self): return self.__getPosition()[1] + self.__getSize()[1] def _setBottom(self, val): if self._constructed(): if self.Floating: self.FloatingBottom = val else: dabo.log.error(_("Cannot set the position of a docked panel")) else: self._properties["Bottom"] = val def _getBottomDockable(self): return self._bottomDockable def _setBottomDockable(self, val): if self._constructed(): self._PaneInfo.BottomDockable(val) self._updateAUI() else: self._properties["BottomDockable"] = val def _getCaption(self): try: return self._caption except AttributeError: self._caption = "" return self._caption def _setCaption(self, val): if self._constructed(): self._caption = val self._PaneInfo.Caption(val) self._updateAUI() else: self._properties["Caption"] = val def _getDestroyOnClose(self): return self._destroyOnClose def _setDestroyOnClose(self, val): if self._constructed(): self._destroyOnClose = val self._PaneInfo.DestroyOnClose(val) self._updateAUI() else: self._properties["DestroyOnClose"] = val def _getDockable(self): return self._bottomDockable or self._leftDockable or self._rightDockable or self._topDockable def _setDockable(self, val): if self._constructed(): self._dockable = self._bottomDockable = self._leftDockable = self._rightDockable = self._topDockable = val self._PaneInfo.Dockable(val) if self.Docked: self.Docked = val self._updateAUI() else: self._properties["Dockable"] = val def _getDocked(self): return self._PaneInfo.IsDocked() def _setDocked(self, val): if self._constructed(): curr = self._PaneInfo.IsDocked() chg = False if val and not curr: self._PaneInfo.Dock() chg = True elif not val and curr: self._PaneInfo.Float() chg = True if chg: self._updateAUI() else: self._properties["Docked"] = val def _getDockSide(self): return {1: "Top", 2: "Right", 3: "Bottom", 4: "Left"}[self._PaneInfo.dock_direction] def _setDockSide(self, val): if self._constructed(): vUp = val[0].upper() self._PaneInfo.dock_direction = {"T": 1, "R": 2, "B": 3, "L": 4}[vUp] self._updateAUI() else: self._properties["DockSide"] = val def _getFloatable(self): return self._floatable def _setFloatable(self, val): if self._constructed(): self._floatable = val self._PaneInfo.Floatable(val) self._updateAUI() else: self._properties["Floatable"] = val def _getFloating(self): return self._PaneInfo.IsFloating() def _setFloating(self, val): if self._constructed(): curr = self._PaneInfo.IsFloating() chg = False if val and not curr: self._PaneInfo.Float() chg = True elif not val and curr: self._PaneInfo.Dock() chg = True if chg: self._updateAUI() else: self._properties["Floating"] = val def _getFloatingBottom(self): return self.FloatingPosition[1] + self.FloatingSize[1] def _setFloatingBottom(self, val): if self._constructed(): ht = self.FloatingSize[1] self._floatingPosition = (self.FloatingPosition[0], val - ht) self._PaneInfo.FloatingPosition(self._floatingPosition) self.Form._refreshState(0) else: self._properties["FloatingBottom"] = val def _getFloatingHeight(self): return self.FloatingSize[1] def _setFloatingHeight(self, val): if self._constructed(): self._floatingSize = (self.FloatingSize[0], val) if self._PaneInfo.IsFloating(): self.GetParent().SetSize(self._floatingSize) else: self._PaneInfo.FloatingSize(self._floatingSize) self.Form._refreshState(0) else: self._properties["FloatingHeight"] = val def _getFloatingLeft(self): return self.FloatingPosition[0] def _setFloatingLeft(self, val): if self._constructed(): self._floatingPosition = (val, self.FloatingPosition[1]) self._PaneInfo.FloatingPosition(self._floatingPosition) self.Form._refreshState(0) else: self._properties["FloatingLeft"] = val def _getFloatingPosition(self): return self._PaneInfo.floating_pos.Get() def _setFloatingPosition(self, val): if self._constructed(): self._PaneInfo.FloatingPosition(val) self.Form._refreshState(0) else: self._properties["FloatingPosition"] = val def _getFloatingRight(self): return self.FloatingPosition[0] + self.FloatingSize[0] def _setFloatingRight(self, val): if self._constructed(): wd = self.FloatingSize[0] self._floatingPosition = (val - wd, self.FloatingPosition[1]) self._PaneInfo.FloatingPosition(self._floatingPosition) self.Form._refreshState(0) else: self._properties["FloatingRight"] = val def _getFloatingSize(self): return self._PaneInfo.floating_size.Get() def _setFloatingSize(self, val): if self._constructed(): self._PaneInfo.FloatingSize(val) self.Form._refreshState(0) else: self._properties["FloatingSize"] = val def _getFloatingTop(self): return self.FloatingPosition[1] def _setFloatingTop(self, val): if self._constructed(): self._floatingPosition = (self.FloatingPosition[0], val) self._PaneInfo.FloatingPosition(self._floatingPosition) self.Form._refreshState(0) else: self._properties["FloatingTop"] = val def _getFloatingWidth(self): return self.FloatingSize[0] def _setFloatingWidth(self, val): if self._constructed(): self._floatingSize = (val, self.FloatingSize[1]) if self._PaneInfo.IsFloating(): self.GetParent().SetSize(self._floatingSize) else: self._PaneInfo.FloatingSize(self._floatingSize) self.Form._refreshState(0) else: self._properties["FloatingWidth"] = val def _getGripperPosition(self): return self._gripperPosition def _setGripperPosition(self, val): if self._constructed(): val = val[0].lower() if not val in ("l", "t"): raise ValueError(_("Only valid GripperPosition values are 'Top' or 'Left'.")) self._gripperPosition = {"l": "Left", "t": "Top"}[val] self._PaneInfo.GripperTop(val == "t") self._updateAUI() else: self._properties["GripperPosition"] = val def _getHeight(self): return self.__getSize()[1] def _setHeight(self, val): if self._constructed(): if self.Floating: self.FloatingHeight = val else: dabo.log.error(_("Cannot set the Size of a docked panel")) else: self._properties["Height"] = val def _getLeft(self): return self.__getPosition()[0] def _setLeft(self, val): if self._constructed(): if self.Floating: self.FloatingLeft = val else: dabo.log.error(_("Cannot set the position of a docked panel")) else: self._properties["Left"] = val def _getLeftDockable(self): return self._leftDockable def _setLeftDockable(self, val): if self._constructed(): self._PaneInfo.LeftDockable(val) self._updateAUI() else: self._properties["LeftDockable"] = val def _getManager(self): try: mgr = self._mgr except AttributeError: mgr = self._mgr = self.Form._mgr return mgr def _getMovable(self): return self._movable def _setMovable(self, val): if self._constructed(): self._movable = val self._PaneInfo.Movable(val) self._updateAUI() else: self._properties["Movable"] = val def _getPaneInfo(self): try: mgr = self._mgr except AttributeError: mgr = self._mgr = self.Form._mgr return mgr.GetPane(self) def _getResizable(self): return self._resizable def _setResizable(self, val): if self._constructed(): self._resizable = val self._PaneInfo.Resizable(val) self._updateAUI() else: self._properties["Resizable"] = val def _getRight(self): return self.__getPosition()[0] + self.__getSize()[0] def _setRight(self, val): if self._constructed(): if self.Floating: self.FloatingRight = val else: dabo.log.error(_("Cannot set the position of a docked panel")) else: self._properties["Right"] = val def _getRightDockable(self): return self._rightDockable def _setRightDockable(self, val): if self._constructed(): self._PaneInfo.RightDockable(val) self._updateAUI() else: self._properties["RightDockable"] = val def _getShowBorder(self): return self._showBorder def _setShowBorder(self, val): if self._constructed(): self._showBorder = val self._PaneInfo.PaneBorder(val) self._updateAUI() else: self._properties["ShowBorder"] = val def _getShowCaption(self): return self._showCaption def _setShowCaption(self, val): if self._constructed(): self._showCaption = val self._PaneInfo.CaptionVisible(val) self._updateAUI() else: self._properties["ShowCaption"] = val def _getShowCloseButton(self): return self._showCloseButton def _setShowCloseButton(self, val): if self._constructed(): self._showCloseButton = val self._PaneInfo.CloseButton(val) self.Form._refreshState(0) self.Form.lockDisplay() self.Docked = not self.Docked dabo.ui.setAfterInterval(100, self, "Docked", not self.Docked) dabo.ui.callAfterInterval(150, self.Form.unlockDisplay) else: self._properties["ShowCloseButton"] = val def _getShowGripper(self): return self._showGripper def _setShowGripper(self, val): if self._constructed(): if val == self._showGripper: return self._showGripper = val self._PaneInfo.Gripper(val) self._updateAUI() else: self._properties["ShowGripper"] = val def _getShowMaximizeButton(self): return self._showMaximizeButton def _setShowMaximizeButton(self, val): if self._constructed(): self._showMaximizeButton = val self._PaneInfo.MaximizeButton(val) self._updateAUI() else: self._properties["ShowMaximizeButton"] = val def _getShowMinimizeButton(self): return self._showMinimizeButton def _setShowMinimizeButton(self, val): if self._constructed(): self._showMinimizeButton = val self._PaneInfo.MinimizeButton(val) self._updateAUI() else: self._properties["ShowMinimizeButton"] = val def _getShowPinButton(self): return self._showPinButton def _setShowPinButton(self, val): if self._constructed(): self._showPinButton = val self._PaneInfo.PinButton(val) self._updateAUI() else: self._properties["ShowPinButton"] = val def _getToolbar(self): return self._toolbar def _getTop(self): return self.__getPosition()[1] def _setTop(self, val): if self._constructed(): if self.Floating: self.FloatingTop = val else: dabo.log.error(_("Cannot set the position of a docked panel")) else: self._properties["Top"] = val def _getTopDockable(self): return self._topDockable def _setTopDockable(self, val): if self._constructed(): self._PaneInfo.TopDockable(val) self._updateAUI() else: self._properties["TopDockable"] = val def _getVisible(self): return self._PaneInfo.IsShown() def _setVisible(self, val): if self._constructed(): self._PaneInfo.Show(val) self._updateAUI() else: self._properties["Visible"] = val def _getWidth(self): return self.__getSize()[0] def _setWidth(self, val): if self._constructed(): if self.Floating: self.FloatingWidth = val else: dabo.log.error(_("Cannot set the Size of a docked panel")) else: self._properties["Width"] = val Bottom = property(_getBottom, _setBottom, None, _("Position in pixels of the bottom side of the panel. Read-only when docked; read-write when floating (int)")) BottomDockable = property(_getBottomDockable, _setBottomDockable, None, _("Can the panel be docked to the bottom edge of the form? Default=True (bool)")) Caption = property(_getCaption, _setCaption, None, _("Text that appears in the title bar (str)")) DestroyOnClose = property(_getDestroyOnClose, _setDestroyOnClose, None, _("When the panel's Close button is clicked, does the panel get destroyed (True) or just hidden (False, default) (bool)")) Dockable = property(_getDockable, _setDockable, None, _("Can the panel be docked to the form? Default=True (bool)")) Docked = property(_getDocked, _setDocked, None, _("Determines whether the pane is floating (False) or docked (True) (bool)")) DockSide = property(_getDockSide, _setDockSide, None, _("""Side of the form that the panel is either currently docked to, or would be if dock() were to be called. Possible values are 'Left', 'Right', 'Top' and 'Bottom'. (str)""")) Floatable = property(_getFloatable, _setFloatable, None, _("Can the panel be undocked from the form and float independently? Default=True (bool)")) Floating = property(_getFloating, _setFloating, None, _("Determines whether the pane is floating (True) or docked (False) (bool)")) FloatingBottom = property(_getFloatingBottom, _setFloatingBottom, None, _("Bottom coordinate of the panel when floating (int)")) FloatingHeight = property(_getFloatingHeight, _setFloatingHeight, None, _("Height of the panel when floating (int)")) FloatingLeft = property(_getFloatingLeft, _setFloatingLeft, None, _("Left coordinate of the panel when floating (int)")) FloatingPosition = property(_getFloatingPosition, _setFloatingPosition, None, _("Position of the panel when floating (2-tuple of ints)")) FloatingRight = property(_getFloatingRight, _setFloatingRight, None, _("Right coordinate of the panel when floating (int)")) FloatingSize = property(_getFloatingSize, _setFloatingSize, None, _("Size of the panel when floating (2-tuple of ints)")) FloatingTop = property(_getFloatingTop, _setFloatingTop, None, _("Top coordinate of the panel when floating (int)")) FloatingWidth = property(_getFloatingWidth, _setFloatingWidth, None, _("Width of the panel when floating (int)")) GripperPosition = property(_getGripperPosition, _setGripperPosition, None, _("If a gripper is shown, is it on the Top or Left side? Default = 'Left' ('Top' or 'Left')")) Height = property(_getHeight, _setHeight, None, _("Position in pixels of the height of the panel. Read-only when docked; read-write when floating (int)")) Left = property(_getLeft, _setLeft, None, _("Position in pixels of the left side of the panel. Read-only when docked; read-write when floating (int)")) LeftDockable = property(_getLeftDockable, _setLeftDockable, None, _("Can the panel be docked to the left edge of the form? Default=True (bool)")) _Manager = property(_getManager, None, None, _("Reference to the AUI manager (for internal use only). (_dDockManager)")) Movable = property(_getMovable, _setMovable, None, _("Can the panel be moved (True, default), or is it in a fixed position (False). (bool)")) _PaneInfo = property(_getPaneInfo, None, None, _("Reference to the AUI PaneInfo object (for internal use only). (wx.aui.PaneInfo)")) Resizable = property(_getResizable, _setResizable, None, _("Can the panel be resized? Default=True (bool)")) Right = property(_getRight, _setRight, None, _("Position in pixels of the right side of the panel. Read-only when docked; read-write when floating (int)")) RightDockable = property(_getRightDockable, _setRightDockable, None, _("Can the panel be docked to the right edge of the form? Default=True (bool)")) ShowBorder = property(_getShowBorder, _setShowBorder, None, _("Should the panel's border be shown when floating? (bool)")) ShowCaption = property(_getShowCaption, _setShowCaption, None, _("Should the panel's Caption be shown when it is docked? Default=True (bool)")) ShowCloseButton = property(_getShowCloseButton, _setShowCloseButton, None, _("Does the panel display a close button when floating? Default=True (bool)")) ShowGripper = property(_getShowGripper, _setShowGripper, None, _("Does the panel display a draggable gripper? Default=False (bool)")) ShowMaximizeButton = property(_getShowMaximizeButton, _setShowMaximizeButton, None, _("Does the panel display a maximize button when floating? Default=False (bool)")) ShowMinimizeButton = property(_getShowMinimizeButton, _setShowMinimizeButton, None, _("Does the panel display a minimize button when floating? Default=False (bool)")) ShowPinButton = property(_getShowPinButton, _setShowPinButton, None, _("Does the panel display a pin button when floating? Default=False (bool)")) Toolbar = property(_getToolbar, None, None, _("Returns True if this is a Toolbar pane. Default=False (bool)")) Top = property(_getTop, _setTop, None, _("Position in pixels of the top side of the panel. Read-only when docked; read-write when floating (int)")) TopDockable = property(_getTopDockable, _setTopDockable, None, _("Can the panel be docked to the top edge of the form? Default=True (bool)")) Visible = property(_getVisible, _setVisible, None, _("Is the panel shown? (bool)")) Width = property(_getWidth, _setWidth, None, _("Position in pixels of the width of the panel. Read-only when docked; read-write when floating (int)")) DynamicCaption = makeDynamicProperty(Caption)
class dTreeView(dcm.dControlMixin, wx.TreeCtrl): """Creates a treeview, which allows display of hierarchical data.""" def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dTreeView # Dictionary for tracking images by key value self.__imageList = {} self.nodes = [] self._rootNode = None # Class to use for creating nodes self._nodeClass = dNode # Default size for images added to the tree. self._imageSize = (16, 16) # Do we set tooltips from the nodes? self._useNodeToolTips = False # Store the default ToolTipText while UseNodeToolTips is True self._storedToolTipText = None style = self._extractKey((properties, attProperties, kwargs), "style", 0) | wx.TR_HAS_VARIABLE_ROW_HEIGHT # Default to showing buttons val = self._extractKey(attProperties, "ShowButtons", None) if val is not None: val = (val == "True") else: val = self._extractKey((properties, kwargs), "ShowButtons", True) if val: style = style | wx.TR_HAS_BUTTONS # Default to showing lines val = self._extractKey(attProperties, "ShowLines", None) if val is not None: val = (val == "True") else: val = self._extractKey((properties, kwargs), "ShowLines", True) if not val: style = style | wx.TR_NO_LINES # Default to showing root node val = self._extractKey(attProperties, "ShowRootNode", None) if val is not None: val = (val == "True") else: val = self._extractKey((properties, kwargs), "ShowRootNode", True) if not val: style = style | wx.TR_HIDE_ROOT # Default to showing root node lines val = self._extractKey(attProperties, "ShowRootNodeLines", None) if val is not None: val = (val == "True") else: val = self._extractKey((properties, kwargs), "ShowRootNodeLines", True) if val: style = style | wx.TR_LINES_AT_ROOT preClass = wx.PreTreeCtrl dcm.dControlMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, style=style, *args, **kwargs) def _initEvents(self): super(dTreeView, self)._initEvents() self.Bind(wx.EVT_LEFT_UP, self._onWxHit) self.Bind(wx.EVT_KEY_UP, self.__onKeyUp) self.Bind(wx.EVT_TREE_SEL_CHANGED, self.__onTreeSel) self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.__onTreeItemCollapse) self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.__onTreeItemExpand) self.Bind(wx.EVT_TREE_ITEM_MENU, self.__onTreeItemContextMenu) self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.__onTreeBeginDrag) self.Bind(wx.EVT_TREE_END_DRAG, self.__onTreeEndDrag) self.Bind(wx.EVT_MOTION, self.__onTreeMouseMove) def __onTreeItemContextMenu(self, evt): self.raiseEvent(dEvents.TreeItemContextMenu, evt) def __onTreeBeginDrag(self, evt): if self._allowDrag(evt): evt.Allow() # We need to select the item being dragged # so we don't try to drag an old selected item self.SelectItem(evt.GetItem()) evt.Skip() self.raiseEvent(dEvents.TreeBeginDrag, evt) def __onTreeEndDrag(self, evt): evt.Skip() # We need to select only our destination node if self.MultipleSelect: self.UnselectAll() self.SelectItem(evt.GetItem()) self.raiseEvent(dEvents.TreeEndDrag, evt) def _allowDrag(self, evt): nd = self.find(evt.GetItem()) return self.allowDrag(nd) def allowDrag(self, node): # Override in subclasses in needed. return True def _getInitPropertiesList(self): original = list(super(dTreeView, self)._getInitPropertiesList()) original.remove("MultipleSelect") return tuple(original) def clear(self, clearImageList=False): self.DeleteAllItems() self.nodes = [] if clearImageList: il = self.GetImageList() if il: il.RemoveAll() self.__imageList = {} def refreshDisplay(self): """ Changing some node appearance properties requires that the tree be collapsed and re-opened in order to update any sizing issues. """ self.lockDisplay() sel = self.Selection ndExp = ((nd, nd.Expanded) for nd in self.nodes if ((not nd.IsRootNode) or self.ShowRootNode)) self.collapseAll() for nd, exp in ndExp: nd.Expanded = exp self.Selection = sel self.unlockDisplay() def getRootNode(self): return self._rootNode def setRootNode(self, txt): itemID = self.AddRoot(txt) ret = self._rootNode = self.NodeClass(self, itemID, None) if self.ShowRootNode: self.SetItemFont(ret.itemID, self.GetFont()) self.nodes.append(ret) return ret def appendNode(self, node, txt): if node is None: # Get the root node ndid = self.GetRootItem() else: ndid = node.itemID itemID = self.AppendItem(ndid, txt) ret = self.NodeClass(self, itemID, node) self.SetItemFont(ret.itemID, self.GetFont()) self.nodes.append(ret) return ret def removeNode(self, node): self.Delete(node.itemID) for n in node.Descendents: self.nodes.remove(n) self.nodes.remove(node) def expand(self, node): self.Expand(node.itemID) def collapse(self, node): self.Collapse(node.itemID) def expandAll(self): nds = self.nodes if not self.ShowRootNode: nds = [nd for nd in self.nodes if not nd.IsRootNode] for n in nds: self.expand(n) def collapseAll(self): nds = self.nodes if not self.ShowRootNode: nds = [nd for nd in self.nodes if not nd.IsRootNode] for n in nds: self.collapse(n) def expandBranch(self, nd): """Expands the specified node, as well as any of its child nodes.""" self.ExpandAllChildren(nd.itemID) def collapseBranch(self, nd): """Collapses the specified node, as well as any of its child nodes.""" self.CollapseAllChildren(nd.itemID) def showNode(self, node): self.EnsureVisible(node.itemID) # Image-handling function def addImage(self, img, key=None): """ Adds the passed image to the control's ImageList, and maintains a reference to it that is retrievable via the key value. """ # Default image size wd, ht = self.ImageSize il = self.GetImageList() if not il: il = wx.ImageList(wd, ht, initialCount=0) self.AssignImageList(il) else: if il.GetImageCount(): wd, ht = il.GetSize(0) if key is None: key = ustr(img) if isinstance(img, basestring): img = dabo.ui.strToBmp(img, width=wd, height=ht) idx = il.Add(img) self.__imageList[key] = idx def setNodeImg(self, node, imgKey, which="normal"): """ Sets the specified node's image to the image corresponding to the specified key. May also optionally pass the index of the image in the image list rather than the key, which is the state of the node. Valid states are: 'normal' 'expanded' 'selected' 'selectedexpanded' """ whichdict = { "normal": wx.TreeItemIcon_Normal, "expanded": wx.TreeItemIcon_Expanded, "selected": wx.TreeItemIcon_Selected, "selectedexpanded": wx.TreeItemIcon_SelectedExpanded } if which.lower() not in whichdict: raise ValueError(_("Invalid Node State: %s") % which) if isinstance(imgKey, int): imgIdx = imgKey else: imgIdx = self.__imageList[imgKey] self.SetItemImage(node.itemID, imgIdx, whichdict[which.lower()]) def getNodeImg(self, node, which="normal"): """ Returns the index of the specified node's image in the current image list, or -1 if no image is set for the node. Which is the state of the node. Valid states are: 'normal' 'expanded' 'selected' 'selectedexpanded' """ whichdict = { "normal": wx.TreeItemIcon_Normal, "expanded": wx.TreeItemIcon_Expanded, "selected": wx.TreeItemIcon_Selected, "selectedexpanded": wx.TreeItemIcon_SelectedExpanded } if which.lower() not in whichdict: raise ValueError(_("Invalid Node State: %s") % which) return self.GetItemImage(node.itemID, whichdict[which.lower()]) def nodeForObject(self, obj): """Given an object, returns the corresponding node.""" try: return [nd for nd in self.nodes if nd._object is obj][0] except IndexError: return None def getParentNode(self, node): """ Returns the node that is the parent of the given node, or None if the node is the root. """ parentID = self.GetItemParent(node.itemID) ret = self.find(parentID) if ret: if isinstance(ret, list): ret = ret[0] else: ret = None return ret def getChildren(self, node): """Returns a list of all nodes that are child nodes of this node.""" ret = [n for n in self.nodes if n.parent == node] return ret def getDescendents(self, node): """Returns a list of all nodes that are direct descendents of this node.""" ret = [] for n in self.nodes: par = n.parent while par: if par == node: ret.append(n) break else: par = par.parent return ret def getSiblings(self, node): """ Returns a list of all nodes at the same level as the specified node. The specified node is included in the list. """ ret = [n for n in self.nodes if n.parent == node.parent] return ret def find(self, srch, top=None): """ Searches the nodes collection for all nodes that match whose text matches the passed search value (if a text value was passed). If a wxPython TreeItemID object is passed, returns a list nodes matching that itemID value. If a specific node is passed in the top property, the search is limited to descendents of that node. Returns a list of matching nodes. """ ret = [] if top is None: nodes = self.nodes else: nodes = top.Descendents if isinstance(srch, basestring): ret = [n for n in nodes if n.Caption == srch] elif isinstance(srch, wx.TreeItemId): ret = [n for n in nodes if n.itemID == srch] return ret def findPattern(self, srchPat, top=None): """ Allows for regexp pattern matching in order to find matching nodes using less than exact matches. If a specific node is passed in the top property, the search is limited to descendents of that node. Returns a list of matching nodes. """ ret = [] if top is None: nodes = self.nodes else: nodes = top.Descendents if isinstance(srchPat, basestring): ret = [n for n in nodes if re.match(srchPat, n.Caption)] return ret # These related functions all use self._getRelative(). # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def nextSibling(self, nd=None): """Returns the next sibling node, or None if there are no more""" return self._getRelative(nd, self.GetNextSibling) def priorSibling(self, nd=None): """Returns the prior sibling node, or None if there are no more""" return self._getRelative(nd, self.GetPrevSibling) def nextNode(self, nd=None): """ If the current node has children, returns the first child node. If it has no children, returns the next sibling. If there are no next siblings, returns the next sibling of the parent node. If the parent node has no more siblings, returns the next sibling of the grand- parent node, etc. Returns None if we are at the absolute bottom of the flattened tree structure. Sometimes referred to as 'flatdown' navigation. """ if not isinstance(nd, dNode): nd = self.nodeForObject(nd) if nd is None: nd = self.Selection if isinstance(nd, list): if nd: nd = nd[0] else: # Empty list return None try: ret = self.getChildren(nd)[0] except IndexError: ret = None if ret is None: ret = self._getRelative(nd, self.GetNextSibling) while ret is None: # No more siblings. Go up the tree, getting the next # sibling of each parent until we either find one, or # we reach the top. nd = self.getParentNode(nd) if nd is None: break ret = self._getRelative(nd, self.GetNextSibling) return ret def priorNode(self, nd=None): """ Returns last child of the prior sibling node. If there are no prior siblings, returns the parent. Sometimes referred to as 'flatup' navigation. """ if not isinstance(nd, dNode): nd = self.nodeForObject(nd) if nd is None: nd = self.Selection if isinstance(nd, list): if nd: nd = nd[0] else: # Empty list return None ret = self._getRelative(nd, self.GetPrevSibling) if ret is None: try: ret = self.getParentNode(nd) except wx.PyAssertionError: pass else: # Find the last child of the last child of the last child... nd = ret kids = self.getChildren(nd) while kids: nd = kids[-1] kids = self.getChildren(nd) ret = nd return ret def _getRelative(self, nd, func): """ Used by nextNode(), nextSibling(), priorNode() and priorSibling() methods for relative movement. """ if nd is None: nd = self.Selection if isinstance(nd, list): if nd: nd = nd[0] else: # Empty list return None try: itemID = func(nd.itemID) ret = [nod for nod in self.nodes if nod.itemID == itemID][0] except IndexError: ret = None return ret # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def makeDirTree(self, dirPath, wildcard=None, ignored=None, showHidden=False, expand=False): """ Make this dTreeView show a filesystem directory hierarchy. You can specify a wildcard pattern: e.g., "\*py" will only include files ending in 'py'. You can also pass a list of wildcards, and files matching any of these will be included in the tree. If no wildcard is specified, all files will be included. You can also specify file patterns to ignore in the 'ignored' parameter. This can be a single string of a file pattern, or a list of such patterns. Any file matching any of these patterns will not be included in the tree. By default, hidden files (i.e., those beginning with a period) are ignored. You can optionally show them by passing True in the showHidden parameter. The tree defaults to fully collapsed; you can change it to fully expanded by passing True in the 'expand' parameter. Warning: Don't use this for huge hierarchies, as it blocks while filling the complete tree, instead of only filling the nodes as they are opened. """ self.clear(clearImageList=True) # Add the standard images for a directory tree self.addImage("folder", "folder") self.addImage("folderopen", "folderopen") self.addImage("normalfile", "file") self.addImage("executablefile", "executablefile") # Add any trailing slash character self._pathNode = {} # Define the function to be passed to os.path.walk def addNode(arg, currDir, fNames): wildcards, ignored, showHid = arg prnt, nm = os.path.split(currDir) if not showHid and nm.startswith("."): return try: nd = self._pathNode[currDir] = self._pathNode[ prnt].appendChild(nm) except (KeyError, AttributeError): # If this is the first entry, we need to set the root if not self._pathNode: nd = self._pathNode[currDir] = self.setRootNode(nm) else: # parent wasn't added, because it was hidden return self.setNodeImg(nd, "folder", "normal") self.setNodeImg(nd, "folderopen", "expanded") nd.ToolTipText = nd._filePath = currDir acceptedNames = ignoredNames = None if wildcards is not None: acceptedNames = [] for wc in wildcards: acceptedNames += glob.glob( os.path.join(currDir, wc.lower())) acceptedNames += glob.glob( os.path.join(currDir, wc.upper())) if ignored is not None: ignoredNames = [] for ig in ignored: ignoredNames += glob.glob(os.path.join( currDir, ig.lower())) ignoredNames += glob.glob(os.path.join( currDir, ig.upper())) for f in fNames: fullName = os.path.join(currDir, f) if os.path.isdir(fullName): # it will be added as a directory continue if not showHid and f.startswith("."): continue if acceptedNames is not None: if fullName not in acceptedNames: continue if ignoredNames is not None: if fullName in ignoredNames: continue kid = nd.appendChild(f) kid._filePath = fullName self.setNodeImg(kid, "file", "normal") kid.ToolTipText = fullName def sortNode(arg, currDir, fNames): if currDir in self._pathNode: self.SortChildren(self._pathNode[currDir].itemID) if wildcard and not isinstance(wildcard, (list, tuple)): # single string passed wildcard = [wildcard] if ignored and not isinstance(ignored, (list, tuple)): # single string passed ignored = [ignored] arg = (wildcard, ignored, showHidden) os.path.walk(dirPath, addNode, arg) os.path.walk(dirPath, sortNode, None) if expand: self.expandAll() def _setAbsoluteFontZoom(self, newZoom): self._currFontZoom = newZoom for node in self.nodes: origFontSize = node._origFontSize = getattr( node, "_origFontSize", node.FontSize) fontSize = origFontSize + newZoom if fontSize > 1: node.FontSize = fontSize if self.Form is not None: dabo.ui.callAfterInterval(200, self.Form.layout) def treeFromStructure(self, stru, topNode=None): """ Given a sequence of items with a standard format, this will construct a tree structure and append it to the specified 'topNode'. If 'topNode' is None, the tree will be cleared, and a new structure containing only the passed info will be created. The info should be passed as a sequence of either lists or tuples, with the first element being the text to be displayed, and, if there are to be child nodes, the second being the child node information. If there are no children for that node, either do not include the second element, or set it to None. If there are child nodes, the child information will be recursively parsed. """ addRoot = (topNode is None) if addRoot: self.DeleteAllItems() if isinstance(stru[0], basestring): # We're at the end of the recursion. Just append the node self.appendNode(topNode, stru[0]) else: for nodes in stru: txt = nodes[0] if addRoot: nd = self.setRootNode(txt) addRoot = False else: nd = self.appendNode(topNode, txt) try: kids = nodes[1] except IndexError: kids = None if kids: self.treeFromStructure(kids, nd) def addDummyData(self): """For testing purposes!""" root = ["This is the root"] kid1 = ["First Child"] kid2 = ["Second Child"] kid3 = ["Third Child"] gk1 = ["Grandkid #1"] gk2 = ["Grandkid #2"] gk3 = ["Grandkid #3"] ggk1 = ["Great-Grandkid #1"] root.append([kid1, kid2, kid3]) kid2.append([gk1, gk2, gk3]) gk2.append(ggk1) self.treeFromStructure([root]) ### NOTE: This will also work # self.DeleteAllItems() # r = self.setRootNode("This is the root") # c1 = r.appendChild("First Child") # c2 = r.appendChild("Second Child") # c3 = r.appendChild("Third Child") # c21 = c2.appendChild("Grandkid #1") # c22 = c2.appendChild("Grandkid #2") # c23 = c2.appendChild("Grandkid #3") # c221 = c22.appendChild("Great-Grandkid #1") def getNodeForID(self, idval): """Given a wx item ID, returns the corresponding node, or None.""" try: ret = [nd for nd in self.nodes if nd.itemID == idval][0] except IndexError: ret = None return ret def getNodeUnderMouse(self, includeSpace=False, includeButton=True): """ Returns the node directly under the mouse, or None if the mouse is not over a node. If 'includeSpace' is True, the empty space to the right of the node is counted as part of the node. Likewise, if 'includeButton' is True, the area for the expanding/collapsing button is considered part of the node. Otherwise, it is considered to not be over any node. """ # The following wxPython constants are available: # wx.TREE_HITTEST_ABOVE: Above the client area. # wx.TREE_HITTEST_BELOW: Below the client area. # wx.TREE_HITTEST_NOWHERE: In the client area but below the last item. # wx.TREE_HITTEST_ONITEMBUTTON: On the button associated with an item. # wx.TREE_HITTEST_ONITEMICON: On the bitmap associated with an item. # wx.TREE_HITTEST_ONITEMINDENT: In the indentation associated with an item. # wx.TREE_HITTEST_ONITEMLABEL: On the label (string) associated with an item. # wx.TREE_HITTEST_ONITEMRIGHT: In the area to the right of an item. # wx.TREE_HITTEST_ONITEMSTATEICON: On the state icon for a tree view item that is in a user-defined state. # wx.TREE_HITTEST_TOLEFT: To the right of the client area. # wx.TREE_HITTEST_TORIGHT: To the left of the client area. ret = None mp = self.getMousePosition() idval, flag = self.HitTest(mp) overFlags = (wx.TREE_HITTEST_ONITEMICON | wx.TREE_HITTEST_ONITEMINDENT | wx.TREE_HITTEST_ONITEMLABEL) if includeSpace: overFlags = overFlags | wx.TREE_HITTEST_ONITEMRIGHT if includeButton: overFlags = overFlags | wx.TREE_HITTEST_ONITEMBUTTON if idval and (flag & overFlags): ret = self.getNodeForID(idval) return ret def getBaseNodeClass(cls): return dNode getBaseNodeClass = classmethod(getBaseNodeClass) # Event-handling code def __onTreeSel(self, evt): self.raiseEvent(dEvents.TreeSelection, evt) def __onKeyUp(self, evt): evt.Skip() if evt.GetKeyCode() in (316, 317, 318, 319): self._onWxHit(evt) def __onTreeItemCollapse(self, evt): self.raiseEvent(dEvents.TreeItemCollapse, evt) def __onTreeItemExpand(self, evt): self.raiseEvent(dEvents.TreeItemExpand, evt) def __onTreeMouseMove(self, evt): if self._useNodeToolTips: nd = self.getNodeUnderMouse() if nd: if nd.ToolTipText: self.ToolTipText = nd.ToolTipText else: self.ToolTipText = nd.Caption else: if self._storedToolTipText is not None: self.ToolTipText = self._storedToolTipText else: self.ToolTipText = "" def _getBaseNodes(self): if self.ShowRootNode: return [self._rootNode] else: return [ nd for nd in self.nodes if nd.parent is not None and nd.parent.IsRootNode ] def _getEditable(self): return self._hasWindowStyleFlag(wx.TR_EDIT_LABELS) def _setEditable(self, val): self._delWindowStyleFlag(wx.TR_EDIT_LABELS) if val: self._addWindowStyleFlag(wx.TR_EDIT_LABELS) def _getImageSize(self): return self._imageSize def _setImageSize(self, val): if self._constructed(): self._imageSize = val else: self._properties["ImageSize"] = val def _getMultipleSelect(self): return self._hasWindowStyleFlag(wx.TR_MULTIPLE) def _setMultipleSelect(self, val): self._delWindowStyleFlag(wx.TR_MULTIPLE) self._delWindowStyleFlag(wx.TR_EXTENDED) self._delWindowStyleFlag(wx.TR_SINGLE) if val: self._addWindowStyleFlag(wx.TR_MULTIPLE) self._addWindowStyleFlag(wx.TR_EXTENDED) else: if self._constructed(): self.lockDisplay() sel = self.Selection self.UnselectAll() self._addWindowStyleFlag(wx.TR_SINGLE) self.Selection = sel self.unlockDisplay() else: self._addWindowStyleFlag(wx.TR_SINGLE) def _getNodeClass(self): return self._nodeClass def _setNodeClass(self, val): if self._constructed(): self._nodeClass = val else: self._properties["NodeClass"] = val def _getSelection(self): if self.MultipleSelect: ids = self.GetSelections() ret = [node for node in self.nodes if node.itemID in ids] else: itemID = self.GetSelection() if itemID: try: ret = [n for n in self.nodes if n.itemID == itemID][0] except IndexError: ret = None else: ret = None return ret def _setSelection(self, node): if self._constructed(): self.UnselectAll() if not node: return if isinstance(node, (list, tuple)): if self.MultipleSelect: for itm in node: self.SelectItem(itm.itemID, True) else: if len(node) > 1: dabo.log.error( _("Attempting to select multiple nodes when MultipleSelect is False" )) self.SelectItem(node[0].itemID) else: self.SelectItem(node.itemID) else: self._properties["Selection"] = node def _getShowButtons(self): return self._hasWindowStyleFlag(wx.TR_HAS_BUTTONS) def _setShowButtons(self, val): if val: self._delWindowStyleFlag(wx.TR_NO_BUTTONS) self._addWindowStyleFlag(wx.TR_HAS_BUTTONS) else: self._delWindowStyleFlag(wx.TR_HAS_BUTTONS) self._addWindowStyleFlag(wx.TR_NO_BUTTONS) if self._constructed(): try: self.refresh() except AttributeError: # Control may not be constructed yet pass def _getShowLines(self): return not self._hasWindowStyleFlag(wx.TR_NO_LINES) def _setShowLines(self, val): self._delWindowStyleFlag(wx.TR_NO_LINES) if not val: self._addWindowStyleFlag(wx.TR_NO_LINES) if self._constructed(): try: self.refresh() except AttributeError: # Control may not be constructed yet pass def _getShowRootNode(self): return not self._hasWindowStyleFlag(wx.TR_HIDE_ROOT) def _setShowRootNode(self, val): self._delWindowStyleFlag(wx.TR_HIDE_ROOT) if not val: self._addWindowStyleFlag(wx.TR_HIDE_ROOT) if self._constructed(): try: self.refresh() except AttributeError: # Control may not be constructed yet pass def _getShowRootNodeLines(self): return self._hasWindowStyleFlag(wx.TR_LINES_AT_ROOT) def _setShowRootNodeLines(self, val): self._delWindowStyleFlag(wx.TR_LINES_AT_ROOT) if val: self._addWindowStyleFlag(wx.TR_LINES_AT_ROOT) if self._constructed(): try: self.refresh() except AttributeError: # Control may not be constructed yet pass def _getUseNodeToolTips(self): return self._useNodeToolTips def _setUseNodeToolTips(self, val): if self._constructed(): if val: self._storedToolTipText = self.ToolTipText else: if self._storedToolTipText is not None: self.ToolTipText = self._storedToolTipText self._useNodeToolTips = val else: self._properties["UseNodeToolTips"] = val BaseNodes = property( _getBaseNodes, None, None, _("""Returns the root node if ShowRootNode is True; otherwise, returns all the nodes who are not children of other nodes (read-only) (list of nodes)""")) Editable = property( _getEditable, _setEditable, None, _("""Specifies whether the tree labels can be edited by the user.""")) ImageSize = property( _getImageSize, _setImageSize, None, _("Size of images added to the tree. Default=(15, 15) (2-tuple of int)" )) MultipleSelect = property( _getMultipleSelect, _setMultipleSelect, None, _("""Specifies whether more than one node may be selected at once.""")) NodeClass = property(_getNodeClass, _setNodeClass, None, _("Class to use when creating nodes (dNode)")) Selection = property( _getSelection, _setSelection, None, _("""Specifies which node or nodes are selected. If MultipleSelect is False, the currently selected node is specified. If MultipleSelect is True, a list of selected nodes is specified.""")) ShowButtons = property( _getShowButtons, _setShowButtons, None, _("""Specifies whether +/- indicators are show at the left of parent nodes.""" )) ShowLines = property( _getShowLines, _setShowLines, None, _("Specifies whether lines are drawn between nodes. (bool)")) ShowRootNode = property( _getShowRootNode, _setShowRootNode, None, _("""Specifies whether the root node is included in the treeview. There can be only one root node, so if you want several root nodes you can fake it by setting ShowRootNode to False. Now, your top child nodes have the visual indication of being sibling root nodes.""")) ShowRootNodeLines = property( _getShowRootNodeLines, _setShowRootNodeLines, None, _("""Specifies whether vertical lines are shown between root siblings.""" )) UseNodeToolTips = property( _getUseNodeToolTips, _setUseNodeToolTips, None, _("""When True, the ToolTipText displayed is taken from the node. Default=False (bool)""")) DynamicEditable = makeDynamicProperty(Editable) DynamicMultipleSelect = makeDynamicProperty(MultipleSelect) DynamicSelection = makeDynamicProperty(Selection) DynamicShowButtons = makeDynamicProperty(ShowButtons) DynamicShowLines = makeDynamicProperty(ShowLines) DynamicShowRootNode = makeDynamicProperty(ShowRootNode) DynamicShowRootNodeLines = makeDynamicProperty(ShowRootNodeLines)
class dSplitter(cm.dControlMixin, wx.SplitterWindow): """ Main class for handling split windows. It will contain two panels (subclass of SplitterPanelMixin), each of which can further split itself in two. """ def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dSplitter unsplitAtt = self._extractKey((kwargs, properties, attProperties), "CanUnsplit", "True") self._canUnsplit = unsplitAtt.upper()[0] == "T" baseStyle = wx.SP_3D | wx.SP_LIVE_UPDATE if self._canUnsplit: baseStyle = baseStyle | wx.SP_PERMIT_UNSPLIT style = self._extractKey((kwargs, properties, attProperties), "style", baseStyle) self._createPanes = self._extractKey(attProperties, "createPanes", None) if self._createPanes is not None: self._createPanes = (self._createPanes == "True") else: self._createPanes = self._extractKey((kwargs, properties), "createPanes", False) self._createSizers = self._extractKey(attProperties, "createSizers", None) if self._createSizers is not None: self._createSizers = (self._createSizers == "True") else: self._createSizers = self._extractKey((kwargs, properties), "createSizers", False) self._splitOnInit = self._extractKey(attProperties, "splitOnInit", None) if self._splitOnInit is not None: self._splitOnInit = (self._splitOnInit == "True") else: self._splitOnInit = self._extractKey((kwargs, properties), "splitOnInit", self._createPanes) # Default to a decent minimum panel size if none is specified mp = self._extractKey(attProperties, "MinimumPanelSize", None) if mp is not None: mp = int(mp) else: mp = self._extractKey((kwargs, properties, attProperties), "MinimumPanelSize", 20) kwargs["MinimumPanelSize"] = mp # Default to vertical split self._orientation = self._extractKey((kwargs, properties, attProperties), "Orientation", "v") self._sashPos = 100 self._sashPercent = 1 self._p1 = self._p2 = None # Default to not showing the context menus on the panels self._showPanelSplitMenu = False preClass = wx.PreSplitterWindow cm.dControlMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, style=style, *args, **kwargs) def _initEvents(self): super(dSplitter, self)._initEvents() self.Bind(wx.EVT_SPLITTER_DCLICK, self._onSashDClick) self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self._onSashPos) def _afterInit(self): # Create the panes if self._createPanes: self.createPanes() if self._splitOnInit: self.split() super(dSplitter, self)._afterInit() def _makeSplitterPanelClass(self, cls): mixin = SplitterPanelMixin if hasattr(self.Form, "isDesignerForm"): mixin = self.Application.getControlClass(mixin) # See if the class already is mixed in with the SplitterPanelMixin if isinstance(cls, mixin): ret = cls else: class MixedSplitterPanel(cls, mixin): def __init__(self, parent, *args, **kwargs): cls.__init__(self, parent, *args, **kwargs) mixin.__init__(self, parent, *args, **kwargs) ret = MixedSplitterPanel return ret def createPanes(self, cls=None, pane=None, force=False): if cls is None: cls = self.PanelClass spCls = self._makeSplitterPanelClass(cls) if pane is None: p1 = p2 = True else: p1 = (pane == 1) p2 = (pane == 2) if p1 and (force or self.Panel1 is None): self.Panel1 = spCls(self) if self._createSizers: self.Panel1.Sizer = dabo.ui.dSizer() if p2 and (force or self.Panel2 is None): self.Panel2 = spCls(self) if self._createSizers: self.Panel2.Sizer = dabo.ui.dSizer() def initialize(self, pnl): self.Initialize(pnl) def layout(self): if not self: return self.Panel1.layout() self.Panel2.layout() def _onSashDClick(self, evt): """ Handle the double-clicking of the sash. This will call the user-customizable onSashDClick() method. """ ## Vetoing the event now will give user code the opportunity to not do the ## default of removing the sash, by calling evt.stop(). evt.Veto() # Update the internal sash position attribute. self._getSashPosition() # Raise a dEvent for other code to bind to, self.raiseEvent(dEvents.SashDoubleClick, evt) def _onSashPos(self, evt): """Fires when the sash position is changed.""" evt.Skip() # Update the internal sash position attribute. self._getSashPosition() sz = {"V": self.Width, "H": self.Height}[self.Orientation[0]] * 1.0 if sz: pct = float(self.SashPosition) / sz self._sashPercent = max(0, min(1, pct)) self.SetSashGravity(self._sashPercent) # Raise a dEvent for other code to bind to, self.raiseEvent(dEvents.SashPositionChanged, evt) def split(self, dir_=None): if self.IsSplit(): return if self.Panel1 is None or self.Panel2 is None: # No panels, so we can't split! Create them. self.createPanes() if dir_: self.Orientation = dir_ # Get the position pos = self.SashPosition if self.Orientation == "Horizontal": self.SplitHorizontally(self.Panel1, self.Panel2, pos) else: self.SplitVertically(self.Panel1, self.Panel2, pos) self.layout() def unsplit(self, win=None): if self.IsSplit(): # Save the sash position self._getSashPosition() self.Unsplit(win) self.layout() def canRemove(self, pnl): ret = self.IsSplit() if not ret: # Make sure that there is at least one level of splitting somewhere obj = pnl while obj.Parent and not ret: obj = obj.Parent if isinstance(obj, dSplitter): ret = self.IsSplit() return ret def remove(self, pnl): if self.IsSplit(): self.unsplit(pnl) else: # If the parent of this is a SplitterPanelMixin, tell it to hide prnt = self.Parent if isinstance(prnt, SplitterPanelMixin): prnt.remove() else: self.Destroy() def toggleSplit(self): """Flips the split status of the control.""" if self.IsSplit(): self.unsplit() else: self.split() def _getCanUnsplit(self): return self._canUnsplit def _getMinPanelSize(self): return self.GetMinimumPaneSize() def _setMinPanelSize(self, val): if self._constructed(): self.SetMinimumPaneSize(val) else: self._properties["MinimumPanelSize"] = val def _getOrientation(self): if self._orientation[0].lower() == "v": return "Vertical" else: return "Horizontal" def _setOrientation(self, val): if self._constructed(): orient = val.lower()[0] if orient in ("h", "v"): self._orientation = {"h": "Horizontal", "v": "Vertical"}[orient] if self.IsSplit(): self.lockDisplay() self.unsplit() self.split() self.unlockDisplay() else: raise ValueError("Orientation can only be 'Horizontal' or 'Vertical'") else: self._properties["Orientation"] = val def _getPanel1(self): return self._p1 def _setPanel1(self, pnl): if self._constructed(): old = self._p1 if self.IsSplit(): if self.Orientation == "Vertical": self.SplitVertically(pnl, self._p2) else: self.SplitHorizontally(pnl, self._p2) else: self.Initialize(pnl) self._p1 = pnl try: old.Destroy() except AttributeError: pass else: self._properties["Panel1"] = pnl def _getPanel2(self): return self._p2 def _setPanel2(self, pnl): if self._constructed(): old = self._p2 self._p2 = pnl if self.IsSplit(): self.ReplaceWindow(self.GetWindow2(), pnl) try: old.Destroy() except AttributeError: pass else: self._properties["Panel2"] = pnl def _getPanelClass(self): try: ret = self._panelClass except AttributeError: ret = self._panelClass = dabo.ui.dPanel return ret def _setPanelClass(self, val): self._panelClass = val def _getSashPercent(self): pos = self._getSashPosition() sz = {"V": self.Width, "H": self.Height}[self.Orientation[0]] if sz: ret = 100 * (float(pos) / float(sz)) else: ret = 0 return ret def _setSashPercent(self, val): if self._constructed(): if 0 <= val <= 100: sz = {"V": self.Width, "H": self.Height}[self.Orientation[0]] pct = val / 100.0 self._setSashPosition(sz * pct) self.SetSashGravity(pct) else: self._properties["SashPercent"] = val def _getSashPosition(self): if self.IsSplit(): self._sashPos = self.GetSashPosition() return self._sashPos def _setSashPosition(self, val): if self._constructed(): self.SetSashPosition(val) # Set the internal prop from the wx Prop self._sashPos = self.GetSashPosition() else: self._properties["SashPosition"] = val def _getShowPanelSplitMenu(self): return self._showPanelSplitMenu def _setShowPanelSplitMenu(self, val): if self._constructed(): self._showPanelSplitMenu = val try: self.Panel1.ShowSplitMenu = val except AttributeError: pass try: self.Panel2.ShowSplitMenu = val except AttributeError: pass else: self._properties["ShowPanelSplitMenu"] = val def _getSplit(self): return self.IsSplit() def _setSplit(self, val): if val: self.split() else: self.unsplit() CanUnsplit = property(_getCanUnsplit, None, None, _("""Can the control be unsplit (i.e., only the first pane visible), even with a non-zero MinimumPanelSize? Can only be set when the control is created; read-only afterwards. Default=True (bool)""")) MinimumPanelSize = property(_getMinPanelSize, _setMinPanelSize, None, _("Controls the minimum width/height of the panels. (int)")) Orientation = property(_getOrientation, _setOrientation, None, _("Determines if the window splits Horizontally or Vertically. (string)")) Panel1 = property(_getPanel1, _setPanel1, None, _("Returns the Top/Left panel. (dPanel)")) Panel2 = property(_getPanel2, _setPanel2, None, _("Returns the Bottom/Right panel. (dPanel)")) PanelClass = property(_getPanelClass, _setPanelClass, None, _("""Class used for creating panels. If the class does not descend from SplitterPanelMixin, that class will be mixed-into the class specified here. This must be set before the panels are created; setting it afterward has no effect unless you destroy the panels and re-create them. Default=dPanel (dPanel)""")) SashPercent = property(_getSashPercent, _setSashPercent, None, _("Percentage of the split window given to Panel1. Range=0-100 (float)")) SashPosition = property(_getSashPosition, _setSashPosition, None, _("Position of the sash when the window is split. (int)")) ShowPanelSplitMenu = property(_getShowPanelSplitMenu, _setShowPanelSplitMenu, None, _("""Determines if the default context menu for split/unsplit is enabled for the panels (default=False) (bool)""")) Split = property(_getSplit, _setSplit, None, _("Returns the split status of the control (bool)")) DynamicMinimumPanelSize = makeDynamicProperty(MinimumPanelSize) DynamicOrientation = makeDynamicProperty(Orientation) DynamicPanel1 = makeDynamicProperty(Panel1) DynamicPanel2 = makeDynamicProperty(Panel2) DynamicSashPosition = makeDynamicProperty(SashPosition) DynamicSplit = makeDynamicProperty(Split)
class dDatePicker(dcm.dDataControlMixin, wx.DatePickerCtrl): """ Creates a DatePicker control. Control purpose is to maintain Date field types, but it can be used for Timestamp data field types too. It's behavior is similar to dDateTextBox control. """ def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._invalidBackColor = "Yellow" self._valueMode = "d" self._timePart = [0, 0, 0, 0] self._lastWasNone = True self._baseClass = dDatePicker preClass = wx.PreDatePickerCtrl pickerMode = self._extractKey((properties, attProperties, kwargs), "PickerMode", "Dropdown")[:1].lower() if pickerMode not in "ds": pickerMode = "d" kwargs["style"] = kwargs.get("style", 0) | \ {"d": wx.DP_DROPDOWN, "s": wx.DP_SPIN}[pickerMode] if self._extractKey((properties, attProperties, kwargs), "AllowNullDate", False): kwargs["style"] |= wx.DP_ALLOWNONE if self._extractKey((properties, attProperties, kwargs), "ForceShowCentury", False): kwargs["style"] |= wx.DP_SHOWCENTURY dcm.dDataControlMixin.__init__(self, preClass, parent, properties, attProperties, *args, **kwargs) self._bindKeys() if self.AllowNullDate: self.SetValue( None ) # Need this for the datetime not to display the current date when Null. def _initEvents(self): super(dDatePicker, self)._initEvents() self.Bind(wx.EVT_DATE_CHANGED, self._onWxHit) def _onWxHit(self, evt): self._userChanged = True self._lastWasNone = False self.flushValue() super(dDatePicker, self)._onWxHit(evt) def dayInterval(self, days): """Adjusts the date by the given number of days; negative values move backwards. """ self.Value += datetime.timedelta(days) def monthInterval(self, months): """Adjusts the date by the given number of months; negative values move backwards. """ val = self.Value mn = val.month + months yr = val.year dy = val.day while mn < 1: yr -= 1 mn += 12 while mn > 12: yr += 1 mn -= 12 # May still be an invalid day for the selected month ok = False while not ok: try: val = val.replace(year=yr, month=mn, day=dy) ok = True except ValueError: dy -= 1 self.Value = val def setCurrentDate(self): if self._valueMode == "d": val = datetime.date.today() else: val = datetime.datetime.now() self.Value = val def setToMonthDay(self, day): val = self.Value if isinstance(day, basestring): if day[:1].lower() == "f": val = val.replace(day=1) elif day[:1].lower() == "l": mn = val.month td = datetime.timedelta(1) while mn == val.month: val += td # We're now at the first of the next month. Go back one. val -= td else: val = val.replace(day=day) self.Value = val def setToYearDay(self, day): val = self.Value if isinstance(day, basestring): if day[:1].lower() == "f": val = val.replace(month=1, day=1) elif day[:1].lower() == "l": val = val.replace(month=12, day=31) self.Value = val def _processKey(self, evt): key = evt.EventData["keyCode"] if key == 43: # + self.dayInterval(1) elif key == 45: # - self.dayInterval(-1) elif key == 116: # T self.setCurrentDate() elif key == 91: # [ self.monthInterval(-1) elif key == 93: # ] self.monthInterval(1) elif key == 109: # m self.setToMonthDay("First") elif key == 104: # h self.setToMonthDay("Last") elif key == 121: # y self.setToYearDay("First") elif key == 114: # r self.setToYearDay("Last") elif key == 100: # d self._setCustomDate() elif key in (dabo.ui.dKeys.key_Delete, dabo.ui.dKeys.key_Back): self.Value = None else: print key def _setCustomDate(self): days = dabo.ui.getInt(message=_("Day shift:"), caption=_("Reschedule day"), Min=-365, Max=365) if days: self.dayInterval(days) def _bindKeys(self): # It seems that on Windows platform there is a bug in # control key handling implementation, because '=' key # is recognized as '+' key. # On Linux, '+' key seems to be unsupported. self.bindKey("+", self._processKey) self.bindKey("-", self._processKey) self.bindKey("d", self._processKey) self.bindKey("t", self._processKey) self.bindKey("[", self._processKey) self.bindKey("]", self._processKey) self.bindKey("m", self._processKey) self.bindKey("h", self._processKey) self.bindKey("y", self._processKey) self.bindKey("r", self._processKey) self.bindKey("=", self._processKey) self.bindKey("backspace", self._processKey) self.bindKey("delete", self._processKey) def _getPyValue(self, val): if self._lastWasNone: val = None elif val: if self._valueMode == "d": val = datetime.date(val.year, val.month, val.day) else: val = val.combine( val, datetime.time(self._timePart[0], self._timePart[1], self._timePart[2], self._timePart[3])) return val def _getWxValue(self, val): if isinstance(val, basestring): val = datetime.datetime.strptime(val, "%Y-%m-%d") elif isinstance(val, tuple): val = datetime.datetime(*val) elif isinstance(val, wx.DateTime): return val if val is not None: self._valueMode = "d" if type(val) == datetime.date else "t" if self._valueMode == "t": if val is None: self._timePart = [0, 0, 0, 0] else: self._timePart[0] = val.hour self._timePart[1] = val.minute self._timePart[2] = val.second self._timePart[3] = val.microsecond if val is None: self._lastWasNone = True if self.AllowNullDate: val = wx.DateTime() else: val = self.GetLowerLimit() else: self._lastWasNone = False val = dateTimePy2Wx(val) return val def setInvalidDate(self): self.Value = wx.DefaultDateTime def GetValue(self): try: val = dateTimeWx2Py(super(dDatePicker, self).GetValue()) except wx.PyAssertionError: val = None return self._getPyValue(val) def SetValue(self, val): val = self._getWxValue(val) try: super(dDatePicker, self).SetValue(val) except ValueError as e: nm = self.Name ue = ustr(e) dabo.log.error( _(u"Object '%(nm)s' has the following error: %(ue)s") % locals()) def _getAllowNullDate(self): return self._hasWindowStyleFlag(wx.DP_ALLOWNONE) def _setAllowNullDate(self, val): if val: self._addWindowStyleFlag(wx.DP_ALLOWNONE) else: self._delWindowStyleFlag(wx.DP_ALLOWNONE) def _getForceShowCentury(self): return self._hasWindowStyleFlag(wx.DP_SHOWCENTURY) def _setForceShowCentury(self): if val: self._addWindowStyleFlag(wx.DP_SHOWCENTURY) else: self._delWindowStyleFlag(wx.DP_SHOWCENTURY) def _getInvalidBackColor(self): return self._invalidBackColor def _setInvalidBackColor(self, val): self._invalidBackColor = val def _getIsDateValid(self): return self.Value is not None def _getMaxValue(self): return self._getPyValue(dateTimeWx2Py(self.UpperLimit)) def _setMaxValue(self, val): if self._constructed(): val = self._getWxValue(val) self.SetRange(self.LowerLimit, val) else: self._properties["MinValue"] = val def _getMinValue(self): return self._getPyValue(dateTimeWx2Py(self.LowerLimit)) def _setMinValue(self, val): if self._constructed(): val = self._getWxValue(val) self.SetRange(val, self.UpperLimit) else: self._properties["MinValue"] = val def _getPickerMode(self): if self._hasWindowStyleFlag(wx.DP_DROPDOWN): mode = "Dropdown" else: mode = "Spin" return mode def _setPickerMode(self, val): mode = val[:1].lower() if mode in "ds": self._addWindowStyleFlag({ "d": wx.DP_DROPDOWN, "s": wx.DP_SPIN }[mode]) else: raise ValueError( _("The only allowed values are: 'Dropdown', 'Spin'.")) def _getValueMode(self): return {"d": "Date", "t": "Timestamp"}[self._valueMode] def _setValueMode(self, val): val = val[:1].lower() if val in "dt": self._valueMode = val else: raise ValueError( _("The only allowed values are: 'Date', 'Timestamp'.")) # Property definitions: AllowNullDate = property( _getAllowNullDate, _setAllowNullDate, None, _("""If True enable Null vale in date. (bool)(Default=False)""")) ForceShowCentury = property( _getForceShowCentury, _setForceShowCentury, None, _("""Regardless of locale setting, century is shown if True. (bool) (Default=False)""")) IsDateValid = property( _getIsDateValid, None, None, _("""Read-only property tells if Value holds valid date type value.""") ) InvalidBackColor = property( _getInvalidBackColor, _setInvalidBackColor, None, _("""Color value used for illegal values or values out-of-teh bounds. (str) (Default="Yellow")""")) MaxValue = property( _getMaxValue, _setMaxValue, None, _("""Holds upper value limit. (date, tuple, str)(Default=None)""")) MinValue = property( _getMinValue, _setMinValue, None, _("""Holds lower value limit. (date, tuple, str)(Default=None)""")) PickerMode = property( _getPickerMode, _setPickerMode, None, _("""Creates control with spin or dropdown calendar. (str) Available values are: - Spin - Dropdown (default)""")) ValueMode = property( _getValueMode, _setValueMode, None, _("""Enables handling Timestamp type. (str)(Default="Date")""")) DynamicMaxValue = makeDynamicProperty(MaxValue) DynamicMinValue = makeDynamicProperty(MinValue)
When False, the value will revert back to the last numeric value when the control loses focus. The default comes from dabo.dTextBox_NumericBlankToZero, which defaults to False.""")) PasswordEntry = property(_getPasswordEntry, _setPasswordEntry, None, _("Specifies whether plain-text or asterisks are echoed. (bool)")) StrictDateEntry = property(_getStrictDateEntry, _setStrictDateEntry, None, _("""Specifies whether date values must be entered in strict ISO8601 format. Default=False. If not strict, dates can be accepted in YYYYMMDD, YYMMDD, and MMDD format, which will be coerced into sensible date values automatically.""")) StrictNumericEntry = property(_getStrictNumericEntry, _setStrictNumericEntry, None, _("""When True, the DataType will be preserved across numeric types. When False, the DataType will respond to user input to convert to the 'obvious' numeric type. Default=True. (bool)""")) Value = property(_getValue, _setValue, None, _("Specifies the current state of the control (the value of the field). (varies)")) # Dynamic property declarations DynamicPasswordEntry = makeDynamicProperty(PasswordEntry) DynamicStrictDateEntry = makeDynamicProperty(StrictDateEntry) DynamicValue = makeDynamicProperty(Value)
MaxRows = property(_getMaxRows, _setMaxRows, None, _("When adding elements to the sizer, controls the max number " "of rows to add before a new column is started. (int)") ) MaxCols = property(_getMaxCols, _setMaxCols, None, _("When adding elements to the sizer, controls the max number " "of columns to add before a new row is started. (int)") ) MaxDimension = property(_getMaxDimension, _setMaxDimension, None, _("When adding elements to the sizer, this property determines " " if we use rows or columns as the limiting value. (char: 'r' or 'c'(default) )") ) Orientation = property(_getMaxDimension, _setMaxDimension, None, _("Alias for the MaxDimensions property. (char: 'r' or 'c'(default) )") ) VGap = property(_getVGap, _setVGap, None, _("Vertical gap between cells in the sizer (int)")) DynamicHGap = makeDynamicProperty(HGap) DynamicMaxRows = makeDynamicProperty(MaxRows) DynamicMaxCols = makeDynamicProperty(MaxCols) DynamicMaxDimension = makeDynamicProperty(MaxDimension) DynamicOrientation = makeDynamicProperty(Orientation) DynamicVGap = makeDynamicProperty(VGap) if __name__ == "__main__": s = dGridSizer()
class dButton(cm.dControlMixin, wx.Button): """ Creates a button that can be pressed by the user to trigger an action. Example:: class MyButton(dabo.ui.dButton): def initProperties(self): self.Caption = "Press Me" def onHit(self, evt): self.Caption = "Press Me one more time" """ def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dButton preClass = wx.PreButton cm.dControlMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, *args, **kwargs) def _initEvents(self): super(dButton, self)._initEvents() self.Bind(wx.EVT_BUTTON, self._onWxHit) def _onCancelButton(self, evt, recurse=True): # This callback exists for when the user presses ESC and this button # is the cancel button. Raise dEvents.Hit. if self.VisibleOnScreen: self.raiseEvent(dEvents.Hit) else: # There may be another cancel button: give it a chance, too: if recurse: otherCancelButton = self.Form.FindWindowById(wx.ID_CANCEL) if otherCancelButton: otherCancelButton._onCancelButton(evt, recurse=False) # Property get/set/del methods follow. Scroll to bottom to see the property # definitions themselves. def _getCancelButton(self): return self.GetId() == wx.ID_CANCEL def _setCancelButton(self, val): if self._constructed(): ## pkm: We can bind the key to self, Parent, or Form (or any object). ## If bound to self, the <esc> keypress will only fire the Hit ## when self has the focus. If bound to self.Parent, Hit will ## fire when self.Parent or any of its children has the focus. ## If bound to self.Form, Hit will fire whenever <esc> is pressed. ## I'm making the decision to bind it to self.Form, even though ## self.Parent is also a valid choice. ### egl: changed the binding on OS X to the form. Parent just doesn't work. ### pkm: followed suit with GTK (we should test Win too). ### pkm: removed GTK to bind to parent because it wasn't working on the form. target = self.Parent if self.Application.Platform in ("Mac"): target = self.Form if val: target.bindKey("esc", self._onCancelButton) self.SetId(wx.ID_CANCEL) else: target.unbindKey("esc") self.SetId(wx.NewId()) else: # In order to get the stock cancel button behavior from the OS, we need # to set the id here. So, getting the stock button behavior must happen # in the constructor, but theoretically we can get the escape behavior # anytime. if val: self._preInitProperties["id"] = wx.ID_CANCEL self._properties["CancelButton"] = val def _getDefaultButton(self): try: v = self._defaultButton except AttributeError: v = self._defaultButton = False return v def _setDefaultButton(self, value): if self._constructed(): if value: # Need to unset default from any other buttons: for child in self.Parent.Children: if child is self: continue try: db = child.DefaultButton except AttributeError: db = False if db: child.DefaultButton = False # Now set it for this button self.SetDefault() else: # No wx-way to unset default button. Probably a rare need, anyway. # One idea would be to create a hidden button, set default to it, # and then destroy it. pass self._defaultButton = value else: self._properties["DefaultButton"] = value # Property definitions: CancelButton = property( _getCancelButton, _setCancelButton, None, _("Specifies whether this command button gets clicked on -Escape-.")) DefaultButton = property( _getDefaultButton, _setDefaultButton, None, _("Specifies whether this command button gets clicked on -Enter-.")) DynamicCancelButton = makeDynamicProperty(CancelButton) DynamicDefaultButton = makeDynamicProperty(DefaultButton)
class dTimer(PM): """Creates a timer, for causing something to happen at regular intervals.""" def __init__(self, parent=None, properties=None, *args, **kwargs): self._baseClass = dTimer super(dTimer, self).__init__(preClass=None, parent=parent, properties=properties, *args, **kwargs) def isRunning(self): return self.Enabled def start(self, interval=-1): if interval >= 0: self.Interval = interval self.Enabled = self.Interval > 0 return self.Enabled def stop(self): self.Enabled = False def release(self): """Make sure that the timer is stopped first""" self.stop() super(dTimer, self).release() # The following methods are not needed except for # compatibility with the various properties. def Show(self, val): pass def GetSize(self): return (-1, -1) def SetBestFittingSize(self, val): pass def GetParent(self): return None def Bind(self, *args, **kwargs): pass def Destroy(self): pass def _onTimerHit(self): if self.Enabled and self.Interval > 0: self.raiseEvent(dEvents.Hit) dabo.ui.callAfterInterval(self.Interval, self._onTimerHit) # property get/set functions def _getEnabled(self): return getattr(self, "_enabled", False) def _setEnabled(self, val): self._enabled = val if val: dabo.ui.callAfterInterval(self.Interval, self._onTimerHit) else: self._properties["Enabled"] = val def _getInterval(self): try: v = self._interval except AttributeError: v = self._interval = 0 return v def _setInterval(self, val): self._interval = val Enabled = property(_getEnabled, _setEnabled, None, _("""Alternative means of starting/stopping the timer, or determining its status. If Enabled is set to True and the timer has a positive value for its Interval, the timer will be started. (bool)""")) Interval = property(_getInterval, _setInterval, None, _("Specifies the timer interval (milliseconds).")) DynamicEnabled = makeDynamicProperty(Enabled) DynamicInterval = makeDynamicProperty(Interval)
MaxCols = property( _getMaxCols, _setMaxCols, None, _("When adding elements to the sizer, controls the max number " "of columns to add before a new row is started. (int)")) MaxDimension = property( _getMaxDimension, _setMaxDimension, None, _("When adding elements to the sizer, this property determines " " if we use rows or columns as the limiting value. (char: 'r' or 'c'(default) )" )) Orientation = property( _getMaxDimension, _setMaxDimension, None, _("Alias for the MaxDimensions property. (char: 'r' or 'c'(default) )") ) VGap = property(_getVGap, _setVGap, None, _("Vertical gap between cells in the sizer (int)")) DynamicHGap = makeDynamicProperty(HGap) DynamicMaxRows = makeDynamicProperty(MaxRows) DynamicMaxCols = makeDynamicProperty(MaxCols) DynamicMaxDimension = makeDynamicProperty(MaxDimension) DynamicOrientation = makeDynamicProperty(Orientation) DynamicVGap = makeDynamicProperty(VGap) if __name__ == "__main__": s = dGridSizer()
class dSlider(dcm.dDataControlMixin, wx.Slider): """ Creates a slider control, allowing editing integer values. Unlike dSpinner, dSlider does not allow entering a value with the keyboard. """ def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dSlider self._continuous = False self._lastVal = None style = self._extractKey((kwargs, properties, attProperties), "style") if style is None: style = wx.SL_AUTOTICKS else: style = style | wx.SL_AUTOTICKS kwargs["style"] = style # These need to be added to the style kwarg in _initProperties self._tickPosition = None self._reversed = False preClass = wx.PreSlider dcm.dDataControlMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, *args, **kwargs) def _initProperties(self): super(dSlider, self)._initProperties() style = self._preInitProperties["style"] if self._tickPosition: tickpos = self.TickPosition[0].upper() style = style | { "T": wx.SL_TOP, "B": wx.SL_BOTTOM, "L": wx.SL_LEFT, "R": wx.SL_RIGHT }[tickpos] if self._reversed: style = style | wx.SL_INVERSE self._preInitProperties["style"] = style def _initEvents(self): super(dSlider, self)._initEvents() self.Bind(wx.EVT_SCROLL, self._onWxHit) def _onWxHit(self, evt): newval = self.GetValue() changed = (newval != self._lastVal) self._lastVal = newval if (changed and self._continuous) or not dabo.ui.isMouseLeftDown(): self.flushValue() super(dSlider, self)._onWxHit(evt) # Property get/set/del methods follow. Scroll to bottom to see the property # definitions themselves. def _getContinuous(self): try: ret = self._continuous except AttributeError: ret = self._continuous = True return ret def _setContinuous(self, val): if self._constructed(): self._continuous = val else: self._properties["Continuous"] = val def _getMax(self): return self.GetMax() def _setMax(self, val): if self._constructed(): currmin = self.GetMin() currval = min(self.GetValue(), val) self.SetRange(currmin, val) self.SetValue(currval) else: self._properties["Max"] = val def _getMin(self): return self.GetMin() def _setMin(self, val): if self._constructed(): currmax = self.GetMax() currval = max(self.GetValue(), val) self.SetRange(val, currmax) self.SetValue(currval) else: self._properties["Min"] = val def _getOrientation(self): if self.GetWindowStyle() & wx.SL_VERTICAL: return "Vertical" else: return "Horizontal" def _setOrientation(self, val): tickpos = self._tickPosition isHoriz = (val.lower()[:1] == "h") if isHoriz and tickpos in ("Left", "Right"): dabo.log.error( _("Cannot set the slider to Horizontal when TickPosition is %s." ) % tickpos) elif not isHoriz and tickpos in ("Top", "Bottom"): dabo.log.error( _("Cannot set the slider to Vertical when TickPosition is %s.") % tickpos) self._delWindowStyleFlag(wx.SL_HORIZONTAL) self._delWindowStyleFlag(wx.SL_VERTICAL) if isHoriz: self._addWindowStyleFlag(wx.SL_HORIZONTAL) else: self._addWindowStyleFlag(wx.SL_VERTICAL) def _getReversed(self): return self._reversed def _setReversed(self, val): # Ignore this once constructed if not self._constructed(): self._reversed = val def _getShowLabels(self): return (self.GetWindowStyle() & wx.SL_LABELS > 0) def _setShowLabels(self, val): self._delWindowStyleFlag(wx.SL_LABELS) if val: self._addWindowStyleFlag(wx.SL_LABELS) def _getTickPosition(self): try: tp = self._tickPosition[0].upper() except TypeError: # No tick position set; return Bottom return "Bottom" return {"T": "Top", "B": "Bottom", "L": "Left", "R": "Right"}[tp] def _setTickPosition(self, val): # Ignore this once constructed if not self._constructed(): self._tickPosition = val # Property definitions: Continuous = property( _getContinuous, _setContinuous, None, _("""When True, the Hit event is raised as the slider moves. When False (default), it is only raised when the thumb control is released. (bool)""")) Max = property( _getMax, _setMax, None, _("Specifies the maximum value for the Slider. Default=100 (int)")) Min = property( _getMin, _setMin, None, _("Specifies the minimum value for the Slider. Default=0 (int)")) Orientation = property( _getOrientation, _setOrientation, None, _("""Specifies whether the Slider is displayed as Horizontal or Vertical. Default='Horizontal' (str)""")) Reversed = property( _getReversed, _setReversed, None, _("""When True, the position of the Min and Max values are reversed. Must be set when the object is created; setting it afterwards has no effect. Default=False (bool)""" )) ShowLabels = property( _getShowLabels, _setShowLabels, None, _("""Specifies if the labels are shown on the slider. Must be set when the object is created; setting it afterwards has no effect. Default=True (bool)""")) TickPosition = property( _getTickPosition, _setTickPosition, None, _("""Position of the tick marks; must be one of Top, Bottom (default), Left or Right. Not fully supported on all platforms. Must be set during object creation; has no effect once created. (str)""")) DynamicOrientation = makeDynamicProperty(Orientation) DynamicMax = makeDynamicProperty(Max) DynamicMin = makeDynamicProperty(Min) DynamicShowLabels = makeDynamicProperty(ShowLabels)
class dShellForm(dSplitForm): def _onDestroy(self, evt): self._clearOldHistory() __builtin__.raw_input = self._oldRawInput def _beforeInit(self, pre): # Set the sash self._sashPct = 0.6 # Class to use for creating the interactive shell self._shellClass = dShell super(dShellForm, self)._beforeInit(pre) def _afterInit(self): super(dShellForm, self)._afterInit() self.cmdHistKey = self.PreferenceManager.command_history self._historyPanel = None self._lastCmd = None # PyShell sets the raw_input function to a function of PyShell, # but doesn't set it back on destroy, resulting in errors later # on if something other than PyShell asks for raw_input (pdb, for # example). self._oldRawInput = __builtin__.raw_input self.bindEvent(dEvents.Destroy, self._onDestroy) splt = self.Splitter splt.MinimumPanelSize = 80 splt.unbindEvent() self.Orientation = "H" self.unsplit() self._splitState = False self.MainSplitter.bindEvent(dEvents.SashDoubleClick, self.sashDoubleClick) self.MainSplitter.bindEvent(dEvents.SashPositionChanged, self.sashPosChanged) cp = self.CmdPanel = self.Panel1 op = self.OutPanel = self.Panel2 cp.unbindEvent(dEvents.ContextMenu) op.unbindEvent(dEvents.ContextMenu) cp.Sizer = dabo.ui.dSizer() op.Sizer = dabo.ui.dSizer() pgf = self.pgfCodeShell = dabo.ui.dPageFrame(cp, PageCount=2) self.pgShell = pgf.Pages[0] self.pgCode = pgf.Pages[1] self.pgShell.Caption = _("Shell") self.pgCode.Caption = _("Code") cp.Sizer.append1x(pgf) self.shell = self.ShellClass(self.pgShell, DroppedTextHandler=self, DroppedFileHandler=self) self.pgShell.Sizer.append1x(self.shell, border=4) # Configure the shell's behavior self.shell.AutoCompSetIgnoreCase(True) self.shell.AutoCompSetAutoHide( False) ## don't hide when the typed string no longer matches self.shell.AutoCompStops( " ") ## characters that will stop the autocomplete self.shell.AutoCompSetFillUps(".(") # This lets you go all the way back to the '.' without losing the AutoComplete self.shell.AutoCompSetCancelAtStart(False) self.shell.Bind(wx.EVT_RIGHT_UP, self.onShellRight) self.shell.Bind(wx.wx.EVT_CONTEXT_MENU, self.onShellContext) # Create the Code control codeControl = dabo.ui.dEditor(self.pgCode, RegID="edtCode", Language="python", OnKeyDown=self.onCodeKeyDown, OnMouseRightDown=self.onCodeRightDown, DroppedTextHandler=self, DroppedFileHandler=self) self.pgCode.Sizer.append1x(codeControl, border=4) # This adds the interpreter's local namespace to the editor for code completion, etc. codeControl.locals = self.shell.interp.locals lbl = dabo.ui.dLabel( self.pgCode, ForeColor="blue", WordWrap=True, Caption= _("""Ctrl-Enter to run the code (or click the button to the right). Ctrl-Up/Down to scroll through history.""")) lbl.FontSize -= 3 runButton = dabo.ui.dButton(self.pgCode, Caption=_("Run"), OnHit=self.onRunCode) hsz = dabo.ui.dSizer("h") hsz.appendSpacer(20) hsz.append(lbl) hsz.append1x(dabo.ui.dPanel(self.pgCode)) hsz.append(runButton, valign="middle") hsz.appendSpacer(20) self.pgCode.Sizer.append(hsz, "x") # Stack to hold code history self._codeStack = [] self._codeStackPos = 0 # Restore the history self.restoreHistory() # Bring up history search self.bindKey("Ctrl+R", self.onHistoryPop) # Show/hide the code editing pane self.bindKey("Ctrl+E", self.onToggleCodePane) # Force the focus to the editor when the code page is activated. def _delayedSetFocus(evt): dabo.ui.callAfter(self.edtCode.setFocus) self.pgCode.bindEvent(dEvents.PageEnter, _delayedSetFocus) # create the output control outControl = dabo.ui.dEditBox(op, RegID="edtOut", ReadOnly=True) op.Sizer.append1x(outControl) outControl.bindEvent(dEvents.MouseRightDown, self.onOutputRightDown) self._stdOut = self.shell.interp.stdout self._stdErr = self.shell.interp.stderr self._pseudoOut = pseudo.PseudoFileOut(write=self.appendOut) self._pseudoErr = pseudo.PseudoFileOut(write=self.appendOut) self.SplitState = True # Make 'self' refer to the calling form, or this form if no calling form. # Make 'bo' refer to the primary bizobj of the calling form, if any. if self.Parent is None: ns = self else: ns = self.Parent bo = getattr(ns, "PrimaryBizobj", None) if bo: self.shell.interp.locals['bo'] = bo self.shell.interp.locals['self'] = ns self.Caption = _("dShellForm: self is %s") % ns.Name self.setStatusText( _("Use this shell to interact with the runtime environment")) self.fillMenu() self.shell.SetFocus() def appendOut(self, tx): ed = self.edtOut ed.Value += tx endpos = ed.GetLastPosition() # Either of these commands should scroll the edit box # to the bottom, but neither do (at least on OS X) when # called directly or via callAfter(). dabo.ui.callAfter(ed.ShowPosition, endpos) dabo.ui.callAfter(ed.SetSelection, endpos, endpos) def addToHistory(self, cmd=None): if cmd is None: cmd = self.shell.history[0] chk = self.cmdHistKey if cmd == self._lastCmd: # Don't add again return # Delete any old instances of this command chk.deleteByValue(cmd) self._lastCmd = cmd stamp = "%s" % int(round(time.time() * 100, 0)) self.cmdHistKey.setValue(stamp, cmd) def _loadHistory(self): ck = self.cmdHistKey cmds = [] for k in ck.getPrefKeys(): cmds.append({"stamp": k, "cmd": ck.get(k)}) dsu = dabo.db.dDataSet(cmds) if dsu: ds = dsu.sort("stamp", "asc") return ds else: return dsu def onToggleCodePane(self, evt): """Toggle between the Code Pane and the Output Pane""" self.pgfCodeShell.cyclePages(1) def processDroppedFiles(self, filelist): """ This will fire if files are dropped on the code editor. If more than one file is dropped, only open the first, and warn the user. """ if len(filelist) > 1: dabo.ui.exclaim(_("Only one file can be dropped at a time")) if self.pgfCodeShell.SelectedPage == self.pgShell: self.shell.AddText(filelist[0]) else: self.edtCode.Value = file(filelist[0]).read() def processDroppedText(self, txt): """Add the text to the code editor.""" cc = self.edtCode currText = cc.Value selStart, selEnd = cc.SelectionPosition cc.Value = "%s%s%s" % (currText[:selStart], txt, currText[selEnd:]) def onHistoryPop(self, evt): """ Let the user type in part of a command, and retrieve the matching commands from their history. """ ds = self._loadHistory() hp = self._HistoryPanel hp.History = ds fp = self.FloatingPanel # We want it centered, so set Owner to None fp.Owner = None hp.clear() fp.show() if hp.ok: cmds = hp.getCmd() for num, cmd in enumerate(cmds): # For all but the first, we need to process the previous command. if num: self.shell.processLine() try: pos = self.shell.history.index(cmd) except ValueError: # Not in the list return self.shell.replaceFromHistory(pos - self.shell.historyIndex) def restoreHistory(self): """ Get the stored history from previous sessions, and set the shell's internal command history list to it. """ ds = self._loadHistory() self.shell.history = [rec["cmd"] for rec in ds] def _clearOldHistory(self): """For performance reasons, only save up to 500 commands.""" numToSave = 500 ck = self.cmdHistKey ds = self._loadHistory() if len(ds) <= numToSave: return cutoff = ds[numToSave]["stamp"] bad = [] for rec in ds: if rec["stamp"] <= cutoff: bad.append(rec["stamp"]) for bs in bad: ck.deletePref(bs) def onRunCode(self, evt, addReturn=True): code = self.edtCode.Value.rstrip() if not code: return # See if this is already in the stack try: self._codeStackPos = self._codeStack.index(code) except ValueError: self._codeStack.append(code) self._codeStackPos = len(self._codeStack) self.edtCode.Value = "" self.shell.Execute(code) # If the last line is indented, run a blank line to complete the block if code.splitlines()[-1][0] in " \t": self.shell.run("", prompt=False) self.addToHistory() self.pgfCodeShell.SelectedPage = self.pgShell def onCodeKeyDown(self, evt): if not evt.controlDown: return keyCode = evt.keyCode if (keyCode == 13): evt.stop() self.onRunCode(None, addReturn=True) elif keyCode in (dKeys.key_Up, dKeys.key_Down): direction = {dKeys.key_Up: -1, dKeys.key_Down: 1}[keyCode] self.moveCodeStack(direction) def moveCodeStack(self, direction): size = len(self._codeStack) pos = self._codeStackPos newpos = max(0, pos + direction) if newpos == size: # at the end; clear the code self._codeStackPos = size - 1 self.edtCode.Value = "" else: code = self._codeStack[newpos] self._codeStackPos = newpos self.edtCode.Value = code def onCodeRightDown(self, evt): dabo.ui.info("Code!") def onOutputRightDown(self, evt): pop = dabo.ui.dMenu() pop.append(_("Clear"), OnHit=self.onClearOutput) if self.edtOut.SelectionLength: pop.append(_("Copy"), OnHit=self.Application.onEditCopy) self.showContextMenu(pop) evt.stop() def onClearOutput(self, evt): self.edtOut.Value = "" def onShellContext(self, evt): pop = dabo.ui.dMenu() if self.SplitState: pmpt = _("Unsplit") else: pmpt = _("Split") pop.append(pmpt, OnHit=self.onSplitContext) self.showContextMenu(pop) evt.StopPropagation() def onShellRight(self, evt): pop = dabo.ui.dMenu() if self.SplitState: pmpt = _("Unsplit") else: pmpt = _("Split") pop.append(pmpt, OnHit=self.onSplitContext) self.showContextMenu(pop) evt.StopPropagation() def onSplitContext(self, evt): self.SplitState = not self.SplitState evt.stop() def onResize(self, evt): self.SashPosition = self._sashPct * self.Height def sashDoubleClick(self, evt): # We don't want the window to unsplit evt.stop() def sashPosChanged(self, evt): self._sashPct = float(self.SashPosition) / self.Height def fillMenu(self): viewMenu = self.MenuBar.getMenu("base_view") if viewMenu.Children: viewMenu.appendSeparator() viewMenu.append(_("Zoom &In"), HotKey="Ctrl+=", OnHit=self.onViewZoomIn, ItemID="view_zoomin", bmp="zoomIn", help=_("Zoom In")) viewMenu.append(_("&Normal Zoom"), HotKey="Ctrl+/", OnHit=self.onViewZoomNormal, ItemID="view_zoomnormal", bmp="zoomNormal", help=_("Normal Zoom")) viewMenu.append(_("Zoom &Out"), HotKey="Ctrl+-", OnHit=self.onViewZoomOut, ItemID="view_zoomout", bmp="zoomOut", help=_("Zoom Out")) viewMenu.append(_("&Toggle Code Pane"), HotKey="Ctrl+E", OnHit=self.onToggleCodePane, ItemID="view_togglecode", bmp="", help=_("Show/hide Code Pane")) editMenu = self.MenuBar.getMenu("base_edit") if editMenu.Children: editMenu.appendSeparator() editMenu.append(_("nClear O&utput"), HotKey="Ctrl+Back", ItemID="edit_clearoutput", OnHit=self.onClearOutput, help=_("Clear Output Window")) def onViewZoomIn(self, evt): self.shell.SetZoom(self.shell.GetZoom() + 1) def onViewZoomNormal(self, evt): self.shell.SetZoom(0) def onViewZoomOut(self, evt): self.shell.SetZoom(self.shell.GetZoom() - 1) @classmethod def getBaseShellClass(cls): return dShell def _getFontSize(self): return self.shell.FontSize def _setFontSize(self, val): if self._constructed(): self.shell.FontSize = val else: self._properties["FontSize"] = val def _getFontFace(self): return self.shell.FontFace def _setFontFace(self, val): if self._constructed(): self.shell.FontFace = val else: self._properties["FontFace"] = val def _getHistoryPanel(self): fp = self.FloatingPanel try: create = self._historyPanel is None except AttributeError: create = True if create: fp.clear() pnl = self._historyPanel = _LookupPanel(fp) pnl.Height = max(200, self.Height - 100) fp.Sizer.append(pnl) fp.fitToSizer() return self._historyPanel def _getShellClass(self): return self._shellClass def _setShellClass(self, val): if self._constructed(): self._shellClass = val else: self._properties["ShellClass"] = val def _getSplitState(self): return self._splitState def _setSplitState(self, val): if self._splitState != val: self._splitState = val if val: self.split() self.shell.interp.stdout = self._pseudoOut self.shell.interp.stderr = self._pseudoErr else: self.unsplit() self.shell.interp.stdout = self._stdOut self.shell.interp.stderr = self._stdErr FontFace = property(_getFontFace, _setFontFace, None, _("Name of the font face used in the shell (str)")) FontSize = property(_getFontSize, _setFontSize, None, _("Size of the font used in the shell (int)")) _HistoryPanel = property( _getHistoryPanel, None, None, _("Popup to display the command history (read-only) (dDialog)")) ShellClass = property( _getShellClass, _setShellClass, None, _("Class to use for the interactive shell (dShell)")) SplitState = property( _getSplitState, _setSplitState, None, _("""Controls whether the output is in a separate pane (default) or intermixed with the commands. (bool)""")) DynamicSplitState = makeDynamicProperty(SplitState)
class dLine(cm.dControlMixin, wx.StaticLine): """ Creates a horizontal or vertical line. If Orientation is "Vertical", Height refers to the length of the line. If Orientation is "Horizontal", Width refers to the length of the line. The other value refers to how wide the control is, which affects how much buffer space will enclose the line, which will appear in the center of this space. """ def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dLine preClass = wx.PreStaticLine cm.dControlMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, *args, **kwargs) def _initEvents(self): super(dLine, self)._initEvents() # property get/set functions def _getOrientation(self): if self._hasWindowStyleFlag(wx.LI_VERTICAL): return "Vertical" else: return "Horizontal" def _setOrientation(self, value): # Note: Orientation must be set before object created. self._delWindowStyleFlag(wx.LI_VERTICAL) self._delWindowStyleFlag(wx.LI_HORIZONTAL) value = ustr(value)[0].lower() if value == "v": self._addWindowStyleFlag(wx.LI_VERTICAL) elif value == "h": self._addWindowStyleFlag(wx.LI_HORIZONTAL) else: raise ValueError("The only possible values are " "'Horizontal' and 'Vertical'.") # property definitions follow: Orientation = property( _getOrientation, _setOrientation, None, "Specifies the Orientation of the line. (str) \n" " Horizontal (default) \n" " Vertical" "This is determined by the Width and Height properties. " "If the Width is greater than the Height, it will be Horizontal. " "Otherwise, it will be Vertical.") DynamicOrientation = makeDynamicProperty(Orientation)
class dEditableList(dcm.dControlMixin, wx.gizmos.EditableListBox): """ Creates an editable list box, complete with buttons to control editing, adding/deleting items, and re-ordering them. """ def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dEditableList preClass = wx.gizmos.EditableListBox self._canAdd = self._extractKey((kwargs, properties, attProperties), "CanAdd", True) if isinstance(self._canAdd, basestring): self._canAdd = (self._canAdd == "True") self._canDelete = self._extractKey((kwargs, properties, attProperties), "CanDelete", True) if isinstance(self._canDelete, basestring): self._canDelete = (self._canDelete == "True") self._canOrder = self._extractKey((kwargs, properties, attProperties), "CanOrder", True) if isinstance(self._canOrder, basestring): self._canOrder = (self._canOrder == "True") self._editable = self._extractKey((kwargs, properties, attProperties), "Editable", True) style = self._extractKey((kwargs, properties, attProperties), "style", 0) if self._canAdd: style = style | wx.gizmos.EL_ALLOW_NEW if self._editable: style = style | wx.gizmos.EL_ALLOW_EDIT if self._canDelete: style = style | wx.gizmos.EL_ALLOW_DELETE kwargs["style"] = style # References to the components of this control self._addButton = None self._deleteButton = None self._editButton = None self._downButton = None self._upButton = None self._panel = None dcm.dControlMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, *args, **kwargs) def GetValue(self): """ This control doesn't natively support values, as it is designed to simply order and/or edit the list. We need to provide this so that the dControlMixin code which calls GetValue() doesn't barf. """ return None def layout(self): """ Calling the native Layout() method isn't sufficient, as it doesn't seem to call the top panel's Layout(). So we'll do it manually. """ self.Layout() self._Panel.Layout() if self.Application.Platform == "Win": self.refresh() ## property get/set methods follow ## def _getAddButton(self): if self._addButton is None: self._addButton = self.GetNewButton() return self._addButton def _getCanAdd(self): return self._canAdd def _setCanAdd(self, val): if self._constructed(): self._canAdd = val self._AddButton.Show(val) self.layout() else: self._properties["CanAdd"] = val def _getCanDelete(self): return self._canDelete def _setCanDelete(self, val): if self._constructed(): self._canDelete = val self._DeleteButton.Show(val) self.layout() else: self._properties["CanDelete"] = val def _getCanOrder(self): return self._canOrder def _setCanOrder(self, val): if self._constructed(): self._canOrder = val self._UpButton.Show(val) self._DownButton.Show(val) else: self._properties["CanOrder"] = val self.layout() def _getCaption(self): return self._Panel.GetChildren()[0].GetLabel() def _setCaption(self, val): if self._constructed(): self._Panel.GetChildren()[0].SetLabel(val) else: self._properties["Caption"] = val def _getChoices(self): return self.GetStrings() def _setChoices(self, val): if self._constructed(): self.SetStrings(val) else: self._properties["Choices"] = val def _getDeleteButton(self): if self._deleteButton is None: self._deleteButton = self.GetDelButton() return self._deleteButton def _getDownButton(self): if self._downButton is None: self._downButton = self.GetDownButton() return self._downButton def _getEditable(self): return self._editable def _setEditable(self, val): if self._constructed(): self._editable = val self._EditButton.Show(val) self.layout() else: self._properties["Editable"] = val def _getEditButton(self): if self._editButton is None: self._editButton = self.GetEditButton() return self._editButton def _getPanel(self): if self._panel is None: self._panel = [ pp for pp in self.Children if isinstance(pp, wx.Panel) ][0] return self._panel def _getUpButton(self): if self._upButton is None: self._upButton = self.GetUpButton() return self._upButton _AddButton = property(_getAddButton, None, None, _("Reference to the new item button (wx.Button)")) CanAdd = property( _getCanAdd, _setCanAdd, None, _("Determines if the user can add new entries to the list (bool)")) DynamicCanAdd = makeDynamicProperty(CanAdd) CanDelete = property( _getCanDelete, _setCanDelete, None, _("Determines if the user can delete entries from the list (bool)")) DynamicCanDelete = makeDynamicProperty(CanDelete) CanOrder = property(_getCanOrder, _setCanOrder, None, _("Determines if the user can re-order items (bool)")) DynamicCanOrder = makeDynamicProperty(CanOrder) Caption = property( _getCaption, _setCaption, None, _("Text that appears in the top panel of the control (str)")) DynamicCaption = makeDynamicProperty(Caption) Choices = property( _getChoices, _setChoices, None, _("List that contains the entries in the control (list)")) _DeleteButton = property( _getDeleteButton, None, None, _("Reference to the delete item button (wx.Button)")) _DownButton = property( _getDownButton, None, None, _("Reference to the move item down button (wx.Button)")) Editable = property( _getEditable, _setEditable, None, _("Determines if the user can change existing entries (bool)")) DynamicEditable = makeDynamicProperty(Editable) _EditButton = property(_getEditButton, None, None, _("Reference to the edit item button (wx.Button)")) _Panel = property( _getPanel, None, None, _("""Reference to the panel that contains the caption and buttons (wx.Panel)""")) _UpButton = property( _getUpButton, None, None, _("Reference to the move item up button (wx.Button)"))
class dSplitForm(dabo.ui.dForm): def __init__(self, *args, **kwargs): self._splitter = None super(dSplitForm, self).__init__(*args, **kwargs) def unsplit(self): self.Splitter.unsplit() def split(self, dir=None): self.Splitter.split(dir) def _getMinPanelSize(self): return self.Splitter.MinPanelSize def _setMinPanelSize(self, val): if self._constructed(): self.Splitter.MinPanelSize = val else: self._properties["MinPanelSize"] = val def _getOrientation(self): return self.Splitter.Orientation def _setOrientation(self, val): if self._constructed(): self.Splitter.Orientation = val else: self._properties["MinPanelSize"] = val def _getPanel1(self): return self.Splitter.Panel1 def _setPanel1(self, pnl): if self._constructed(): self.Splitter.Panel1 = pnl else: self._properties["Panel1"] = pnl def _getPanel2(self): return self.Splitter.Panel2 def _setPanel2(self, pnl): if self._constructed(): self.Splitter.Panel2 = pnl else: self._properties["Panel2"] = pnl def _getSashPosition(self): return self.Splitter.SashPosition def _setSashPosition(self, val): if self._constructed(): self.Splitter.SashPosition = val else: self._properties["SashPosition"] = val def _getSplitter(self): if self._splitter is None: win = self._splitter = dSplitter(self, createPanes=True, createSizers=True, RegID="MainSplitter") def addToSizer(frm, itm): if not frm.Sizer: dabo.ui.callAfter(addToSizer, frm, itm) else: frm.Sizer.append1x(itm) frm.layout() win.Visible = True dabo.ui.callAfter(addToSizer, self, win) return self._splitter MinPanelSize = property( _getMinPanelSize, _setMinPanelSize, None, _("Controls the minimum width/height of the panels. (int)")) Orientation = property( _getOrientation, _setOrientation, None, _("Determines if the window splits Horizontally or Vertically. (str)") ) Panel1 = property(_getPanel1, _setPanel1, None, _("Returns the Top/Left panel. (SplitterPanel)")) Panel2 = property(_getPanel2, _setPanel2, None, _("Returns the Bottom/Right panel. (SplitterPanel)")) SashPosition = property( _getSashPosition, _setSashPosition, None, _("Position of the sash when the window is split. (int)")) Splitter = property( _getSplitter, None, None, _("Reference to the main splitter in the form (dSplitter")) DynamicMinPanelSize = makeDynamicProperty(MinPanelSize) DynamicOrientation = makeDynamicProperty(Orientation) DynamicSashPosition = makeDynamicProperty(SashPosition)
class dNode(dObject): """Wrapper class for the tree nodes.""" def __init__(self, tree, itemID, parent): self._baseClass = dNode self.tree = tree # The 'itemID' in this case is a wxPython wx.TreeItemID object used # by wx to work with separate nodes. self.itemID = itemID self.parent = parent # Nodes can have objects associated with them self._object = None # Nodes can also be associated with a file path self._filePath = None # Custom text to display as a tooltip self._toolTipText = None # Add minimal Dabo functionality self.afterInit() def afterInit(self): pass def expand(self): self.tree.expand(self) def collapse(self): self.tree.collapse(self) def show(self): self.tree.showNode(self) def release(self): self.tree.removeNode(self) def appendChild(self, txt): return self.tree.appendNode(self, txt) def removeChild(self, txt): """Removes the child node whose text matches the passed value""" mtch = self.tree.find(txt) # We have a list of matching nodes. Find the first whose parent # is this object, and delete it for m in mtch: if m.parent == self: self.tree.removeNode(m) break return def _onFontPropsChanged(self, evt): # Sent by the dFont object when any props changed. Wx needs to be notified: self.tree.SetItemFont(self.itemID, self.Font._nativeFont) def _constructed(self): # For compatibility with mixin props. return True # Property definition code begins here def _getBackColor(self): return self.tree.GetItemBackgroundColour(self.itemID).Get() def _setBackColor(self, val): if isinstance(val, basestring): val = dColors.colorTupleFromName(val) self.tree.SetItemBackgroundColour(self.itemID, val) def _getCap(self): try: ret = self.tree.GetItemText(self.itemID) except dabo.ui.assertionException: ret = "" return ret def _setCap(self, val): self.tree.SetItemText(self.itemID, val) def _getChildren(self): return self.tree.getChildren(self) def _getDescendents(self): return self.tree.getDescendents(self) def _getExpanded(self): return self.tree.IsExpanded(self.itemID) def _setExpanded(self, val): try: if val: self.tree.expand(self) else: self.tree.collapse(self) except wx._core.PyAssertionError: # Happens when expandAll() is called and the root node is hidden # especially from dTreeView.refreshDisplay() pass def _getFont(self): if hasattr(self, "_font"): v = self._font else: v = self.Font = dabo.ui.dFont( _nativeFont=self.tree.GetItemFont(self.itemID)) return v def _setFont(self, val): assert isinstance(val, dabo.ui.dFont) self._font = val if not self.IsRootNode or self.tree.ShowRootNode: # On some platforms exception is raised while operation # on hidden root node. self.tree.SetItemFont(self.itemID, val._nativeFont) val.bindEvent(dEvents.FontPropertiesChanged, self._onFontPropsChanged) dabo.ui.callAfterInterval(100, self.tree.refreshDisplay) def _getFontBold(self): try: return self.Font.Bold except AttributeError: return False def _setFontBold(self, val): self.Font.Bold = val dabo.ui.callAfterInterval(100, self.tree.refreshDisplay) def _getFontDescription(self): try: return self.Font.Description except AttributeError: return "" def _getFontInfo(self): try: return self.Font._nativeFont.GetNativeFontInfoDesc() except AttributeError: return "" def _getFontItalic(self): try: return self.Font.Italic except AttributeError: return False def _setFontItalic(self, val): self.Font.Italic = val dabo.ui.callAfterInterval(100, self.tree.refreshDisplay) def _getFontFace(self): try: return self.Font.Face except AttributeError: return "" def _setFontFace(self, val): self.Font.Face = val dabo.ui.callAfterInterval(100, self.tree.refreshDisplay) def _getFontSize(self): try: return self.Font.Size except AttributeError: return 10 def _setFontSize(self, val): self.Font.Size = val dabo.ui.callAfterInterval(100, self.tree.refreshDisplay) def _getFontUnderline(self): try: return self.Font.Underline except AttributeError: return False def _setFontUnderline(self, val): self.Font.Underline = val dabo.ui.callAfterInterval(100, self.tree.refreshDisplay) def _getForeColor(self): return self.tree.GetItemTextColour(self.itemID).Get() def _setForeColor(self, val): if isinstance(val, basestring): val = dColors.colorTupleFromName(val) self.tree.SetItemTextColour(self.itemID, val) def _getFullCaption(self): ret = self.Caption if self.parent: ret = "%s.%s" % (self.parent._getFullCaption(), ret) return ret def _getImg(self): return self.tree.getNodeImg(self) def _setImg(self, key): return self.tree.setNodeImg(self, key) dabo.ui.callAfterInterval(100, self.tree.refreshDisplay) def _getIsRootNode(self): try: ret = self._isRootNode except AttributeError: ret = self._isRootNode = (self.tree.GetRootItem() == self.itemID) return ret def _getObject(self): return self._object def _setObject(self, val): if self._constructed(): self._object = val else: self._properties["Object"] = val def _getSel(self): sel = self.tree.Selection if isinstance(sel, list): ret = self in sel else: ret = (self == sel) return ret def _setSel(self, val): self.tree.SelectItem(self.itemID, val) def _getSiblings(self): return self.tree.getSiblings(self) def _getToolTipText(self): return self._toolTipText def _setToolTipText(self, val): if self._constructed(): self._toolTipText = val else: self._properties["ToolTipText"] = val BackColor = property( _getBackColor, _setBackColor, None, _("Background color of this node (str, 3-tuple, or wx.Colour)")) Caption = property(_getCap, _setCap, None, _("Returns/sets the text of this node. (str)")) Children = property( _getChildren, None, None, _("List of all nodes for which this is their parent node. (list of dNodes)" )) Descendents = property( _getDescendents, None, None, _("List of all nodes for which this node is a direct ancestor. (list of dNodes)" )) Expanded = property( _getExpanded, _setExpanded, None, _("Represents whether the node is Expanded (True) or collapsed. (bool)" )) Font = property(_getFont, _setFont, None, _("The font properties of the node. (obj)")) FontBold = property(_getFontBold, _setFontBold, None, _("Specifies if the node font is bold-faced. (bool)")) FontDescription = property( _getFontDescription, None, None, _("Human-readable description of the node's font settings. (str)")) FontFace = property(_getFontFace, _setFontFace, None, _("Specifies the font face for the node. (str)")) FontInfo = property( _getFontInfo, None, None, _("Specifies the platform-native font info string for the node. Read-only. (str)" )) FontItalic = property( _getFontItalic, _setFontItalic, None, _("Specifies whether the node's font is italicized. (bool)")) FontSize = property( _getFontSize, _setFontSize, None, _("Specifies the point size of the node's font. (int)")) FontUnderline = property( _getFontUnderline, _setFontUnderline, None, _("Specifies whether node text is underlined. (bool)")) ForeColor = property( _getForeColor, _setForeColor, None, _("Foreground (text) color of this node (str, 3-tuple, or wx.Colour)") ) FullCaption = property( _getFullCaption, None, None, _("Full dot-separated string of the captions of this node and its ancestors (read-only) (str)" )) Image = property( _getImg, _setImg, None, _("""Sets the image that is displayed on the node. This is determined by the key value passed, which must refer to an image already added to the parent tree. When used to retrieve an image, it returns the index of the node's image in the parent tree's image list. (int)""")) IsRootNode = property( _getIsRootNode, None, None, _("Returns True if this is the root node (read-only) (bool)")) Object = property( _getObject, _setObject, None, _("Optional object associated with this node. Default=None (object)")) Selected = property(_getSel, _setSel, None, _("Is this node selected?. (bool)")) Siblings = property( _getSiblings, None, None, _("List of all nodes with the same parent node. (list of dNodes)")) ToolTipText = property( _getToolTipText, _setToolTipText, None, _("""Text to display when the mouse hovers over this node. The tree's UseNodeToolTips property must be True for this to have any effect. (str)""" )) DynamicBackColor = makeDynamicProperty(BackColor) DynamicCaption = makeDynamicProperty(Caption) DynamicFont = makeDynamicProperty(Font) DynamicFontBold = makeDynamicProperty(FontBold) DynamicFontFace = makeDynamicProperty(FontFace) DynamicFontItalic = makeDynamicProperty(FontItalic) DynamicFontSize = makeDynamicProperty(FontSize) DynamicFontUnderline = makeDynamicProperty(FontUnderline) DynamicForeColor = makeDynamicProperty(ForeColor) DynamicImage = makeDynamicProperty(Image) DynamicSelected = makeDynamicProperty(Selected) DynamicToolTipText = makeDynamicProperty(ToolTipText)
class dBitmapButton(cm.dControlMixin, dim.dImageMixin, wx.BitmapButton): """ Creates a button with a picture. The button can have up to three pictures associated with it: Picture: the normal picture shown on the button. DownPicture: the picture displayed when the button is depressed. FocusPicture: the picture displayed when the button has the focus. Otherwise, dBitmapButton behaves the same as a normal dButton. """ def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dBitmapButton preClass = wx.PreBitmapButton # Initialize the self._*picture attributes self._picture = self._downPicture = self._focusPicture = "" # These atts underlie the image sizing properties. self._imgScale = self._imgHt = self._imgWd = None # This controls whether the button automatically resizes # itself when its Picture changes. self._autoSize = False # On some platforms, we need to add some 'breathing room' # around the bitmap image in order for it to appear correctly self._bmpBorder = 10 dim.dImageMixin.__init__(self) cm.dControlMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, *args, **kwargs) def _initEvents(self): super(dBitmapButton, self)._initEvents() self.Bind(wx.EVT_BUTTON, self._onWxHit) def _sizeToBitmap(self): if self.Picture: bmp = self.Bitmap self.Size = (bmp.GetWidth() + self._bmpBorder, bmp.GetHeight() + self._bmpBorder) # Property get/set/del methods follow. Scroll to bottom to see the property # definitions themselves. def _getAutoSize(self): return self._autoSize def _setAutoSize(self, val): self._autoSize = val def _getBmpBorder(self): return self._bmpBorder def _setBmpBorder(self, val): self._bmpBorder = val if self._autoSize: self._sizeToBitmap() def _getBorderStyle(self): if self._hasWindowStyleFlag(wx.BU_AUTODRAW): return "Simple" elif self._hasWindowStyleFlag(wx.NO_BORDER): return "None" else: return "Default" def _setBorderStyle(self, val): self._delWindowStyleFlag(wx.NO_BORDER) self._delWindowStyleFlag(wx.BU_AUTODRAW) if val == "None": self._addWindowStyleFlag(wx.NO_BORDER) elif val == "Simple": self._addWindowStyleFlag(wx.BU_AUTODRAW) def _getCancelButton(self): # need to implement return False def _setCancelButton(self, val): warnings.warn(_("CancelButton isn't implemented yet."), Warning) def _getDefaultButton(self): if self.Parent is not None: return self.Parent.GetDefaultItem() == self else: return False def _setDefaultButton(self, val): if self._constructed(): if val: if self.Parent is not None: self.Parent.SetDefaultItem(self._pemObject) else: if self._pemObject.GetParent().GetDefaultItem( ) == self._pemObject: # Only change the default item to None if it wasn't self: if another object # is the default item, setting self.DefaultButton = False shouldn't also set # that other object's DefaultButton to False. self.SetDefaultItem(None) else: self._properties["DefaultButton"] = val def _getDownBitmap(self): return self.GetBitmapSelected() def _getDownPicture(self): return self._downPicture def _setDownPicture(self, val): self._downPicture = val if self._constructed(): if isinstance(val, wx.Bitmap): bmp = val else: bmp = dabo.ui.strToBmp(val, self._imgScale, self._imgWd, self._imgHt) self.SetBitmapSelected(bmp) else: self._properties["DownPicture"] = val def _getFocusBitmap(self): return self.GetBitmapFocus() def _getFocusPicture(self): return self._focusPicture def _setFocusPicture(self, val): self._focusPicture = val if self._constructed(): if isinstance(val, wx.Bitmap): bmp = val else: bmp = dabo.ui.strToBmp(val, self._imgScale, self._imgWd, self._imgHt) self.SetBitmapFocus(bmp) else: self._properties["FocusPicture"] = val def _getNormalBitmap(self): return self.GetBitmapLabel() def _getNormalPicture(self): return self._picture def _setNormalPicture(self, val): self._picture = val if self._constructed(): if isinstance(val, wx.Bitmap): bmp = val else: bmp = dabo.ui.strToBmp(val, self._imgScale, self._imgWd, self._imgHt) self.SetBitmapLabel(bmp) # If the others haven't been specified, default them to the same if not self._downPicture: self.SetBitmapSelected(bmp) if not self._focusPicture: self.SetBitmapFocus(bmp) if self._autoSize: self._sizeToBitmap() else: self._properties["Picture"] = val # Property definitions: AutoSize = property( _getAutoSize, _setAutoSize, None, _("Controls whether the button resizes when the Picture changes. (bool)" )) Bitmap = property( _getNormalBitmap, None, None, _("""The bitmap normally displayed on the button. (wx.Bitmap)""")) BitmapBorder = property( _getBmpBorder, _setBmpBorder, None, _("""Extra space around the bitmap, used when auto-sizing. (int)""")) BorderStyle = property( _getBorderStyle, _setBorderStyle, None, _("""Specifies the type of border for this window. (String). Possible choices are: "None" - No border "Simple" - Border like a regular button """)) CancelButton = property( _getCancelButton, _setCancelButton, None, _("Specifies whether this Bitmap button gets clicked on -Escape-.")) DefaultButton = property( _getDefaultButton, _setDefaultButton, None, _("Specifies whether this Bitmap button gets clicked on -Enter-.")) DownBitmap = property( _getDownBitmap, None, None, _("The bitmap displayed on the button when it is depressed. (wx.Bitmap)" )) DownPicture = property( _getDownPicture, _setDownPicture, None, _("Specifies the image displayed on the button when it is depressed. (str)" )) FocusBitmap = property( _getFocusBitmap, None, None, _("The bitmap displayed on the button when it receives focus. (wx.Bitmap)" )) FocusPicture = property( _getFocusPicture, _setFocusPicture, None, _("Specifies the image displayed on the button when it receives focus. (str)" )) Picture = property( _getNormalPicture, _setNormalPicture, None, _("""Specifies the image normally displayed on the button. This is the default if none of the other Picture properties are specified. (str)""")) DynamicAutoSize = makeDynamicProperty(AutoSize) DynamicBitmap = makeDynamicProperty(Bitmap) DynamicBitmapBorder = makeDynamicProperty(BitmapBorder) DynamicCancelButton = makeDynamicProperty(CancelButton) DynamicDefaultButton = makeDynamicProperty(DefaultButton) DynamicDownBitmap = makeDynamicProperty(DownBitmap) DynamicDownPicture = makeDynamicProperty(DownPicture) DynamicFocusBitmap = makeDynamicProperty(FocusBitmap) DynamicFocusPicture = makeDynamicProperty(FocusPicture) DynamicPicture = makeDynamicProperty(Picture)
def _getValue(self): return self.GetValue() def _setValue(self, val): if self._constructed(): currVal = self.Value if type(currVal) != type(val): val = self._coerceValue(val, currVal) if (type(currVal) != type(val) or currVal != val): setter = self.SetValue if hasattr(self, "ChangeValue"): setter = self.ChangeValue try: setter(val) except (TypeError, ValueError), e: nm = self._name dabo.log.error(_("Could not set value of %(nm)s to %(val)s. Error message: %(e)s") % locals()) self._afterValueChanged() else: self._properties["Value"] = val Value = property(_getValue, _setValue, None, _("""Specifies the current state of the control (the value of the field). (varies)""")) DynamicValue = makeDynamicProperty(Value)
MaxValue = property(_getMaxValue, _setMaxValue, None, _("""Holds upper value limit. (date, tuple, str)(Default=None)""")) MinValue = property(_getMinValue, _setMinValue, None, _("""Holds lower value limit. (date, tuple, str)(Default=None)""")) PickerMode = property(_getPickerMode, _setPickerMode, None, _("""Creates control with spin or dropdown calendar. (str) Available values are: - Spin - Dropdown (default)""")) ValueMode = property(_getValueMode, _setValueMode, None, _("""Enables handling Timestamp type. (str)(Default="Date")""")) DynamicMaxValue = makeDynamicProperty(MaxValue) DynamicMinValue = makeDynamicProperty(MinValue) if __name__ == "__main__": import datetime import test class TestBase(dDatePicker): def onValueChanged(self, evt): print "onValueChanged" test.Test().runTest(TestBase, AllowNullDate=True, Value=datetime.date(1970,12,03)) test.Test().runTest(TestBase, BackColor="orange", PickerMode="Spin", AllowNullDate=True)
class dPage(dabo.ui.dScrollPanel): """Creates a page to appear as a tab in a pageframe.""" def __init__(self, *args, **kwargs): self._caption = "" self._pendingUpdates = False self._deferredUpdates = False kwargs["AlwaysResetSizer"] = self._extractKey(kwargs, "AlwaysResetSizer", True) super(dPage, self).__init__(*args, **kwargs) self._baseClass = dPage def _afterInit(self): self.initSizer() self.itemsCreated = False super(dPage, self)._afterInit() def _initEvents(self): super(dPage, self)._initEvents() self.bindEvent(dEvents.PageEnter, self.__onPageEnter) self.bindEvent(dEvents.PageLeave, self.__onPageLeave) def initSizer(self): """Set up the default vertical box sizer for the page.""" try: szCls = self.Parent.PageSizerClass except AttributeError: # Not part of a paged control return if szCls is not None: self.Sizer = szCls("vertical") def _createItems(self): self.lockDisplay() self.createItems() self.itemsCreated = True self.layout() dabo.ui.callAfter(self.unlockDisplay) def createItems(self): """ Create the controls in the page. Called when the page is entered for the first time, allowing subclasses to delay-populate the page. """ pass def update(self): if self.DeferredUpdates: try: if self.Parent.SelectedPage == self: self._pendingUpdates = False else: self._pendingUpdates = True return except (ValueError, AttributeError): pass super(dPage, self).update() def __onPageEnter(self, evt): if not self.itemsCreated: self._createItems() if self._pendingUpdates: dabo.ui.callAfter(self.update) def __onPageLeave(self, evt): if hasattr(self, "Form"): form = self.Form if hasattr(form, "activeControlValid"): form.activeControlValid() def _saveLastActiveControl(self): self._lastFocusedControl = self.Form.ActiveControl def _restoreLastActiveControl(self): if getattr(self, "_lastFocusedControl", None): self.Form.ActiveControl = self._lastFocusedControl def _getPagePosition(self): """Returns the position of this page within its parent.""" try: ret = self.Parent.Pages.index(self) except (ValueError, AttributeError): ret = -1 return ret def _getCaption(self): # Need to determine which page we are ret = "" pos = self._getPagePosition() if pos > -1: ret = self.Parent.GetPageText(pos) if not ret: ret = self._caption return ret def _setCaption(self, val): self._caption = val if self._constructed(): pos = self._getPagePosition() if pos > -1: self.Parent.SetPageText(pos, val) else: self._properties["Caption"] = val def _getDeferredUpdates(self): return self._deferredUpdates def _setDeferredUpdates(self, val): self._deferredUpdates = val def _getImage(self): return self.Parent.getPageImage(self) def _setImage(self, imgKey): if self._constructed(): self.Parent.setPageImage(self, imgKey) else: self._properties["Image"] = imgKey Caption = property(_getCaption, _setCaption, None, _("The text identifying this particular page. (str)")) DeferredUpdates = property( _getDeferredUpdates, _setDeferredUpdates, None, _("Allow to defer controls updates until page become active. (bool)")) Image = property( _getImage, _setImage, None, _("""Sets the image that is displayed on the page tab. This is determined by the key value passed, which must refer to an image already added to the parent pageframe. When used to retrieve an image, it returns the index of the page's image in the parent pageframe's image list. (int)""")) DynamicCaption = makeDynamicProperty(Caption) DynamicImage = makeDynamicProperty(Image)
class dLabel(cm.dControlMixin, AlignmentMixin, wx.StaticText): """Creates a static label, to make a caption for another control, for example.""" _layout_on_set_caption = True def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dLabel self._wordWrap = False self._inResizeEvent = False self._resetAutoResize = True preClass = wx.PreStaticText cm.dControlMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, *args, **kwargs) self.bindEvent(dEvents.Resize, self.__onResize) def __onResize(self, evt): """ Event binding is set when WordWrap=True. Tell the label to wrap to its current width. """ if self.WordWrap: if self._inResizeEvent: return self._inResizeEvent = True dabo.ui.callAfterInterval(100, self.__resizeExecute) def __resizeExecute(self): # We need to set the caption to the internally-saved caption, since # WordWrap can introduce additional linefeeds. try: self.Parent.lockDisplay() except dabo.ui.deadObjectException: # Form is being destroyed; bail return self.SetLabel(self._caption) wd = {True: self.Width, False: -1}[self.WordWrap] self.Wrap(wd) dabo.ui.callAfterInterval(50, self.__endResize) def __endResize(self): """ To prevent infinite loops while resizing, the _inResizeEvent flag must be reset outside of the execution method. """ self.Parent.unlockDisplayAll() self._inResizeEvent = False # property get/set functions def _getAutoResize(self): return not self._hasWindowStyleFlag(wx.ST_NO_AUTORESIZE) def _setAutoResize(self, val): self._delWindowStyleFlag(wx.ST_NO_AUTORESIZE) if not val: self._addWindowStyleFlag(wx.ST_NO_AUTORESIZE) def _getFontBold(self): return super(dLabel, self)._getFontBold() def _setFontBold(self, val): super(dLabel, self)._setFontBold(val) if self._constructed(): # This will force an auto-resize self.SetLabel(self.GetLabel()) def _getFontFace(self): return super(dLabel, self)._getFontFace() def _setFontFace(self, val): super(dLabel, self)._setFontFace(val) if self._constructed(): # This will force an auto-resize self.SetLabel(self.GetLabel()) def _getFontItalic(self): return super(dLabel, self)._getFontItalic() def _setFontItalic(self, val): super(dLabel, self)._setFontItalic(val) if self._constructed(): # This will force an auto-resize self.SetLabel(self.GetLabel()) def _getFontSize(self): return super(dLabel, self)._getFontSize() def _setFontSize(self, val): super(dLabel, self)._setFontSize(val) if self._constructed(): # This will force an auto-resize self.SetLabel(self.GetLabel()) def _getWordWrap(self): return self._wordWrap def _setWordWrap(self, val): if self._constructed(): changed = (self._wordWrap != val) if not changed: return self._wordWrap = val if val: # Make sure AutoResize is False. if self.AutoResize: self._resetAutoResize = True self.AutoResize = False try: dabo.ui.callAfter(self.Parent.layout) except AttributeError: # Parent has no layout() method. pass else: # reset the value self.AutoResize = self._resetAutoResize self.__resizeExecute() else: self._properties["WordWrap"] = val # property definitions follow: AutoResize = property(_getAutoResize, _setAutoResize, None, _("""Specifies whether the length of the caption determines the size of the label. This cannot be True if WordWrap is also set to True. Default=True. (bool)""") ) FontBold = property(_getFontBold, _setFontBold, None, _("Sets the Bold of the Font (int)") ) FontFace = property(_getFontFace, _setFontFace, None, _("Sets the face of the Font (int)") ) FontItalic = property(_getFontItalic, _setFontItalic, None, _("Sets the Italic of the Font (int)") ) FontSize = property(_getFontSize, _setFontSize, None, _("Sets the size of the Font (int)") ) WordWrap = property(_getWordWrap, _setWordWrap, None, _("""When True, the Caption is wrapped to the Width. Note that the control must have sufficient Height to display any wrapped text. Default=False (bool)""")) DynamicFontBold = makeDynamicProperty(FontBold) DynamicFontFace = makeDynamicProperty(FontFace) DynamicFontItalic = makeDynamicProperty(FontItalic) DynamicFontSize = makeDynamicProperty(FontSize) DynamicWordWrap = makeDynamicProperty(WordWrap)
class dSpinner(dabo.ui.dDataPanel, wx.Control): """ Control for allowing a user to increment a value by discreet steps across a range of valid values. """ def __init__(self, parent, properties=None, attProperties=None, TextBoxClass=None, *args, **kwargs): self.__constructed = False self._spinWrap = False self._min = 0 self._max = 100 self._increment = 1 nm = self._extractKey((properties, attProperties, kwargs), "NameBase", "") if not nm: nm = self._extractKey((properties, attProperties, kwargs), "Name", "dSpinner") super(dSpinner, self).__init__(parent=parent, properties=properties, attProperties=attProperties, *args, **kwargs) self._baseClass = dSpinner # Create the child controls if TextBoxClass is None: TextBoxClass = dabo.ui.dTextBox self._proxy_textbox = TextBoxClass(self, Value=0, Width=32, StrictNumericEntry=False, _EventTarget=self) self._proxy_spinner = _dSpinButton(parent=self, _EventTarget=self) self.__constructed = True self.Sizer = dabo.ui.dSizer("h") self.Sizer.append1x(self._proxy_textbox) self.Sizer.append(self._proxy_spinner, "expand") self.layout() self.Bind(wx.EVT_WINDOW_DESTROY, self.__onWxDestroy) # Because several properties could not be set until after the child # objects were created, we need to manually call _setProperties() here. self._properties["NameBase"] = nm self._setNameAndProperties(self._properties, **kwargs) ps = self._proxy_spinner pt = self._proxy_textbox # Set an essentially infinite range. We'll handle the range ourselves. ps.SetRange(-2**30, 2**30) # We'll also control wrapping ourselves self._proxy_spinner._addWindowStyleFlag(wx.SP_WRAP) ps.Bind(wx.EVT_SPIN_UP, self.__onWxSpinUp) ps.Bind(wx.EVT_SPIN_DOWN, self.__onWxSpinDown) #ps.Bind(wx.EVT_SPIN, self._onWxHit) pt.Bind(wx.EVT_TEXT, self._onWxHit) pt.Bind(wx.EVT_KEY_DOWN, self._onWxKeyDown) ps.Bind(wx.EVT_KEY_DOWN, self._onWxKeyDown) #self.bindEvent(dEvents.KeyChar, self._onChar) self._rerestoreValue() dabo.ui.callAfter(self.layout) def __onWxDestroy(self, evt): # This doesn't otherwise happen self.raiseEvent(dEvents.Destroy) def _rerestoreValue(self): # Hack because when restoreValue() was originally called in onCreate, # the name of the control hadn't been set yet. if self.SaveRestoreValue: self.restoreValue() # Additionally, if the user never changes the Value, _value will be None: self._value = self.Value def _constructed(self): """Returns True if the ui object has been fully created yet, False otherwise.""" return self.__constructed def _toDec(self, val): """Convenience method for converting various types to decimal.""" return decimal(ustr(val)) def _toFloat(self, val): """Convenience method for converting various types to float.""" return float(ustr(val)) def _coerceTypes(self, newVal, minn, maxx, margin): """ Handle the problems when min/max/increment values are of one type, and the edited value another. """ typN = type(newVal) # Only problem here is Decimal and float combinations if typN == decimal: margin = self._toDec(margin) if type(maxx) == float: maxx = self._toDec(maxx) if type(minn) == float: minn = self._toDec(minn) elif typN == float: if type(maxx) == decimal: maxx = float(maxx) if type(minn) == decimal: minn = float(minn) return minn, maxx, margin def _applyIncrement(self, op): """ Returns the value obtained by modifying the current value by the increment according to the passed operation. It expects to be passed either operator.add or operator.sub. """ curr = self.Value inc = self.Increment try: ret = op(curr, inc) except TypeError: # Usually Decimal/float problems tCurr = type(curr) if tCurr == decimal: ret = op(curr, self._toDec(inc)) elif tCurr == float: ret = op(curr, self._toFloat(inc)) return ret def _spin(self, direction, spinType=None): assert direction in ("up", "down") incrementFunc = operator.add margin = 0.0001 if direction == "down": incrementFunc = operator.sub margin = -0.0001 ret = True newVal = self._applyIncrement(incrementFunc) minn, maxx, margin = self._coerceTypes(newVal, self.Min, self.Max, margin) valueToSet = None if direction == "up": diff = newVal - maxx if diff < margin: valueToSet = newVal elif self._spinWrap: valueToSet = minn else: ret = False if ret: self.raiseEvent(dEvents.SpinUp, spinType=spinType) self.raiseEvent(dEvents.Spinner, spinType=spinType) else: diff = newVal - minn if diff > margin: valueToSet = newVal elif self._spinWrap: valueToSet = maxx else: ret = False if ret: self.raiseEvent(dEvents.SpinDown, spinType=spinType) self.raiseEvent(dEvents.Spinner, spinType=spinType) self._checkBounds() if ret: self._userChanged = True self.Value = valueToSet self._userChanged = True self.flushValue() self.raiseEvent(dEvents.Hit, hitType=spinType) return ret def __onWxSpinUp(self, evt): """Respond to the wx event by raising the Dabo event.""" self._spin("up", spinType="button") def __onWxSpinDown(self, evt): """Respond to the wx event by raising the Dabo event.""" self._spin("down", spinType="button") def _checkBounds(self): """Make sure that the value is within the current Min/Max""" if self._proxy_textbox.Value < self.Min: self._proxy_textbox.Value = self._proxy_spinner.Value = self.Min elif self._proxy_textbox.Value > self.Max: self._proxy_textbox.Value = self._proxy_spinner.Value = self.Max def _onWxHit(self, evt): # Determine what type of event caused Hit to be raised. if evt is None: typ = "key" elif evt.GetEventObject() is self._proxy_textbox: typ = "text" else: typ = "spin" super(dSpinner, self)._onWxHit(evt, hitType=typ) def _onWxKeyDown(self, evt): """ Handle the case where the user presses the up/down arrows to activate the spinner. """ keys = dabo.ui.dKeys kc = evt.GetKeyCode() if kc in (keys.key_Up, keys.key_Numpad_up): self._spin("up", spinType="key") elif kc in (keys.key_Down, keys.key_Numpad_down): self._spin("down", spinType="key") else: evt.Skip() def flushValue(self): self._checkBounds() super(dSpinner, self).flushValue() def _numericStringVal(self, val): """ If passed a string, attempts to convert it to the appropriate numeric type. If such a conversion is not possible, returns None. """ ret = val if isinstance(val, basestring): if val.count(locale.localeconv()["decimal_point"]) > 0: func = decimal else: func = int try: ret = func(val) except ValueError: ret = None return ret def fontZoomIn(self, amt=1): """Zoom in on the font, by setting a higher point size.""" self._proxy_textbox._setRelativeFontZoom(amt) def fontZoomOut(self, amt=1): """Zoom out on the font, by setting a lower point size.""" self._proxy_textbox._setRelativeFontZoom(-amt) def fontZoomNormal(self): """Reset the font zoom back to zero.""" self._proxy_textbox._setAbsoluteFontZoom(0) def getBlankValue(self): return 0 # Property get/set definitions begin here def _getButtonWidth(self): return self._proxy_spinner.Width def _setButtonWidth(self, val): if self._constructed(): self._proxy_spinner.Width = val else: self._properties["ButtonWidth"] = val def _getChildren(self): # The native wx control will return the items that make up this composite # control, which our user doesn't want. return [] def _getIncrement(self): return self._increment def _setIncrement(self, val): if self._constructed(): self._increment = val else: self._properties["Increment"] = val def _getMax(self): return self._max def _setMax(self, val): if self._constructed(): self._max = val self._checkBounds() else: self._properties["Max"] = val def _getMin(self): return self._min def _setMin(self, val): if self._constructed(): self._min = val self._checkBounds() else: self._properties["Min"] = val def _getSpinnerWrap(self): return self._spinWrap def _setSpinnerWrap(self, val): if self._constructed(): self._spinWrap = val else: self._properties["SpinnerWrap"] = val def _getValue(self): try: return self._proxy_textbox.Value except AttributeError: return None def _setValue(self, val): if self._constructed(): self._proxy_textbox._inDataUpdate = self._inDataUpdate if isinstance(val, (int, long, float, decimal)): self._proxy_textbox.Value = val else: numVal = self._numericStringVal(val) if numVal is None: dabo.log.error( _("Spinner values must be numeric. Invalid:'%s'") % val) else: self._proxy_textbox.Value = val self._proxy_textbox._inDataUpdate = False else: self._properties["Value"] = val ButtonWidth = property( _getButtonWidth, _setButtonWidth, None, _("""Allows the developer to explicitly specify the width of the up/down buttons.""" )) Children = property( _getChildren, None, None, _("""Returns a list of object references to the children of this object. Only applies to containers. Children will be None for non-containers. (list or None)""")) Increment = property( _getIncrement, _setIncrement, None, _("Amount the control's value changes when the spinner buttons are clicked (int/float)" )) Max = property(_getMax, _setMax, None, _("Maximum value for the control (int/float)")) Min = property(_getMin, _setMin, None, _("Minimum value for the control (int/float)")) SpinnerWrap = property( _getSpinnerWrap, _setSpinnerWrap, None, _("Specifies whether the spinner value wraps at the high/low value. (bool)" )) Value = property(_getValue, _setValue, None, _("Value of the control (int/float)")) DynamicIncrement = makeDynamicProperty(Increment) DynamicMax = makeDynamicProperty(Max) DynamicMin = makeDynamicProperty(Min) DynamicSpinnerWrap = makeDynamicProperty(SpinnerWrap) # Pass-through props. These are simply ways of exposing the text control's props # through this control _proxyDict = {} Alignment = makeProxyProperty( _proxyDict, "Alignment", "_proxy_textbox", ) BackColor = makeProxyProperty(_proxyDict, "BackColor", ("_proxy_textbox", "self")) Enabled = makeProxyProperty(_proxyDict, "Enabled", ("self", "_proxy_spinner", "_proxy_textbox")) Font = makeProxyProperty(_proxyDict, "Font", "_proxy_textbox") FontInfo = makeProxyProperty(_proxyDict, "FontInfo", "_proxy_textbox") FontSize = makeProxyProperty(_proxyDict, "FontSize", "_proxy_textbox") FontFace = makeProxyProperty(_proxyDict, "FontFace", "_proxy_textbox") FontBold = makeProxyProperty(_proxyDict, "FontBold", "_proxy_textbox") FontItalic = makeProxyProperty(_proxyDict, "FontItalic", "_proxy_textbox") FontUnderline = makeProxyProperty(_proxyDict, "FontUnderline", "_proxy_textbox") ForeColor = makeProxyProperty(_proxyDict, "ForeColor", "_proxy_textbox") Height = makeProxyProperty(_proxyDict, "Height", ("self", "_proxy_spinner", "_proxy_textbox")) ReadOnly = makeProxyProperty(_proxyDict, "ReadOnly", "_proxy_textbox") SelectOnEntry = makeProxyProperty(_proxyDict, "SelectOnEntry", "_proxy_textbox") ToolTipText = makeProxyProperty( _proxyDict, "ToolTipText", ("self", "_proxy_spinner", "_proxy_textbox")) Visible = makeProxyProperty(_proxyDict, "Visible", ("self", "_proxy_spinner", "_proxy_textbox"))
class dRadioList(cim.dControlItemMixin, wx.Panel): """ Creates a group of radio buttons, allowing mutually-exclusive choices. Like a dDropdownList, use this to present the user with multiple choices and for them to choose from one of the choices. Where the dDropdownList is suitable for lists of one to a couple hundred choices, a dRadioList is really only suitable for lists of one to a dozen at most. """ def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dRadioList self._sizerClass = dabo.ui.dBorderSizer self._buttonClass = _dRadioButton self._showBox = True self._caption = "" preClass = wx.PrePanel style = self._extractKey((properties, attProperties, kwargs), "style", 0) style = style | wx.TAB_TRAVERSAL kwargs["style"] = style # Tracks individual member radio buttons. self._items = [] self._selpos = 0 # Tracks timing to determine whether any of the buttons # have changed focus so got/lost events can be raised self._lastGotFocusEvent = self._lastLostFocusEvent = 0 # Default spacing between buttons. Can be changed with the # 'ButtonSpacing' property. self._buttonSpacing = 5 cim.dControlItemMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, *args, **kwargs) def _resetChoices(self): # Need to override as the base behavior calls undefined Clear() and AppendItems() pass def getBaseButtonClass(cls): return _dRadioButton getBaseButtonClass = classmethod(getBaseButtonClass) def _checkSizer(self): """Makes sure the sizer is created before setting props that need it.""" if self.Sizer is None: self.Sizer = self.SizerClass(self, orientation=self.Orientation, Caption=self.Caption) def _onWxHit(self, evt): pos = self._items.index(evt.GetEventObject()) self.PositionValue = pos # This allows the event processing to properly # set the EventData["index"] properly. evt.SetInt(pos) self._userChanged = True super(dRadioList, self)._onWxHit(evt) def _onButtonGotFocus(self, wxEvt): # Received from individual buttons now = time.time() if now - self._lastLostFocusEvent > .01: # Newly focused; raise the event. # Missing uiEvent parameter in call? See note below. self.raiseEvent(dEvents.GotFocus) self._lastGotFocusEvent = now def _onButtonLostFocus(self, wxEvt): # Received from individual buttons now = time.time() self._lastLostFocusEvent = now @dabo.ui.deadCheck def checkForFocus(timeCalled): if timeCalled - self._lastGotFocusEvent > .01: # No other button has gotten focus in the intervening time # Don't raise event if parent form loses focus! # Doing it on Windows platform raises global Python exception. app = self.Application if app is None or app.ActiveForm == self.Form: # Passing wxEvt as uiEvent parameter under some conditions # causes Python interpreter crash or unspecified problems # with GC. I decided to remove this reference for both, # GotFocus and LostFocus event of control to retain symmetry. self.raiseEvent(dEvents.LostFocus) # Normal changing selection of buttons will cause buttons to lose focus; # we need to see if this control has truly lost focus. dabo.ui.callAfter(checkForFocus, now) def layout(self): """Wrap the wx version of the call, if possible.""" self.Layout() try: # Call the Dabo version, if present self.Sizer.layout() except AttributeError: pass if self.Application.Platform == "Win": self.refresh() def _setSelection(self, val): """ Set the selected state of the buttons to match this control's Value. """ for pos, itm in enumerate(self._items): itm.SetValue(pos == val) def enableKey(self, itm, val=True): """Enables or disables an individual button, referenced by key value.""" index = self.Keys[itm] self._items[index].Enabled = val def enablePosition(self, itm, val=True): """Enables or disables an individual button, referenced by position (index).""" self._items[itm].Enabled = val def enableString(self, itm, val=True): """Enables or disables an individual button, referenced by string display value.""" mtch = [ btn for btn in self.Children if isinstance(btn, _dRadioButton) and btn.Caption == itm ] try: itm = mtch[0] idx = self._items.index(itm) self._items[idx].Enabled = val except IndexError: dabo.log.error( _("Could not find a button with Caption of '%s'") % itm) def enable(self, itm, val=True): """ Enables or disables an individual button. The itm argument specifies which button to enable/disable, and its type depends on the setting of self.ValueType: ============ ==================== "position" The item is referenced by index position. "string" The item is referenced by its string display value. "key" The item is referenced by its key value. ============ ==================== """ if self.ValueMode == "position": self.enablePosition(itm, val) elif self.ValueMode == "string": self.enableString(itm, val) elif self.ValueMode == "key": self.enableKey(itm, val) def showKey(self, itm, val=True): """Shows or hides an individual button, referenced by key value.""" index = self.Keys[itm] self._items[index].Visible = val self.layout() def showPosition(self, itm, val=True): """Shows or hides an individual button, referenced by position (index).""" self._items[itm].Visible = val self.layout() def showString(self, itm, val=True): """Shows or hides an individual button, referenced by string display value.""" mtch = [btn for btn in self._items if btn.Caption == itm] if mtch: mtch[0].Visible = val self.layout() def show(self, itm, val=True): """ Shows or hides an individual button. The itm argument specifies which button to hide/show, and its type depends on the setting of self.ValueType: ============ ==================== "position" The item is referenced by index position. "string" The item is referenced by its string display value. "key" The item is referenced by its key value. ============ ==================== """ if self.ValueMode == "position": self.showPosition(itm, val) elif self.ValueMode == "string": self.showString(itm, val) elif self.ValueMode == "key": self.showKey(itm, val) def _getFudgedButtonSpacing(self): val = self._buttonSpacing if "linux" in sys.platform: # Buttons too widely spaced on Linux. Fudge it down... val -= 9 val = (val, 0) if self.Orientation[:1].lower() == "h" else (0, val) return val # Property get/set/del methods follow. Scroll to bottom to see the property # definitions themselves. def _getButtonClass(self): return self._buttonClass def _setButtonClass(self, val): self._buttonClass = val def _getButtonSpacing(self): return self._buttonSpacing def _setButtonSpacing(self, val): if self._constructed(): self._buttonSpacing = val self._checkSizer() sizer = self.Sizer spacing = self._getFudgedButtonSpacing() for itm in sizer.ChildSpacers: sizer.setItemProp(itm, "Spacing", spacing) self.layout() else: self._properties["ButtonSpacing"] = val def _getCaption(self): ret = self._caption if isinstance(self.Sizer, dabo.ui.dBorderSizer): ret = self._caption = self.Sizer.Caption return ret def _setCaption(self, val): if self._constructed(): self._checkSizer() self._caption = val try: self.Sizer.Caption = val except AttributeError: pass try: self.Parent.layout() except AttributeError: self.layout() else: self._properties["Caption"] = val def _getChoices(self): try: _choices = self._choices except AttributeError: _choices = self._choices = [] return _choices def _setChoices(self, choices): if self._constructed(): self._checkSizer() # Save the current values for possible re-setting afterwards. old_length = len(self.Choices) sv = (self.KeyValue, self.StringValue, self.PositionValue) [itm.release() for itm in self._items] self._choices = choices self._items = [] self.Sizer.clear() for idx, itm in enumerate(choices): style = 0 if idx == 0: if len(choices) == 1: style = wx.RB_SINGLE else: style = wx.RB_GROUP else: self.Sizer.appendSpacer(self._getFudgedButtonSpacing()) btn = self.ButtonClass(self, Caption=itm, style=style) btn._index = idx self.Sizer.append(btn) self._items.append(btn) if old_length: # Try each saved value to restore which button is active: if self.Keys: self.KeyValue = sv[0] if not self.Keys or self.KeyValue != sv[0]: try: self.StringValue = sv[1] except ValueError: self.PositionValue = sv[2] if self.PositionValue != sv[2]: # Bail! self.PositionValue = 0 else: self.PositionValue = 0 self.layout() else: self._properties["Choices"] = choices def _getOrientation(self): return getattr(self, "_orientation", "Vertical") def _setOrientation(self, val): if self._constructed(): self._checkSizer() if val[0].lower() not in "hv": val = "vertical" self._orientation = self.Sizer.Orientation = val # Reset button spacing also. self.ButtonSpacing = self.ButtonSpacing else: self._properties["Orientation"] = val def _getPositionValue(self): return self._selpos def _setPositionValue(self, val): if self._constructed(): self._selpos = val self._setSelection(val) else: self._properties["PositionValue"] = val def _getShowBox(self): return self._showBox def _setShowBox(self, val): if self._constructed(): fromSz = self.Sizer if fromSz is None: # Control hasn't been constructed yet dabo.ui.setAfter(self, "ShowBox", val) return self._showBox = val parent = fromSz.Parent isInSizer = fromSz.ControllingSizer is not None if isInSizer: csz = fromSz.ControllingSizer pos = fromSz.getPositionInSizer() szProps = csz.getItemProps(fromSz) isBorderSz = isinstance(fromSz, dabo.ui.dBorderSizer) needChange = (val and not isBorderSz) or (not val and isBorderSz) if not needChange: return if isBorderSz: toCls = fromSz.getNonBorderedClass() toSz = toCls() else: toCls = fromSz.getBorderedClass() toSz = toCls(parent) toSz.Caption = self._caption toSz.Orientation = fromSz.Orientation memberItems = fromSz.Children members = [fromSz.getItem(mem) for mem in memberItems] memberProps = dict.fromkeys(members) szProps = ("Border", "Proportion", "Expand", "HAlign", "VAlign", "BorderSides") for pos, member in enumerate(members): pd = {} for sp in szProps: pd[sp] = fromSz.getItemProp(memberItems[pos], sp) memberProps[member] = pd for member in members[::-1]: try: fromSz.remove(member) except AttributeError: # probably a spacer pass setSizer = (parent is not None) and (parent.Sizer is fromSz) if setSizer: parent.Sizer = None # Delete the old sizer. fromSz.release() if setSizer: parent.Sizer = toSz if isInSizer: itm = csz.insert(pos, toSz) csz.setItemProps(itm, szProps) for member in members: itm = toSz.append(member) toSz.setItemProps(itm, memberProps[member]) try: self.Parent.layout() except AttributeError: self.layout() else: self._properties["ShowBox"] = val def _getSizerClass(self): return self._sizerClass def _setSizerClass(self, val): self._sizerClass = val def _getStringValue(self): try: ret = self._items[self._selpos].Caption except IndexError: ret = None return ret def _setStringValue(self, val): if self._constructed(): try: idx = [ btn._index for btn in self._items if btn.Caption == val ][0] self.PositionValue = idx except IndexError: if val is not None: # No such string. raise ValueError, _( "No radio button matching '%s' was found.") % val else: self._properties["StringValue"] = val # Property definitions: ButtonClass = property( _getButtonClass, _setButtonClass, None, _("Class to use for the radio buttons. Default=_dRadioButton (dRadioButton)" )) ButtonSpacing = property( _getButtonSpacing, _setButtonSpacing, None, _("Spacing in pixels between buttons in the control (int)")) Caption = property( _getCaption, _setCaption, None, _("String to display on the box surrounding the control (str)")) Choices = property( _getChoices, _setChoices, None, _("""Specifies the string choices to display in the list. -> List of strings. Read-write at runtime. The list index becomes the PositionValue, and the string becomes the StringValue.""")) Orientation = property( _getOrientation, _setOrientation, None, _("""Specifies whether this is a vertical or horizontal RadioList. String. Possible values: 'Vertical' (the default) 'Horizontal'""")) PositionValue = property( _getPositionValue, _setPositionValue, None, _("""Specifies the position (index) of the selected button. Integer. Read-write at runtime. Returns the current position, or sets the current position.""")) ShowBox = property(_getShowBox, _setShowBox, None, _("Is the surrounding box visible? (bool)")) SizerClass = property( _getSizerClass, _setSizerClass, None, _("Class to use for the border sizer. Default=dabo.ui.dBorderSizer (dSizer)" )) StringValue = property( _getStringValue, _setStringValue, None, _("""Specifies the text of the selected button. String. Read-write at runtime. Returns the text of the current item, or changes the current position to the position with the specified text. An exception is raised if there is no position with matching text.""")) DynamicOrientation = makeDynamicProperty(Orientation) DynamicPositionValue = makeDynamicProperty(PositionValue) DynamicStringValue = makeDynamicProperty(StringValue)
class dCheckBox(dcm.dDataControlMixin, wx.CheckBox): """Creates a checkbox, allowing editing boolean values.""" def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._baseClass = dCheckBox preClass = wx.PreCheckBox dcm.dDataControlMixin.__init__(self, preClass, parent, properties=properties, attProperties=attProperties, *args, **kwargs) def _initEvents(self): super(dCheckBox, self)._initEvents() self.Bind(wx.EVT_CHECKBOX, self._onWxHit) def _initProperties(self): self._3StateToValue = { wx.CHK_UNCHECKED: False, wx.CHK_CHECKED: True, wx.CHK_UNDETERMINED: None } self._ValueTo3State = dict( [[v, k] for k, v in self._3StateToValue.iteritems()]) super(dCheckBox, self)._initProperties() def _getInitPropertiesList(self): additional = ["ThreeState", "Alignment"] original = list(super(dCheckBox, self)._getInitPropertiesList()) return tuple(original + additional) def _onWxHit(self, evt): self._userChanged = True self.flushValue() super(dCheckBox, self)._onWxHit(evt) def getBlankValue(self): return False # property get/set functions def _getAlignment(self): if self._hasWindowStyleFlag(wx.ALIGN_RIGHT): return "Right" else: return "Left" def _setAlignment(self, val): self._delWindowStyleFlag(wx.ALIGN_RIGHT) if val.lower()[0] == "r": self._addWindowStyleFlag(wx.ALIGN_RIGHT) elif val.lower()[0] == "l": pass else: raise ValueError( _("The only possible values are 'Left' and 'Right'.")) def _getThreeState(self): return self._hasWindowStyleFlag(wx.CHK_3STATE) def _setThreeState(self, val): self._delWindowStyleFlag(wx.CHK_3STATE) if val == True: self._addWindowStyleFlag(wx.CHK_3STATE) def _getUserThreeState(self): return self._hasWindowStyleFlag(wx.CHK_ALLOW_3RD_STATE_FOR_USER) def _setUserThreeState(self, val): self._delWindowStyleFlag(wx.CHK_ALLOW_3RD_STATE_FOR_USER) if val == True: self._addWindowStyleFlag(wx.CHK_ALLOW_3RD_STATE_FOR_USER) def _getValue(self): if not self._hasWindowStyleFlag(wx.CHK_3STATE): return dcm.dDataControlMixin._getValue(self) else: return self._3StateToValue.get(self.Get3StateValue(), None) def _setValue(self, val): if self._constructed(): if not self._hasWindowStyleFlag(wx.CHK_3STATE): dcm.dDataControlMixin._setValue(self, val) else: try: state = self._ValueTo3State[val] except KeyError: state = False self.Set3StateValue(state) else: self._properties["Value"] = val # property definitions follow: Alignment = property( _getAlignment, _setAlignment, None, _("""Specifies the alignment of the text. Left : Checkbox to left of text (default) Right : Checkbox to right of text""")) ThreeState = property( _getThreeState, _setThreeState, None, _("""Specifies wether the checkbox support 3 states. True : Checkbox supports 3 states False : Checkbox supports 2 states (default)""")) UserThreeState = property( _getUserThreeState, _setUserThreeState, None, _("""Specifies whether the user is allowed to set the third state. True : User is allowed to set the third state. False : User isn't allowed to set the third state.(default)""")) Value = property( _getValue, _setValue, None, _("Specifies the current state of the control (the value of the field). (varies)" )) DynamicAlignment = makeDynamicProperty(Alignment) DynamicThreeState = makeDynamicProperty(ThreeState) DynamicUserThreeState = makeDynamicProperty(UserThreeState) DynamicValue = makeDynamicProperty(Value)
class dNumericBox(dtbm.dTextBoxMixin, masked.NumCtrl): """This is a specialized textbox class that maintains numeric values.""" def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): localeData = locale.localeconv() enc = locale.getdefaultlocale()[1] self._baseClass = dNumericBox kwargs["integerWidth"] = self._extractKey( (properties, attProperties, kwargs), "IntegerWidth", 10) kwargs["fractionWidth"] = self._extractKey( (properties, attProperties, kwargs), "DecimalWidth", 2) kwargs["Alignment"] = self._extractKey( (properties, attProperties, kwargs), "Alignment", "Right") kwargs["selectOnEntry"] = self._extractKey( (properties, attProperties, kwargs), "SelectOnEntry", self.SelectOnEntry) groupChar = self._extractKey((properties, attProperties, kwargs), "GroupChar", localeData["thousands_sep"].decode(enc)) # Group char can't be empty string. if groupChar or groupChar >= " ": kwargs["groupChar"] = groupChar kwargs["groupDigits"] = True else: kwargs["groupChar"] = " " kwargs["groupDigits"] = False kwargs["autoSize"] = self._extractKey( (properties, attProperties, kwargs), "AutoWidth", True) kwargs["allowNegative"] = self._extractKey( (properties, attProperties, kwargs), "AllowNegative", True) kwargs["useParensForNegatives"] = self._extractKey( (properties, attProperties, kwargs), "ParensForNegatives", False) kwargs["decimalChar"] = self._extractKey( (properties, attProperties, kwargs), "DecimalChar", localeData["decimal_point"].decode(enc)) kwargs["foregroundColour"] = self._extractKey( (properties, attProperties, kwargs), "ForeColor", "Black") kwargs["validBackgroundColour"] = self._extractKey( (properties, attProperties, kwargs), "BackColor", wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) kwargs["invalidBackgroundColour"] = self._extractKey( (properties, attProperties, kwargs), "InvalidBackColor", "Yellow") kwargs["signedForegroundColour"] = self._extractKey( (properties, attProperties, kwargs), "SignedForeColor", "Red") kwargs["allowNone"] = self._extractKey( (properties, attProperties, kwargs), "AllowNoneValue", False) kwargs["max"] = self._extractKey((properties, attProperties, kwargs), "MaxValue", None) kwargs["min"] = self._extractKey((properties, attProperties, kwargs), "MinValue", None) # Base class 'limited' property is inconvenient. kwargs["limited"] = False fontFace = self._extractKey((properties, attProperties, kwargs), "FontFace", "") if not fontFace and self.Application.Platform in ("Win", ): fontFace = "Tahoma" elif not fontFace and self.Application.Platform in ("Mac", ): fontFace = "Lucida Grande" if fontFace: kwargs["FontFace"] = fontFace dtbm.dTextBoxMixin.__init__(self, masked.NumCtrl, parent, properties, attProperties, *args, **kwargs) #--- Public interface. def flushValue(self): # Because dTextBoxMixin method is improper here, # we use superclass method instead. return ddcm.dDataControlMixin.flushValue(self) def getBlankValue(self): dec = self.DecimalWidth if dec > 0: return Decimal("0.%s" % ("0" * dec)) else: return 0 def GetParensForNegatives(self): return self._useParensForNegatives def update(self): # Be very careful! If limits across allowed value, # control value will be automatically reseted to default value. maxVal = self.MaxValue self.MaxValue = None minVal = self.MinValue self.MinValue = None super(dNumericBox, self).update() if not "MaxValue" in self._dynamic: self.MaxValue = maxVal if not "MinValue" in self._dynamic: self.MinValue = minVal #--- Internal class interface. def _initEvents(self): super(dNumericBox, self)._initEvents() self.bindEvent(dEvents.GotFocus, self._onGotFocusFix) self.bindEvent(dEvents.LostFocus, self._onLostFocusFix) def _onGotFocusFix(self, evt): dabo.ui.callAfter(self._fixInsertionPoint) def _onLostFocusFix(self, evt): if self.LimitValue: max = self.MaxValue min = self.MinValue #if (max is not None and not (max >= self._value)) or \ # (min is not None and not (self._value >= min)): # evt.stop() # self.setFocus() def _onWxHit(self, evt, *args, **kwargs): # This fix wx masked controls issue firing multiple EVT_TEXT events. if self._value != self.Value: super(dNumericBox, self)._onWxHit(evt, *args, **kwargs) def _fixInsertionPoint(self): """Fixes insertion point position when value change or when getting focus with mouse click.""" if self.Enabled and not self.ReadOnly: dw = self.DecimalWidth if dw > 0: self.InsertionPoint = self._masklength - dw - 1 else: self.InsertionPoint = self._masklength if self.SelectOnEntry: dabo.ui.callAfter(self.select, 0, self.InsertionPoint) #--- Properties methods. def _getGroupChar(self): if self.GetGroupDigits(): ret = self.GetGroupChar() else: ret = None return ret def _setGroupChar(self, val): """Set GroupChar to None to avoid grouping.""" if self._constructed(): if val is None: self.SetGroupDigits(False) else: self.SetGroupChar(val) self.SetGroupDigits(True) else: self._properties["GroupChar"] = val def _getAllowNegative(self): return self.GetAllowNegative() def _setAllowNegative(self, val): if self._constructed(): self.SetAllowNegative(val) else: self._properties["AllowNegative"] = val def _getAllowNoneValue(self): return self.GetAllowNone() def _setAllowNoneValue(self, val): if self._constructed(): self.SetAllowNone(val) else: self._properties["AllowNoneValue"] = val def _getAutoWidth(self): return self.GetAutoSize() def _setAutoWidth(self, val): if self._constructed(): self.SetAutoSize(val) else: self._properties["AutoWidth"] = val def _getLimitValue(self): return getattr(self, "_limitValue", False) def _setLimitValue(self, val): self._limitValue = bool(val) def _getMinValue(self): val = self.GetMin() if val is not None and self._lastDataType is Decimal: val = Decimal(str(val)) return val def _setMinValue(self, val): if self._constructed(): if isinstance(val, Decimal): val = float(val) self.SetMin(val) else: self._properties["MinValue"] = val def _getMaxValue(self): val = self.GetMax() if val is not None and self._lastDataType is Decimal: val = Decimal(str(val)) return val def _setMaxValue(self, val): if self._constructed(): if isinstance(val, Decimal): val = float(val) self.SetMax(val) else: self._properties["MaxValue"] = val def _getIntegerWidth(self): return self.GetIntegerWidth() def _setIntegerWidth(self, val): if self._constructed(): self.SetIntegerWidth(val) else: self._properties["IntegerWidth"] = val def _getInvalidBackColor(self): return self.GetInvalidBackgroundColour() def _setInvalidBackColor(self, val): if self._constructed(): self.SetInvalidBackgroundColour(val) else: self._properties["InvalidBackColor"] = val def _getDecimalChar(self): return self.GetDecimalChar() def _setDecimalChar(self, val): if self._constructed(): self.SetDecimalChar(val) else: self._properties["DecimalChar"] = val def _getDecimalWidth(self): return self.GetFractionWidth() def _setDecimalWidth(self, val): if self._constructed(): self.SetFractionWidth(val) else: self._properties["DecimalWidth"] = val def _getParensForNegatives(self): return self.GetUseParensForNegatives() def _setParensForNegatives(self, val): if self._constructed(): self.SetUseParensForNegatives(val) else: self._properties["ParensForNegatives"] = val def _getSignedForeColor(self): return self.GetSignedForegroundColour() def _setSignedForeColor(self, val): if self._constructed(): self.SetSignedForegroundColour(val) else: self._properties["SignedForeColor"] = val def _getValue(self): val = ddcm.dDataControlMixin._getValue(self) if self._lastDataType is Decimal: val = Decimal(str(val)) elif self._lastDataType is NoneType: chkVal = int(val) if chkVal != val: val = Decimal(str(val)) elif chkVal <> 0: val = chkVal else: val = None return val def _setValue(self, val): self._lastDataType = type(val) if self._lastDataType is Decimal: val = float(val) elif val is None: val = float(0) ddcm.dDataControlMixin._setValue(self, val) dabo.ui.callAfter(self._fixInsertionPoint) def _getSelectOnEntry(self): try: return self.GetSelectOnEntry() except AttributeError: return False def _setSelectOnEntry(self, val): self.SetSelectOnEntry(bool(val)) #--- Properties definitions. AllowNegative = property( _getAllowNegative, _setAllowNegative, None, _("""Enables/disables negative numbers. (bool) Default=True""")) AllowNoneValue = property( _getAllowNoneValue, _setAllowNoneValue, None, _("""Enables/disables undefined value - None. (bool) Default=False""")) AutoWidth = property( _getAutoWidth, _setAutoWidth, None, _("""Indicates whether or not the control should set its own width based on the integer and fraction widths. (bool) Default=True""")) DecimalChar = property( _getDecimalChar, _setDecimalChar, None, _("""Defines character that will be used to represent the decimal point. (str) Default value comes from locale setting.""")) DecimalWidth = property( _getDecimalWidth, _setDecimalWidth, None, _("""Tells how many decimal places to show for numeric value. (int) Default=2""")) GroupChar = property( _getGroupChar, _setGroupChar, None, _("""What grouping character will be used if allowed. If set to None, no grouping is allowed. (str) Default value comes from locale setting.""")) IntegerWidth = property( _getIntegerWidth, _setIntegerWidth, None, _("""Indicates how many places to the right of any decimal point should be allowed in the control. (int) Default=10""")) InvalidBackColor = property( _getInvalidBackColor, _setInvalidBackColor, None, _("""Color value used for illegal values or values out-of-bounds. (str) Default='Yellow'""")) LimitValue = property( _getLimitValue, _setLimitValue, None, _("""Limit control value to Min and Max bounds. When set to True, if invalid, will be automatically reseted to default. When False, only background color will change. (bool) Default=False""")) MaxValue = property( _getMaxValue, _setMaxValue, None, _("""The maximum value that the control should allow. Set to None if limit is disabled. (int, decimal) Default=None""")) MinValue = property( _getMinValue, _setMinValue, None, _("""The minimum value that the control should allow. Set to None if limit is disabled. (int, decimal) Default=None""")) ParensForNegatives = property( _getParensForNegatives, _setParensForNegatives, None, _("""If true, this will cause negative numbers to be displayed with parens rather than with sign mark. (bool) Default=False""")) SelectOnEntry = property( _getSelectOnEntry, _setSelectOnEntry, None, _("""Specifies whether all text gets selected upon receiving focus. (bool) Default=False""")) SignedForeColor = property( _getSignedForeColor, _setSignedForeColor, None, _("""Color value used for negative values of the control. (str) Default='Red'""")) Value = property( _getValue, _setValue, None, _("""Specifies the current state of the control (the value of the field). (int, Decimal)""")) DynamicMaxValue = makeDynamicProperty(MaxValue) DynamicMinValue = makeDynamicProperty(MinValue)
class dBorderSizer(dabo.ui.dSizerMixin, wx.StaticBoxSizer): """ A BorderSizer is a regular box sizer, but with a visible box around the perimiter. You must either create the box first and pass it to the dBorderSizer's constructor, or pass a parent object, and the box will be created for you in the constructor as a child object of the parent you passed. """ def __init__(self, box, orientation="h", properties=None, **kwargs): self._baseClass = dBorderSizer self._border = 0 self._parent = None # Make sure that they got the params in the right order if isinstance(box, basestring): box, orientation = orientation, box if not isinstance(box, dabo.ui.dBox): prnt = box box = dabo.ui.dBox(prnt) box.sendToBack() # Convert Dabo orientation to wx orientation orient = self._extractKey((kwargs, properties), "Orientation", orientation) if orient[0].lower() == "v": orientation = wx.VERTICAL else: orientation = wx.HORIZONTAL wx.StaticBoxSizer.__init__(self, box, orientation) self._properties = {} # The keyword properties can come from either, both, or none of: # + the properties dict # + the kwargs dict # Get them sanitized into one dict: if properties is not None: # Override the class values for k, v in properties.items(): self._properties[k] = v properties = self._extractKeywordProperties(kwargs, self._properties) self.setProperties(properties) if kwargs: # Some kwargs haven't been handled. bad = ", ".join(kwargs.keys()) raise TypeError( ("Invalid keyword arguments passed to dBorderSizer: %s") % kwargs) # Mark the box as part of the sizer self.Box._belongsToBorderSizer = True self._afterInit() def getNonBorderedClass(self): """Return the class that is the non-border sizer version of this class.""" return dabo.ui.dSizer def _getBackColor(self): return self.Box.BackColor def _setBackColor(self, val): self.Box.BackColor = val def _getBox(self): return self.GetStaticBox() def _getCaption(self): return self.Box.Caption def _setCaption(self, val): self.Box.Caption = val def _getFontBold(self): return self.Box.FontBold def _setFontBold(self, val): self.Box.FontBold = val def _getFontFace(self): return self.Box.FontFace def _setFontFace(self, val): self.Box.FontFace = val def _getFontItalic(self): return self.Box.FontItalic def _setFontItalic(self, val): self.Box.FontItalic = val def _getFontSize(self): return self.Box.FontSize def _setFontSize(self, val): self.Box.FontSize = val def _getFontUnderline(self): return self.Box.FontUnderline def _setFontUnderline(self, val): self.Box.FontUnderline = val BackColor = property(_getBackColor, _setBackColor, None, _("Color of the box background (str or tuple)")) Box = property(_getBox, None, None, _("Reference to the box used in the sizer (dBox)")) Caption = property(_getCaption, _setCaption, None, _("Caption for the box (str)")) FontBold = property( _getFontBold, _setFontBold, None, _("Controls the bold setting of the box caption (bool)")) FontFace = property(_getFontFace, _setFontFace, None, _("Controls the type face of the box caption (str)")) FontItalic = property( _getFontItalic, _setFontItalic, None, _("Controls the italic setting of the box caption (bool)")) FontSize = property(_getFontSize, _setFontSize, None, _("Size of the box caption font (int)")) FontUnderline = property( _getFontUnderline, _setFontUnderline, None, _("Controls the underline setting of the box caption (bool)")) # Dynamic property declarations DynamicBackColor = makeDynamicProperty(BackColor) DynamicCaption = makeDynamicProperty(Caption) DynamicFontBold = makeDynamicProperty(FontBold) DynamicFontFace = makeDynamicProperty(FontFace) DynamicFontItalic = makeDynamicProperty(FontItalic) DynamicFontSize = makeDynamicProperty(FontSize) DynamicFontUnderline = makeDynamicProperty(FontUnderline)
class dScrollPanel(_PanelMixin, wx.ScrolledWindow): """ This is a basic container for controls that allows scrolling. Panels can contain subpanels to unlimited depth, making them quite flexible for many uses. Consider laying out your forms on panels instead, and then adding the panel to the form. """ def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): self._horizontalScroll = self._verticalScroll = True self._baseClass = dScrollPanel preClass = wx.PreScrolledWindow kwargs["AlwaysResetSizer"] = self._extractKey((properties, kwargs, attProperties), "AlwaysResetSizer", True) _PanelMixin.__init__(self, preClass=preClass, parent=parent, properties=properties, attProperties=attProperties, *args, **kwargs) self.SetScrollRate(10, 10) self.Bind(wx.EVT_SCROLLWIN, self.__onWxScrollWin) def __onWxScrollWin(self, evt): evtClass = dabo.ui.getScrollWinEventClass(evt) self.raiseEvent(evtClass, evt) evt.Skip() def scrollHorizontally(self, amt): """Change the horizontal scroll position by 'amt' units.""" self._scroll(amt, 0) def scrollVertically(self, amt): """Change the vertical scroll position by 'amt' units.""" # Y scrolling is a negative change self._scroll(0, -amt) def _scroll(self, xOff, yOff): x,y = self.GetViewStart() self.Scroll(x+xOff, y+yOff) dabo.ui.callAfterInterval(250, self.layout) def pageLeft(self): self.pageHorizontally(-1) def pageRight(self): self.pageHorizontally(1) def pageHorizontally(self, direction): """Scroll horizontally one 'page' width.""" sz = self.GetScrollPageSize(wx.HORIZONTAL) if sz: x,y = self.GetViewStart() self.Scroll(x + (direction * sz), y) def pageUp(self): self.pageVertically(-1) def pageDown(self): self.pageVertically(1) def pageVertically(self, direction): """Scroll vertically one 'page' height.""" sz = self.GetScrollPageSize(wx.VERTICAL) if sz: x,y = self.GetViewStart() self.Scroll(x, y + (direction * sz)) def _getChildren(self): ret = super(dScrollPanel, self)._getChildren() return [kid for kid in ret if isinstance(kid, dabo.ui.dPemMixinBase.dPemMixinBase)] def _setChildren(self, val): super(dScrollPanel, self)._setChildren(val) def _getHorizontalScroll(self): return self._horizontalScroll def _setHorizontalScroll(self, val, do=False): if do: self._horizontalScroll = val self.EnableScrolling(self._horizontalScroll, self._verticalScroll) rt = self.GetScrollPixelsPerUnit() self.SetScrollRate({True:rt[0], False:0}[val], rt[1]) else: # on Mac at least, this is needed when setting from the constructor. dabo.ui.callAfter(self._setHorizontalScroll, val, do=True) def _getVerticalScroll(self): return self._verticalScroll def _setVerticalScroll(self, val, do=False): if do: self._verticalScroll = val self.EnableScrolling(self._horizontalScroll, self._verticalScroll) rt = self.GetScrollPixelsPerUnit() self.SetScrollRate(rt[0], {True:rt[1], False:0}[val]) else: dabo.ui.callAfter(self._setVerticalScroll, val, do=True) Children = property(_getChildren, _setChildren, None, _("""Child controls of this panel. This excludes the wx-specific scroll bars (list of objects)""")) HorizontalScroll = property(_getHorizontalScroll, _setHorizontalScroll, None, _("Controls whether this object will scroll horizontally (default=True) (bool)")) VerticalScroll = property(_getVerticalScroll, _setVerticalScroll, None, _("Controls whether this object will scroll vertically (default=True) (bool)")) DynamicHorizontalScroll = makeDynamicProperty(HorizontalScroll) DynamicVerticalScroll = makeDynamicProperty(VerticalScroll)
=============== =================== Clip Only that part of the image that fits in the control's size is displayed Proportional The image resizes to fit the control without changing its original proportions. (default) Stretch The image resizes to the Height/Width of the control. =============== =================== """)) Value = property(_getValue, _setValue, None, _("Image content for this control (binary img data)")) _Image = property(_getImg, None, None, _("Underlying image handler object (wx.Image)")) DynamicPicture = makeDynamicProperty(Picture) DynamicScaleMode = makeDynamicProperty(ScaleMode) if __name__ == "__main__": from dabo.dApp import dApp class ImgForm(dabo.ui.dForm): def afterInit(self): self.Caption = "dImage Demonstration" self.mainPanel = mp = dabo.ui.dPanel(self) self.Sizer.append1x(mp) sz = dabo.ui.dSizer("v") mp.Sizer = sz # Create a panel with horiz. and vert. sliders self.imgPanel = dabo.ui.dPanel(mp) self.VSlider = dabo.ui.dSlider(mp, Orientation="V", Min=1, Max=100,
class dMenuItem(pm.dPemMixin, wx.MenuItem): """Creates a menu item, which is usually represented as a string.""" def __init__(self, parent=None, properties=None, *args, **kwargs): self._baseClass = dMenuItem preClass = wx.MenuItem self.Parent = parent ## see comments in _setCaption for explanation of below: text = kwargs.get("text", "") if not text: text = "dMenuItem" kwargs["text"] = text # Main text of the menu item self._caption = "" # Holds the key combination used to trigger the menu self._hotKey = None # Holds the unique ID, if any self._itemID = None pm.dPemMixin.__init__(self, preClass, parent, properties, *args, **kwargs) def _initEvents(self): ## wx.MenuItems don't have a Bind() of their own, so this serves to ## override the base behavior in dPemMixin._initEvents() which has ## a bunch of wx Events that don't exist for menu items (IOW, don't ## call the superclass method!). if self.Application is not None: # Set up a mechanism to catch menu selected events # and re-raise Dabo dEvents.Hit events. If Application # is None, however, this won't work because of wx limitations. self.Application.uiApp.Bind(wx.EVT_MENU, self.__onWxHit, self) self.Application.uiApp.Bind(wx.EVT_MENU_HIGHLIGHT, self.__onWxMenuHighlight, self) # Handle delayed event bindings if self._delayedEventBindings: dabo.ui.callAfter(self._bindDelayed) def __onWxMenuHighlight(self, evt): self.raiseEvent(dEvents.MenuHighlight) evt.Skip() def __onWxHit(self, evt): self.raiseEvent(dEvents.Hit, evt) evt.Skip(False) def _redefine(self): """Combine the Caption and HotKey into the format needed by wxPython.""" cap = self.Caption hk = self.HotKey if not cap: return if hk: cap = "%s\t%s" % (cap, hk) curr = self.GetItemLabel() ## pkm: On Windows at least, setting the Icon needs to happen before setting the caption. self.SetBitmap(self.Icon) if ustr(cap) != ustr(curr): ## Win32 seems to need to clear the caption first, or funkiness ## can arise. And win32 in wx2.8 needs for the caption to never be ## an empty string, or you'll get an invalid stock id assertion. self.SetItemLabel(" ") if cap == "": cap = " " self.SetItemLabel(cap) def _getCaption(self): return self._caption def _setCaption(self, val): if self._constructed(): tabsplit = val.split("\t") if len(tabsplit) > 1: # They're using the technique of caption + tab + hotkey # Override any prior setting of self.HotKey with the new one. self._hotKey = tabsplit[1] self._caption = tabsplit[0] self._redefine() else: self._properties["Caption"] = val def _getEnabled(self): return self.IsEnabled() def _setEnabled(self, val): if self._constructed(): self.Enable(bool(val)) else: self._properties["Enabled"] = val def _getForm(self): return self.Parent.Form def _getHelpText(self): return self.GetHelp() def _setHelpText(self, val): if self._constructed(): self.SetHelp(val) else: self._properties["HelpText"] = val def _getHotKey(self): return self._hotKey def _setHotKey(self, val): if self._constructed(): self._hotKey = val self._redefine() else: self._properties["HotKey"] = val def _getIcon(self): return self.GetBitmap() def _setIcon(self, val): if self._constructed(): if val in (None, ""): return if isinstance(val, basestring): # Icon name was passed; get the actual bitmap val = dabo.ui.strToBmp(val) self.SetBitmap(val) # Win32 at least needs the following line, or the caption # will look really funky, but Linux can't have this line or # the underlined hotkeys will get messed up. I don't know about # Mac so I'll leave that alone for now: if wx.PlatformInfo[0] == "__WXMSW__": # if self.Application.Platform in ("Win",): self.Caption = self.Caption else: self._properties["Icon"] = val def _getItemID(self): return self._itemID def _setItemID(self, val): if self._constructed(): self._itemID = val else: self._properties["ItemID"] = val def _getParent(self): try: ret = self._parent except AttributeError: ret = self._parent = None return ret def _setParent(self, val): self._parent = val Caption = property(_getCaption, _setCaption, None, _("Specifies the text of the menu item.")) Enabled = property( _getEnabled, _setEnabled, None, _("Specifies whether the menu item can be interacted with.")) Form = property(_getForm, None, None, _("Specifies the containing form.")) HelpText = property( _getHelpText, _setHelpText, None, _("Specifies the help text associated with this menu. (str)")) HotKey = property(_getHotKey, _setHotKey, None, _("Key combination that will trigger the menu (str)")) Icon = property(_getIcon, _setIcon, None, _("Specifies the icon for the menu item.")) ItemID = property( _getItemID, _setItemID, None, _("""Identifying value for this menuitem. NOTE: there is no checking for duplicate values; it is the responsibility to ensure that ItemID values are unique within a menu. (varies)""")) Parent = property(_getParent, _setParent, None, _("Specifies the parent menu.")) DynamicCaption = makeDynamicProperty(Caption) DynamicEnabled = makeDynamicProperty(Enabled) DynamicIcon = makeDynamicProperty(Icon) DynamicHelpText = makeDynamicProperty(HelpText)