class GroupCti(AbstractCti):
    """ Read only config Tree Item that only stores None. It can be used to group CTIs
    """
    def __init__(self, nodeName, defaultData=None, expanded=True):
        """ Constructor. For the parameters see the AbstractCti constructor documentation.
        """
        super(GroupCti, self).__init__(nodeName, defaultData, expanded=expanded)


    def _enforceDataType(self, data):
        """ Passes the data as is; no conversion.
        """
        return data


    def _dataToString(self, data):
        """ Conversion function used to convert the (default)data to the display value.
            Returns an empty string.
        """
        return ""


    def createEditor(self, delegate, parent, _option):
        """ Creates a hidden widget so that only the reset button is visible during editing.
            :type option: QStyleOptionViewItem
        """
        return GroupCtiEditor(self, delegate, parent=parent) class AbstractCti(BaseTreeItem):
    """ TreeItem for use in a ConfigTreeModel. (CTI = Config Tree Item)

        Abstract class. You must implement the _enforceDataType method that ensures the the data
        is stored internally in the correct type. Also, implement the createEditor method if you
        want you CTI to be editable (which is usually the case). If your CTI is not editable, make
        sure that valueColumnItemFlags does not return Qt.ItemIsEditable to prevent createEditor
        from being called by the delegate.

        Just like the BaseTreeItem every node has a name and a full path that is a slash-separated
        string of the full path leading from the root node to the current node. For instance, the
        path may be '/scale/x', the nodeName in that case is 'x'.

        CTIs are used to store a certain configuration value. It can be queried by the configValue
        property. The type of this value differs between descendants of AbstractCti, but a sub class
        should always return the same type. For example, the ColorCti.value always returns a
        QColor object. The displayValue returns the string representation for use in the tables;
        by default this returns: str(self.value)

        The underlying data is usually stored in that type as well but this is not necessarily so.
        A ChoiceCti, which represents a combo box, stores a list of choices and an index that is
        the actual choice made by the user. ChoiceCti.data contains the index while
        ChoiceCti.configValue returns: choices[index]. Note that the constructor expects the data
        as input parameter. The constructor calls the _enforceDataType method to convert the data
        to the correct type.

        The purpose of the defaultData is to reset a config data to its initial value when the
        user clicks on the reset button during editing.
    """
    def __init__(self, nodeName, defaultData, enabled=True, expanded=True):
        """ Constructor
            :param nodeName: name of this node (used to construct the node path).
            :param defaultData: default data to which the data can be reset by the reset button.
            :param enabled: True if the item is enabled
            :param expanded: True if the item is expanded
        """
        super(AbstractCti, self).__init__(nodeName=nodeName)

        self._defaultData = self._enforceDataType(defaultData)
        self._data = self.defaultData
        self._enabled = enabled
        self._expanded = expanded
        self._blockRefresh = False


    def finalize(self):
        """ Can be used to cleanup resources. Should be called explicitly.
            Recursively calls the _closeRecources method on all children and then on itself.
            Descendants should override _closeRecources to do the actual closing of resources.
        """
        for child in self.childItems:
            child.finalize()

        # In contrast to the BaseRti there is no close() method, _closeResources is directly called
        # because it only is needed when the config is deleted and catching exceptions makes no
        # sense.
        self._closeResources()


    def _closeResources(self):
        """ Can be overridden to close the underlying resources or disconnect signals.
            The default implementation does nothing.
            Is called by self.finalize when the cti is deleted. There is no corresponding
            _openResources; all resources are claimed in the constructor.
        """
        pass


    @property
    def debugInfo(self):
        """ Returns the string with debugging information
        """
        return ""


    def _dataToString(self, data):
        """ Conversion function used to convert the (default)data to the display value.
        """
        return str(data)

    @property
    def displayValue(self):
        """ Returns the string representation of data for use in the tree view.
            If a descendant overrides this, it should probably also override displayDefaultValue.
        """
        return self._dataToString(self.data)

    @property
    def displayDefaultValue(self):
        """ Returns the string representation of default data for use in the tree view.
        """
        return self._dataToString(self.defaultData)

    @property
    def configValue(self):
        """ Returns the configuration value of this item.
            By default this is the same as the underlying data but it can be overridden,
        """
        return self.data

    @property
    def data(self):
        """ Returns the data of this item.
        """
        return self._data

    @data.setter
    def data(self, data):
        """ Sets the data of this item.
            Does type conversion to ensure data is always of the correct type.
        """
        # Descendants should convert the data to the desired type here
        self._data = self._enforceDataType(data)

    @property
    def defaultData(self):
        """ Returns the default data of this item.
        """
        return self._defaultData

    @defaultData.setter
    def defaultData(self, defaultData):
        """ Sets the data of this item.
            Does type conversion to ensure default data is always of the correct type.
        """
        # Descendants should convert the data to the desired type here
        self._defaultData = self._enforceDataType(defaultData)

    def _enforceDataType(self, value):
        """ Converts data to the type of this CTI.
            Used by the setter to ensure that the data and defaultData have the correct type
        """
        raise NotImplementedError()

    @property
    def checkState(self):
        """ Returns how the checkbox for this cti should look like. Returns None for no checkbox.
            :rtype: Qt.CheckState or None
        """
        return None

    @checkState.setter
    def checkState(self, checkState):
        """ Allows the data to be set given a Qt.CheckState.
            Abstract method; you must override it if valueColumnItemFlags returns
            Qt.ItemIsUserCheckable
        """
        raise NotImplementedError()

    @property
    def enabled(self):
        """ Returns the enabled flag which determines if the user can interact with this item.
        """
        return self._enabled

    @enabled.setter
    def enabled(self, enabled):
        """ Sets the enabled flag which determines if the user can interact with this item.
        """
        #logger.debug("Setting enabled = {:5} for {}".format(enabled, self))
        self._enabled = enabled


    @property
    def expanded(self):
        """ Returns the expanded flag which keeps
        """
        return self._expanded

    @expanded.setter
    def expanded(self, expanded):
        """ Keeps track if the config item is expanded.
            Needed to keep state between changing inspectors.
            Note, this can only be used as an initial expanded value but does not update the tree
            when it's constructed. Use ConfigTree.expand for that.
        """
        #logger.debug("Setting expanded = {!r:5} for {}".format(expanded, self))
        self._expanded = expanded


    def enableBranch(self, enabled):
        """ Sets the enabled member to True or False for a node and all it's children
        """
        self.enabled = enabled
        for child in self.childItems:
            child.enableBranch(enabled)


    def resetToDefault(self, resetChildren=True):
        """ Resets the data to the default data. By default the children will be reset as well
        """
        self.data = self.defaultData
        if resetChildren:
            for child in self.childItems:
                child.resetToDefault(resetChildren=True)


    def getRefreshBlocked(self):
        """ If True the calls to _refreshNodeFromTarget are blocked.
            This calls self.model.getRefreshBlocked(), so this value is always equal for all
            config tree items in the model
        """
        return self.model.getRefreshBlocked()


    def refreshFromTarget(self, level=0):
        """ Refreshes the configuration tree from the target it monitors (if present).
            Recursively call _refreshNodeFromTarget for itself and all children. Subclasses should
            typically override _refreshNodeFromTarget instead of this function.
            During updateTarget's execution refreshFromTarget is blocked to avoid loops.
        """
        if self.getRefreshBlocked():
            logger.debug("_refreshNodeFromTarget blocked")
            return

        if False and level == 0:
            logger.debug("refreshFromTarget: {}".format(self.nodePath))

        self._refreshNodeFromTarget()
        for child in self.childItems:
            child.refreshFromTarget(level=level + 1)


    def _refreshNodeFromTarget(self):
        """ Refreshes the configuration from the target it monitors (if present).
            The default implementation does nothing; subclasses can override it.
            During updateTarget's execution refreshFromTarget is blocked to avoid loops.
            Typically called from inspector.drawTarget
        """
        pass


    def updateTarget(self, level=0):
        """ Applies the configuration to the target it monitors (if present).
            Recursively call _updateTargetFromNode for itself and all children. Subclasses should
            typically override _updateTargetFromNode instead of this function.

            :param level: the level of recursion.
        """
        #if level == 0:
        #    logger.debug("updateTarget: {}".format(self.nodePath))

        self._updateTargetFromNode()
        for child in self.childItems:
            child.updateTarget(level = level + 1)


    def _updateTargetFromNode(self):
        """ Applies the configuration to the target it monitors (if present).
            The default implementation does nothing; subclasses can override it.
            During updateTarget's execution refreshFromTarget is blocked to avoid loops.
        """
        pass


    def logBranch(self, indent=0, level=logging.DEBUG):
        """ Logs the item and all descendants, one line per child
        """
        line = "{} ({})".format(self, self.configValue)
        if 0:
            print(indent * "    " + line)
        else:
            logger.log(level, indent * "    " + line)
        for childItems in self.childItems:
            childItems.logBranch(indent + 1, level=level)


    #################
    # serialization #
    #################


    def _nodeMarshall(self):
        """ Returns the non-recursive marshalled value of this CTI. Is called by marshall()
        """
        return self.data


    def marshall(self):
        """ Recursively retrieves values as a dictionary to be used for persistence.

            Typically descendants should override _nodeMarshall instead of this function.
        """
        nodeMar = self._nodeMarshall()
        assert not isinstance(nodeMar, dict), "Node marshall returns dict: {}".format(self)

        childDct = {}
        for childCti in self.childItems:
            childDct[childCti.nodeName] = childCti.marshall()

        if nodeMar is not None and childDct:
            res = {'_data': nodeMar, '_sub': childDct}
            return res
        elif childDct:
            return childDct
        else:
            return nodeMar


    def _nodeUnmarshall(self, data):
        """ Initializes itself non-recursively from data. Is called by unmarshall()
        """
        self.data = data


    def unmarshall(self, cfg):
        """ Initializes itself recursively from a config dict form the persistent settings.

            Typically descendants should override _nodeMarshall instead of this function.
        """
        if isinstance(cfg, dict):
            if '_data' in cfg and '_sub' in cfg:
                self._nodeUnmarshall(cfg['_data'])
                self.unmarshall(cfg['_sub'])
            else:
                for childName, childCfg in cfg.items():
                    try:
                        childCti = self.childByNodeName(childName)
                    except IndexError as _ex:
                        logger.warning("Unable to set values for: {}".format(childName))
                    else:
                        childCti.unmarshall(childCfg)
        else:
            self._nodeUnmarshall(cfg)



    # def marshall(self):
    #     """ Recursively retrieves values as a dictionary to be used for persistence.
    #
    #         Typically descendants should override _nodeMarshall instead of this function.
    #     """
    #
    #     if self.childItems:
    #         dct = {'_data': self._nodeMarshall()}
    #         dct['_sub'] = childDct = {}
    #         for childCti in self.childItems:
    #             childRes = childCti.marshall()
    #             childDct[childCti.nodeName] = childRes
    #     else:
    #         return self._nodeMarshall()
    #
    #     return dct

    ########################
    # Editor look and feel #
    ########################

    @property
    def valueColumnItemFlags(self):
        """ Returns Qt.ItemFlag enum that will be used for the value column in the config tree.
            These flags determine how the user can interact with the value column (e.g. can edit).

            Note that the ConfigTreeModel may override them: it will add the Qt.ItemIsEnabled and
            Qt.ItemIsSelectable to the flags.

            The base implementation of valueColumnItemFlags returns Qt.ItemIsEditable. Make sure to
            implement the createEditor abstract method if Qt.ItemIsEditable is included in the
            result.
        """
        return Qt.ItemIsEditable


    def createEditor(self, delegate, parent, option):
        """ Creates an editor (QWidget) for editing.
            It's parent will be set by the ConfigItemDelegate class that calls this method.

            :param delegate: the delegate that called this function
            :type  delegate: ConfigItemDelegate
            :param parent: The parent widget for the editor
            :type  parent: QWidget
            :param option: describes the parameters used to draw an item in a view widget.
            :type  option: QStyleOptionViewItem
        """
        raise NotImplementedError("createEditor not implemented while Qt.ItemIsEditable is set") class AbstractCtiEditor(QtWidgets.QWidget):
    """ An editor for use in the ConfigTreeView (CTI).

        It consists of a horizontal collection of child widgets, the last of which is a reset
        button which will reset the config data to its default when clicked.

        You must implemented setData and getData, which pass the data from the
        QConfigItemDelegate to the editor and back.
    """
    def __init__(self, cti, delegate, subEditors=None, parent=None):
        """ Wraps the child widgets in a horizontal layout and appends a reset button.

            Maintains a reference to the ConfigTreeItem (cti) and to delegate, this last reference
            is so that we can command the delegate to commit and close the editor.

            The subEditors must be a list of QWidgets. Note that the sub editors do not yet have
            to be initialized with editor data since setData will be called by the delegate
            after construction. There it can be taken care of.
        """
        super(AbstractCtiEditor, self).__init__(parent=parent)

        # Prevent underlying table cell from being visible if the editor doesn't fill the cell
        self.setAutoFillBackground(True)

        self._subEditors = []
        self.delegate = delegate
        self.cti = cti

        # From the QAbstractItemDelegate.createEditor docs: The returned editor widget should have
        # Qt.StrongFocus; otherwise, QMouseEvents received by the widget will propagate to the view.
        # The view's background will shine through unless the editor paints its own background
        # (e.g., with setAutoFillBackground()).
        self.setFocusPolicy(Qt.StrongFocus)

        self.hBoxLayout = QtWidgets.QHBoxLayout()
        self.hBoxLayout.setContentsMargins(0, 0, 0, 0)
        self.hBoxLayout.setSpacing(0)
        self.setLayout(self.hBoxLayout)

        self.resetButton = QtWidgets.QToolButton()
        self.resetButton.setText("Reset")
        self.resetButton.setToolTip("Reset to default value.")
        self.resetButton.setIcon(QtGui.QIcon(os.path.join(icons_directory(), 'reset-l.svg')))
        self.resetButton.setFocusPolicy(Qt.NoFocus)
        self.resetButton.clicked.connect(self.resetEditorValue)
        self.hBoxLayout.addWidget(self.resetButton, alignment=Qt.AlignRight)

        self.cti.model.sigItemChanged.connect(self.modelItemChanged)

        for subEditor in (subEditors if subEditors is not None else []):
            self.addSubEditor(subEditor)


    def finalize(self):
        """ Called at clean up, when the editor is closed. Can be used to disconnect signals.
            This is often called after the client (e.g. the inspector) is updated. If you want to
            take action before the update, override prepareCommit instead.
            Be sure to call the finalize of the super class if you override this function.
        """
        for subEditor in self._subEditors:
            self.removeSubEditor(subEditor)

        self.cti.model.sigItemChanged.disconnect(self.modelItemChanged)
        self.resetButton.clicked.disconnect(self.resetEditorValue)
        self.cti = None # just to make sure it's not used again.
        self.delegate = None


    @QtSlot()
    def prepareCommit(self):
        """ Called just before the data is committed.
            Can be used to take action before the client (e.g. the inspector) is updated.
        """
        logger.debug("Committing data from: {}".format(self.cti.nodePath))


    def addSubEditor(self, subEditor, isFocusProxy=False):
        """ Adds a sub editor to the layout (at the right but before the reset button)
            Will add the necessary event filter to handle tabs and sets the strong focus so
            that events will not propagate to the tree view.

            If isFocusProxy is True the sub editor will be the focus proxy of the CTI.
        """
        self.hBoxLayout.insertWidget(len(self._subEditors), subEditor)
        self._subEditors.append(subEditor)

        subEditor.installEventFilter(self)
        subEditor.setFocusPolicy(Qt.StrongFocus)

        if isFocusProxy:
            self.setFocusProxy(subEditor)

        return subEditor


    def removeSubEditor(self, subEditor):
        """ Removes the subEditor from the layout and removes the event filter.
        """
        if subEditor is self.focusProxy():
            self.setFocusProxy(None)

        subEditor.removeEventFilter(self)
        self._subEditors.remove(subEditor)
        self.hBoxLayout.removeWidget(subEditor)


    def setData(self, value):
        """ Provides the editor widget with a data to manipulate.
            Value originates from the ConfigTreeModel.data(role=QEditRole).
        """
        raise NotImplementedError()


    def getData(self):
        """ Gets data from the editor widget.
            Should return a value that can be set into the ConfigTreeModel with the QEditRole.
        """
        raise NotImplementedError()


    def eventFilter(self, watchedObject, event):
        """ Calls commitAndClose when the tab and back-tab are pressed.
            This is necessary because, normally the event filter of QStyledItemDelegate does this
            for us. However, that event filter works on this object, not on the sub editor.
        """
        if event.type() == QtCore.QEvent.KeyPress:
            key = event.key()
            if key in (Qt.Key_Tab, Qt.Key_Backtab):
                self.commitAndClose()
                return True
            else:
                return False

        return super(AbstractCtiEditor, self).eventFilter(watchedObject, event)


    @QtSlot(BaseTreeItem)
    def modelItemChanged(self, cti):
        """ Called when the an Config Tree Item (CTI) in the model has changed.

            If the CTI is a different one than the CTI that belongs to this editor, the editor
            is closed. This can happen if the user has checked a checkbox. Qt does not close other
            editors in the view in that case, so this is why we do it here.

            If the cti parameter is the CTI belonging to this editor, nothing is done. We don't
            close the editor because the user may want to continue editing.
        """
        if cti is not self.cti:
            logger.debug("Another config tree item has changed: {}. Closing editor for {}"
                         .format(cti, self.cti))
            self.delegate.closeEditor.emit(self, QtWidgets.QAbstractItemDelegate.NoHint) # CLOSES SELF!
        else:
            logger.debug("Cti of this editor has changed: {}".format(cti))


    @QtSlot()
    def commitAndClose(self):
        """ Commits the data of the sub editor and instructs the delegate to close this ctiEditor.

            The delegate will emit the closeEditor signal which is connected to the closeEditor
            method of the ConfigTreeView class. This, in turn will, call the finalize method of
            this object so that signals can be disconnected and resources can be freed. This is
            complicated but I don't see a simpler solution.
        """
        if self.delegate:
            self.delegate.commitData.emit(self)
            self.delegate.closeEditor.emit(self, QtWidgets.QAbstractItemDelegate.NoHint) # CLOSES SELF!
        else:
            # QAbstractItemView.closeEditor is sometimes called directly, without the
            # QAbstractItemDelegate.closeEditor signal begin emitted, e.g when the currentItem
            # changes. Therefore the commitAndClose method can be called twice, if we call it
            # explicitly as well (e.g. in FontCtiEditor.execFontDialog(). We guard against this.
            logger.debug("AbstractCtiEditor.commitAndClose: editor already closed (ignored).")


    @QtSlot(bool)
    def resetEditorValue(self, checked=False):
        """ Resets the editor to the default value. Also resets the children.
        """
        # Block all signals to prevent duplicate inspector updates.
        # No need to restore, the editors will be deleted after the reset.
        for subEditor in self._subEditors:
            subEditor.blockSignals(True)

        self.cti.resetToDefault(resetChildren=True)
        # This will commit the children as well.
        self.setData(self.cti.defaultData)
        self.commitAndClose()


    def paintEvent(self, event):
        """ Reimplementation of paintEvent to allow for style sheets
            See: http://qt-project.org/wiki/How_to_Change_the_Background_Color_of_QWidget
        """
        opt = QtWidgets.QStyleOption()
        opt.initFrom(self)
        painter = QtGui.QPainter(self)
        self.style().drawPrimitive(QtWidgets.QStyle.PE_Widget, opt, painter, self)
        painter.end() class  BoolCti(AbstractCti):
    """ Config Tree Item to store an boolean. It can be edited using a check box
    """
    def __init__(self, nodeName, defaultData, expanded=True,
                 childrenDisabledValue=False):
        """ Constructor. For the parameters see the AbstractCti constructor documentation.

            If the data is equal to childrenDisabledValue, the node's children will be
            disabled.

            (The data will always be a bool so if childrenDisabledValue == None,
            the children will never be disabled.)
        """
        super(BoolCti, self).__init__(nodeName, defaultData, expanded=expanded)
        self.childrenDisabledValue = childrenDisabledValue


    @property
    def debugInfo(self):
        """ Returns a string with debugging information
        """
        return "{} (enabled={}, childDisabledVal={})".format(self.configValue, self.enabled,
                                                             self.childrenDisabledValue)


    def _enforceDataType(self, data):
        """ Converts to bool so that self.data always is of that type.
        """
        return bool(data)


    @property
    def data(self):
        """ Returns the data of this item.
        """
        return self._data


    @data.setter
    def data(self, data):
        """ Sets the data of this item.
            Does type conversion to ensure data is always of the correct type.
        """
        # Descendants should convert the data to the desired type here
        self._data = self._enforceDataType(data)

        #logger.debug("BoolCti.setData: {} for {}".format(data, self))
        enabled = self.enabled
        self.enableBranch(enabled and self.data != self.childrenDisabledValue)
        self.enabled = enabled


    @property
    def displayValue(self):
        """ Returns empty string since a checkbox will displayed in the value-column instead.
        """
        return ""


    def insertChild(self, childItem, position=None):
        """ Inserts a child item to the current item.

            Overridden from BaseTreeItem.
        """
        childItem = super(BoolCti, self).insertChild(childItem, position=None)

        enableChildren = self.enabled and self.data != self.childrenDisabledValue
        #logger.debug("BoolCti.insertChild: {} enableChildren={}".format(childItem, enableChildren))
        childItem.enableBranch(enableChildren)
        childItem.enabled = enableChildren
        return childItem


    @property
    def valueColumnItemFlags(self):
        """ Returns Qt.ItemIsUserCheckable so that a check box will be drawn in the config tree.
            Note that the flags include Qt.ItemIsEditable; this makes the reset button will appear.
        """
        return Qt.ItemIsUserCheckable | Qt.ItemIsEditable


    @property
    def checkState(self):
        """ Returns Qt.Checked or Qt.Unchecked.
        """
        if self.data is True:
            return Qt.Checked
        elif self.data is False:
            return Qt.Unchecked
        else:
            raise ValueError("Unexpected data: {!r}".format(self.data))


    @checkState.setter
    def checkState(self, checkState):
        """ Sets the data to given a Qt.CheckState (Qt.Checked or Qt.Unchecked).
        """
        if checkState == Qt.Checked:
            logger.debug("BoolCti.checkState setting to True")
            self.data = True
        elif checkState == Qt.Unchecked:
            logger.debug("BoolCti.checkState setting to False")
            self.data = False
        else:
            raise ValueError("Unexpected check state: {!r}".format(checkState))


    def enableBranch(self, enabled):
        """ Sets the enabled member to True or False for a node and all it's children
        """
        self.enabled = enabled

        # Disabled children and further descendants
        enabled = enabled and self.data != self.childrenDisabledValue

        for child in self.childItems:
            child.enableBranch(enabled)


    def createEditor(self, delegate, parent, _option):
        """ Creates a hidden widget so that only the reset button is visible during editing.
            :type option: QStyleOptionViewItem
        """
        return GroupCtiEditor(self, delegate, parent=parent) class BoolGroupCti(AbstractCti):
    """ Config Tree Item to store a nullable boolean (True, False or None).

        It can be edited using a tri-state check box. However, the user can not set the value
        to partially checked directly, it will only be partially checked if some of its children
        are checked and others are not! This is a bug/behavior of Qt that won't be fixed.
        See: https://bugreports.qt.io/browse/QTBUG-7674 and
             http://comments.gmane.org/gmane.comp.lib.qt.general/925
    """
    def __init__(self, nodeName, defaultData=None, expanded=True):
        """ Constructor. For the parameters see the AbstractCti constructor documentation.
        """
        super(BoolGroupCti, self).__init__(nodeName, defaultData, expanded=expanded)


    def _enforceDataType(self, data):
        """ Converts to bool so that self.data always is of that type.
        """
        return None if data is None else bool(data)


    @property
    def displayValue(self):
        """ Returns empty string since a checkbox will displayed in the value column instead.
        """
        return ""


    @property
    def valueColumnItemFlags(self):
        """ Returns Qt.ItemIsUserCheckable so that a check box will be drawn in the config tree.
            Note that the flags include Qt.ItemIsEditable; this makes the reset button will appear.
        """
        return Qt.ItemIsTristate | Qt.ItemIsUserCheckable | Qt.ItemIsEditable


    @property
    def checkState(self):
        """ Returns Qt.Checked or Qt.Unchecked if all children are checked or unchecked, else
            returns Qt.PartiallyChecked
        """
        #commonData = self.childItems[0].data if self.childItems else Qt.PartiallyChecked
        commonData = None

        for child in self.childItems:
            if isinstance(child, BoolCti):
                if commonData is not None and child.data != commonData:
                    return Qt.PartiallyChecked
                commonData = child.data

        if commonData is True:
            return Qt.Checked
        elif commonData is False:
            return Qt.Unchecked
        else:
            raise AssertionError("Please report this bug: commonData: {!r}".format(commonData))


    @checkState.setter
    def checkState(self, checkState):
        """ Sets the data to given a Qt.CheckState (Qt.Checked or Qt.Unchecked).
        """
        logger.debug("checkState setter: {}".format(checkState))
        if checkState == Qt.Checked:
            commonData = True
        elif checkState == Qt.Unchecked:
            commonData = False
        elif checkState == Qt.PartiallyChecked:
            commonData = None
            # This never occurs, see remarks above in the classes' docstring
            assert False, "This never happens. Please report if it does."
        else:
            raise ValueError("Unexpected check state: {!r}".format(checkState))

        for child in self.childItems:
            if isinstance(child, BoolCti):
                child.data = commonData


    def createEditor(self, delegate, parent, _option):
        """ Creates a hidden widget so that only the reset button is visible during editing.
            :type option: QStyleOptionViewItem
        """
        return GroupCtiEditor(self, delegate, parent=parent) class ChoiceCti(AbstractCti):
    """ Config Tree Item to store a choice between strings.

        A QCombobox will pop-up if the user clicks on the cell to edit it.
    """
    def __init__(self, nodeName, defaultData=0, configValues=None, displayValues=None,
                 editable=False, insertPolicy=QtWidgets.QComboBox.InsertAtBottom,
                 completer=NOT_SPECIFIED):
        """ Constructor.

            The defaultData is an integers that is used to store the currentIndex.
            The displayValues parameter must be a list of strings, which will be displayed in the
            combo box. The configValues should be a list of the same size with the configValues
            that each 'choice' represents, e.g. choice 'dashed' maps to configValue Qt.DashLine.
            If displayValues is None, the configValues are used as displayValues.

            The defaultData can be set to a negative value, e.g. use -1 to select the last item
            by default. However, it will be converted to a positive value in the constructor.

            :param defaultData: the default index in the combobox that is used for editing
            :param configValues: list of configValue items to populate the combobox with
            :param displayValues: list of possible displayValue items.
                If None the config values are used as display values as well.
            :param editable: if True the combobox will be editable
            :param insertPolicy: determines where user-inserted items should appear in the combobox
                must be of type QtWidgets.QComboBox.InsertPolicy
            :param completer: a QCompleter that will be used by the combobox for auto-completion.
                If NotSpecified a default completer is used, i.e. case-insensitive auto-completion.
                Use None, 0 or False to disable auto-completion.

            For the  parameters see the AbstractCti constructor documentation.

        """
        check_class(insertPolicy, QtWidgets.QComboBox.InsertPolicy)
        if completer: # completer may be False
            check_class(completer, QtWidgets.QCompleter)

        self.editable = editable
        self.insertPolicy = insertPolicy
        self.completer = completer

        self._configValues = [] if configValues is None else configValues
        if displayValues is None:
            self._displayValues = copy.copy(self._configValues)
        else:
            assert not editable, "No separate displayValues may be set if the CTI is editable"
            self._displayValues = displayValues

        assert len(self._configValues) == len(self._displayValues),\
            "If set, configValues must have the same length as displayValues."

        self._defaultConfigValues = copy.copy(self._configValues)

        # Set after self._displayValues are defined. The parent constructor call _enforceDataType
        super(ChoiceCti, self).__init__(nodeName, defaultData)


    def _enforceDataType(self, data):
        """ Converts to int so that this CTI always stores that type.

            The data be set to a negative value, e.g. use -1 to select the last item
            by default. However, it will be converted to a positive by this method.
        """
        idx = int(data)
        if idx < 0:
            idx += len(self._displayValues)
        assert 0 <= idx < len(self._displayValues), \
            "Index should be >= 0 and < {}. Got {}".format(len(self._displayValues), idx)
        return idx


    @property
    def configValue(self):
        """ The currently selected configValue
        """
        return self._configValues[self.data]


    @property
    def iterConfigValues(self):
        """ Returns a generator expression to iterate over al config values
        """
        return (value for value in self._configValues)


    @property
    def iterDisplayValues(self):
        """ Returns a generator expression to iterate over al display values
        """
        return (value for value in self._displayValues)


    def _dataToString(self, data):
        """ Conversion function used to convert the (default)data to the display value.
        """
        choices = self._displayValues
        return str(choices[data])


    def _nodeMarshall(self):
        """ Returns the non-recursive marshalled value of this CTI. Is called by marshall()
        """
        if self.editable:
            return [self.data, self._configValues]
        else:
            return self.data


    def _nodeUnmarshall(self, data):
        """ Initializes itself non-recursively from data. Is called by unmarshall()
        """
        if self.editable:
            curIdx, configValues = data
            self._configValues = configValues
            self._displayValues = configValues
            self.data = curIdx
        else:
            self.data = data


    @property
    def debugInfo(self):
        """ Returns the string with debugging information
        """
        return repr(self._displayValues)


    def createEditor(self, delegate, parent, option):
        """ Creates a ChoiceCtiEditor.
            For the parameters see the AbstractCti constructor documentation.
        """
        return ChoiceCtiEditor(self, delegate, parent=parent)


    def insertValue(self, pos, configValue, displayValue=None):
        """ Will insert the configValue in the configValues and the displayValue in the
            displayValues list.
            If displayValue is None, the configValue is set in the displayValues as well
        """
        self._configValues.insert(pos, configValue)
        self._displayValues.insert(pos, displayValue if displayValue is not None else configValue)


    def removeValue(self, configValue):
        """ Will remove the configValue and the corresponding displayValue from the lists.
        """
        self.removeValueByIndex(self._configValues.index(configValue))


    def removeValueByIndex(self, index):
        """ Will remove the configValue and the corresponding displayValue given the index.
        """
        del self._configValues[index]
        del self._displayValues[index] class IntCti(AbstractCti):
    """ Config Tree Item to store an integer. It can be edited using a QSpinBox.
    """
    def __init__(self, nodeName, defaultData=0,
                 minValue = None, maxValue = None, stepSize = 1,
                 prefix='', suffix='', specialValueText=None):
        """ Constructor.

            :param minValue: minimum data allowed when editing (use None for no minimum)
            :param maxValue: maximum data allowed when editing (use None for no maximum)
            :param stepSize: steps between values when editing (default = 1)
            :param prefix: prepended to the start of the displayed value in the spinbox
            :param suffix: prepended to the end of the displayed value in the spinbox
            :param specialValueText: if set, this text will be displayed when the the minValue
                is selected. It is up to the cti user to interpret this as a special case.

            For the (other) parameters see the AbstractCti constructor documentation.
        """
        super(IntCti, self).__init__(nodeName, defaultData)

        self.minValue = minValue
        self.maxValue = maxValue
        self.stepSize = stepSize
        self.prefix = prefix
        self.suffix = suffix
        self.specialValueText = specialValueText


    def _enforceDataType(self, data):
        """ Converts to int so that this CTI always stores that type.
        """
        return int(data)


    def _dataToString(self, data):
        """ Conversion function used to convert the (default)data to the display value.
        """
        if self.specialValueText is not None and data == self.minValue:
            return self.specialValueText
        else:
            return "{}{}{}".format(self.prefix, data, self.suffix)


    @property
    def debugInfo(self):
        """ Returns the string with debugging information
        """
        return ("enabled = {}, min = {}, max = {}, step = {}, specVal = {}"
                .format(self.enabled, self.minValue, self.maxValue, self.stepSize, self.specialValueText))


    def createEditor(self, delegate, parent, option):
        """ Creates a IntCtiEditor.
            For the parameters see the AbstractCti constructor documentation.
        """
        return IntCtiEditor(self, delegate, parent=parent) class SnFloatCti(AbstractCti):
    """ Config Tree Item to store a floating point number in Scientific Notation, e.g. 3.14e01
    """
    def __init__(self, nodeName, defaultData=0,
                 minValue = None, maxValue = None, precision = 2,
                 prefix='', suffix='', specialValueText=None):
        """ Constructor.

            :param minValue: minimum data allowed when editing (use None for no minimum)
            :param maxValue: maximum data allowed when editing (use None for no maximum)
            :param decimals: Sets how many decimals the spin box will use for displaying.
                Note: The maximum, minimum and value might change as a result of changing this.
            :param prefix: prepended to the start of the displayed value in the spinbox
            :param suffix: prepended to the end of the displayed value in the spinbox
            :param specialValueText: if set, this text will be displayed when the the minValue
                is selected. It is up to the cti user to interpret this as a special case.

            For the (other) parameters see the AbstractCti constructor documentation.
        """
        super(SnFloatCti, self).__init__(nodeName, defaultData)

        self._precision = precision
        self.minValue = minValue
        self.maxValue = maxValue
        self.prefix = prefix
        self.suffix = suffix
        self.specialValueText = specialValueText


    @property
    def precision(self):
        """ Returns precision used in the string formatting. I.e. {:{width}.{precision}g)}
            When using the :g format, the following holds, decimals = precision - 1. Therefore
            this property returns self.decimals - 1.
            See https://docs.python.org/2/library/string.html#format-specification-mini-language
        """
        return self._precision


    @precision.setter
    def precision(self, precision):
        """ Returns precision used in the string formatting. I.e. {:{width}.{precision}g)}
            When using the :g format, the following holds, decimals = precision - 1. Therefore
            this property returns self.decimals - 1.
            See https://docs.python.org/2/library/string.html#format-specification-mini-language
        """
        self._precision = int(precision)


    def _enforceDataType(self, data):
        """ Converts to float so that this CTI always stores that type.

            Replaces infinite with the maximum respresentable float.
            Raises a ValueError if data is a NaN.
        """
        value = float(data)
        if math.isnan(value):
            raise ValueError("SnFloatCti can't store NaNs")

        if math.isinf(value):
            if value > 0:
                logger.warning("Replacing inf by the largest representable float")
                value = sys.float_info.max
            else:
                logger.warning("Replacing -inf by the smallest representable float")
                value = -sys.float_info.max

        return value


    def _dataToString(self, data):
        """ Conversion function used to convert the (default)data to the display value.
        """
        if self.specialValueText is not None and data == self.minValue:
            return self.specialValueText
        else:
            return "{}{:.{}g}{}".format(self.prefix, data, self.precision, self.suffix)


    @property
    def debugInfo(self):
        """ Returns the string with debugging information
        """
        return ("enabled = {}, min = {}, max = {}, precision = {}, specVal = {}"
                .format(self.enabled, self.minValue, self.maxValue,
                        self.precision, self.specialValueText))


    def createEditor(self, delegate, parent, option):
        """ Creates a FloatCtiEditor.
            For the parameters see the AbstractCti constructor documentation.
        """
        return SnFloatCtiEditor(self, delegate, self.precision, parent=parent) class FloatCti(AbstractCti):
    """ Config Tree Item to store a floating point number. It can be edited using a QDoubleSpinBox.
    """
    def __init__(self, nodeName, defaultData=0,
                 minValue = None, maxValue = None, stepSize = 1.0, decimals = 2,
                 prefix='', suffix='', specialValueText=None):
        """ Constructor.

            :param minValue: minimum data allowed when editing (use None for no minimum)
            :param maxValue: maximum data allowed when editing (use None for no maximum)
            :param stepSize: steps between values when editing (default = 1)
            :param decimals: Sets how many decimals the spin box will use for displaying.
                Note: The maximum, minimum and value might change as a result of changing this.
            :param prefix: prepended to the start of the displayed value in the spinbox
            :param suffix: prepended to the end of the displayed value in the spinbox
            :param specialValueText: if set, this text will be displayed when the the minValue
                is selected. It is up to the cti user to interpret this as a special case.

            For the (other) parameters see the AbstractCti constructor documentation.
        """
        super(FloatCti, self).__init__(nodeName, defaultData)

        self.decimals = decimals
        self.minValue = minValue
        self.maxValue = maxValue
        self.stepSize = stepSize
        self.prefix = prefix
        self.suffix = suffix
        self.specialValueText = specialValueText


    def _enforceDataType(self, data):
        """ Converts to float so that this CTI always stores that type.

            Replaces infinite with the maximum respresentable float.
            Raises a ValueError if data is a NaN.
        """
        value = float(data)
        if math.isnan(value):
            raise ValueError("FloatCti can't store NaNs")

        if math.isinf(value):
            if value > 0:
                logger.warning("Replacing inf by the largest representable float")
                value = sys.float_info.max
            else:
                logger.warning("Replacing -inf by the smallest representable float")
                value = -sys.float_info.max

        return value


    def _dataToString(self, data):
        """ Conversion function used to convert the (default)data to the display value.
        """
        if self.specialValueText is not None and data == self.minValue:
            return self.specialValueText
        else:
            return "{}{:.{}f}{}".format(self.prefix, data, self.decimals, self.suffix)


    @property
    def debugInfo(self):
        """ Returns the string with debugging information
        """
        return ("min = {}, max = {}, step = {}, decimals = {}, specVal = {}"
                .format(self.minValue, self.maxValue, self.stepSize,
                        self.decimals, self.specialValueText))


    def createEditor(self, delegate, parent, option):
        """ Creates a FloatCtiEditor.
            For the parameters see the AbstractCti constructor documentation.
        """
        return FloatCtiEditor(self, delegate, parent=parent) class PenCti(BoolCti):
    """ Config Tree Item to configure a QPen for drawing lines.

        It will create children for the pen color, width and style. It will not create a child
        for the brush.
    """
    def __init__(self, nodeName, defaultData, resetTo=None, expanded=True,
                 includeNoneStyle=False, includeZeroWidth=False):
        """ Sets the children's default value using the resetTo value.

            The resetTo value must be a QPen or value that can be converted to QPen. It is used
            to initialize the child nodes' defaultValues. If resetTo is None, the default QPen
            will be used, which is a black solid pen of width 1.0.

            (resetTo is not called 'defaultData' since the PenCti itself always has a data and
            defaultData of None. That is, it does not store the data itself but relies on its
            child nodes). The default data, is used to indicate if the pen is enabled.

            If includeNonStyle is True, an None-option will be prepended to the style choice
        """
        super(PenCti, self).__init__(nodeName, defaultData, expanded=expanded,
                                     childrenDisabledValue=False)
        # We don't need a similar initFrom parameter.
        qPen = QtGui.QPen(resetTo)

        self.colorCti = self.insertChild(ColorCti('color', defaultData=qPen.color()))
        defaultIndex = PEN_STYLE_CONFIG_VALUES.index(qPen.style()) + int(includeNoneStyle)
        self.styleCti = self.insertChild(createPenStyleCti('style', defaultData=defaultIndex,
                                                           includeNone=includeNoneStyle))
        self.widthCti = self.insertChild(createPenWidthCti('width', defaultData=qPen.widthF(),
                                                zeroValueText=' ' if includeZeroWidth else None))


    @property
    def configValue(self):
        """ Creates a QPen made of the children's config values.
        """
        if not self.data:
            return None
        else:
            pen = QtGui.QPen()
            pen.setCosmetic(True)
            pen.setColor(self.colorCti.configValue)
            style = self.styleCti.configValue
            if style is not None:
                pen.setStyle(style)
            pen.setWidthF(self.widthCti.configValue)
            return pen


    def createPen(self, altStyle=None, altWidth=None):
        """ Creates a pen from the config values with the style overridden by altStyle if the
            None-option is selected in the combo box.
        """
        pen = self.configValue
        if pen is not None:

            style = self.findByNodePath('style').configValue
            if style is None and altStyle is not None:
                pen.setStyle(altStyle)

            width = self.findByNodePath('width').configValue
            if width == 0.0 and altWidth is not None:
                #logger.debug("Setting altWidth = {!r}".format(altWidth))
                pen.setWidthF(altWidth)

        return pen class ColorCti(AbstractCti):
    """ Config Tree Item to store a color.
    """
    def __init__(self, nodeName, defaultData=''):
        """ Constructor.
            For the (other) parameters see the AbstractCti constructor documentation.
        """
        super(ColorCti, self).__init__(nodeName, defaultData)

    def _enforceDataType(self, data):
        """ Converts to str so that this CTI always stores that type.
        """
        qColor = QtGui.QColor(data)
        if not qColor.isValid():
            raise ValueError("Invalid color specification: {!r}".format(data))
        return qColor

    def _dataToString(self, data):
        """ Conversion function used to convert the (default)data to the display value.
        """
        return data.name().upper()

    @property
    def decoration(self):
        """ Returns the data (QColor) to be displayed as decoration
        """
        return self.data


    def _nodeMarshall(self):
        """ Returns the non-recursive marshalled value of this CTI. Is called by marshall()
        """
        return self.data.name()


    def _nodeUnmarshall(self, data):
        """ Initializes itself non-recursively from data. Is called by unmarshall()
        """
        self.data = QtGui.QColor(data)


    def createEditor(self, delegate, parent, option):
        """ Creates a ColorCtiEditor.
            For the parameters see the AbstractCti constructor documentation.
        """
        return ColorCtiEditor(self, delegate, parent=parent) def createPenStyleCti(nodeName, defaultData=0, includeNone=False):
    """ Creates a ChoiceCti with Qt PenStyles.
        If includeEmtpy is True, the first option will be None.
    """
    displayValues=PEN_STYLE_DISPLAY_VALUES
    configValues=PEN_STYLE_CONFIG_VALUES
    if includeNone:
        displayValues = [''] + list(displayValues)
        configValues = [None] + list(configValues)
    return ChoiceCti(nodeName, defaultData,
                     displayValues=displayValues, configValues=configValues) def createPenWidthCti(nodeName, defaultData=1.0, zeroValueText=None):
    """ Creates a FloatCti with defaults for configuring a QPen width.

        If specialValueZero is set, this string will be displayed when 0.0 is selected.
        If specialValueZero is None, the minValue will be 0.1
    """
    # A pen line width of zero indicates a cosmetic pen. This means that the pen width is
    # always drawn one pixel wide, independent of the transformation set on the painter.
    # Note that line widths other than 1 may be slow when anti aliasing is on.
    return FloatCti(nodeName, defaultData=defaultData, specialValueText=zeroValueText,
                    minValue=0.1 if zeroValueText is None else 0.0,
                    maxValue=100, stepSize=0.1, decimals=1) class UntypedCti(AbstractCti):
    """ Config Tree Item to store a any type of data as long as it can be edited with a QLineEdit.

        Typically it's better to use 'typed' CTIs, where the data is always internally stored in
        the same type (enforced by _enforceDataType).

        This item is non-editable and can, for instance, be used for introspection.
    """
    def __init__(self, nodeName, defaultData='', doc=''):
        """ Constructor. For the parameters see the AbstractCti constructor documentation.
        """
        super(UntypedCti, self).__init__(nodeName, defaultData)
        self.doc = doc

    def _enforceDataType(self, value):
        """ Since UntypedCti can store any type of data no conversion will be done.
        """
        return value

    def createEditor(self, delegate, parent, option):
        """ Creates an UntypedCtiEditor.
            For the parameters see the AbstractCti constructor documentation.
            Note: since the item is not editable this will never be called.
        """
        return UntypedCtiEditor(self, delegate, parent=parent)

    @property
    def valueColumnItemFlags(self):
        """ Returns the flags determine how the user can interact with the value column.
            Returns Qt.NoItemFlags so that the item is not editable
        """
        return Qt.NoItemFlags class ArgosColorLegendItem(ColorLegendItem):
    """ Wrapper around pgcolorbar.colorlegend.ColorLegendItem.

        Suppresses the FutureWarning of PyQtGraph in _updateHistogram.
        Overrides the _imageItemHasIntegerData method.
        Adds context menu with reset color scale action.
        Middle mouse click resets the axis with the settings in the config tree.
    """
    # Use a QueuedConnection to connect to sigResetColorScale so that the reset is scheduled after
    # all current events have been processed. Otherwise the mouseReleaseEvent may look for a
    # PlotCurveItem that is no longer present after the reset, which results in a RuntimeError:
    # wrapped C/C++ object of type PlotCurveItem has been deleted.
    sigResetColorScale = QtSignal()  # Signal the inspectors to reset the color scale


    def __init__(self, *args, histHeightPercentile=99.0, **kwargs):
        """ Constructor
        """
        super(ArgosColorLegendItem, self).__init__(
            *args, histHeightPercentile=histHeightPercentile, **kwargs)
        self.resetColorScaleAction = QtWidgets.QAction("Reset Color Range", self)
        self.resetColorScaleAction.triggered.connect(self.emitResetColorScaleSignal)
        self.resetColorScaleAction.setToolTip("Reset the range of the color scale.")
        self.addAction(self.resetColorScaleAction)


    @classmethod
    def _imageItemHasIntegerData(cls, imageItem):
        """ Returns True if the imageItem contains integer data.

            Overriden so that the ImagePlotItem can replace integer arrays with float arrays (to
            plot masked values as NaNs) while it still calculates the histogram bins as if it
            where integers (to prevent aliasing)
        """
        check_class(imageItem, pg.ImageItem, allow_none=True)

        if hasattr(imageItem, '_wasIntegerData'):
            return imageItem._wasIntegerData
        else:
            return super(ArgosColorLegendItem, cls)._imageItemHasIntegerData(imageItem)


    def emitResetColorScaleSignal(self):
        """ Emits the sigColorScaleReset to request the inspectors to reset the color scale
        """
        logger.debug("Emitting sigColorScaleReset() for {!r}".format(self))
        self.sigResetColorScale.emit()


    def mouseClickEvent(self, mouseClickEvent):
        """ Handles (PyQtGraph) mouse click events.

            Overrides the middle mouse click to reset using the settings in the config tree.

            Opens the context menu if a right mouse button was clicked. (We can't simply use
            setContextMenuPolicy(Qt.ActionsContextMenu because the PlotItem class does not derive
            from QWidget).

            :param mouseClickEvent: pyqtgraph.GraphicsScene.mouseEvents.MouseClickEvent
        """
        if mouseClickEvent.button() in self.resetRangeMouseButtons:
            self.emitResetColorScaleSignal()
            mouseClickEvent.accept()

        elif mouseClickEvent.button() == QtCore.Qt.RightButton:
            contextMenu = QtWidgets.QMenu()
            for action in self.actions():
                contextMenu.addAction(action)

            screenPos = mouseClickEvent.screenPos() # Screenpos is a QPointF, convert to QPoint.
            screenX = round(screenPos.x())
            screenY = round(screenPos.y())
            contextMenu.exec_(QtCore.QPoint(screenX, screenY))

        else:
            super(ArgosColorLegendItem, self).mouseClickEvent(mouseClickEvent) class HistogramLUTItem(GraphicsWidget):
    """
    This is a graphicsWidget which provides controls for adjusting the display of an image.
    Includes:

    - Image histogram
    - Movable region over histogram to select black/white levels
    - Gradient editor to define color lookup table for single-channel images
    """
    sigLookupTableChanged = QtSignal(object)
    sigLevelsChanged = QtSignal(object)
    sigLevelChangeFinished = QtSignal(object)

    def __init__(self, image=None, fillHistogram=True):
        """
        If *image* (ImageItem) is provided, then the control will be automatically linked to the image and changes to the control will be immediately reflected in the image's appearance.
        By default, the histogram is rendered with a fill. For performance, set *fillHistogram* = False.
        """
        GraphicsWidget.__init__(self)
        self.lut = None
        self.imageItem = lambda: None  # fake a dead weakref

        self.layout = QtWidgets.QGraphicsGridLayout()
        self.setLayout(self.layout)
        self.layout.setContentsMargins(1,1,1,1)
        self.layout.setSpacing(0)
        self.vb = ViewBox(parent=self)
        self.vb.setMaximumWidth(152)
        self.vb.setMinimumWidth(45)
        self.vb.setMouseEnabled(x=False, y=True)
        self.gradient = GradientEditorItem()
        self.gradient.setOrientation('right')
        self.gradient.loadPreset('grey')
        self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal)
        self.region.setZValue(1000)
        self.vb.addItem(self.region)
        self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, parent=self)
        self.layout.addItem(self.axis, 0, 0)
        self.layout.addItem(self.vb, 0, 1)
        self.layout.addItem(self.gradient, 0, 2)
        self.range = None
        self.gradient.setFlag(self.gradient.ItemStacksBehindParent)
        self.vb.setFlag(self.gradient.ItemStacksBehindParent)

        #self.grid = GridItem()
        #self.vb.addItem(self.grid)

        self.gradient.sigGradientChanged.connect(self.gradientChanged)
        self.region.sigRegionChanged.connect(self.regionChanging)
        self.region.sigRegionChangeFinished.connect(self.regionChanged)
        self.vb.sigRangeChanged.connect(self.viewRangeChanged)
        self.plot = PlotDataItem()
        self.plot.rotate(90)
        self.fillHistogram(fillHistogram)

        self.vb.addItem(self.plot)
        self.autoHistogramRange()

        if image is not None:
            self.setImageItem(image)
        #self.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)

    def fillHistogram(self, fill=True, level=0.0, color=(100, 100, 200)):
        if fill:
            self.plot.setFillLevel(level)
            self.plot.setFillBrush(color)
        else:
            self.plot.setFillLevel(None)

    #def sizeHint(self, *args):
        #return QtCore.QSizeF(115, 200)

    def paint(self, p, *args):
        pen = self.region.lines[0].pen
        rgn = self.getLevels()
        p1 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[0]))
        p2 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[1]))
        gradRect = self.gradient.mapRectToParent(self.gradient.gradRect.rect())
        for pen in [fn.mkPen('k', width=3), pen]:
            p.setPen(pen)
            p.drawLine(p1, gradRect.bottomLeft())
            p.drawLine(p2, gradRect.topLeft())
            p.drawLine(gradRect.topLeft(), gradRect.topRight())
            p.drawLine(gradRect.bottomLeft(), gradRect.bottomRight())
        #p.drawRect(self.boundingRect())


    def setHistogramRange(self, mn, mx, padding=0.1):
        """Set the Y range on the histogram plot. This disables auto-scaling."""
        self.vb.enableAutoRange(self.vb.YAxis, False)
        self.vb.setYRange(mn, mx, padding)

        #d = mx-mn
        #mn -= d*padding
        #mx += d*padding
        #self.range = [mn,mx]
        #self.updateRange()
        #self.vb.setMouseEnabled(False, True)
        #self.region.setBounds([mn,mx])

    def autoHistogramRange(self):
        """Enable auto-scaling on the histogram plot."""
        self.vb.enableAutoRange(self.vb.XYAxes)
        #self.range = None
        #self.updateRange()
        #self.vb.setMouseEnabled(False, False)

    #def updateRange(self):
        #self.vb.autoRange()
        #if self.range is not None:
            #self.vb.setYRange(*self.range)
        #vr = self.vb.viewRect()

        #self.region.setBounds([vr.top(), vr.bottom()])

    def setImageItem(self, img):
        """Set an ImageItem to have its levels and LUT automatically controlled
        by this HistogramLUTItem.
        """
        self.imageItem = weakref.ref(img)
        img.sigImageChanged.connect(self.imageChanged)
        img.setLookupTable(self.getLookupTable)  ## send function pointer, not the result
        #self.gradientChanged()
        self.regionChanged()
        self.imageChanged(autoLevel=True)
        #self.vb.autoRange()

    def viewRangeChanged(self):
        self.update()

    def gradientChanged(self):
        if self.imageItem() is not None:
            if self.gradient.isLookupTrivial():
                self.imageItem().setLookupTable(None) #lambda x: x.astype(np.uint8))
            else:
                self.imageItem().setLookupTable(self.getLookupTable)  ## send function pointer, not the result

        self.lut = None
        #if self.imageItem is not None:
            #self.imageItem.setLookupTable(self.gradient.getLookupTable(512))
        self.sigLookupTableChanged.emit(self)

    def getLookupTable(self, img=None, n=None, alpha=None):
        """Return a lookup table from the color gradient defined by this
        HistogramLUTItem.
        """
        if n is None:
            if img.dtype == np.uint8:
                n = 256
            else:
                n = 512
        if self.lut is None:
            self.lut = self.gradient.getLookupTable(n, alpha=alpha)
        return self.lut

    def regionChanged(self):
        #if self.imageItem is not None:
            #self.imageItem.setLevels(self.region.getRegion())
        self.sigLevelChangeFinished.emit(self)
        #self.update()

    def regionChanging(self):
        if self.imageItem() is not None:
            self.imageItem().setLevels(self.region.getRegion())
        self.sigLevelsChanged.emit(self)
        self.update()

    def imageChanged(self, autoLevel=False, autoRange=False):
        profiler = debug.Profiler()
        img = self.imageItem().image
        if img is None:
            histRange = None
        else:
            histRange = (np.nanmin(img), np.nanmax(img))

        h = self.imageItem().getHistogram(range=histRange)
        profiler('get histogram')
        if h[0] is None:
            return
        self.plot.setData(*h)
        profiler('set plot')
        if autoLevel:
            mn = h[0][0]
            mx = h[0][-1]
            self.region.setRegion([mn, mx])
            profiler('set region')

    def getLevels(self):
        """Return the min and max levels.
        """
        return self.region.getRegion()

    def setLevels(self, mn, mx):
        """Set the min and max levels.
        """
        self.region.setRegion([mn, mx]) API_PYSIDE2 = 'pyside2'
