def _resize_items(self): """ Size the splitter based on the 'width' or 'height' attributes of the Traits UI view elements. """ use_widths = (self.orientation() == QtCore.Qt.Horizontal) # Get the requested size for the items. sizes = [] for item in self._group.content: if use_widths: sizes.append(item.width) else: sizes.append(item.height) # Find out how much space is available. if use_widths: total = self.width() else: total = self.height() # Allocate requested space. avail = total remain = 0 for i, sz in enumerate(sizes): if avail <= 0: break if sz >= 0: if sz >= 1: sz = min(sz, avail) else: sz *= total sz = int(sz) sizes[i] = sz avail -= sz else: remain += 1 # Allocate the remainder to those parts that didn't request a width. if remain > 0: remain = int(avail / remain) for i, sz in enumerate(sizes): if sz < 0: sizes[i] = remain # If all requested a width, allocate the remainder to the last item. else: sizes[-1] += avail self.setSizes(sizes)
def find(self, name, stack=None): """ Finds a specified ViewElement within the specified (optional) search context. """ # Assume search starts from the beginning the of the search order: i = 0 # If a stack was specified, see if there is a matching entry in the # stack already: if stack is not None: for ssi in stack: if name == ssi.id: # Match found, resume search at next ViewElements object # in the search order: i = ssi.context + 1 break # Search for a matching name starting at the specified ViewElements # object in the search order: for j, ves in enumerate(self._get_search_order()[i:]): result = ves.content.get(name) if result is not None: # Match found. If there is a stack, push matching name and # ViewElements context onto it: if stack is not None: stack[0:0] = [SearchStackItem(id=name, context=i + j)] # Return the ViewElement object that matched the name: return result # Indicate no match was found: return None
def find ( self, name, stack = None ): """ Finds a specified ViewElement within the specified (optional) search context. """ # Assume search starts from the beginning the of the search order: i = 0 # If a stack was specified, see if there is a matching entry in the # stack already: if stack is not None: for ssi in stack: if name == ssi.id: # Match found, resume search at next ViewElements object # in the search order: i = ssi.context + 1 break # Search for a matching name starting at the specified ViewElements # object in the search order: for j, ves in enumerate( self._get_search_order()[i:] ): result = ves.content.get( name ) if result is not None: # Match found. If there is a stack, push matching name and # ViewElements context onto it: if stack is not None: stack[0:0] = [ SearchStackItem( id = name, context = i + j ) ] # Return the ViewElement object that matched the name: return result # Indicate no match was found: return None
def update_editor(self): """ Updates the editor when the object trait changes externally to the editor. """ self.mapper = QtCore.QSignalMapper(self.control) # Disconnect the editor from any control about to be destroyed: self._dispose_items() list_pane = self._list_pane layout = list_pane.layout() # 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 is_fake = (resizable and (len(self.value) == 0)) if is_fake: self.empty_list() else: # Asking the mapper to send the sender to the callback method self.mapper.mapped[QtCore.QObject].connect(self.popup_menu) editor = self._editor for index, value in enumerate(self.value): row, column = divmod(index, self.factory.columns) # Account for the fact that we have <columns> number of # pairs column = column * 2 if resizable: # Connecting the new button to the mapper control = IconButton('list_editor.png', self.mapper.map) # Setting the mapping and asking it to send the index of the # sender to the callback method self.mapper.setMapping(control, control) layout.addWidget(control, row, column) 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 if isinstance(pcontrol, QtGui.QWidget): layout.addWidget(pcontrol, row, column + 1) else: layout.addLayout(pcontrol, row, column + 1) # QScrollArea can have problems if the widget being scrolled is set too # early (ie. before it contains something). if self.control.widget() is None: self.control.setWidget(list_pane)
def update_editor ( self ): """ Updates the editor when the object trait changes externally to the editor. """ self.mapper = QtCore.QSignalMapper(self.control) # Disconnect the editor from any control about to be destroyed: self._dispose_items() list_pane = self._list_pane layout = list_pane.layout() # 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 is_fake = (resizable and (len( self.value ) == 0)) if is_fake: self.empty_list() else: # Asking the mapper to send the sender to the callback method self.mapper.mapped[QtCore.QObject].connect(self.popup_menu) editor = self._editor for index, value in enumerate(self.value): row, column = divmod(index, self.factory.columns) # Account for the fact that we have <columns> number of # pairs column = column * 2 if resizable: # Connecting the new button to the mapper control = IconButton('list_editor.png', self.mapper.map) # Setting the mapping and asking it to send the index of the # sender to the callback method self.mapper.setMapping(control, control) layout.addWidget(control, row, column) 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 if isinstance(pcontrol, QtGui.QWidget): layout.addWidget(pcontrol, row, column+1) else: layout.addLayout(pcontrol, row, column+1) # QScrollArea can have problems if the widget being scrolled is set too # early (ie. before it contains something). if self.control.widget() is None: self.control.setWidget(list_pane)
def update_editor ( self ): """ Updates the editor when the object trait changes external to the editor. """ ts = self._ts for i, value in enumerate( self.value ): setattr( ts, 'f%d' % i, value )
def update_editor(self): """ Updates the editor when the object trait changes external to the editor. """ ts = self._ts for i, value in enumerate(self.value): setattr(ts, 'f%d' % i, value)
def _selected_changed(self, old, new): """ Activates the corresponding dock window when the 'selected' trait of the editor is changed. """ for i, value in enumerate(self._uis): if new == value[1]: value[0].activate() break return
def dockable_tab_activated ( self, dock_control, activated ): """ Handles a notebook tab being activated or deactivated. Sets the value of the editor's selected trait to the activated dock_control's object. """ for i, value in enumerate( self.editor._uis ): if dock_control is value[0] and activated: self.editor.selected = value[1] break
def dockable_tab_activated(self, dock_control, activated): """ Handles a notebook tab being activated or deactivated. Sets the value of the editor's selected trait to the activated dock_control's object. """ for i, value in enumerate(self.editor._uis): if dock_control is value[0] and activated: self.editor.selected = value[1] break
def close_dock_control(self, dock_control, abort): """ Closes a DockControl. """ if abort: return super(DockableListElement, self).close_dock_control(dock_control, False) view_object = self.ui.context['object'] for i, value in enumerate(self.editor._uis): if view_object is value[2]: del self.editor.value[i] return False
def close_dock_control ( self, dock_control, abort ): """ Closes a DockControl. """ if abort: return super( DockableListElement, self ).close_dock_control( dock_control, False ) view_object = self.ui.context[ 'object' ] for i, value in enumerate( self.editor._uis ): if view_object is value[2]: del self.editor.value[ i ] return False
def _fill_panel(panel, content, ui, item_handler=None): """Fill a page based container panel with content. """ active = 0 for index, item in enumerate(content): page_name = item.get_label(ui) if page_name == "": page_name = "Page %d" % index if isinstance(item, Group): if item.selected: active = index gp = _GroupPanel(item, ui, suppress_label=True) page = gp.control sub_page = gp.sub_control # If the result is the same type with only one page, collapse it # down into just the page. if type(sub_page) is type(panel) and sub_page.count() == 1: new = sub_page.widget(0) if isinstance(panel, QtGui.QTabWidget): sub_page.removeTab(0) else: sub_page.removeItem(0) elif isinstance(page, QtGui.QWidget): new = page else: new = QtGui.QWidget() new.setLayout(page) layout = new.layout() if layout is not None: layout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) else: new = QtGui.QWidget() layout = QtGui.QVBoxLayout(new) layout.setContentsMargins(0, 0, 0, 0) item_handler(item, layout) # Add the content. if isinstance(panel, QtGui.QTabWidget): panel.addTab(new, page_name) else: panel.addItem(new, page_name) panel.setCurrentIndex(active)
def _fill_panel(panel, content, ui, item_handler=None): """Fill a page based container panel with content. """ active = 0 for index, item in enumerate(content): page_name = item.get_label(ui) if page_name == "": page_name = "Page %d" % index if isinstance(item, Group): if item.selected: active = index gp = _GroupPanel(item, ui, suppress_label=True) page = gp.control sub_page = gp.sub_control # If the result is the same type with only one page, collapse it # down into just the page. if type(sub_page) is type(panel) and sub_page.count() == 1: new = sub_page.widget(0) if isinstance(panel, QtGui.QTabWidget): sub_page.removeTab(0) else: sub_page.removeItem(0) elif isinstance(page, QtGui.QWidget): new = page else: new = QtGui.QWidget() new.setLayout(page) layout = new.layout() if layout is not None: layout.setAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) else: new = QtGui.QWidget() layout = QtGui.QVBoxLayout(new) layout.setContentsMargins(0, 0, 0, 0) item_handler(item, layout) # Add the content. if isinstance(panel, QtGui.QTabWidget): panel.addTab(new, page_name) else: panel.addItem(new, page_name) panel.setCurrentIndex(active)
def merge_undo ( self, undo_item ): """ Merges two undo items if possible. """ # Discard undo items that are identical to us. This is to eliminate # the same undo item being created by multiple listeners monitoring the # same list for changes: if (isinstance( undo_item, self.__class__ ) and (self.object is undo_item.object) and (self.name == undo_item.name) and (self.index == undo_item.index)): added = undo_item.added removed = undo_item.removed if ((len( self.added ) == len( added )) and (len( self.removed ) == len( removed ))): for i, item in enumerate( self.added ): if item is not added[i]: break else: for i, item in enumerate( self.removed ): if item is not removed[i]: break else: return True return False
def merge_undo(self, undo_item): """ Merges two undo items if possible. """ # Discard undo items that are identical to us. This is to eliminate # the same undo item being created by multiple listeners monitoring the # same list for changes: if (isinstance(undo_item, self.__class__) and (self.object is undo_item.object) and (self.name == undo_item.name) and (self.index == undo_item.index)): added = undo_item.added removed = undo_item.removed if ((len(self.added) == len(added)) and (len(self.removed) == len(removed))): for i, item in enumerate(self.added): if item is not added[i]: break else: for i, item in enumerate(self.removed): if item is not removed[i]: break else: return True return False
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() list_pane = self._list_pane layout = list_pane.layout() # 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 is_fake = (resizable and (len( self.value ) == 0)) if is_fake: self.empty_list() editor = self._editor # FIXME: Add support for more than one column. for index, value in enumerate(self.value): if resizable: control = IconButton('list_editor.png', self.popup_menu) layout.addWidget(control, index, 0) 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 if isinstance(pcontrol, QtGui.QWidget): layout.addWidget(pcontrol, index, 1) else: layout.addLayout(pcontrol, index, 1) # QScrollArea can have problems if the widget being scrolled is set too # early (ie. before it contains something). if self.control.widget() is None: self.control.setWidget(list_pane)
def update_page_name ( self, object, name, old, new ): """ Handles the trait defining a particular page's name being changed. """ for i, value in enumerate(self._uis): page, ui, _, _ = value if object is ui.info.object: name = None handler = getattr(self.ui.handler, '%s_%s_page_name' % (self.object_name, self.name), None) if handler is not None: name = handler(self.ui.info, object) if name is None: name = str(xgetattr(object, self.factory.page_name[1:], '???')) self.control.setTabText(self.control.indexOf(page), name) break
def replace_include ( self, view_elements ): """ Replaces any items that have an **id** attribute with an Include object with the same ID value, and puts the object with the ID into the specified ViewElements object. Parameters ---------- view_elements : ViewElements object A set of Group, Item, and Include objects """ for i, item in enumerate( self.content ): if item.is_includable(): id = item.id if id in view_elements.content: raise TraitError, \ "Duplicate definition for view element '%s'" % id self.content[ i ] = Include( id ) view_elements.content[ id ] = item item.replace_include( view_elements )
def replace_include(self, view_elements): """ Replaces any items that have an **id** attribute with an Include object with the same ID value, and puts the object with the ID into the specified ViewElements object. Parameters ---------- view_elements : ViewElements object A set of Group, Item, and Include objects """ for i, item in enumerate(self.content): if item.is_includable(): id = item.id if id in view_elements.content: raise TraitError, \ "Duplicate definition for view element '%s'" % id self.content[i] = Include(id) view_elements.content[id] = item item.replace_include(view_elements)
def update_page_name ( self ): """ Handles the trait defining a particular page's name being changed. """ changed = False for i, value in enumerate( self._uis ): dock_control, user_object, view_object, monitoring = value if dock_control.control is not None: name = None handler = getattr( self.ui.handler, '%s_%s_page_name' % ( self.object_name, self.name ), None ) if handler is not None: name = handler( self.ui.info, user_object ) if name is None: name = unicode( xgetattr( view_object, self.factory.page_name[1:], u'???' ) ) changed |= (dock_control.name != name) dock_control.name = name if changed: self.update_layout()
def _fill_panel(panel, content, ui, item_handler=None): """Fill a page based container panel with content. """ active = 0 for index, item in enumerate(content): page_name = item.get_label(ui) if page_name == "": page_name = "Page %d" % index if isinstance(item, Group): if item.selected: active = index gp = _GroupPanel(item, ui, suppress_label=True) page = gp.control sub_page = gp.sub_control # If the result is a TabSheet type with only one page, collapse it # down into just the page. if (isinstance(sub_page, TabSheet) and sub_page.getComponentCount() == 1): sub_page = sub_page.getTab(0).getComponent() else: new = page else: new = VerticalLayout() new.setMargin(False) item_handler(item, new) # Add the content. if isinstance(panel, TabSheet): panel.addTab(new, page_name) else: panel.addComponent(new, page_name) panel.setSelectedTab(panel.getTab(active))
def __init__ ( self, editor ): """ Initializes the object. """ factory = editor.factory types = factory.types labels = factory.labels editors = factory.editors cols = factory.cols # Save the reference to the editor: self.editor = editor # Get the tuple we are mirroring: object = editor.value # For each tuple field, add a trait with the appropriate trait # definition and default value: content = [] self.fields = len( object ) len_labels = len( labels ) len_editors = len( editors ) if types is None: type = editor.value_trait.handler if isinstance( type, Tuple ): types = type.types if not isinstance( types, SequenceTypes ): types = [ types ] len_types = len( types ) if len_types == 0: types = [ Any ] len_types = 1 for i, value in enumerate( object ): type = types[ i % len_types ] auto_set = enter_set = None if isinstance(type, TraitType): auto_set = type.auto_set enter_set = type.enter_set if auto_set is None: auto_set = editor.factory.auto_set if enter_set is None: enter_set = editor.factory.enter_set label = '' if i < len_labels: label = labels[i] field_editor = None if i < len_editors: field_editor = editors[i] name = 'f%d' % i self.add_trait( name, type( value, event = 'field', auto_set = auto_set, enter_set = enter_set ) ) item = Item( name = name, label = label, editor = field_editor ) if cols <= 1: content.append( item ) else: if (i % cols) == 0: group = Group( orientation = 'horizontal' ) content.append( group ) group.content.append( item ) self.view = View( Group( show_labels = (len_labels != 0), *content ) )
def merge_undo(self, undo_item): """ Merges two undo items if possible. """ # Undo items are potentially mergeable only if they are of the same # class and refer to the same object trait, so check that first: if (isinstance(undo_item, self.__class__) and (self.object is undo_item.object) and (self.name == undo_item.name)): v1 = self.new_value v2 = undo_item.new_value t1 = type(v1) if t1 is type(v2): if isinstance(t1, basestring): # Merge two undo items if they have new values which are # strings which only differ by one character (corresponding # to a single character insertion, deletion or replacement # operation in a text editor): n1 = len(v1) n2 = len(v2) n = min(n1, n2) i = 0 while (i < n) and (v1[i] == v2[i]): i += 1 if v1[i + (n2 <= n1):] == v2[i + (n2 >= n1):]: self.new_value = v2 return True elif isSequenceType(v1): # Merge sequence types only if a single element has changed # from the 'original' value, and the element type is a # simple Python type: v1 = self.old_value if isSequenceType(v1): # Note: wxColour says it's a sequence type, but it # doesn't support 'len', so we handle the exception # just in case other classes have similar behavior: try: if len(v1) == len(v2): diffs = 0 for i, item in enumerate(v1): titem = type(item) item2 = v2[i] if ((titem not in SimpleTypes) or (titem is not type(item2)) or (item != item2)): diffs += 1 if diffs >= 2: return False if diffs == 0: return False self.new_value = v2 return True except: pass elif t1 in NumericTypes: # Always merge simple numeric trait changes: self.new_value = v2 return True return False
def __init__(self, editor): """ Initializes the object. """ factory = editor.factory types = factory.types labels = factory.labels editors = factory.editors cols = factory.cols # Save the reference to the editor: self.editor = editor # Get the tuple we are mirroring: object = editor.value # For each tuple field, add a trait with the appropriate trait # definition and default value: content = [] self.fields = len(object) len_labels = len(labels) len_editors = len(editors) if types is None: type = editor.value_trait.handler if isinstance(type, Tuple): types = type.types if not isinstance(types, SequenceTypes): types = [types] len_types = len(types) if len_types == 0: types = [Any] len_types = 1 for i, value in enumerate(object): type = types[i % len_types] auto_set = enter_set = None if isinstance(type, TraitType): auto_set = type.auto_set enter_set = type.enter_set if auto_set is None: auto_set = editor.factory.auto_set if enter_set is None: enter_set = editor.factory.enter_set label = '' if i < len_labels: label = labels[i] field_editor = None if i < len_editors: field_editor = editors[i] name = 'f%d' % i self.add_trait( name, type(value, event='field', auto_set=auto_set, enter_set=enter_set)) item = Item(name=name, label=label, editor=field_editor) if cols <= 1: content.append(item) else: if (i % cols) == 0: group = Group(orientation='horizontal') content.append(group) group.content.append(item) self.view = View(Group(show_labels=(len_labels != 0), *content))
def create_notebook_for_items(content, ui, parent, group, item_handler=None, is_dock_window=False): """ Creates a notebook and adds a list of groups or items to it as separate pages. """ if is_dock_window: nb = parent else: dw = DockWindow(parent, handler=ui.handler, handler_args=(ui.info,), id=ui.id) if group is not None: dw.theme = group.dock_theme nb = dw.control pages = [] count = 0 has_theme = (group is not None) and (group.group_theme is not None) # Create a notebook page for each group or item in the content: active = 0 for index, item in enumerate(content): if isinstance(item, Group): # Create the group as a nested DockWindow item: if item.selected: active = index sg_sizer, resizable, contents = fill_panel_for_group(nb, item, ui, suppress_label=True, is_dock_window=True) # If the result is a region (i.e. notebook) with only one page, # collapse it down into just the contents of the region: if isinstance(contents, DockRegion) and (len(contents.contents) == 1): contents = contents.contents[0] # Add the content to the notebook as a new page: pages.append(contents) else: # Create the new page as a simple DockControl containing the # specified set of controls: page_name = item.get_label(ui) count += 1 if page_name == "": page_name = "Page %d" % count sizer = wx.BoxSizer(wx.VERTICAL) if has_theme: image_panel, image_sizer = add_image_panel(nb, group) panel = image_panel.control image_sizer.Add(sizer, 1, wx.EXPAND) else: panel = TraitsUIPanel(nb, -1) panel.SetSizer(sizer) pages.append( DockControl( name=page_name, image=item.image, id=item.get_id(), style=item.dock, dockable=DockableViewElement(ui=ui, element=item), export=item.export, control=panel, ) ) item_handler(item, panel, sizer) panel.GetSizer().Fit(panel) region = DockRegion(contents=pages, active=active) # If the caller is a DockWindow, return the region as the result: if is_dock_window: return region nb.SetSizer(DockSizer(contents=DockSection(contents=[region]))) # Return the notebook as the result: return nb
def merge_undo ( self, undo_item ): """ Merges two undo items if possible. """ # Undo items are potentially mergeable only if they are of the same # class and refer to the same object trait, so check that first: if (isinstance( undo_item, self.__class__ ) and (self.object is undo_item.object) and (self.name == undo_item.name)): v1 = self.new_value v2 = undo_item.new_value t1 = type( v1 ) if t1 is type( v2 ): if isinstance(t1, basestring): # Merge two undo items if they have new values which are # strings which only differ by one character (corresponding # to a single character insertion, deletion or replacement # operation in a text editor): n1 = len( v1 ) n2 = len( v2 ) n = min( n1, n2 ) i = 0 while (i < n) and (v1[i] == v2[i]): i += 1 if v1[i + (n2 <= n1):] == v2[i + (n2 >= n1):]: self.new_value = v2 return True elif isSequenceType( v1 ): # Merge sequence types only if a single element has changed # from the 'original' value, and the element type is a # simple Python type: v1 = self.old_value if isSequenceType( v1 ): # Note: wxColour says it's a sequence type, but it # doesn't support 'len', so we handle the exception # just in case other classes have similar behavior: try: if len( v1 ) == len( v2 ): diffs = 0 for i, item in enumerate( v1 ): titem = type( item ) item2 = v2[i] if ((titem not in SimpleTypes) or (titem is not type( item2 )) or (item != item2)): diffs += 1 if diffs >= 2: return False if diffs == 0: return False self.new_value = v2 return True except: pass elif t1 in NumericTypes: # Always merge simple numeric trait changes: self.new_value = v2 return True return False