class CustomEditor ( Editor ): """ Custom style of editor for instances. If selection among instances is allowed, the editor displays a combo box listing instances that can be selected. If the current instance is editable, the editor displays a panel containing trait editors for all the instance's traits. """ # Background color when an item can be dropped on the editor: ok_color = DropColor # The orientation of the instance editor relative to the instance selector: orientation = 'vertical' # Class constant: extra = 0 #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # List of InstanceChoiceItem objects used by the editor items = Property # The view to use for displaying the instance view = AView #--------------------------------------------------------------------------- # Finishes initializing the editor by creating the underlying toolkit # widget: #--------------------------------------------------------------------------- def init ( self, parent ): """ Finishes initializing the editor by creating the underlying toolkit widget. """ factory = self.factory if factory.name != '': self._object, self._name, self._value = \ self.parse_extended_name( factory.name ) # Create a panel to hold the object trait's view: if factory.editable: self.control = self._panel = parent = Panel() # Build the instance selector if needed: selectable = factory.selectable droppable = factory.droppable items = self.items for item in items: droppable |= item.is_droppable() selectable |= item.is_selectable() if selectable: self._object_cache = {} item = self.item_for( self.value ) if item is not None: self._object_cache[ id( item ) ] = self.value self._choice = ComboBox() self._choice.addCallback(self.update_object, ValueChangeEvent) self.set_tooltip( self._choice ) if factory.name != '': self._object.on_trait_change( self.rebuild_items, self._name, dispatch = 'ui' ) self._object.on_trait_change( self.rebuild_items, self._name + '_items', dispatch = 'ui' ) factory.on_trait_change( self.rebuild_items, 'values', dispatch = 'ui' ) factory.on_trait_change( self.rebuild_items, 'values_items', dispatch = 'ui' ) self.rebuild_items() elif droppable: self._choice = TextField() self._choice.setReadOnly(True) self.set_tooltip( self._choice ) if droppable: raise NotImplementedError # self._choice.SetDropTarget( PythonDropTarget( self ) ) orientation = factory.orientation if orientation == 'default': orientation = self.orientation if (selectable or droppable) and factory.editable: if orientation == 'vertical': layout = VerticalLayout() else: layout = HorizontalLayout() layout.setSizeUndefined() parent.addComponent(layout) # layout.setParent(parent) layout.setMargin(False) layout.addComponent(self._choice) if orientation == 'vertical': hline = Label('<hr />', Label.CONTENT_XHTML) layout.addComponent(hline) # FIXME: explicit parent size self.create_editor(parent, layout) elif self.control is None: if self._choice is None: self._choice = ComboBox() self._choice.addCallback(self.update_object, ValueChangeEvent) self.control = self._choice else: if orientation == 'vertical': layout = VerticalLayout() else: layout = HorizontalLayout() parent.addComponent(layout) layout.setMargin(False) layout.setSizeUndefined() self.create_editor(parent, layout) # Synchronize the 'view' to use: # fixme: A normal assignment can cause a crash (for unknown reasons) in # some cases, so we make sure that no notifications are generated: self.trait_setq( view = factory.view ) self.sync_value( factory.view_name, 'view', 'from' ) #--------------------------------------------------------------------------- # Creates the editor control: #--------------------------------------------------------------------------- def create_editor(self, parent, layout): """ Creates the editor control. """ self._panel = Panel() layout.addComponent(self._panel) #--------------------------------------------------------------------------- # Gets the current list of InstanceChoiceItem items: #--------------------------------------------------------------------------- def _get_items ( self ): """ Gets the current list of InstanceChoiceItem items. """ if self._items is not None: return self._items factory = self.factory if self._value is not None: values = self._value() + factory.values else: values = factory.values items = [] adapter = factory.adapter for value in values: if not isinstance( value, InstanceChoiceItem ): value = adapter( object = value ) items.append( value ) self._items = items return items #--------------------------------------------------------------------------- # Rebuilds the object selector list: #--------------------------------------------------------------------------- def rebuild_items ( self ): """ Rebuilds the object selector list. """ # Clear the current cached values: self._items = None # Rebuild the contents of the selector list: name = -1 value = self.value choice = self._choice choice.removeAllItems() for i, item in enumerate(self.items): if item.is_selectable(): choice.addItem( str(item.get_name()) ) if item.is_compatible( value ): name = i # Reselect the current item if possible: if name >= 0: ids = choice.getContainerPropertyIds() choice.select( ids[name] ) else: # Otherwise, current value is no longer valid, try to discard it: try: self.value = None except: pass #--------------------------------------------------------------------------- # Returns the InstanceChoiceItem for a specified object: #--------------------------------------------------------------------------- def item_for ( self, object ): """ Returns the InstanceChoiceItem for a specified object. """ for item in self.items: if item.is_compatible( object ): return item return None #--------------------------------------------------------------------------- # Returns the view to use for a specified object: #--------------------------------------------------------------------------- def view_for ( self, object, item ): """ Returns the view to use for a specified object. """ view = '' if item is not None: view = item.get_view() if view == '': view = self.view return self.ui.handler.trait_view_for( self.ui.info, view, object, self.object_name, self.name ) #--------------------------------------------------------------------------- # Handles the user selecting a new value from the combo box: #--------------------------------------------------------------------------- def update_object(self, event): """ Handles the user selecting a new value from the combo box. """ text = event.getProperty().getValue() name = unicode(text) for item in self.items: if name == item.get_name(): id_item = id( item ) object = self._object_cache.get( id_item ) if object is None: object = item.get_object() if (not self.factory.editable) and item.is_factory: view = self.view_for( object, self.item_for( object ) ) view.ui( object, self.control, 'modal' ) if self.factory.cachable: self._object_cache[ id_item ] = object self.value = object self.resynch_editor() break #--------------------------------------------------------------------------- # 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. """ # Synchronize the editor contents: self.resynch_editor() # Update the selector (if any): choice = self._choice item = self.item_for( self.value ) if (choice is not None) and (item is not None): name = item.get_name( self.value ) if self._object_cache is not None: idx = choice.findText(name) if idx < 0: idx = choice.count() choice.addItem( str(name) ) ids = choice.getContainerPropertyIds() choice.setCurrentIndex( ids[idx] ) else: choice.setValue(name) #--------------------------------------------------------------------------- # Resynchronizes the contents of the editor when the object trait changes # external to the editor: #--------------------------------------------------------------------------- def resynch_editor ( self ): """ Resynchronizes the contents of the editor when the object trait changes externally to the editor. """ panel = self._panel if panel is not None: # Dispose of the previous contents of the panel: layout = panel.getParent() if layout is None: layout = VerticalLayout() panel.addComponent(layout) # layout.setParent(panel) layout.setMargin(False) elif self._ui is not None: self._ui.dispose() self._ui = None else: layout.removeAllComponents() # Create the new content for the panel: stretch = 0 value = self.value if not isinstance( value, HasTraits ): str_value = '' if value is not None: str_value = self.str_value control = Label() control.setValue(str_value) else: view = self.view_for( value, self.item_for( value ) ) context = value.trait_context() handler = None if isinstance( value, Handler ): handler = value context.setdefault( 'context', self.object ) context.setdefault( 'context_handler', self.ui.handler ) self._ui = ui = view.ui( context, panel, 'subpanel', value.trait_view_elements(), handler, self.factory.id ) control = ui.control self.scrollable = ui._scrollable ui.parent = self.ui if view.resizable or view.scrollable or ui._scrollable: stretch = 1 # FIXME: Handle stretch. layout.addComponent(control) #--------------------------------------------------------------------------- # Disposes of the contents of an editor: #--------------------------------------------------------------------------- def dispose ( self ): """ Disposes of the contents of an editor. """ # Make sure we aren't hanging on to any object refs: self._object_cache = None if self._ui is not None: self._ui.dispose() if self._choice is not None: if self._object is not None: self._object.on_trait_change( self.rebuild_items, self._name, remove = True ) self._object.on_trait_change( self.rebuild_items, self._name + '_items', remove = True ) self.factory.on_trait_change( self.rebuild_items, 'values', remove = True ) self.factory.on_trait_change( self.rebuild_items, 'values_items', remove = True ) super( CustomEditor, self ).dispose() #--------------------------------------------------------------------------- # Handles an error that occurs while setting the object's trait value: #--------------------------------------------------------------------------- def error ( self, excp ): """ Handles an error that occurs while setting the object's trait value. """ pass #--------------------------------------------------------------------------- # Returns the editor's control for indicating error status: #--------------------------------------------------------------------------- def get_error_control ( self ): """ Returns the editor's control for indicating error status. """ return (self._choice or self.control) #-- UI preference save/restore interface ----------------------------------- #--------------------------------------------------------------------------- # Restores any saved user preference information associated with the # editor: #--------------------------------------------------------------------------- def restore_prefs ( self, prefs ): """ Restores any saved user preference information associated with the editor. """ ui = self._ui if (ui is not None) and (prefs.get( 'id' ) == ui.id): ui.set_prefs( prefs.get( 'prefs' ) ) #--------------------------------------------------------------------------- # Returns any user preference information associated with the editor: #--------------------------------------------------------------------------- def save_prefs ( self ): """ Returns any user preference information associated with the editor. """ ui = self._ui if (ui is not None) and (ui.id != ''): return { 'id': ui.id, 'prefs': ui.get_prefs() } return None #-- Traits event handlers -------------------------------------------------- def _view_changed ( self, view ): self.resynch_editor()