API_PYQT5 = 'pyqt5'
ALL_API = [API_PYQT5, API_PYSIDE2]
QT_API_NAME = os.environ.get('QT_API')
    QT_API_NAME = None
        QT_API_NAME = API_PYQT5
        QT_API_NAME = API_PYSIDE2
        QT_API_NAME = API_PYQT5
        QT_API_NAME = API_PYSIDE2 def setWidgetSizePolicy(widget, horPolicy=None, verPolicy=None):
    """ Sets the size policy of a widget.
    """
    sizePolicy = widget.sizePolicy()

    if horPolicy is not None:
        sizePolicy.setHorizontalPolicy(horPolicy)

    if verPolicy is not None:
        sizePolicy.setVerticalPolicy(verPolicy)

    widget.setSizePolicy(sizePolicy)
    return sizePolicy class CmLibModelSingleton(CmLibModel, SingletonMixin):

    def __init__(self, **kwargs):
        super(CmLibModelSingleton, self).__init__(CmLibSingleton.instance(), **kwargs) DEFAULT_COLOR_MAP = "MatPlotLib/Magma" def check_class(obj, target_class, allow_none = False):
    """ Checks that the  obj is a (sub)type of target_class.
        Raises a TypeError if this is not the case.

        :param obj: object whos type is to be checked
        :type obj: any type
        :param target_class: target type/class
        :type target_class: any class or type
        :param allow_none: if true obj may be None
        :type allow_none: boolean
    """
    if not isinstance(obj, target_class):
        if not (allow_none and obj is None):
            raise TypeError("obj must be a of type {}, got: {}"
                            .format(target_class, type(obj))) def nanPercentileOfSubsampledArrayWithMask(arrayWithMask, percentiles, subsample, *args, **kwargs):
    """ Sub samples the array and then calls maskedNanPercentile on this.

        If subsample is False, no sub sampling is done and it just calls maskedNanPercentile
    """
    check_class(subsample, bool)
    check_class(arrayWithMask, ArrayWithMask)

    maskedArray = arrayWithMask.asMaskedArray()

    if subsample:
        maskedArray = _subsampleArray(maskedArray)

    return _maskedNanPercentile(maskedArray, percentiles, *args, **kwargs)
