class ThemedCheckboxEditor(BasicEditorFactory): # The editor class to be created: klass = _ThemedCheckboxEditor # The checkbox label: label = Str # The basic theme for the checkbox (i.e. the 'off' state): theme = ATheme # The optional 'on' state theme for the checkbox: on_theme = ATheme # The optional 'hover off' state theme for the checkbox: hover_off_theme = ATheme # The optional 'hover on' state theme for the checbox: hover_on_theme = ATheme # The optional image to display in the checkbox (i.e. the 'off' state): image = Image('cb_off') # The optional 'on' state image to display in the checkbox: on_image = Image('cb_on') # The optional 'hover off' state image to display in the checkbox: hover_off_image = Image('cb_hover_off') # The optional 'hover on' state image to display in the checkbox: hover_on_image = Image('cb_hover_on') # The position of the image relative to the text: position = Position # The amount of space between the image and the text: spacing = Spacing
class VolumeManagerAdapter(TabularAdapter): """ TabularEditor adapter for use with the VolumeManager view. """ # The table columns: columns = [('Image Name', 'image')] # Adapter properties: text = Property image = Property # Image definitions: alert_image = Image('@std:alert16') #-- Property Implementations ----------------------------------------------- def _get_text(self): return self.item.image.image_name def _get_image(self): if self.item.modified: return self.alert_image return None
class SimpleEditor(Editor): """ Simple style of editor for lists, which displays a scrolling list box with only one item visible at a time. A icon next to the list box displays a menu of operations on the list. """ #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # The kind of editor to create for each list item kind = Str # Is the list of items being edited mutable? mutable = Bool # The image used by the editor: image = Image('list_editor') # The bitmap used by the editor: bitmap = Property #--------------------------------------------------------------------------- # Class constants: #--------------------------------------------------------------------------- # Whether the list is displayed in a single row single_row = True #--------------------------------------------------------------------------- # Normal list item menu: #--------------------------------------------------------------------------- # Menu for modifying the list list_menu = """ Add Before [_menu_before]: self.add_before() Add After [_menu_after]: self.add_after() --- Delete [_menu_delete]: self.delete_item() --- Move Up [_menu_up]: self.move_up() Move Down [_menu_down]: self.move_down() Move to Top [_menu_top]: self.move_top() Move to Bottom [_menu_bottom]: self.move_bottom() """ #--------------------------------------------------------------------------- # Empty list item menu: #--------------------------------------------------------------------------- empty_list_menu = """ Add: self.add_empty() """ #--------------------------------------------------------------------------- # Finishes initializing the editor by creating the underlying toolkit # widget: #--------------------------------------------------------------------------- def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ # Initialize the trait handler to use: trait_handler = self.factory.trait_handler if trait_handler is None: trait_handler = self.object.base_trait(self.name).handler self._trait_handler = trait_handler # Create a scrolled window to hold all of the list item controls: self.control = wxsp.ScrolledPanel(parent, -1) self.control.SetBackgroundColour(parent.GetBackgroundColour()) self.control.SetAutoLayout(True) # Remember the editor to use for each individual list item: editor = self.factory.editor if editor is None: editor = trait_handler.item_trait.get_editor() self._editor = getattr(editor, self.kind) # Set up the additional 'list items changed' event handler needed for # a list based trait. Note that we want to fire the update_editor_item # only when the items in the list change and not when intermediate # traits change. Therefore, replace "." by ":" in the extended_name # when setting up the listener. extended_name = self.extended_name.replace('.', ':') self.context_object.on_trait_change(self.update_editor_item, extended_name + '_items?', dispatch='ui') self.set_tooltip() #--------------------------------------------------------------------------- # Disposes of the contents of an editor: #--------------------------------------------------------------------------- def dispose(self): """ Disposes of the contents of an editor. """ extended_name = self.extended_name.replace('.', ':') self.context_object.on_trait_change(self.update_editor_item, extended_name + '_items?', remove=True) self._dispose_items() super(SimpleEditor, self).dispose() #--------------------------------------------------------------------------- # Updates the editor when the object trait changes external to the editor: #--------------------------------------------------------------------------- def update_editor(self): """ Updates the editor when the object trait changes externally to the editor. """ # Disconnect the editor from any control about to be destroyed: self._dispose_items() # Get rid of any previous contents: list_pane = self.control list_pane.SetSizer(None) list_pane.DestroyChildren() # Create all of the list item trait editors: trait_handler = self._trait_handler resizable = ((trait_handler.minlen != trait_handler.maxlen) and self.mutable) item_trait = trait_handler.item_trait factory = self.factory list_sizer = wx.FlexGridSizer(len(self.value), (1 + resizable) * factory.columns, 0, 0) j = resizable for i in range(factory.columns): list_sizer.AddGrowableCol(j) j += (1 + resizable) values = self.value index = 0 width, height = 0, 0 is_fake = (resizable and (values is None or len(values) == 0)) if is_fake: values = [item_trait.default_value()[1]] panel_height = 0 editor = self._editor for value in values: width1 = height = 0 if resizable: control = ImageControl(list_pane, self.bitmap, -1, self.popup_menu) width1, height = control.GetSize() width1 += 4 try: proxy = ListItemProxy(self.object, self.name, index, item_trait, value) if resizable: control.proxy = proxy peditor = editor(self.ui, proxy, 'value', self.description, list_pane).set(object_name='') peditor.prepare(list_pane) pcontrol = peditor.control pcontrol.proxy = proxy except: if not is_fake: raise pcontrol = wx.Button(list_pane, -1, 'sample') pcontrol.Fit() width2, height2 = size = pcontrol.GetSize() pcontrol.SetMinSize(size) width = max(width, width1 + width2) height = max(height, height2) panel_height += height if resizable: list_sizer.Add(control, 0, wx.LEFT | wx.RIGHT, 2) list_sizer.Add(pcontrol, 0, wx.EXPAND) index += 1 list_pane.SetSizer(list_sizer) if not self.mutable: #list_sizer.SetDimension(0,0,width, panel_height) list_pane.SetInitialSize(list_sizer.GetSize()) if is_fake: self._cur_control = control self.empty_list() control.Destroy() pcontrol.Destroy() rows = 1 if not self.single_row: rows = self.factory.rows # Make sure we have valid values set for width and height (in case there # was no data to base them on): if width == 0: width = 100 if panel_height == 0: panel_height = 20 list_pane.SetMinSize( wx.Size(width + ((trait_handler.maxlen > rows) * scrollbar_dx), panel_height)) list_pane.SetupScrolling() list_pane.GetParent().Layout() #--------------------------------------------------------------------------- # Updates the editor when an item in the object trait changes external to # the editor: #--------------------------------------------------------------------------- def update_editor_item(self, obj, name, event): """ Updates the editor when an item in the object trait changes externally to the editor. """ # If this is not a simple, single item update, rebuild entire editor: if (len(event.removed) != 1) or (len(event.added) != 1): self.update_editor() return # Otherwise, find the proxy for this index and update it with the # changed value: for control in self.control.GetChildren(): proxy = control.proxy if proxy.index == event.index: proxy.value = event.added[0] break #--------------------------------------------------------------------------- # Creates an empty list entry (so the user can add a new item): #--------------------------------------------------------------------------- def empty_list(self): """ Creates an empty list entry (so the user can add a new item). """ control = ImageControl(self.control, self.bitmap, -1, self.popup_empty_menu) control.is_empty = True proxy = ListItemProxy(self.object, self.name, -1, None, None) pcontrol = wx.StaticText(self.control, -1, ' (Empty List)') pcontrol.proxy = control.proxy = proxy self.reload_sizer([(control, pcontrol)]) #--------------------------------------------------------------------------- # Reloads the layout from the specified list of ( button, proxy ) pairs: #--------------------------------------------------------------------------- def reload_sizer(self, controls, extra=0): """ Reloads the layout from the specified list of ( button, proxy ) pairs. """ sizer = self.control.GetSizer() for i in xrange(2 * len(controls) + extra): sizer.Remove(0) index = 0 for control, pcontrol in controls: sizer.Add(control, 0, wx.LEFT | wx.RIGHT, 2) sizer.Add(pcontrol, 1, wx.EXPAND) control.proxy.index = index index += 1 sizer.Layout() self.control.SetVirtualSize(sizer.GetMinSize()) #--------------------------------------------------------------------------- # Returns the associated object list and current item index: #--------------------------------------------------------------------------- def get_info(self): """ Returns the associated object list and current item index. """ proxy = self._cur_control.proxy return (proxy.list, proxy.index) #--------------------------------------------------------------------------- # Displays the empty list editor popup menu: #--------------------------------------------------------------------------- def popup_empty_menu(self, control): """ Displays the empty list editor popup menu. """ self._cur_control = control menu = MakeMenu(self.empty_list_menu, self, True, self.control).menu self.control.PopupMenu(menu, control.GetPosition()) menu.Destroy() #--------------------------------------------------------------------------- # Displays the list editor popup menu: #--------------------------------------------------------------------------- def popup_menu(self, control): """ Displays the list editor popup menu. """ self._cur_control = control # Makes sure that any text that was entered get's added (Pressure #145): control.SetFocus() proxy = control.proxy index = proxy.index menu = MakeMenu(self.list_menu, self, True, self.control).menu len_list = len(proxy.list) not_full = (len_list < self._trait_handler.maxlen) self._menu_before.enabled(not_full) self._menu_after.enabled(not_full) self._menu_delete.enabled(len_list > self._trait_handler.minlen) self._menu_up.enabled(index > 0) self._menu_top.enabled(index > 0) self._menu_down.enabled(index < (len_list - 1)) self._menu_bottom.enabled(index < (len_list - 1)) self.control.PopupMenu(menu, control.GetPosition()) menu.Destroy() #--------------------------------------------------------------------------- # Adds a new value at the specified list index: #--------------------------------------------------------------------------- def add_item(self, offset): """ Adds a new value at the specified list index. """ list, index = self.get_info() index += offset item_trait = self._trait_handler.item_trait value = item_trait.default_value_for(self.object, self.name) self.value = list[:index] + [value] + list[index:] self.update_editor() #--------------------------------------------------------------------------- # Inserts a new item before the current item: #--------------------------------------------------------------------------- def add_before(self): """ Inserts a new item before the current item. """ self.add_item(0) #--------------------------------------------------------------------------- # Inserts a new item after the current item: #--------------------------------------------------------------------------- def add_after(self): """ Inserts a new item after the current item. """ self.add_item(1) #--------------------------------------------------------------------------- # Adds a new item when the list is empty: #--------------------------------------------------------------------------- def add_empty(self): """ Adds a new item when the list is empty. """ list, index = self.get_info() self.add_item(0) #--------------------------------------------------------------------------- # Delete the current item: #--------------------------------------------------------------------------- def delete_item(self): """ Delete the current item. """ list, index = self.get_info() self.value = list[:index] + list[index + 1:] self.update_editor() #--------------------------------------------------------------------------- # Move the current item up one in the list: #--------------------------------------------------------------------------- def move_up(self): """ Move the current item up one in the list. """ list, index = self.get_info() self.value = (list[:index - 1] + [list[index], list[index - 1]] + list[index + 1:]) self.update_editor() #--------------------------------------------------------------------------- # Moves the current item down one in the list: #--------------------------------------------------------------------------- def move_down(self): """ Moves the current item down one in the list. """ list, index = self.get_info() self.value = (list[:index] + [list[index + 1], list[index]] + list[index + 2:]) self.update_editor() #--------------------------------------------------------------------------- # Moves the current item to the top of the list: #--------------------------------------------------------------------------- def move_top(self): """ Moves the current item to the top of the list. """ list, index = self.get_info() self.value = [list[index]] + list[:index] + list[index + 1:] self.update_editor() #--------------------------------------------------------------------------- # Moves the current item to the bottom of the list: #--------------------------------------------------------------------------- def move_bottom(self): """ Moves the current item to the bottom of the list. """ list, index = self.get_info() self.value = list[:index] + list[index + 1:] + [list[index]] self.update_editor() #-- Property Implementations ----------------------------------------------- @cached_property def _get_bitmap(self): return convert_bitmap(self.image) #-- Private Methods -------------------------------------------------------- def _dispose_items(self): """ Disposes of each current list item. """ for control in self.control.GetChildren(): editor = getattr(control, '_editor', None) if editor is not None: editor.dispose() editor.control = None #-- Trait initializers ---------------------------------------------------- def _kind_default(self): """ Returns a default value for the 'kind' trait. """ return self.factory.style + '_editor' def _mutable_default(self): """ Trait handler to set the mutable trait from the factory. """ return self.factory.mutable
class ThemedControl ( ThemedWindow ): #-- Public Traits ---------------------------------------------------------- # An (optional) image to be drawn inside the control: image = Image( event = 'updated' ) # The (optional) text to be displayed inside the control: text = Str( event = 'updated' ) # The position of the image relative to the text: position = Position( event = 'updated' ) # The amount of spacing between the image and the text: spacing = Spacing( event = 'updated' ) # Is the text value private (like a password): password = Bool( False, event = 'updated' ) # An additional, optional offset to apply to the text/image position: offset = Tuple( Int, Int ) # Minimum default size for the control: min_size = Tuple( Int, Int ) # Is the control enabled: enabled = Bool( True ) #-- Private Traits --------------------------------------------------------- # An event fired when any display related value changes: updated = Event # The underlying wx.Window control: control = Instance( wx.Window ) # The current text value to display: current_text = Property( depends_on = 'text, password' ) # The best size for the control: best_size = Property # The size of the current text: text_size = Property( depends_on = 'current_text, control' ) # The position and size of the current text within the control: text_bounds = Property( depends_on = 'updated' ) # The position and size of the current bitmap within the control: bitmap_bounds = Property( depends_on = 'updated' ) #-- Public Methods --------------------------------------------------------- def create_control ( self, parent ): """ Creates the underlying wx.Window object. """ self.control = control = wx.Window( parent, -1, size = wx.Size( 70, 20 ), style = wx.FULL_REPAINT_ON_RESIZE | wx.WANTS_CHARS ) # Initialize the control (set-up event handlers, ...): self.init_control() # Make sure all internal state gets initialized: self._image_changed( self.image ) # Make sure the control is sized correctly: size = self.best_size control.SetMinSize( size ) control.SetSize( size ) return control #-- Property Implementations ----------------------------------------------- @cached_property def _get_current_text ( self ): """ Returns the current text to display. """ if self.password: return '*' * len( self.text ) return self.text def _get_best_size ( self ): """ Returns the 'best' size for the control. """ cx, cy, cdx, cdy = self._get_bounds_for( TheControl ) mdx, mdy = self.min_size return wx.Size( max( cdx, mdx ), max( cdy, mdy ) ) @cached_property def _get_text_size ( self ): """ Returns the text size information for the window. """ text = self.current_text if (text == '') or (self.control is None): return ZeroTextSize return self.control.GetFullTextExtent( text ) @cached_property def _get_text_bounds ( self ): """ Returns the position and size of the text within the window. """ return self._get_bounds_for( TheText ) @cached_property def _get_bitmap_bounds ( self ): """ Returns the size and position of the bitmap within the window. """ return self._get_bounds_for( TheBitmap ) #-- Event Handlers --------------------------------------------------------- @on_trait_change( 'theme.+, image' ) def _updated_changed ( self ): """ Handles any update related trait being changed. """ if self.control is not None: self.control.Refresh() def _image_changed ( self, image ): """ Handles the image being changed by updating the corresponding bitmap. """ if image is None: self._bitmap = None else: self._bitmap = image.create_image().ConvertToBitmap() #-- wxPython Event Handlers ------------------------------------------------ def _paint_fg ( self, dc ): """ Paints the foreground into the specified device context. """ self.enabled = self.control.IsEnabled() # Get the text and image offset to use: theme = self.theme or default_theme label = theme.label ox, oy = label.left, label.top ox2, oy2 = self.offset ox += ox2 oy += oy2 # Draw the bitmap (if any): ix, iy, idx, idy = self.bitmap_bounds if idx != 0: dc.DrawBitmap( self._bitmap, ix + ox, iy + oy, True ) # Draw the text (if any): tx, ty, tdx, tdy = self.text_bounds if tdx != 0: dc.SetBackgroundMode( wx.TRANSPARENT ) dc.SetTextForeground( theme.content_color ) dc.SetFont( self.control.GetFont() ) dc.DrawText( self.current_text, tx + ox, ty + oy ) def _size ( self, event ): """ Handles the control being resized. """ super( ThemedControl, self )._size( event ) self.updated = True #-- Private Methods -------------------------------------------------------- def _get_bounds_for ( self, item ): """ Returns all text and image related position and size information. """ control = self.control if control is None: return EmptyBounds tdx, tdy, descent, leading = self.text_size bitmap = self._bitmap if bitmap is None: bdx, bdy = 0, 0 else: bdx, bdy = bitmap.GetWidth(), bitmap.GetHeight() if (tdx + bdx) == 0: return EmptyBounds wdx, wdy = control.GetClientSizeTuple() spacing = (tdx != 0) * (bdx != 0) * self.spacing theme = self.theme or default_theme slice = theme.image_slice or ImageSlice() content = theme.content position = self.position if position in ( 'above', 'below' ): cdx = max( tdx, bdx ) cdy = tdy + spacing + bdy else: cdx = tdx + spacing + bdx cdy = max( tdy, bdy ) if item == TheControl: cdx += content.left + content.right cdy += content.top + content.bottom return ( 0, 0, max( slice.left + slice.right, slice.xleft + slice.xright + cdx ), max( slice.top + slice.bottom, slice.xtop + slice.xbottom + cdy ) ) alignment = theme.alignment if alignment == 'default': alignment = self.default_alignment if alignment == 'left': x = slice.xleft + content.left elif alignment == 'center': x = slice.xleft + content.left + ((wdx - slice.xleft - slice.xright - content.left - content.right - cdx) / 2) else: x = wdx - slice.xright - content.right - cdx if position == 'left': bx = x tx = bx + bdx + spacing elif position == 'right': tx = x bx = tx + tdx + spacing else: x += (max( tdx, bdx ) / 2) tx = x - (tdx / 2 ) bx = x - (bdx / 2 ) y = slice.xtop + content.top + ((wdy - slice.xtop - slice.xbottom - content.top - content.bottom - cdy) / 2) if position == 'above': by = y ty = by + bdy + spacing elif position == 'below': ty = y by = ty + tdy + spacing else: y += (max( tdy, bdy ) / 2) ty = y - ((tdy + 1) / 2) by = y - (bdy / 2) if item == TheText: return ( tx, ty, tdx, tdy ) return ( bx, by, bdx, bdy )