class ProcessAttributes(Controller): ''' This is the base class for managing attributes for a process. ''' def __init__(self, process, schema_dict): super(ProcessAttributes, self).__init__() self._process = process self._schema_dict = schema_dict self.editable_attributes = OrderedDict() self.parameter_attributes = {} def __getinitargs__(self): # needed for self.copy() return (self._process, self._schema_dict) def set_parameter_attributes(self, parameter, schema, editable_attributes, fixed_attibute_values): if parameter in self.parameter_attributes: raise KeyError('Attributes already set for parameter %s' % parameter) if isinstance(editable_attributes, six.string_types) or isinstance( editable_attributes, EditableAttributes): editable_attributes = [editable_attributes] parameter_editable_attributes = [] for ea in editable_attributes: add_editable_attributes = False if isinstance(ea, six.string_types): key = ea ea = self.editable_attributes.get(key) if ea is None: ea = getattr(self._schema_dict[schema], key)() self.editable_attributes[key] = ea add_editable_attributes = True elif isinstance(ea, EditableAttributes): key = ea if key not in self.editable_attributes: self.editable_attributes[key] = ea add_editable_attributes = True else: raise TypeError( 'Invalid value for editable attributes: {0}'.format(ea)) parameter_editable_attributes.append(ea) if add_editable_attributes: for name, trait in six.iteritems(ea.user_traits()): self.add_trait(name, trait) f = SomaPartial(set_attribute, ea) self.on_trait_change(f, name) self.parameter_attributes[parameter] = (parameter_editable_attributes, fixed_attibute_values) def get_parameters_attributes(self): pa = {} for parameter, trait in six.iteritems(self._process.user_traits()): if trait.output: if hasattr(self._process, 'id'): process_name = self._process.id else: process_name = self._process.name attributes = { 'generated_by_process': process_name, 'generated_by_parameter': parameter } else: attributes = {} editable_fixed = self.parameter_attributes.get(parameter, ([], {})) editable_attributes, fixed_attibute_values = editable_fixed for ea in editable_attributes: for attribute in ea.user_traits(): value = getattr(ea, attribute) attributes[attribute] = value attributes.update(fixed_attibute_values) if attributes: pa[parameter] = attributes return pa
class ProcessAttributes(Controller): ''' This is the base class for managing attributes for a process. It stores attributes associated with a Process, for each of its parameters. Attribute values can be accessed "globally" (for the whole process) as ProcessAttributes instance traits. Or each parameter attributes set may be accessed individually, using :meth:`get_parameters_attributes()`. To define attributes for a process, the programmer may subclass ProcessAttributes and define some EditableAttributes in it. Each EditableAttributes is a group of attributes (traits in the EditableAttributes instance or subclass). A ProcessAttributes subclass should be registered to a factory to be linked to a process name, using the `factory_id` class variable. See Capsul :ref:`advanced usage doc <completion>` for details. ''' def __init__(self, process, schema_dict): super(ProcessAttributes, self).__init__() self._process = process self._schema_dict = schema_dict self.editable_attributes = OrderedDict() self.parameter_attributes = {} def __getinitargs__(self): # needed for self.copy() return (self._process, self._schema_dict) def set_parameter_attributes(self, parameter, schema, editable_attributes, fixed_attibute_values, allow_list=True): ''' Set attributes associated with a single process parameter. Parameters ---------- parameter: str process parameter name schema: str schema used for it (input, output, shared) editable_attributes: str, EditableAttributes instance, or list of them EditableAttributes or id containing attributes traits fixed_attibute_values: dict (str/str) values of non-editable attributes allow_list: bool if True (the default), it the process parameter is a list, then attributes are transformed into lists. ''' if parameter in self.parameter_attributes: if schema == 'link': return # this is just a lower priority raise KeyError('Attributes already set for parameter %s' % parameter) process = self._process if isinstance(process, ProcessNode): process = process.process if parameter not in process._instance_traits(): print('WARNING: parameter', parameter, 'not in process', process.name) return if isinstance(editable_attributes, six.string_types) \ or isinstance(editable_attributes, EditableAttributes): editable_attributes = [editable_attributes] parameter_editable_attributes = [] for ea in editable_attributes: add_editable_attributes = False if isinstance(ea, six.string_types): key = ea ea = self.editable_attributes.get(key) if ea is None: ea = getattr(self._schema_dict[schema], key)() self.editable_attributes[key] = ea add_editable_attributes = True elif isinstance(ea, EditableAttributes): key = ea if key not in self.editable_attributes: self.editable_attributes[key] = ea add_editable_attributes = True else: raise TypeError( 'Invalid value for editable attributes: {0}'.format(ea)) parameter_editable_attributes.append(ea) if add_editable_attributes: is_list = allow_list \ and isinstance(process.trait(parameter).trait_type, traits.List) for name in list(ea.user_traits().keys()): # don't use items() since traits may change during iter. trait = ea.trait(name) if is_list: trait = traits.List(trait) ea.remove_trait(name) ea.add_trait(name, trait) if name in self.user_traits(): if not is_list \ and isinstance(self.trait(name).trait_type, traits.List): # a process attribute trait may have been changed # into a list. Here we assume attributes are only # strings (at this point - it can be changed at # higher lever afterwards), so change it back into # a single value trait. self.remove_trait(name) self.add_trait(name, trait) # else don't add it again: this way non-list versions # of attributes get priority. If both exist, lists # should get one single value (otherwise there is an # ambiguity or inconsistency), so the attribute should # not be a list. else: self.add_trait(name, trait) f = SomaPartial(set_attribute, ea) self.on_trait_change(f, name) self.parameter_attributes[parameter] = (parameter_editable_attributes, fixed_attibute_values) def get_parameters_attributes(self): ''' Get attributes for each process parameter ''' pa = {} process = self._process if isinstance(process, ProcessNode): process = process.process for parameter, trait in six.iteritems(process.user_traits()): if trait.output: if hasattr(process, 'id'): process_name = process.id else: process_name = process.name attributes = { 'generated_by_process': process_name, 'generated_by_parameter': parameter } else: attributes = {} editable_fixed = self.parameter_attributes.get(parameter, ([], {})) editable_attributes, fixed_attibute_values = editable_fixed for ea in editable_attributes: for attribute in ea.user_traits(): value = getattr(ea, attribute) attributes[attribute] = value attributes.update(fixed_attibute_values) if attributes: pa[parameter] = attributes return pa def copy(self, with_values=True): ''' overloads :meth:`soma.Controller.copy` ''' other = self.__class__(self._process, self._schema_dict) ea_map = {} for parameter, pa in six.iteritems(self.parameter_attributes): if parameter not in other.parameter_attributes: # otherwise assume this has been done in a subclass constructor eas, fa = pa oeas = [] for ea in eas: oea = ea_map.get(ea) if oea is None: oea = ea.copy() oeas.append(oea) other.set_parameter_attributes(parameter, '', oeas, fa, allow_list=False) # copy the values if with_values: for name in self.user_traits(): #print('copy attribs:', self, name, getattr(self, name)) #print(' to:', other.trait(name).trait_type) #if isinstance(other.trait(name).trait_type, traits.List): #print(' ', other.trait(name).inner_traits[0].trait_type) #if isinstance(self.trait(name).trait_type, traits.List): #print(' self list:', self.trait(name).inner_traits[0].trait_type) setattr(other, name, getattr(self, name)) return other def copy_to_single(self, with_values=True): ''' Similar to :meth:`copy`, excepts that it converts list attributes into single values. This is useful within the completion system infrastructure, to get from an attributes set containing lists (process parameters which are lists), a single value allowing to determine a single path. This method is merely useful to the end user. ''' other = ProcessAttributes(self._process, self._schema_dict) ea_map = {} for parameter, pa in six.iteritems(self.parameter_attributes): if parameter not in other.parameter_attributes: # otherwise assume this has been done in a subclass constructor eas, fa = pa oeas = [] for ea in eas: oea = ea_map.get(ea) if oea is None: oea = EditableAttributes() for name, trait in six.iteritems(ea.user_traits()): if isinstance(trait.trait_type, traits.List): trait = trait.inner_traits[0] oea.add_trait(name, trait) ea_map[ea] = oea oeas.append(oea) other.set_parameter_attributes(parameter, '', oeas, fa, allow_list=False) # copy the values if with_values: for name in self.user_traits(): value = getattr(self, name) if isinstance(value, list): if len(value) != 0: value = value[0] else: value = self.trait(name).inner_traits[0].default if value is not None: setattr(other, name, value) return other
class ControllerWidget(QtGui.QWidget): """ Class that create a widget to set the controller parameters. """ # Parameter to store the mapping between the string trait descriptions and # the associated control classes _defined_controls = {} def __init__(self, controller, parent=None, name=None, live=False, hide_labels=False, select_controls=None, editable_labels=False): """ Method to initilaize the ControllerWidget class. Parameters ---------- controller: derived Controller instance (mandatory) a class derived from the Controller class we want to parametrize with a widget. parent: QtGui.QWidget (optional, default None) the controller widget parent widget. name: (optional, default None) the name of this controller widget live: bool (optional, default False) if True, synchronize the edited values in the widget with the controller values on the fly, otherwise, wait the synchronization instruction to update the controller values. hide_labels: bool (optional, default False) if True, don't show the labels associated with the controls select_controls: str (optional, default None) parameter to select specific conrtoller traits. Authorized options are 'inputs' or 'outputs'. editable_labels: bool (optional, default False) if True, labels (trait keys) may be edited by the user, their modification will trigger a signal. """ # Inheritance super(ControllerWidget, self).__init__(parent) QtCore.QResource.registerResource( os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'widgets_icons.rcc')) # Class parameters self.controller = controller self.live = live self.hide_labels = hide_labels self.select_controls = select_controls # Parameter to store the connection status between the # controller widget and the controller self.connected = False # Parameter to store all the controller widget controls: # the keys correspond to the control name (a control name is # associated to a controller trait with the same name), the # dictionary elements are 4-uplets of the form (trait, control_class, # control_instance, control_label). self._controls = {} self._keys_connections = {} self.editable_labels = editable_labels # If possilbe, set the widget name if name: self.setObjectName(name) # Create the layout of the controller widget # We will add all the controls to this layout self._grid_layout = QtGui.QGridLayout() self._grid_layout.setAlignment(QtCore.Qt.AlignTop) self._grid_layout.setSpacing(3) self._grid_layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self._grid_layout) self._groups = OrderedDict() # Create all the layout controls associated with the controller values # we want to tune (ie the user traits) self._create_controls() self.connect_keys() # Start the event loop that check for wrong edited fields (usefull # when we work off line, otherwise the traits make the job but it is # still user friendly). self._check() # Set the synchrinization between this object and the input controller: # 1) synchronize the edited values in the widget with the controller # values on the fly if self.live: self.connect() # 2) initialize the controller widget with the controller values and # wait synchronization instructions to update the controller values. else: self.update_controller_widget() # # Public members # def is_valid(self): """ Check that all edited fields are correct. Returns ------- valid: bool True if all the controller widget controls are correctly filled, False otherwise """ # Initilaized the output valid = True # Go through all the controller widget controls for control_name, control_groups in six.iteritems(self._controls): for group_name, control in six.iteritems(control_groups): # Unpack the control item trait, control_class, control_instance, control_label = control # Call the current control specific check method valid = control_class.is_valid(control_instance) # Stop checking if a wrong control has been found if not valid: break return valid def update_controller(self): """ Update the controller. At the end the controller traits values will match the controller widget user defined parameters. """ # Go through all the controller widget controls for control_name, control_groups in six.iteritems(self._controls): for group_name, control in six.iteritems(control_groups): # Unpack the control item trait, control_class, control_instance, control_label = control # Call the current control specific update controller method control_class.update_controller(self, control_name, control_instance) def update_controller_widget(self): """ Update the controller widget. At the end the controller widget user editable parameters will match the controller traits values. """ # Go through all the controller widget controls for control_name, control_groups in six.iteritems(self._controls): for group_name, control in six.iteritems(control_groups): # Unpack the control item trait, control_class, control_instance, control_label = control # Call the current control specific update controller widget # method control_class.update_controller_widget(self, control_name, control_instance) def connect(self): """ Connect the controller trait and the controller widget controls At the end al control will be connected with the associated trait, and when a 'user_traits_changed' signal is emited, the controls are updated (ie, deleted if necessary). """ # If the controller and controller widget are not yet connected if not self.connected: # Go through all the controller widget controls for control_name, control_groups in six.iteritems(self._controls): for group_name, control in six.iteritems(control_groups): # Unpack the control item trait, control_class, control_instance, control_label \ = control # Call the current control specific connection method logger.debug("Connecting control '{0}: {1}'...".format( control_name, control_instance)) control_class.connect(self, control_name, control_instance) # Add an event connected with the 'user_traits_changed' controller # signal: update the controls self.controller.on_trait_change(self.update_controls, "user_traits_changed", dispatch='ui') # if 'visible_groups' is a trait, connect it to groups if self.controller.trait('visible_groups'): self.controller.on_trait_change(self.groups_vibility_changed, 'visible_groups', dispatch='ui') # Update the controller widget values self.update_controller_widget() # Update the connection status self.connected = True def connect_keys(self): if not self.editable_labels or self._keys_connections: return keys_connect = {} # Go through all the controller widget controls for control_name, control_groups in six.iteritems(self._controls): for group_name, control in six.iteritems(control_groups): hook1 = partial(self.__class__._key_modified, weakref.proxy(self), control_name) hook2 = partial(self.__class__._delete_key, weakref.proxy(self), control_name) #control = self._controls[control_name] label_control = control[3] if isinstance(label_control, tuple): label_control = label_control[0] label_control.userModification.connect(hook1) label_control.buttonPressed.connect(hook2) keys_connect[control_name] = (label_control, hook1, hook2) self._keys_connections = keys_connect def disconnect(self): """ Disconnect the controller trait and the controller widget controls """ # If the controller and controller widget are connected if self.connected: # Remove the 'update_controls' event connected with the # 'user_traits_changed' controller signal self.controller.on_trait_change(self.update_controls, "user_traits_changed", remove=True) # if 'visible_groups' is a trait, connect it to groups if self.controller.trait('visible_groups'): self.controller.on_trait_change(self.groups_vibility_changed, 'visible_groups', remove=True) # Go through all the controller widget controls for control_name, control_groups in six.iteritems(self._controls): for group_name, control in six.iteritems(control_groups): # Unpack the control item trait, control_class, control_instance, control_label \ = control # Call the current control specific disconnection method control_class.disconnect(self, control_name, control_instance) # Update the connection status self.connected = False def disconnect_keys(self): for control_name, connections in six.iteritems(self._keys_connections): label_widget, hook1, hook2 = connections label_widget.userModification.disconnect(hook1) label_widget.buttonPressed.disconnect(hook2) self._keys_connections = {} def update_controls(self): """ Event to refresh controller widget controls and intern parameters. The refresh is done off line, ie. we need first to disconnect the controller and the controller widget. """ # Get the controller traits user_traits = self.controller.user_traits() # Assess the refreshing is done off line was_connected = self.connected if was_connected: self.disconnect() self.disconnect_keys() # Go through all the controller widget controls to_remove_controls = [] for control_name, control_groups in six.iteritems(self._controls): for group_name, control in six.iteritems(control_groups): # Message logger.debug( "Check if we need to update '{0}': trait in '{1}' " "different from '{2}'?".format( control_name, control, user_traits.get(control_name))) # Unpack the control item trait, control_class, control_instance, control_label = control # If the the controller trait is different from the trait # associated with the control if user_traits.get(control_name) != trait: # Close and schedule for deletation the control widget control_instance.close() control_instance.deleteLater() # Close and schedule for deletation the control labels if isinstance(control_label, tuple): for label in control_label: label.close() label.deleteLater() elif control_label: control_label.close() control_label.deleteLater() # Store the controls to be removed to_remove_controls.append(control_name) # Delete all dead controls from the class '_controls' intern parameter for control_name in to_remove_controls: logger.debug("Delete control '{0}'.".format(control_name)) del self._controls[control_name] # Recreate all the layout controls associated with the controller # values we want to tune (ie the user_traits): this procedure check # if the control has not already been created. self._create_controls() # Restore the connection status if was_connected: self.connect() self.connect_keys() # Update the widget geometry self.updateGeometry() # # Private members # def _check(self): """ Check that all edited fields are correct. At the end the controls with wrong values will be colored in red. """ # Go through all the controller widget controls for control_name, control_groups in six.iteritems(self._controls): for group_name, control in six.iteritems(control_groups): # Unpack the control item trait, control_class, control_instance, control_label = control # Call the current control specific check method control_class.check(control_instance) def _create_controls(self): """ Method that will create a control for each user trait of the controller. Controller trait parameters that cannot be maped to controls will not appear in the user interface. """ # Select only the controller traits of interest all_traits = self.controller.user_traits() if self.select_controls is None: selected_traits = all_traits elif self.select_controls == "inputs": selected_traits = dict( (trait_name, trait) for trait_name, trait in six.iteritems(all_traits) if trait.output == False) elif self.select_controls == "outputs": selected_traits = dict( (trait_name, trait) for trait_name, trait in six.iteritems(all_traits) if trait.output == True) else: raise Exception( "Unrecognized 'select_controls' option '{0}'. Valid " "options are 'inputs' or 'outputs'.".format( self.select_controls)) # Go through all the controller user traits skipped = set(['visible_groups']) for trait_name, trait in six.iteritems(selected_traits): if trait_name in skipped: continue # Create the widget self.create_control(trait_name, trait) def create_control(self, trait_name, trait): """ Create a control associated to a trait. Parameters ---------- trait_name: str (mandatory) the name of the trait from which we want to create a control. The control widget will share the same name trait: Trait (mandatory) a trait item """ # Search if the current trait has already been processed control_groups = self._controls.get(trait_name) control_instances = [] control_labels = [] # If no control has been found in the class intern parameters if control_groups is None: # Call the search function that will map the trait type to the # corresponding control type control_class = self.get_control_class(trait) # If no control has been found, skip this trait and print # an error message. Note that the parameter will not be # accessible in the user interface. if control_class is None: logger.error("No control defined for trait '{0}': {1}. This " "parameter will not be accessible in the " "user interface.".format(trait_name, trait_ids(trait))) return # handle groups layouts = [] groups = trait.groups if groups: for group in groups: group_widget = self._groups.get(group) if group_widget is None: group_widget = self._create_group_widget(group) self._groups[group] = group_widget layouts.append(group_widget.hideable_widget.layout()) else: layouts.append(self._grid_layout) group = None for i, layout in enumerate(layouts): if groups: group = groups[i] control_instance, control_label \ = self._create_control_in_layout(trait_name, trait, layout, group) control_instances.append(control_instance) if control_label: if not isinstance(control_label, tuple): control_label = [control_label] control_labels += list(control_label) if isinstance(control_label[0], QtGui.QLabel): control_label[0].setTextInteractionFlags( QtCore.Qt.TextSelectableByKeyboard | QtCore.Qt.TextSelectableByMouse) # Otherwise, the control associated with the current trait name is # already inserted in the grid layout, just unpack the values # contained in the private '_controls' class parameter else: for group, control in six.iteritems(control_groups): trait, control_class, control_instance, control_label = control control_instances.append(control_instance) if control_label: if isinstance(control_label, tuple): control_labels += list(control_label) else: control_labels.append(control_label) # Each trait has a hidden property. Take care of this information hide = (getattr(trait, 'hidden', False) or getattr(trait, 'unused', False)) # Show/Hide the control and associated labels for control_instance in control_instances: control_instance.setVisible(not hide) for label in control_labels: label.setVisible(not hide) ## Show the control and associated labels #else: #for control_instance in control_instances: #control_instance.show() #for label in control_labels: #label.show() def _create_group_widget(self, group): group_widget = QtGui.QGroupBox() last_row = self._grid_layout.rowCount() self._grid_layout.addWidget(group_widget, last_row, 0, 1, 2) lay1 = QtGui.QVBoxLayout() lay1.setContentsMargins(0, 0, 0, 0) lay2 = QtGui.QHBoxLayout() lay1.addLayout(lay2) lay2.setContentsMargins(10, 0, 0, 0) lay2.addWidget(QtGui.QLabel('<html><em>%s</em></html>' % group)) lay2.addStretch(1) icon = QtGui.QIcon() icon.addPixmap( QtGui.QPixmap(_fromUtf8(":/soma_widgets_icons/nav_down")), QtGui.QIcon.Normal, QtGui.QIcon.Off) group_widget.fold_button = QtGui.QPushButton(icon, '') group_widget.fold_button.setFixedSize(30, 20) lay2.addWidget(group_widget.fold_button) widget = QtGui.QWidget() group_widget.setLayout(lay1) lay1.addWidget(widget) group_widget.hideable_widget = widget layout = QtGui.QGridLayout() widget.setLayout(layout) layout.setAlignment(QtCore.Qt.AlignTop) layout.setSpacing(3) layout.setContentsMargins(5, 5, 5, 5) group_widget.setAlignment(QtCore.Qt.AlignLeft) visible_groups = getattr(self.controller, 'visible_groups', set()) if group in visible_groups: show = True else: show = False group_widget.hideable_widget.setVisible(show) if not show: icon = QtGui.QIcon() icon.addPixmap( QtGui.QPixmap(_fromUtf8(":/soma_widgets_icons/nav_right")), QtGui.QIcon.Normal, QtGui.QIcon.Off) group_widget.fold_button.setIcon(icon) #group_widget.fold_button.clicked.connect(SomaPartial( #self._toggle_group_visibility, group)) # FIXME: if we use this, self gets deleted somewhere. This is not # normal. group_widget.fold_button.clicked.connect( partial(self.__class__._toggle_group_visibility, weak_proxy(self), group)) return group_widget def _set_group_visibility(self, group, checked): group_widget = self._groups[group] group_widget.hideable_widget.setVisible(checked) icon = QtGui.QIcon() if checked: icon.addPixmap( QtGui.QPixmap(_fromUtf8(":/soma_widgets_icons/nav_down")), QtGui.QIcon.Normal, QtGui.QIcon.Off) else: icon.addPixmap( QtGui.QPixmap(_fromUtf8(":/soma_widgets_icons/nav_right")), QtGui.QIcon.Normal, QtGui.QIcon.Off) group_widget.fold_button.setIcon(icon) def _toggle_group_visibility(self, group, checked=False): visible_groups = getattr(self.controller, 'visible_groups', set()) if group in visible_groups: show = False visible_groups.remove(group) else: show = True visible_groups.add(group) self._set_group_visibility(group, show) self.controller.visible_groups = visible_groups def _create_control_in_layout(self, trait_name, trait, layout, group=None): # Call the search function that will map the trait type to the # corresponding control type control_class = self.get_control_class(trait) # FIXME: for now we use a hack for compound/either traits, until # we write a "real" GUI for them if isinstance(trait.trait_type, (traits.TraitCompound, traits.Either)): # compound trait: use the 1st trait = trait.handler.handlers[0].as_ctrait() # Create the control instance and associated label if self.editable_labels: label_class = DeletableLineEdit else: label_class = QtGui.QLabel control_instance, control_label = control_class.create_widget( self, trait_name, getattr(self.controller, trait_name), trait, label_class) control_class.is_valid(control_instance) # If the trait contains a description, insert a tool tip to the # control instance tooltip = "" if trait.desc: tooltip = "<b>" + trait_name + ":</b> " + trait.desc control_instance.setToolTip(tooltip) # Get the last empty row in the grid layout # Trick: If the grid layout is empty check the element 0 # (not realistic but te grid layout return None) last_row = layout.rowCount() widget_item = layout.itemAtPosition(last_row, 1) while widget_item is None and last_row > 0: last_row -= 1 widget_item = layout.itemAtPosition(last_row, 1) last_row += 1 # If the control has no label, append the control in a two # columns span area of the grid layout if control_label is None: # Grid layout 'addWidget' parameters: QWidget, int row, # int column, int rowSpan, int columnSpan layout.addWidget(control_instance, last_row, 0, 1, 2) # If the control has two labels, add a first row with the # labels (one per column), and add the control in # the next row of the grid layout in the second column elif isinstance(control_label, tuple): # Get the number of label nb_of_labels = len(control_label) # If more than two labels are detected, print an error message. # We actually consider only the two first labels and skip the # others if nb_of_labels > 2: logger.error("To many labels associated with control " "'{0}': {1}. Only consider the two first " "labels and skip the others".format( trait_name, control_label)) # Append each label in different columns if not self.hide_labels: layout.addWidget(control_label[0], last_row, 0) if nb_of_labels >= 2: layout.addWidget(control_label[1], last_row, 1) # Append the control in the next row in the second column layout.addWidget(control_instance, last_row + 1, 1) # Otherwise, append the label and control in two separate # columns of the grid layout else: # Append the label in the first column if not self.hide_labels: layout.addWidget(control_label, last_row, 0) # Append the control in the second column layout.addWidget(control_instance, last_row, 1, 1, 1) # Store some informations about the inserted control in the # private '_controls' class parameter # Keys: the trait names # Parameters: the trait - the control name - the control - and # the labels associated with the control self._controls.setdefault(trait_name, {})[group] = (trait, control_class, control_instance, control_label) return control_instance, control_label def _key_modified(self, old_key): """ Dict / open controller key modification callback """ control_groups = self._controls[old_key] control_labels = [] for group_name, control in six.iteritems(control_groups): control_labels += control[3] for control_label in control_labels: key = str(control_label.text()) was_connected = self.connected if was_connected: self.disconnect() self.disconnect_keys() controller = self.controller trait = controller.trait(old_key) controller.add_trait(key, trait) setattr(controller, key, getattr(controller, old_key)) controller.remove_trait(old_key) self._controls[key] = self._controls[old_key] del self._controls[old_key] if was_connected: self.connect() # reconnect label widget self.connect_keys() # self.update_controls() # not even needed if hasattr(self, 'main_controller_def'): main_control_class, controller_widget, control_name, frame = \ self.main_controller_def main_control_class.update_controller(controller_widget, control_name, frame) # self.update_controller() def _delete_key(self, key): """ Dict / open controller key deletion callback """ controller = self.controller trait = controller.trait(key) controller.remove_trait(key) self.update_controls() if hasattr(self, 'main_controller_def'): main_control_class, controller_widget, control_name, frame = \ self.main_controller_def main_control_class.update_controller(controller_widget, control_name, frame) # self.update_controller() def groups_vibility_changed(self): visible_groups = self.controller.visible_groups or set() for group, group_widget in six.iteritems(self._groups): if group in visible_groups: show = True else: show = False self._set_group_visibility(group, show) # # Class Methods # @classmethod def get_control_class(cls, trait): """ Find the control associated with the input trait. The mapping is defined in the global class parameter '_defined_controls'. Parameters ---------- cls: ControllerWidget (mandatory) a ControllerWidget class trait: Trait (mandatory) a trait item Returns ------- control_class: class the control class associated with the input trait. If no match has been found, return None """ # Initilaize the output variable control_class = None # Go through the trait string description: can have multiple element # when either trait is used # Todo:: we actualy need to create all the controls and let the user # choose which one he wants to fill. for trait_id in trait_ids(trait): # Recursive construction: consider only the top level trait_id = trait_id.split("_")[0] # Try to get the control class control_class = cls._defined_controls.get(trait_id) # Stop when we have a match if control_class is not None: break return control_class
class ProcessAttributes(Controller): ''' This is the base class for managing attributes for a process. ''' def __init__(self, process, schema_dict): super(ProcessAttributes, self).__init__() self._process = process self._schema_dict = schema_dict self.editable_attributes = OrderedDict() self.parameter_attributes = {} def set_parameter_attributes(self, parameter, schema, editable_attributes, fixed_attibute_values): if parameter in self.parameter_attributes: raise KeyError('Attributes already set for parameter %s' % parameter) if isinstance(editable_attributes, six.string_types) or isinstance(editable_attributes, EditableAttributes): editable_attributes = [editable_attributes] parameter_editable_attributes = [] for ea in editable_attributes: add_editable_attributes = False if isinstance(ea, six.string_types): key = ea ea = self.editable_attributes.get(key) if ea is None: ea = getattr(self._schema_dict[schema], key)() self.editable_attributes[key] = ea add_editable_attributes = True elif isinstance(ea, EditableAttributes): key = ea if key not in self.editable_attributes: self.editable_attributes[key] = ea add_editable_attributes = True else: raise TypeError('Invalid value for editable attributes: {0}'.format(ea)) parameter_editable_attributes.append(ea) if add_editable_attributes: for name, trait in six.iteritems(ea.user_traits()): self.add_trait(name, trait) f = SomaPartial(set_attribute, ea) self.on_trait_change(f, name) self.parameter_attributes[parameter] = (parameter_editable_attributes, fixed_attibute_values) def get_parameters_attributes(self): pa = {} for parameter, trait in six.iteritems(self._process.user_traits()): if trait.output: if hasattr(self._process, 'id'): process_name = self._process.id else: process_name = self._process.name attributes = { 'generated_by_process': process_name, 'generated_by_parameter': parameter} else: attributes = {} editable_fixed = self.parameter_attributes.get(parameter, ([], {})) editable_attributes, fixed_attibute_values = editable_fixed for ea in editable_attributes: for attribute in ea.user_traits(): value = getattr(ea, attribute) attributes[attribute] = value attributes.update(fixed_attibute_values) if attributes: pa[parameter] = attributes return pa