import logging
import numpy as np
import pyqtgraph as pg
from functools import partial
from collections import OrderedDict
from cmlib import ColorSelectionWidget, ColorMap, makeColorBarPixmap
from cmlib import CmMetaData, CatalogMetaData
from argos.config.groupcti import GroupCti
from argos.config.abstractcti import AbstractCti, AbstractCtiEditor
from argos.config.boolcti import BoolCti, BoolGroupCti
from argos.config.choicecti import ChoiceCti
from argos.config.intcti import IntCti
from argos.config.floatcti import SnFloatCti, FloatCti
from argos.config.qtctis import PenCti, ColorCti, createPenStyleCti, createPenWidthCti
from argos.config.untypedcti import UntypedCti
from argos.inspector.pgplugins.colorbar import ArgosColorLegendItem
from argos.inspector.pgplugins.pghistlutitem import HistogramLUTItem
from argos.qt import QtCore, QtGui, QtWidgets, QtSlot
from argos.qt.misc import setWidgetSizePolicy
from argos.repo.colors import CmLibModelSingleton, DEFAULT_COLOR_MAP
from argos.utils.cls import check_class
from argos.utils.masks import nanPercentileOfSubsampledArrayWithMask
from pyqtgraph.graphicsItems.GradientEditorItem import Gradients as GRADIENTS
from pgcolorbar.colorlegend import ColorLegendItem
                 ):
        """ Constructor.
            The target axis is specified by viewBox and axisNumber (0 for x-axis, 1 for y-axis)
        """
        super(PgAxisFlipCti, self).__init__(nodeName, defaultData=defaultData)
        check_class(viewBox, pg.ViewBox)
        assert axisNumber in (X_AXIS, Y_AXIS), "axisNumber must be 0 or 1"
        self.viewBox = viewBox
        self.axisNumber = axisNumber


    def _updateTargetFromNode(self):
        """ Applies the configuration to its target axis
        """
        if self.axisNumber == X_AXIS:
            self.viewBox.invertX(self.configValue)
        else:
            self.viewBox.invertY(self.configValue)



