Source code for soma.qt_gui.controller_widget
# -*- coding: utf-8 -*-
#
# SOMA - Copyright (C) CEA, 2015
# Distributed under the terms of the CeCILL-B license, as published by
# the CEA-CNRS-INRIA. Refer to the LICENSE file or to
# http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
# for details.
#
# System import
from __future__ import absolute_import
from __future__ import print_function
import logging
import os
import sys
import six
# Define the logger
logger = logging.getLogger(__name__)
# Soma import
from soma.qt_gui import qt_backend
from soma.qt_gui.qt_backend import QtGui, QtCore, Qt
from soma.controller import trait_ids
from soma.qt_gui.timered_widgets import TimeredQLineEdit
from soma.functiontools import partial
from soma.sorted_dictionary import OrderedDict
from soma.functiontools import SomaPartial
import weakref
from soma.utils.weak_proxy import get_ref, weak_proxy
import traits.api as traits
import inspect
import sip
# Qt import
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
qt_backend.set_qt_backend(compatible_qt5=True)
# setup notification in Qt GUI thread
qt_backend.init_traitsui_handler()
[docs]class ScrollControllerWidget(Qt.QScrollArea):
""" Class that create a widget to set the controller parameters.
The widget is placed in a scroll area when large sets of parameters have
to be tuned.
"""
def __init__(self, controller, parent=None, name=None, live=False,
hide_labels=False, select_controls=None,
disable_controller_widget=False, override_control_types=None,
user_data=None, userlevel=0):
""" Method to initilaize the ScrollControllerWidget 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 synchronization instructions 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'.
disable_controller_widget: bool (optional, default False)
if True disable the controller widget.
override_control_types: dict (optional)
if given, this is a "factory" dict assigning new controller editor
types to some traits types.
user_data: any type (optional)
optional user data that can be accessed by individual control
editors
userlevel: int
the current user level: some traits may be marked with a non-zero userlevel, and will only be visible if the ControllerWidget userlevel is more than (or equal) the trait level.
"""
# Inheritance
super(ScrollControllerWidget, self).__init__(parent)
self.user_data = user_data
# Allow the application to resize the scroll area items
self.setWidgetResizable(True)
self.setSizePolicy(QtGui.QSizePolicy.Preferred,
QtGui.QSizePolicy.Preferred)
# Display a surounding box
self.setFrameShape(QtGui.QFrame.StyledPanel)
# Create the controller widget
self.controller_widget = ControllerWidget(
controller, parent, name, live, hide_labels, select_controls,
override_control_types=override_control_types, user_data=user_data,
userlevel=userlevel)
self.controller_widget.layout().setContentsMargins(2, 2, 2, 2)
self.controller_widget.setSizePolicy(QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Preferred)
# Enable / disabled the controller widget
self.controller_widget.setEnabled(not disable_controller_widget)
# Set the controller widget in the scroll area
self.setWidget(self.controller_widget)
def __del__(self):
# disconnect underlying ControllerWidget so that it can be deleted.
self.controller_widget.disconnect()
@property
def userlevel(self):
if hasattr(self, 'controller_widget'):
return self.controller_widget.userlevel
return 0
@userlevel.setter
def userlevel(self, value):
if hasattr(self, 'controller_widget'):
self.controller_widget.userlevel = value
def update_controller(self):
self.controller_widget.update_controller()
[docs]class DeletableLineEdit(QtGui.QWidget):
""" Close button + line editor, used for modifiable key labels
"""
userModification = QtCore.Signal()
buttonPressed = QtCore.Signal()
def __init__(self, text=None, parent=None):
super(DeletableLineEdit, self).__init__(parent)
layout = QtGui.QHBoxLayout()
self.setLayout(layout)
delete_button = QtGui.QToolButton()
layout.addWidget(delete_button)
# Set the tool icons
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(
_fromUtf8(":/soma_widgets_icons/delete")),
QtGui.QIcon.Normal, QtGui.QIcon.Off)
delete_button.setIcon(icon)
self.line_edit = TimeredQLineEdit(text)
layout.addWidget(self.line_edit)
self.line_edit.userModification.connect(self.userModification)
delete_button.pressed.connect(self.buttonPressed)
def text(self):
return self.line_edit.text()
def setText(self, text):
self.line_edit.setText(text)
[docs]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, override_control_types=None,
user_data=None, userlevel=0):
""" 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.
override_control_types: dict (optional)
if given, this is a "factory" dict assigning new controller editor
types to some traits types.
user_data: any type (optional)
optional user data that can be accessed by individual control
editors
userlevel: int
the current user level: some traits may be marked with a non-zero userlevel, and will only be visible if the ControllerWidget userlevel is more than (or equal) the trait level.
"""
# Inheritance
super(ControllerWidget, self).__init__(parent)
self._userlevel = userlevel
if override_control_types:
# copy dict
self._defined_controls = dict(self.__class__._defined_controls)
self._defined_controls.update(override_control_types)
QtCore.QResource.registerResource(os.path.join(os.path.dirname(
os.path.dirname(__file__)), 'resources', 'widgets_icons.rcc'))
# Class parameters
if controller is None:
raise ValueError('null controller')
if controller is traits.Undefined:
raise ValueError('undefined controller')
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
self.user_data = user_data
# 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
#
[docs] 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
@property
def userlevel(self):
return self._userlevel
@userlevel.setter
def userlevel(self, value):
self._userlevel = value
self.update_controls()
[docs] 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)
[docs] 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)
[docs] 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()
# notifications should be removed when the GUI is destroyed
# (on C++ side)
self.destroyed.connect(
partial(self.static_disconnect, self.controller,
self.update_controls, self.groups_vibility_changed,
self._controls))
# 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
[docs] @staticmethod
def static_disconnect(controller, update_controls, groups_vibility_changed,
controls, widget):
''' disconnect() cannot be called at the right time.
static_disconnect() is called via a Qt signat when the widget is
destroyed. But the python part is already destroyed then.
'''
controller.on_trait_change(
update_controls, "user_traits_changed", remove=True)
# if 'visible_groups' is a trait, connect it to groups
if controller.trait('visible_groups'):
controller.on_trait_change(
groups_vibility_changed, 'visible_groups', remove=True)
# Go through all the controller widget controls
for control_name, control_groups in six.iteritems(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
try:
control_class.disconnect(widget, control_name,
control_instance)
except Exception:
pass # probably something already deleted
[docs] 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
try:
control_class.disconnect(self, control_name,
control_instance)
except Exception:
pass # probably something already deleted
# 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 = {}
[docs] 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:
if not sip.isdeleted(label):
label.close()
label.deleteLater()
elif control_label:
if not sip.isdeleted(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 not trait.output)
elif self.select_controls == "outputs":
selected_traits = dict(
(trait_name, trait)
for trait_name, trait in six.iteritems(all_traits)
if trait.output)
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)
[docs] 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)
control_label[0].setContextMenuPolicy(
Qt.Qt.CustomContextMenu)
control_label[0].customContextMenuRequested.connect(
partial(self._label_context_menu,
weakref.ref(control_label[0]), trait_name))
# 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):
_, _, 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)
or (getattr(trait, 'userlevel', 0) is not None
and trait.userlevel > self.userlevel))
# Show/Hide the control and associated labels
for control_instance in control_instances:
control_instance.setVisible(not hide)
for label in control_labels:
if not sip.isdeleted(label): # sometimes happens...
label.setVisible(not hide)
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, user_data=self.user_data)
control_class.is_valid(control_instance)
# If the trait contains a description, insert a tool tip to the
# control instance
type_name = type(trait.trait_type).__name__.split('.')[-1]
#protected = ''
#if self.controller.is_parameter_protected(trait_name):
#protected = ' - modified'
tooltip = "<b>%s</b> (%s)" % (trait_name, type_name)
if trait.desc:
tooltip += "<b>:</b> " + trait.desc
if control_label:
if isinstance(control_label, tuple):
control_label[0].setToolTip(tooltip)
else:
control_label.setToolTip(tooltip)
else:
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):
print('group_name:', group_name, ', control:', control)
if isinstance(control[3], list):
control_labels += control[3]
else:
control_labels.append(control[3])
key = None
for control_label in control_labels:
if hasattr(control_label, 'text'):
key = str(control_label.text())
was_connected = self.connected
if was_connected:
self.disconnect()
self.disconnect_keys()
if key is None:
print('Modified dict key widget cannot be found', file=sys.stderr)
return
controller = self.controller
print('add trait on:', 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)
[docs] def get_control_class(self, trait):
""" Find the control associated with the input trait.
The mapping is defined in the global class parameter
'_defined_controls'.
Parameters
----------
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
todo = [trait]
done = set()
while todo:
trait = todo.pop(0)
done.add(trait)
trait_id = None
ids = trait_ids(trait)
if len(ids) >= 2:
trait_id = 'Compound'
elif len(ids) == 1:
trait_id = ids[0]
if trait_id is not None:
control_class = self._defined_controls.get(trait_id)
# Recursive construction: consider only the top level
while control_class is None:
split_id = trait_id.rsplit("_", 1)
if len(split_id) == 1:
break
trait_id = split_id[0]
# Try to get the control class
control_class = self._defined_controls.get(trait_id)
if control_class is not None:
break
# not found: look in superclasses
bases = trait.trait_type.__class__.__bases__ \
+ trait.__class__.__bases__
for base in bases:
if issubclass(base, traits.TraitType):
trait = self._instantiate_trait(base)
todo.append(trait)
if control_class is None:
# fallback to a label displaying "this value cannot be seen/edited"
control_class = self._defined_controls.get('unknown')
if inspect.isfunction(control_class):
# the function may instantiate a specialized type dynamically
control_class = control_class(trait)
return control_class
def _instantiate_trait(self, trait_type):
if issubclass(trait_type, traits.BaseRange):
return trait_type(0)
return trait_type()
def _label_context_menu(self, widget, control_name, pos):
menu = Qt.QMenu(control_name, None)
protect = menu.addAction('%s modified' % control_name)
protect.setCheckable(True)
protected = self.controller.is_parameter_protected(control_name)
protect.setChecked(protected)
menu.exec_(widget().mapToGlobal(pos))
if protect.isChecked() != protected:
self.controller.protect_parameter(control_name,
protect.isChecked())
from soma.qt_gui.controls import controls
# Fill the controller widget mapping between the string trait descriptions and
# the associated control classes
ControllerWidget._defined_controls.update(controls)