class PgAxisLabelCti(ChoiceCti):
    """ Configuration tree item that is linked to the axis label.
    """
    def __init__(self, plotItem, axisPosition, collector,
                 nodeName='label', defaultData=0, configValues=None):
        """ Constructor
            :param plotItem:
            :param axisPosition: 'left', 'right', 'bottom', 'top'
            :param collector: needed to get the collector.rtiInfo
            :param nodeName: the node name of this config tree item (default = label
            :param defaultData:
            :param configValues:
        """
        super(PgAxisLabelCti, self).__init__(nodeName, editable=True,
                                             defaultData=defaultData, configValues=configValues)
        assert axisPosition in VALID_AXIS_POSITIONS,\
            "axisPosition must be in {}".format(VALID_AXIS_POSITIONS)
        self.collector = collector
        self.plotItem = plotItem
        self.axisPosition = axisPosition


    def _updateTargetFromNode(self):
        """ Applies the configuration to the target axis it monitors.
            The axis label will be set to the configValue. If the configValue equals
             NO_LABEL_STR, the label will be hidden.
        """
        rtiInfo = self.collector.rtiInfo
        self.plotItem.setLabel(self.axisPosition, self.configValue.format(**rtiInfo))
        self.plotItem.showLabel(self.axisPosition, self.configValue != NO_LABEL_STR)



class PgAxisShowCti(BoolCti):
    """ BoolCti that toggles axisPosition showing/hiding an axis.
    """
    def __init__(self, plotItem, axisPosition, nodeName='show', defaultData=True):
        """ Constructor.
            The target axis is specified by imagePlotItem and axisPosition.
            axisPosition must be one of: 'left', 'right', 'bottom', 'top'

            NOTE: the PyQtGraph showAxis seems not to work. TODO: investigate.
        """
        super(PgAxisShowCti, self).__init__(nodeName, defaultData=defaultData)
        assert axisPosition in VALID_AXIS_POSITIONS,\
            "axisPosition must be in {}".format(VALID_AXIS_POSITIONS)

        self.plotItem = plotItem
        self.axisPosition = axisPosition


    def _updateTargetFromNode(self):
        """ Applies the configuration to its target axis
        """
        logger.debug("showAxis: {}, {}".format(self.axisPosition, self.configValue))
        self.plotItem.showAxis(self.axisPosition, show=self.configValue) # Seems not to work



class PgAxisLogModeCti(BoolCti):
    """ BoolCti that toggles an axis between logarithmic vs linear mode.
    """
    def __init__(self, plotItem, axisNumber, nodeName='logarithmic', defaultData=False):
        """ Constructor.
            The target axis is specified by imagePlotItem and axisNumber (0 for x-axis, 1 for y-axis)
        """
        super(PgAxisLogModeCti, self).__init__(nodeName, defaultData=defaultData)
        self.plotItem = plotItem
        self.axisNumber = axisNumber


    def _updateTargetFromNode(self):
        """ Applies the configuration to its target axis
        """
        if self.axisNumber == X_AXIS:
            xMode, yMode = self.configValue, None
        else:
            xMode, yMode = None, self.configValue

        self.plotItem.setLogMode(x=xMode, y=yMode)



class PgGridCti(BoolGroupCti):
    """ CTI for toggling the the grid on and off.

        Has child CTIs for toggling the X and Y axes separately. These are typically not added
        as children of PgAxisCti objects so that the user can enable both grids with one checkbox.
        Also includes a child to configure the transparency (alpha) of the grid.
    """
    def __init__(self, plotItem, nodeName="grid", defaultData=True, expanded=False):
        """ Constructor.
            The target axis is specified by viewBox and axisNumber (0 for x-axis, 1 for y-axis)
        """
        super(PgGridCti, self).__init__(nodeName, defaultData=defaultData, expanded=expanded)
        check_class(plotItem, pg.PlotItem)
        self.plotItem = plotItem

        self.xGridCti = self.insertChild(BoolCti('x-axis', defaultData))

        self.yGridCti = self.insertChild(BoolCti('y-axis', defaultData))