예제 #1
0
class TabButton(QToolButton):
    def __init__(self, parent=None, **kwargs):
        QToolButton.__init__(self, parent, **kwargs)
        self.setToolButtonStyle(Qt.ToolButtonIconOnly)
        self.setCheckable(True)

        self.__flat = True
        self.__showMenuIndicator = False

    def setFlat(self, flat):
        if self.__flat != flat:
            self.__flat = flat
            self.update()

    def flat(self):
        return self.__flat

    flat_ = Property(bool, fget=flat, fset=setFlat, designable=True)

    def setShownMenuIndicator(self, show):
        if self.__showMenuIndicator != show:
            self.__showMenuIndicator = show
            self.update()

    def showMenuIndicator(self):
        return self.__showMenuIndicator

    showMenuIndicator_ = Property(bool,
                                  fget=showMenuIndicator,
                                  fset=setShownMenuIndicator,
                                  designable=True)

    def paintEvent(self, event):
        opt = QStyleOptionToolButton()
        self.initStyleOption(opt)
        if self.__showMenuIndicator and self.isChecked():
            opt.features |= QStyleOptionToolButton.HasMenu
        if self.__flat:
            # Use default widget background/border styling.
            StyledWidget_paintEvent(self, event)

            p = QStylePainter(self)
            p.drawControl(QStyle.CE_ToolButtonLabel, opt)
        else:
            p = QStylePainter(self)
            p.drawComplexControl(QStyle.CC_ToolButton, opt)

    def sizeHint(self):
        opt = QStyleOptionToolButton()
        self.initStyleOption(opt)
        if self.__showMenuIndicator and self.isChecked():
            opt.features |= QStyleOptionToolButton.HasMenu
        style = self.style()

        hint = style.sizeFromContents(QStyle.CT_ToolButton, opt, opt.iconSize,
                                      self)
        return hint
예제 #2
0
class SchemeArrowAnnotation(BaseSchemeAnnotation):
    """
    An arrow annotation in the scheme.
    """

    color_changed = Signal(unicode)

    def __init__(self,
                 start_pos,
                 end_pos,
                 color="red",
                 anchor=None,
                 parent=None):
        BaseSchemeAnnotation.__init__(self, parent)
        self.__start_pos = start_pos
        self.__end_pos = end_pos
        self.__color = color
        self.__anchor = anchor

    def set_line(self, start_pos, end_pos):
        """
        Set arrow lines start and end position (``(x, y)`` tuples).
        """
        if self.__start_pos != start_pos or self.__end_pos != end_pos:
            self.__start_pos = start_pos
            self.__end_pos = end_pos
            self.geometry_changed.emit()

    def start_pos(self):
        """
        Start position of the arrow (base point).
        """
        return self.__start_pos

    start_pos = Property(tuple, fget=start_pos)

    def end_pos(self):
        """
        End position of the arrow (arrow head points toward the end).
        """
        return self.__end_pos

    end_pos = Property(tuple, fget=end_pos)

    def set_geometry(self, (start_pos, end_pos)):
        """
        Set the geometry of the arrow as a start and end position tuples
        (e.g. ``set_geometry(((0, 0), (100, 0))``).

        """
        self.set_line(start_pos, end_pos)
예제 #3
0
class LineEditButton(QToolButton):
    """
    A button in the :class:`LineEdit`.
    """
    def __init__(self, parent=None, flat=True, **kwargs):
        QToolButton.__init__(self, parent, **kwargs)

        self.__flat = flat

    def setFlat(self, flat):
        if self.__flat != flat:
            self.__flat = flat
            self.update()

    def flat(self):
        return self.__flat

    flat_ = Property(bool, fget=flat, fset=setFlat,
                     designable=True)

    def paintEvent(self, event):
        if self.__flat:
            opt = QStyleOptionToolButton()
            self.initStyleOption(opt)
            p = QStylePainter(self)
            p.drawControl(QStyle.CE_ToolButtonLabel, opt)
        else:
            QToolButton.paintEvent(self, event)
예제 #4
0
class StrengthIndicator(QLabel):
    """A password strength indicator.

    This is a label that gives feedback on the strength of a password.
    """

    Poor, Good, Excellent = range(3)

    stylesheet = """
        StrengthIndicator { border: 1px solid black; }
        StrengthIndicator[strength="0"] { background-color: #ff2929; }
        StrengthIndicator[strength="1"] { background-color: #4dd133; }
        StrengthIndicator[strength="2"] { background-color: #4dd133; }
    """

    def __init__(self, parent=None):
        super(StrengthIndicator, self).__init__(parent)
        self._strength = 0
        self.setStyleSheet(self.stylesheet)

    def getStrength(self):
        return self._strength

    def setStrength(self, strength):
        self._strength = strength
        if strength == self.Poor:
            self.setText('Poor')
        elif strength == self.Good:
            self.setText('Good')
        elif strength == self.Excellent:
            self.setText('Excellent')
        self.setStyleSheet(self.stylesheet)

    strength = Property(int, getStrength, setStrength)
예제 #5
0
class ColorButton(QPushButton):
    """
    Color choosing push button
    """
    __pyqtSignals__ = ("colorChanged(QColor)", )

    def __init__(self, parent=None):
        QPushButton.__init__(self, parent)
        self.setFixedSize(20, 20)
        self.setIconSize(QSize(12, 12))
        self.connect(self, SIGNAL("clicked()"), self.choose_color)
        self._color = QColor()

    def choose_color(self):
        color = QColorDialog.getColor(self._color, self.parentWidget())
        if color.isValid():
            self.set_color(color)

    def get_color(self):
        return self._color

    @Slot(QColor)
    def set_color(self, color):
        if color != self._color:
            self._color = color
            self.emit(SIGNAL("colorChanged(QColor)"), self._color)
            pixmap = QPixmap(self.iconSize())
            pixmap.fill(color)
            self.setIcon(QIcon(pixmap))

    color = Property("QColor", get_color, set_color)
예제 #6
0
class FeatureEditor(QtGui.QFrame):
    featureChanged = Signal()
    featureEdited = Signal()

    modifiedChanged = Signal([], [bool])

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        layout = QtGui.QFormLayout(
            fieldGrowthPolicy=QtGui.QFormLayout.ExpandingFieldsGrow
        )
        self.nameedit = QtGui.QLineEdit(
            sizePolicy=QSizePolicy(QSizePolicy.Minimum,
                                   QSizePolicy.Fixed)
        )
        self.expressionedit = QtGui.QPlainTextEdit(
            tabChangesFocus=True,
        )
        high = PythonSyntaxHighlighter(self.expressionedit.document())
        layout.addRow(self.tr("Name"), self.nameedit)
        layout.addRow(self.tr("Expression"), self.expressionedit)
        self.setLayout(layout)

        self.nameedit.editingFinished.connect(self._invalidate)
        self.expressionedit.textChanged.connect(self._invalidate)

        self._modified = False

    def setModified(self, modified):
        if not type(modified) is bool:
            raise TypeError

        if self._modified != modified:
            self._modified = modified
            self.modifiedChanged.emit()
            self.modifiedChanged[bool].emit(modified)

    def modified(self):
        return self._modified

    modified = Property(bool, modified, setModified,
                        notify=modifiedChanged)

    def setEditorData(self, data):
        self.nameedit.setText(data.name)
        self.expressionedit.setPlainText(data.expression)
        self.setModified(False)
        self.featureChanged.emit()

    def editorData(self):
        return FeatureDescriptor(name=self.nameedit.text(),
                                 expression=self.nameedit.toPlainText())

    def _invalidate(self):
        self.setModified(True)
        self.featureEdited.emit()
        self.featureChanged.emit()
예제 #7
0
class GraphicsTextEdit(QGraphicsTextItem):
    """
    QGraphicsTextItem subclass defining an additional placeholderText
    property (text displayed when no text is set).

    """
    def __init__(self, *args, **kwargs):
        QGraphicsTextItem.__init__(self, *args, **kwargs)

        self.__placeholderText = ""

    def setPlaceholderText(self, text):
        """
        Set the placeholder text. This is shown when the item has no text,
        i.e when `toPlainText()` returns an empty string.

        """
        if self.__placeholderText != text:
            self.__placeholderText = text
            if not self.toPlainText():
                self.update()

    def placeholderText(self):
        """
        Return the placeholder text.
        """
        return str(self.__placeholderText)

    placeholderText_ = Property(str,
                                placeholderText,
                                setPlaceholderText,
                                doc="Placeholder text")

    def paint(self, painter, option, widget=None):
        QGraphicsTextItem.paint(self, painter, option, widget)

        # Draw placeholder text if necessary
        if not (self.toPlainText() and self.toHtml()) and \
                self.__placeholderText and \
                not (self.hasFocus() and \
                     self.textInteractionFlags() & Qt.TextEditable):
            brect = self.boundingRect()
            painter.setFont(self.font())
            metrics = painter.fontMetrics()
            text = metrics.elidedText(self.__placeholderText, Qt.ElideRight,
                                      brect.width())
            color = self.defaultTextColor()
            color.setAlpha(min(color.alpha(), 150))
            painter.setPen(QPen(color))
            painter.drawText(brect, Qt.AlignTop | Qt.AlignLeft, text)
예제 #8
0
class SplitterResizer(QObject):
    """An object able to control the size of a widget in a
    QSpliter instance.

    """
    def __init__(self, parent=None):
        QObject.__init__(self, parent)
        self.__splitter = None
        self.__widget = None
        self.__animationEnabled = True
        self.__size = -1
        self.__expanded = False
        self.__animation = QPropertyAnimation(self, b"size_", self)

        self.__action = QAction("toogle-expanded", self, checkable=True)
        self.__action.triggered[bool].connect(self.setExpanded)

    def setSize(self, size):
        """Set the size of the controlled widget (either width or height
        depending on the orientation).

        """
        if self.__size != size:
            self.__size = size
            self.__update()

    def size(self):
        """Return the size of the widget in the splitter (either height of
        width) depending on the splitter orientation.

        """
        if self.__splitter and self.__widget:
            index = self.__splitter.indexOf(self.__widget)
            sizes = self.__splitter.sizes()
            return sizes[index]
        else:
            return -1

    size_ = Property(int, fget=size, fset=setSize)

    def setAnimationEnabled(self, enable):
        """Enable/disable animation.
        """
        self.__animation.setDuration(0 if enable else 200)

    def animationEnabled(self):
        return self.__animation.duration() == 0

    def setSplitterAndWidget(self, splitter, widget):
        """Set the QSplitter and QWidget instance the resizer should control.

        .. note:: the widget must be in the splitter.

        """
        if splitter and widget and not splitter.indexOf(widget) > 0:
            raise ValueError("Widget must be in a spliter.")

        if self.__widget:
            self.__widget.removeEventFilter()
        self.__splitter = splitter
        self.__widget = widget

        if widget:
            widget.installEventFilter(self)

        self.__update()

    def toogleExpandedAction(self):
        """Return a QAction that can be used to toggle expanded state.
        """
        return self.__action

    def open(self):
        """Open the controlled widget (expand it to it sizeHint).
        """
        self.__expanded = True
        self.__action.setChecked(True)

        if not (self.__splitter and self.__widget):
            return

        size = self.size()
        if size > 0:
            # Already has non zero size.
            return

        hint = self.__widget.sizeHint()

        if self.__splitter.orientation() == Qt.Vertical:
            end = hint.height()
        else:
            end = hint.width()

        self.__animation.setStartValue(0)
        self.__animation.setEndValue(end)
        self.__animation.start()

    def close(self):
        """Close the controlled widget (shrink to size 0).
        """
        self.__expanded = False
        self.__action.setChecked(False)

        if not (self.__splitter and self.__widget):
            return

        self.__animation.setStartValue(self.size())
        self.__animation.setEndValue(0)
        self.__animation.start()

    def setExpanded(self, expanded):
        """Set the expanded state.

        """
        if self.__expanded != expanded:
            if expanded:
                self.open()
            else:
                self.close()

    def expanded(self):
        """Return the expanded state.
        """
        return self.__expanded

    def __update(self):
        """Update the splitter sizes.
        """
        if self.__splitter and self.__widget:
            splitter = self.__splitter
            index = splitter.indexOf(self.__widget)
            sizes = splitter.sizes()
            current = sizes[index]
            diff = current - self.__size
            sizes[index] = self.__size
            sizes[index - 1] = sizes[index - 1] + diff

            self.__splitter.setSizes(sizes)

    def eventFilter(self, obj, event):
        if event.type() == QEvent.Resize:
            if self.__splitter.orientation() == Qt.Vertical:
                size = event.size().height()
            else:
                size = event.size().width()

            if self.__expanded and size == 0:
                self.__action.setChecked(False)
                self.__expanded = False
            elif not self.__expanded and size > 0:
                self.__action.setChecked(True)
                self.__expanded = True

        return QObject.eventFilter(self, obj, event)
예제 #9
0
class OASYSWidgetsScheme(WidgetsScheme):
    #: Signal emitted when the working directory changes.
    working_directory_changed = Signal(str)
    workspace_units_changed = Signal(int)

    def __init__(self, parent=None, title=None, description=None,
                 working_directory=None, workspace_units=None):
        settings = QSettings()

        self.__working_directory = (
            working_directory or
            settings.value("output/default-working-directory",
                           os.path.expanduser("~/Oasys"), type=str))

        if not os.path.exists(self.__working_directory):
            os.makedirs(self.__working_directory, exist_ok=True)

        #QSettings().setValue("output/default-units", 1)

        self.__workspace_units = (
            workspace_units or
            settings.value("output/default-units", 1, type=int))

        super().__init__(parent, title=title, description=description)

        # Replace the signal manager from.
        self.signal_manager.setParent(None)
        self.signal_manager.deleteLater()
        sip.delete(self.signal_manager)
        sip.delete(self.widget_manager)

        self.set_loop_flags(Scheme.AllowLoops)
        self.signal_manager = OASYSSignalManager(self)
        self.widget_manager = OASYSWidgetManager()
        self.widget_manager.set_scheme(self)

    def set_working_directory(self, working_directory):
        """
        Set the scheme working_directory.
        """
        if self.__working_directory != working_directory:
            self.__working_directory = working_directory
            self.working_directory_changed.emit(working_directory)

    def working_directory(self):
        """
        The working_directory of the scheme.
        """
        return self.__working_directory

    def set_workspace_units(self, units):
        """
        Set the scheme units.
        """
        if self.__workspace_units != units:
            self.__workspace_units = units
            self.workspace_units_changed.emit(units)

    def workspace_units(self):
        """
        The units of the scheme.
        """
        return self.__workspace_units

    working_directory = Property(str,
                                 fget=working_directory,
                                 fset=set_working_directory)

    workspace_units = Property(str,
                               fget=workspace_units,
                               fset=set_workspace_units)



    def save_to(self, stream, pretty=True, pickle_fallback=False):
        """
        Reimplemented from Scheme.save_to.
        """
        if isinstance(stream, str):
            stream = open(stream, "wb")

        self.sync_node_properties()

        tree = readwrite.scheme_to_etree(self, pickle_fallback=pickle_fallback)
        root = tree.getroot()
        root.set("working_directory", self.working_directory or "")
        root.set("workspace_units", str(self.workspace_units) or "")

        if pretty:
            readwrite.indent(tree.getroot(), 0)

        if sys.version_info < (2, 7):
            # in Python 2.6 the write does not have xml_declaration parameter.
            tree.write(stream, encoding="utf-8")
        else:
            tree.write(stream, encoding="utf-8", xml_declaration=True)
예제 #10
0
파일: nodeitem.py 프로젝트: CHANAYA/orange3
class NodeItem(QGraphicsObject):
    """
    An widget node item in the canvas.
    """

    #: Signal emitted when the scene position of the node has changed.
    positionChanged = Signal()

    #: Signal emitted when the geometry of the channel anchors changes.
    anchorGeometryChanged = Signal()

    #: Signal emitted when the item has been activated (by a mouse double
    #: click or a keyboard)
    activated = Signal()

    #: The item is under the mouse.
    hovered = Signal()

    #: Span of the anchor in degrees
    ANCHOR_SPAN_ANGLE = 90

    #: Z value of the item
    Z_VALUE = 100

    def __init__(self, widget_description=None, parent=None, **kwargs):
        QGraphicsObject.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setFlag(QGraphicsItem.ItemHasNoContents, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, True)

        # central body shape item
        self.shapeItem = None

        # in/output anchor items
        self.inputAnchorItem = None
        self.outputAnchorItem = None

        # title text item
        self.captionTextItem = None

        # error, warning, info items
        self.errorItem = None
        self.warningItem = None
        self.infoItem = None

        self.__title = ""
        self.__processingState = 0
        self.__progress = -1
        self.__statusMessage = ""

        self.__error = None
        self.__warning = None
        self.__info = None

        self.__anchorLayout = None
        self.__animationEnabled = False

        self.setZValue(self.Z_VALUE)
        self.setupGraphics()

        self.setWidgetDescription(widget_description)

    @classmethod
    def from_node(cls, node):
        """
        Create an :class:`NodeItem` instance and initialize it from a
        :class:`SchemeNode` instance.

        """
        self = cls()
        self.setWidgetDescription(node.description)
        #        self.setCategoryDescription(node.category)
        return self

    @classmethod
    def from_node_meta(cls, meta_description):
        """
        Create an `NodeItem` instance from a node meta description.
        """
        self = cls()
        self.setWidgetDescription(meta_description)
        return self

    def setupGraphics(self):
        """
        Set up the graphics.
        """
        shape_rect = QRectF(-24, -24, 48, 48)

        self.shapeItem = NodeBodyItem(self)
        self.shapeItem.setShapeRect(shape_rect)
        self.shapeItem.setAnimationEnabled(self.__animationEnabled)

        # Rect for widget's 'ears'.
        anchor_rect = QRectF(-31, -31, 62, 62)
        self.inputAnchorItem = SinkAnchorItem(self)
        input_path = QPainterPath()
        start_angle = 180 - self.ANCHOR_SPAN_ANGLE / 2
        input_path.arcMoveTo(anchor_rect, start_angle)
        input_path.arcTo(anchor_rect, start_angle, self.ANCHOR_SPAN_ANGLE)
        self.inputAnchorItem.setAnchorPath(input_path)

        self.outputAnchorItem = SourceAnchorItem(self)
        output_path = QPainterPath()
        start_angle = self.ANCHOR_SPAN_ANGLE / 2
        output_path.arcMoveTo(anchor_rect, start_angle)
        output_path.arcTo(anchor_rect, start_angle, -self.ANCHOR_SPAN_ANGLE)
        self.outputAnchorItem.setAnchorPath(output_path)

        self.inputAnchorItem.hide()
        self.outputAnchorItem.hide()

        # Title caption item
        self.captionTextItem = NameTextItem(self)

        self.captionTextItem.setPlainText("")
        self.captionTextItem.setPos(0, 33)

        def iconItem(standard_pixmap):
            item = GraphicsIconItem(self,
                                    icon=standard_icon(standard_pixmap),
                                    iconSize=QSize(16, 16))
            item.hide()
            return item

        self.errorItem = iconItem(QStyle.SP_MessageBoxCritical)
        self.warningItem = iconItem(QStyle.SP_MessageBoxWarning)
        self.infoItem = iconItem(QStyle.SP_MessageBoxInformation)

    # TODO: Remove the set[Widget|Category]Description. The user should
    # handle setting of icons, title, ...
    def setWidgetDescription(self, desc):
        """
        Set widget description.
        """
        self.widget_description = desc
        if desc is None:
            return

        icon = icon_loader.from_description(desc).get(desc.icon)
        if icon:
            self.setIcon(icon)

        if not self.title():
            self.setTitle(desc.name)

        if desc.inputs:
            self.inputAnchorItem.show()
        if desc.outputs:
            self.outputAnchorItem.show()

        tooltip = NodeItem_toolTipHelper(self)
        self.setToolTip(tooltip)

    def setWidgetCategory(self, desc):
        """
        Set the widget category.
        """
        self.category_description = desc
        if desc and desc.background:
            background = NAMED_COLORS.get(desc.background, desc.background)
            color = QColor(background)
            if color.isValid():
                self.setColor(color)

    def setIcon(self, icon):
        """
        Set the node item's icon (:class:`QIcon`).
        """
        if isinstance(icon, QIcon):
            self.icon_item = GraphicsIconItem(self.shapeItem,
                                              icon=icon,
                                              iconSize=QSize(36, 36))
            self.icon_item.setPos(-18, -18)
        else:
            raise TypeError

    def setColor(self, color, selectedColor=None):
        """
        Set the widget color.
        """
        if selectedColor is None:
            selectedColor = saturated(color, 150)
        palette = create_palette(color, selectedColor)
        self.shapeItem.setPalette(palette)

    def setPalette(self, palette):
        # TODO: The palette should override the `setColor`
        raise NotImplementedError

    def setTitle(self, title):
        """
        Set the node title. The title text is displayed at the bottom of the
        node.

        """
        self.__title = title
        self.__updateTitleText()

    def title(self):
        """
        Return the node title.
        """
        return self.__title

    title_ = Property(str, fget=title, fset=setTitle, doc="Node title text.")

    def setFont(self, font):
        """
        Set the title text font (:class:`QFont`).
        """
        if font != self.font():
            self.prepareGeometryChange()
            self.captionTextItem.setFont(font)
            self.__updateTitleText()

    def font(self):
        """
        Return the title text font.
        """
        return self.captionTextItem.font()

    def setAnimationEnabled(self, enabled):
        """
        Set the node animation enabled state.
        """
        if self.__animationEnabled != enabled:
            self.__animationEnabled = enabled
            self.shapeItem.setAnimationEnabled(enabled)

    def animationEnabled(self):
        """
        Are node animations enabled.
        """
        return self.__animationEnabled

    def setProcessingState(self, state):
        """
        Set the node processing state i.e. the node is processing
        (is busy) or is idle.

        """
        if self.__processingState != state:
            self.__processingState = state
            self.shapeItem.setProcessingState(state)
            if not state:
                # Clear the progress meter.
                self.setProgress(-1)
                if self.__animationEnabled:
                    self.shapeItem.ping()

    def processingState(self):
        """
        The node processing state.
        """
        return self.__processingState

    processingState_ = Property(int,
                                fget=processingState,
                                fset=setProcessingState)

    def setProgress(self, progress):
        """
        Set the node work progress state (number between 0 and 100).
        """
        if progress is None or progress < 0 or not self.__processingState:
            progress = -1

        progress = max(min(progress, 100), -1)
        if self.__progress != progress:
            self.__progress = progress
            self.shapeItem.setProgress(progress)
            self.__updateTitleText()

    def progress(self):
        """
        Return the node work progress state.
        """
        return self.__progress

    progress_ = Property(float,
                         fget=progress,
                         fset=setProgress,
                         doc="Node progress state.")

    def setStatusMessage(self, message):
        """
        Set the node status message text.

        This text is displayed below the node's title.

        """
        if self.__statusMessage != message:
            self.__statusMessage = message
            self.__updateTitleText()

    def statusMessage(self):
        return self.__statusMessage

    def setStateMessage(self, message):
        """
        Set a state message to display over the item.

        Parameters
        ----------
        message : UserMessage
            Message to display. `message.severity` is used to determine
            the icon and `message.contents` is used as a tool tip.

        """
        # TODO: Group messages by message_id not by severity
        # and deprecate set[Error|Warning|Error]Message
        if message.severity == UserMessage.Info:
            self.setInfoMessage(message.contents)
        elif message.severity == UserMessage.Warning:
            self.setWarningMessage(message.contents)
        elif message.severity == UserMessage.Error:
            self.setErrorMessage(message.contents)

    def setErrorMessage(self, message):
        if self.__error != message:
            self.__error = message
            self.__updateMessages()

    def setWarningMessage(self, message):
        if self.__warning != message:
            self.__warning = message
            self.__updateMessages()

    def setInfoMessage(self, message):
        if self.__info != message:
            self.__info = message
            self.__updateMessages()

    def newInputAnchor(self):
        """
        Create and return a new input :class:`AnchorPoint`.
        """
        if not (self.widget_description and self.widget_description.inputs):
            raise ValueError("Widget has no inputs.")

        anchor = AnchorPoint()
        self.inputAnchorItem.addAnchor(anchor, position=1.0)

        positions = self.inputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.inputAnchorItem.setAnchorPositions(positions)

        return anchor

    def removeInputAnchor(self, anchor):
        """
        Remove input anchor.
        """
        self.inputAnchorItem.removeAnchor(anchor)

        positions = self.inputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.inputAnchorItem.setAnchorPositions(positions)

    def newOutputAnchor(self):
        """
        Create and return a new output :class:`AnchorPoint`.
        """
        if not (self.widget_description and self.widget_description.outputs):
            raise ValueError("Widget has no outputs.")

        anchor = AnchorPoint(self)
        self.outputAnchorItem.addAnchor(anchor, position=1.0)

        positions = self.outputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.outputAnchorItem.setAnchorPositions(positions)

        return anchor

    def removeOutputAnchor(self, anchor):
        """
        Remove output anchor.
        """
        self.outputAnchorItem.removeAnchor(anchor)

        positions = self.outputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.outputAnchorItem.setAnchorPositions(positions)

    def inputAnchors(self):
        """
        Return a list of all input anchor points.
        """
        return self.inputAnchorItem.anchorPoints()

    def outputAnchors(self):
        """
        Return a list of all output anchor points.
        """
        return self.outputAnchorItem.anchorPoints()

    def setAnchorRotation(self, angle):
        """
        Set the anchor rotation.
        """
        self.inputAnchorItem.setRotation(angle)
        self.outputAnchorItem.setRotation(angle)
        self.anchorGeometryChanged.emit()

    def anchorRotation(self):
        """
        Return the anchor rotation.
        """
        return self.inputAnchorItem.rotation()

    def boundingRect(self):
        # TODO: Important because of this any time the child
        # items change geometry the self.prepareGeometryChange()
        # needs to be called.
        return self.childrenBoundingRect()

    def shape(self):
        # Shape for mouse hit detection.
        # TODO: Should this return the union of all child items?
        return self.shapeItem.shape()

    def __updateTitleText(self):
        """
        Update the title text item.
        """
        text = ['<div align="center">%s' % escape(self.title())]

        status_text = []

        progress_included = False
        if self.__statusMessage:
            msg = escape(self.__statusMessage)
            format_fields = dict(parse_format_fields(msg))
            if "progress" in format_fields and len(format_fields) == 1:
                # Insert progress into the status text format string.
                spec, _ = format_fields["progress"]
                if spec != None:
                    progress_included = True
                    progress_str = "{0:.0f}%".format(self.progress())
                    status_text.append(msg.format(progress=progress_str))
            else:
                status_text.append(msg)

        if self.progress() >= 0 and not progress_included:
            status_text.append("%i%%" % int(self.progress()))

        if status_text:
            text += [
                "<br/>", '<span style="font-style: italic">',
                "<br/>".join(status_text), "</span>"
            ]
        text += ["</div>"]
        text = "".join(text)

        # The NodeItems boundingRect could change.
        self.prepareGeometryChange()
        self.captionTextItem.setHtml(text)
        self.captionTextItem.document().adjustSize()
        width = self.captionTextItem.textWidth()
        self.captionTextItem.setPos(-width / 2.0, 33)

    def __updateMessages(self):
        """
        Update message items (position, visibility and tool tips).
        """
        items = [self.errorItem, self.warningItem, self.infoItem]

        messages = [self.__error, self.__warning, self.__info]
        for message, item in zip(messages, items):
            item.setVisible(bool(message))
            item.setToolTip(message or "")

        shown = [item for item in items if item.isVisible()]
        count = len(shown)
        if count:
            spacing = 3
            rects = [item.boundingRect() for item in shown]
            width = sum(rect.width() for rect in rects)
            width += spacing * max(0, count - 1)
            height = max(rect.height() for rect in rects)
            origin = self.shapeItem.boundingRect().top() - spacing - height
            origin = QPointF(-width / 2, origin)
            for item, rect in zip(shown, rects):
                item.setPos(origin)
                origin = origin + QPointF(rect.width() + spacing, 0)

    def mousePressEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            return QGraphicsObject.mousePressEvent(self, event)
        else:
            event.ignore()

    def mouseDoubleClickEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            QGraphicsObject.mouseDoubleClickEvent(self, event)
            QTimer.singleShot(0, self.activated.emit)
        else:
            event.ignore()

    def contextMenuEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            return QGraphicsObject.contextMenuEvent(self, event)
        else:
            event.ignore()

    def focusInEvent(self, event):
        self.shapeItem.setHasFocus(True)
        return QGraphicsObject.focusInEvent(self, event)

    def focusOutEvent(self, event):
        self.shapeItem.setHasFocus(False)
        return QGraphicsObject.focusOutEvent(self, event)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSelectedChange:
            self.shapeItem.setSelected(value)
            self.captionTextItem.setSelectionState(value)
        elif change == QGraphicsItem.ItemPositionHasChanged:
            self.positionChanged.emit()

        return QGraphicsObject.itemChange(self, change, value)
    def set_geometry(self, (start_pos, end_pos)):
        """
        Set the geometry of the arrow as a start and end position tuples
        (e.g. ``set_geometry(((0, 0), (100, 0))``).

        """
        self.set_line(start_pos, end_pos)

    def geometry(self):
        """
        Return the start and end positions of the arrow.
        """
        return (self.start_pos, self.end_pos)

    geometry = Property(tuple, fget=geometry, fset=set_geometry)

    def set_color(self, color):
        """
        Set the fill color for the arrow as a string (`#RGB`, `#RRGGBB`,
        `#RRRGGGBBB`, `#RRRRGGGGBBBB` format or one of SVG color keyword
        names).

        """
        check_type(color, (basestring, QString))
        color = unicode(color)
        if self.__color != color:
            self.__color = color
            self.color_changed.emit(color)

    def color(self):
예제 #12
0
class PasswordItem(QLabel):

    stylesheet = """
        PasswordItem[selected="false"]
                { color: palette(window-text); background-color: white; }
        PasswordItem[selected="true"]
                { color: palette(highlighted-text); background-color: palette(highlight); }
        QLabel { height: 18px; }
    """

    def __init__(self, vault, version, parent=None):
        super(PasswordItem, self).__init__(parent)
        self.vault = vault
        self._selected = False
        self.updateData(version)

    def getSelected(self):
        return self._selected

    def setSelected(self, selected):
        self._selected = selected
        # Re-calculate style
        self.setStyleSheet(self.stylesheet)

    selected = Property(bool, getSelected, setSelected)

    clicked = Signal(str, str)

    @Slot(dict)
    def updateData(self, version):
        self.version = version
        self.setText(version.get('name', ''))

    @Slot()
    def copyUsernameToClipboard(self):
        username = self.version.get('username', '')
        QApplication.instance().copyToClipboard(username)

    @Slot()
    def copyPasswordToClipboard(self):
        password = self.version.get('password', '')
        QApplication.instance().copyToClipboard(password, 60)

    @Slot()
    def editPassword(self):
        pwview = QApplication.instance().mainWindow().passwordView()
        pwview.editPassword(self.vault, self.version)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.clicked.emit(self.vault, self.version['id'])
        elif event.button() == Qt.RightButton:
            self.showContextMenu(event.pos())

    def mouseDoubleClickEvent(self, event):
        self.editPassword()

    def showContextMenu(self, pos):
        menu = QMenu(self)
        action = menu.addAction('Copy Username')
        action.setShortcut(QKeySequence('CTRL-U'))
        action.triggered.connect(self.copyUsernameToClipboard)
        action = menu.addAction('Copy Password')
        action.setShortcut(QKeySequence('CTRL-C'))
        action.triggered.connect(self.copyPasswordToClipboard)
        menu.addSeparator()
        action = menu.addAction('Edit')
        action.triggered.connect(self.editPassword)
        action = menu.addAction('Delete')
        action.triggered.connect(self.deleteItem)
        menu.popup(self.mapToGlobal(pos))

    @Slot()
    def deleteItem(self):
        backend = QApplication.instance().backend()
        version = {
            'id': self.version['id'],
            'name': self.version['name'],
            '_type': self.version['_type']
        }
        backend.delete_version(self.vault, version)
예제 #13
0
파일: node.py 프로젝트: yisuax11/orange2
class SchemeNode(QObject):
    """
    A node in a :class:`.Scheme`.

    Parameters
    ----------
    description : :class:`WidgetDescription`
        Node description instance.
    title : str, optional
        Node title string (if None `description.name` is used).
    position : tuple
        (x, y) tuple of floats for node position in a visual display.
    properties : dict
        Additional extra instance properties (settings, widget geometry, ...)
    parent : :class:`QObject`
        Parent object.

    """
    def __init__(self,
                 description,
                 title=None,
                 position=None,
                 properties=None,
                 parent=None):
        QObject.__init__(self, parent)
        self.description = description

        if title is None:
            title = description.name

        self.__title = title
        self.__position = position or (0, 0)
        self.__progress = -1
        self.__processing_state = 0
        self.__status_message = ""
        self.__state_messages = {}
        self.properties = properties or {}

    def input_channels(self):
        """
        Return a list of input channels (:class:`InputSignal`) for the node.
        """
        return list(self.description.inputs)

    def output_channels(self):
        """
        Return a list of output channels (:class:`OutputSignal`) for the node.
        """
        return list(self.description.outputs)

    def input_channel(self, name):
        """
        Return the input channel matching `name`. Raise a `ValueError`
        if not found.

        """
        for channel in self.input_channels():
            if channel.name == name:
                return channel
        raise ValueError("%r is not a valid input channel name for %r." % \
                         (name, self.description.name))

    def output_channel(self, name):
        """
        Return the output channel matching `name`. Raise an `ValueError`
        if not found.

        """
        for channel in self.output_channels():
            if channel.name == name:
                return channel
        raise ValueError("%r is not a valid output channel name for %r." % \
                         (name, self.description.name))

    #: The title of the node has changed
    title_changed = Signal(unicode)

    def set_title(self, title):
        """
        Set the node title.
        """
        if self.__title != title:
            self.__title = unicode(title)
            self.title_changed.emit(self.__title)

    def title(self):
        """
        The node title.
        """
        return self.__title

    title = Property(unicode, fset=set_title, fget=title)

    #: Position of the node in the scheme has changed
    position_changed = Signal(tuple)

    def set_position(self, pos):
        """
        Set the position (``(x, y)`` tuple) of the node.
        """
        if self.__position != pos:
            self.__position = pos
            self.position_changed.emit(pos)

    def position(self):
        """
        ``(x, y)`` tuple containing the position of the node in the scheme.
        """
        return self.__position

    position = Property(tuple, fset=set_position, fget=position)

    #: Node's progress value has changed.
    progress_changed = Signal(float)

    def set_progress(self, value):
        """
        Set the progress value.
        """
        if self.__progress != value:
            self.__progress = value
            self.progress_changed.emit(value)

    def progress(self):
        """
        The current progress value. -1 if progress is not set.
        """
        return self.__progress

    progress = Property(float, fset=set_progress, fget=progress)

    #: Node's processing state has changed.
    processing_state_changed = Signal(int)

    def set_processing_state(self, state):
        """
        Set the node processing state.
        """
        if self.__processing_state != state:
            self.__processing_state = state
            self.processing_state_changed.emit(state)

    def processing_state(self):
        """
        The node processing state, 0 for not processing, 1 the node is busy.
        """
        return self.__processing_state

    processing_state = Property(int,
                                fset=set_processing_state,
                                fget=processing_state)

    def set_tool_tip(self, tool_tip):
        if self.__tool_tip != tool_tip:
            self.__tool_tip = tool_tip

    def tool_tip(self):
        return self.__tool_tip

    tool_tip = Property(str, fset=set_tool_tip, fget=tool_tip)

    #: The node's status tip has changes
    status_message_changed = Signal(unicode)

    def set_status_message(self, text):
        if self.__status_message != text:
            self.__status_message = text
            self.status_message_changed.emit(text)

    def status_message(self):
        return self.__status_message

    #: The node's state message has changed
    state_message_changed = Signal(UserMessage)

    def set_state_message(self, message):
        """
        Set a message to be displayed by a scheme view for this node.
        """
        if message.message_id in self.__state_messages and \
                not message.contents:
            del self.__state_messages[message.message_id]

        self.__state_messages[message.message_id] = message
        self.state_message_changed.emit(message)

    def state_messages(self):
        """
        Return a list of all state messages.
        """
        return self.__state_messages.values()

    def __str__(self):
        return u"SchemeNode(description_id=%s, title=%r, ...)" % \
                (str(self.description.id), self.title)

    def __repr__(self):
        return str(self)
예제 #14
0
class SchemeTextAnnotation(BaseSchemeAnnotation):
    """
    Text annotation in the scheme.
    """

    # Signal emitted when the annotation text changes.
    text_changed = Signal(str)

    # Signal emitted when the annotation text font changes.
    font_changed = Signal(dict)

    def __init__(self, rect, text="", font=None, anchor=None, parent=None):
        BaseSchemeAnnotation.__init__(self, parent)
        self.__rect = rect
        self.__text = text
        self.__font = {} if font is None else font
        self.__anchor = anchor

    def set_rect(self, rect):
        """
        Set the text geometry bounding rectangle (``(x, y, width, height)``
        tuple).

        """
        if self.__rect != rect:
            self.__rect = rect
            self.geometry_changed.emit()

    def rect(self):
        """
        Text bounding rectangle
        """
        return self.__rect

    rect = Property(tuple, fget=rect, fset=set_rect)

    def set_geometry(self, rect):
        """
        Set the text geometry (same as ``set_rect``)
        """
        self.set_rect(rect)

    def geometry(self):
        """
        Text annotation geometry (same as ``rect``
        """
        return self.rect

    geometry = Property(tuple, fget=geometry, fset=set_geometry)

    def set_text(self, text):
        """
        Set the annotation text.
        """
        check_type(text, str)
        text = str(text)
        if self.__text != text:
            self.__text = text
            self.text_changed.emit(text)

    def text(self):
        """
        Annotation text.
        """
        return self.__text

    text = Property(tuple, fget=text, fset=set_text)

    def set_font(self, font):
        """
        Set the annotation's font as a dictionary of font properties
        (at the moment only family and size are used).

            >>> annotation.set_font({"family": "Helvetica", "size": 16})

        """
        check_type(font, dict)
        font = dict(font)
        if self.__font != font:
            self.__font = font
            self.font_changed.emit(font)

    def font(self):
        """
        Annotation's font property dictionary.
        """
        return dict(self.__font)

    font = Property(str, fget=font, fset=set_font)
예제 #15
0
class FeatureEditor(QtGui.QFrame):
    FUNCTIONS = dict(
        chain(
            [(key, val)
             for key, val in math.__dict__.items() if not key.startswith("_")],
            [("str", str)]))
    featureChanged = Signal()
    featureEdited = Signal()

    modifiedChanged = Signal(bool)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        layout = QtGui.QFormLayout(
            fieldGrowthPolicy=QtGui.QFormLayout.ExpandingFieldsGrow)
        layout.setContentsMargins(0, 0, 0, 0)
        self.nameedit = QtGui.QLineEdit(placeholderText="Name...",
                                        sizePolicy=QSizePolicy(
                                            QSizePolicy.Minimum,
                                            QSizePolicy.Fixed))
        self.expressionedit = QtGui.QLineEdit(placeholderText="Expression...")

        self.attrs_model = itemmodels.VariableListModel(["Select feature"],
                                                        parent=self)
        self.attributescb = QtGui.QComboBox(
            minimumContentsLength=16,
            sizeAdjustPolicy=QtGui.QComboBox.
            AdjustToMinimumContentsLengthWithIcon,
            sizePolicy=QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum,
                                         QtGui.QSizePolicy.Minimum))
        self.attributescb.setModel(self.attrs_model)

        sorted_funcs = sorted(self.FUNCTIONS)
        self.funcs_model = itemmodels.PyListModelTooltip()
        self.funcs_model.setParent(self)

        self.funcs_model[:] = chain(["Select function"], sorted_funcs)
        self.funcs_model.tooltips[:] = chain(
            [''], [self.FUNCTIONS[func].__doc__ for func in sorted_funcs])

        self.functionscb = QtGui.QComboBox(
            minimumContentsLength=16,
            sizeAdjustPolicy=QtGui.QComboBox.
            AdjustToMinimumContentsLengthWithIcon,
            sizePolicy=QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum,
                                         QtGui.QSizePolicy.Minimum))
        self.functionscb.setModel(self.funcs_model)

        hbox = QtGui.QHBoxLayout()
        hbox.addWidget(self.attributescb)
        hbox.addWidget(self.functionscb)

        layout.addRow(self.nameedit, self.expressionedit)
        layout.addRow(self.tr(""), hbox)
        self.setLayout(layout)

        self.nameedit.editingFinished.connect(self._invalidate)
        self.expressionedit.textChanged.connect(self._invalidate)
        self.attributescb.currentIndexChanged.connect(self.on_attrs_changed)
        self.functionscb.currentIndexChanged.connect(self.on_funcs_changed)

        self._modified = False

    def setModified(self, modified):
        if not type(modified) is bool:
            raise TypeError

        if self._modified != modified:
            self._modified = modified
            self.modifiedChanged.emit(modified)

    def modified(self):
        return self._modified

    modified = Property(bool, modified, setModified, notify=modifiedChanged)

    def setEditorData(self, data, domain):
        self.nameedit.setText(data.name)
        self.expressionedit.setText(data.expression)
        self.setModified(False)
        self.featureChanged.emit()
        self.attrs_model[:] = ["Select feature"]
        if domain:
            self.attrs_model[:] += chain(domain.attributes, domain.class_vars,
                                         domain.metas)

    def editorData(self):
        return FeatureDescriptor(name=self.nameedit.text(),
                                 expression=self.nameedit.text())

    def _invalidate(self):
        self.setModified(True)
        self.featureEdited.emit()
        self.featureChanged.emit()

    def on_attrs_changed(self):
        index = self.attributescb.currentIndex()
        if index > 0:
            attr = sanitized_name(self.attrs_model[index].name)
            self.insert_into_expression(attr)
            self.attributescb.setCurrentIndex(0)

    def on_funcs_changed(self):
        index = self.functionscb.currentIndex()
        if index > 0:
            func = self.funcs_model[index]
            if func in [
                    "atan2", "fmod", "ldexp", "log", "pow", "copysign", "hypot"
            ]:
                self.insert_into_expression(func + "(,)")
                self.expressionedit.cursorBackward(False, 2)
            elif func in ["e", "pi"]:
                self.insert_into_expression(func)
            else:
                self.insert_into_expression(func + "()")
                self.expressionedit.cursorBackward(False)
            self.functionscb.setCurrentIndex(0)

    def insert_into_expression(self, what):
        cp = self.expressionedit.cursorPosition()
        ct = self.expressionedit.text()
        text = ct[:cp] + what + ct[cp:]
        self.expressionedit.setText(text)
        self.expressionedit.setFocus()
예제 #16
0
class SchemeArrowAnnotation(BaseSchemeAnnotation):
    """
    An arrow annotation in the scheme.
    """

    color_changed = Signal(str)

    def __init__(self,
                 start_pos,
                 end_pos,
                 color="red",
                 anchor=None,
                 parent=None):
        BaseSchemeAnnotation.__init__(self, parent)
        self.__start_pos = start_pos
        self.__end_pos = end_pos
        self.__color = color
        self.__anchor = anchor

    def set_line(self, start_pos, end_pos):
        """
        Set arrow lines start and end position (``(x, y)`` tuples).
        """
        if self.__start_pos != start_pos or self.__end_pos != end_pos:
            self.__start_pos = start_pos
            self.__end_pos = end_pos
            self.geometry_changed.emit()

    def start_pos(self):
        """
        Start position of the arrow (base point).
        """
        return self.__start_pos

    start_pos = Property(tuple, fget=start_pos)

    def end_pos(self):
        """
        End position of the arrow (arrow head points toward the end).
        """
        return self.__end_pos

    end_pos = Property(tuple, fget=end_pos)

    def set_geometry(self, geometry):
        """
        Set the geometry of the arrow as a start and end position tuples
        (e.g. ``set_geometry(((0, 0), (100, 0))``).

        """
        (start_pos, end_pos) = geometry
        self.set_line(start_pos, end_pos)

    def geometry(self):
        """
        Return the start and end positions of the arrow.
        """
        return (self.start_pos, self.end_pos)

    geometry = Property(tuple, fget=geometry, fset=set_geometry)

    def set_color(self, color):
        """
        Set the fill color for the arrow as a string (`#RGB`, `#RRGGBB`,
        `#RRRGGGBBB`, `#RRRRGGGGBBBB` format or one of SVG color keyword
        names).

        """
        check_type(color, str)
        color = str(color)
        if self.__color != color:
            self.__color = color
            self.color_changed.emit(color)

    def color(self):
        """
        The arrow's fill color.
        """
        return self.__color

    color = Property(str, fget=color, fset=set_color)
예제 #17
0
파일: toolbox.py 프로젝트: yisuax11/orange2
class ToolBox(QFrame):
    """
    A tool box widget.
    """
    # Emitted when a tab is toggled.
    tabToogled = Signal(int, bool)

    def setExclusive(self, exclusive):
        """
        Set exclusive tabs (only one tab can be open at a time).
        """
        if self.__exclusive != exclusive:
            self.__exclusive = exclusive
            self.__tabActionGroup.setExclusive(exclusive)
            checked = self.__tabActionGroup.checkedAction()
            if checked is None:
                # The action group can be out of sync with the actions state
                # when switching between exclusive states.
                actions_checked = [
                    page.action for page in self.__pages
                    if page.action.isChecked()
                ]
                if actions_checked:
                    checked = actions_checked[0]

            # Trigger/toggle remaining open pages
            if exclusive and checked is not None:
                for page in self.__pages:
                    if checked != page.action and page.action.isChecked():
                        page.action.trigger()

    def exclusive(self):
        """
        Are the tabs in the toolbox exclusive.
        """
        return self.__exclusive

    exclusive_ = Property(bool,
                          fget=exclusive,
                          fset=setExclusive,
                          designable=True,
                          doc="Exclusive tabs")

    def __init__(self, parent=None, **kwargs):
        QFrame.__init__(self, parent, **kwargs)

        self.__pages = []
        self.__tabButtonHeight = -1
        self.__tabIconSize = QSize()
        self.__exclusive = False
        self.__setupUi()

    def __setupUi(self):
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        # Scroll area for the contents.
        self.__scrollArea = \
                _ToolBoxScrollArea(self, objectName="toolbox-scroll-area")

        self.__scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.__scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.__scrollArea.setSizePolicy(QSizePolicy.MinimumExpanding,
                                        QSizePolicy.MinimumExpanding)
        self.__scrollArea.setFrameStyle(QScrollArea.NoFrame)
        self.__scrollArea.setWidgetResizable(True)

        # A widget with all of the contents.
        # The tabs/contents are placed in the layout inside this widget
        self.__contents = QWidget(self.__scrollArea,
                                  objectName="toolbox-contents")

        # The layout where all the tab/pages are placed
        self.__contentsLayout = QVBoxLayout()
        self.__contentsLayout.setContentsMargins(0, 0, 0, 0)
        self.__contentsLayout.setSizeConstraint(QVBoxLayout.SetMinAndMaxSize)
        self.__contentsLayout.setSpacing(0)

        self.__contents.setLayout(self.__contentsLayout)

        self.__scrollArea.setWidget(self.__contents)

        layout.addWidget(self.__scrollArea)

        self.setLayout(layout)
        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)

        self.__tabActionGroup = \
                QActionGroup(self, objectName="toolbox-tab-action-group")

        self.__tabActionGroup.setExclusive(self.__exclusive)

        self.__actionMapper = QSignalMapper(self)
        self.__actionMapper.mapped[QObject].connect(self.__onTabActionToogled)

    def setTabButtonHeight(self, height):
        """
        Set the tab button height.
        """
        if self.__tabButtonHeight != height:
            self.__tabButtonHeight = height
            for page in self.__pages:
                page.button.setFixedHeight(height)

    def tabButtonHeight(self):
        """
        Return the tab button height.
        """
        return self.__tabButtonHeight

    def setTabIconSize(self, size):
        """
        Set the tab button icon size.
        """
        if self.__tabIconSize != size:
            self.__tabIconSize = size
            for page in self.__pages:
                page.button.setIconSize(size)

    def tabIconSize(self):
        """
        Return the tab icon size.
        """
        return self.__tabIconSize

    def tabButton(self, index):
        """
        Return the tab button at `index`
        """
        return self.__pages[index].button

    def tabAction(self, index):
        """
        Return open/close action for the tab at `index`.
        """
        return self.__pages[index].action

    def addItem(self, widget, text, icon=None, toolTip=None):
        """
        Append the `widget` in a new tab and return its index.

        Parameters
        ----------
        widget : :class:`QWidget`
            A widget to be inserted. The toolbox takes ownership
            of the widget.

        text : str
            Name/title of the new tab.

        icon : :class:`QIcon`, optional
            An icon for the tab button.

        toolTip : str, optional
            Tool tip for the tab button.

        """
        return self.insertItem(self.count(), widget, text, icon, toolTip)

    def insertItem(self, index, widget, text, icon=None, toolTip=None):
        """
        Insert the `widget` in a new tab at position `index`.

        See also
        --------
        ToolBox.addItem

        """
        button = self.createTabButton(widget, text, icon, toolTip)

        self.__contentsLayout.insertWidget(index * 2, button)
        self.__contentsLayout.insertWidget(index * 2 + 1, widget)

        widget.hide()

        page = _ToolBoxPage(index, widget, button.defaultAction(), button)
        self.__pages.insert(index, page)

        for i in range(index + 1, self.count()):
            self.__pages[i] = self.__pages[i]._replace(index=i)

        self.__updatePositions()

        # Show (open) the first tab.
        if self.count() == 1 and index == 0:
            page.action.trigger()

        self.__updateSelected()

        self.updateGeometry()
        return index

    def removeItem(self, index):
        """
        Remove the widget at `index`.

        .. note:: The widget hidden but is is not deleted.

        """
        self.__contentsLayout.takeAt(2 * index + 1)
        self.__contentsLayout.takeAt(2 * index)
        page = self.__pages.pop(index)

        # Update the page indexes
        for i in range(index, self.count()):
            self.__pages[i] = self.__pages[i]._replace(index=i)

        page.button.deleteLater()

        # Hide the widget and reparent to self
        # This follows QToolBox.removeItem
        page.widget.hide()
        page.widget.setParent(self)

        self.__updatePositions()
        self.__updateSelected()

        self.updateGeometry()

    def count(self):
        """
        Return the number of widgets inserted in the toolbox.
        """
        return len(self.__pages)

    def widget(self, index):
        """
        Return the widget at `index`.
        """
        return self.__pages[index].widget

    def createTabButton(self, widget, text, icon=None, toolTip=None):
        """
        Create the tab button for `widget`.
        """
        action = QAction(text, self)
        action.setCheckable(True)

        if icon:
            action.setIcon(icon)

        if toolTip:
            action.setToolTip(toolTip)
        self.__tabActionGroup.addAction(action)
        self.__actionMapper.setMapping(action, action)
        action.toggled.connect(self.__actionMapper.map)

        button = ToolBoxTabButton(self, objectName="toolbox-tab-button")
        button.setDefaultAction(action)
        button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        if self.__tabIconSize.isValid():
            button.setIconSize(self.__tabIconSize)

        if self.__tabButtonHeight > 0:
            button.setFixedHeight(self.__tabButtonHeight)

        return button

    def ensureWidgetVisible(self, child, xmargin=50, ymargin=50):
        """
        Scroll the contents so child widget instance is visible inside
        the viewport.

        """
        self.__scrollArea.ensureWidgetVisible(child, xmargin, ymargin)

    def sizeHint(self):
        hint = self.__contentsLayout.sizeHint()

        if self.count():
            # Compute max width of hidden widgets also.
            scroll = self.__scrollArea
            scroll_w = scroll.verticalScrollBar().sizeHint().width()
            frame_w = self.frameWidth() * 2 + scroll.frameWidth() * 2
            max_w = max([p.widget.sizeHint().width() for p in self.__pages])
            hint = QSize(
                max(max_w, hint.width()) + scroll_w + frame_w, hint.height())

        return QSize(200, 200).expandedTo(hint)

    def __onTabActionToogled(self, action):
        page = find(self.__pages, action, key=attrgetter("action"))
        on = action.isChecked()
        page.widget.setVisible(on)
        index = page.index

        if index > 0:
            # Update the `previous` tab buttons style hints
            previous = self.__pages[index - 1].button
            flag = QStyleOptionToolBoxV2.NextIsSelected
            if on:
                previous.selected |= flag
            else:
                previous.selected &= ~flag

            previous.update()

        if index < self.count() - 1:
            next = self.__pages[index + 1].button
            flag = QStyleOptionToolBoxV2.PreviousIsSelected
            if on:
                next.selected |= flag
            else:
                next.selected &= ~flag

            next.update()

        self.tabToogled.emit(index, on)

        self.__contentsLayout.invalidate()

    def __updateSelected(self):
        """Update the tab buttons selected style flags.
        """
        if self.count() == 0:
            return

        opt = QStyleOptionToolBoxV2

        def update(button, next_sel, prev_sel):
            if next_sel:
                button.selected |= opt.NextIsSelected
            else:
                button.selected &= ~opt.NextIsSelected

            if prev_sel:
                button.selected |= opt.PreviousIsSelected
            else:
                button.selected &= ~opt.PreviousIsSelected

            button.update()

        if self.count() == 1:
            update(self.__pages[0].button, False, False)
        elif self.count() >= 2:
            pages = self.__pages
            for i in range(1, self.count() - 1):
                update(pages[i].button, pages[i + 1].action.isChecked(),
                       pages[i - 1].action.isChecked())

    def __updatePositions(self):
        """Update the tab buttons position style flags.
        """
        if self.count() == 0:
            return
        elif self.count() == 1:
            self.__pages[0].button.position = QStyleOptionToolBoxV2.OnlyOneTab
        else:
            self.__pages[0].button.position = QStyleOptionToolBoxV2.Beginning
            self.__pages[-1].button.position = QStyleOptionToolBoxV2.End
            for p in self.__pages[1:-1]:
                p.button.position = QStyleOptionToolBoxV2.Middle

        for p in self.__pages:
            p.button.update()
예제 #18
0
class WidgetToolBox(ToolBox):
    """
    `WidgetToolBox` widget shows a tool box containing button grids of
    actions for a :class:`QtWidgetRegistry` item model.

    """

    triggered = Signal(QAction)
    hovered = Signal(QAction)

    def __init__(self, parent=None):
        ToolBox.__init__(self, parent)
        self.__model = None
        self.__iconSize = QSize(25, 25)
        self.__buttonSize = QSize(50, 50)
        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)

    def setIconSize(self, size):
        """
        Set the widget icon size (icons in the button grid).
        """
        self.__iconSize = size
        for widget in map(self.widget, range(self.count())):
            widget.setIconSize(size)

    def iconSize(self):
        """
        Return the widget buttons icon size.
        """
        return self.__iconSize

    iconSize_ = Property(QSize,
                         fget=iconSize,
                         fset=setIconSize,
                         designable=True)

    def setButtonSize(self, size):
        """
        Set fixed widget button size.
        """
        self.__buttonSize = size
        for widget in map(self.widget, range(self.count())):
            widget.setButtonSize(size)

    def buttonSize(self):
        """Return the widget button size
        """
        return self.__buttonSize

    buttonSize_ = Property(QSize,
                           fget=buttonSize,
                           fset=setButtonSize,
                           designable=True)

    def saveState(self):
        """
        Return the toolbox state (as a `QByteArray`).

        .. note:: Individual tabs are stored by their action's text.

        """
        version = 2

        actions = map(self.tabAction, range(self.count()))
        expanded = [action for action in actions if action.isChecked()]
        expanded = [action.text() for action in expanded]

        byte_array = QByteArray()
        stream = QDataStream(byte_array, QIODevice.WriteOnly)
        stream.writeInt(version)
        stream.writeQStringList(expanded)

        return byte_array

    def restoreState(self, state):
        """
        Restore the toolbox from a :class:`QByteArray` `state`.

        .. note:: The toolbox should already be populated for the state
                  changes to take effect.

        """
        # In version 1 of saved state the state was saved in
        # a simple dict repr string.
        if isinstance(state, QByteArray):
            stream = QDataStream(state, QIODevice.ReadOnly)
            version = stream.readInt()
            if version == 2:
                expanded = stream.readQStringList()
                for action in map(self.tabAction, range(self.count())):
                    if (action.text() in expanded) != action.isChecked():
                        action.trigger()

                return True
        return False

    def setModel(self, model):
        """
        Set the widget registry model (:class:`QStandardItemModel`) for
        this toolbox.

        """
        if self.__model is not None:
            self.__model.itemChanged.disconnect(self.__on_itemChanged)
            self.__model.rowsInserted.disconnect(self.__on_rowsInserted)
            self.__model.rowsRemoved.disconnect(self.__on_rowsRemoved)

        self.__model = model
        if self.__model is not None:
            self.__model.itemChanged.connect(self.__on_itemChanged)
            self.__model.rowsInserted.connect(self.__on_rowsInserted)
            self.__model.rowsRemoved.connect(self.__on_rowsRemoved)

        self.__initFromModel(self.__model)

    def __initFromModel(self, model):
        for cat_item in iter_item(model.invisibleRootItem()):
            self.__insertItem(cat_item, self.count())

    def __insertItem(self, item, index):
        """
        Insert category item at index.
        """
        grid = WidgetToolGrid()
        grid.setModel(item.model(), item.index())

        grid.actionTriggered.connect(self.triggered)
        grid.actionHovered.connect(self.hovered)

        grid.setIconSize(self.__iconSize)
        grid.setButtonSize(self.__buttonSize)

        text = item.text()
        icon = item.icon()
        tooltip = item.toolTip()

        # Set the 'tab-title' property to text.
        grid.setProperty("tab-title", text)
        grid.setObjectName("widgets-toolbox-grid")

        self.insertItem(index, grid, text, icon, tooltip)
        button = self.tabButton(index)

        # Set the 'highlight' color
        if item.data(Qt.BackgroundRole) is not None:
            brush = item.background()
        elif item.data(QtWidgetRegistry.BACKGROUND_ROLE) is not None:
            brush = item.data(QtWidgetRegistry.BACKGROUND_ROLE)
        else:
            brush = self.palette().brush(QPalette.Button)

        if not brush.gradient():
            gradient = create_gradient(brush.color())
            brush = QBrush(gradient)

        palette = button.palette()
        palette.setBrush(QPalette.Highlight, brush)
        button.setPalette(palette)

    def __on_itemChanged(self, item):
        """
        Item contents have changed.
        """
        parent = item.parent()
        if parent is self.__model.invisibleRootItem():
            button = self.tabButton(item.row())
            button.setIcon(item.icon())
            button.setText(item.text())
            button.setToolTip(item.toolTip())

    def __on_rowsInserted(self, parent, start, end):
        """
        Items have been inserted in the model.
        """
        # Only the top level items (categories) are handled here.
        if not parent is not None:
            root = self.__model.invisibleRootItem()
            for i in range(start, end + 1):
                item = root.child(i)
                self.__insertItem(item, i)

    def __on_rowsRemoved(self, parent, start, end):
        """
        Rows have been removed from the model.
        """
        # Only the top level items (categories) are handled here.
        if not parent is not None:
            for i in range(end, start - 1, -1):
                self.removeItem(i)
class Scheme(QObject):
    """
    An :class:`QObject` subclass representing the scheme widget workflow
    with annotations.

    Parameters
    ----------
    parent : :class:`QObject`
        A parent QObject item (default `None`).
    title : str
        The scheme title.
    description : str
        A longer description of the scheme.


    Attributes
    ----------
    nodes : list of :class:`.SchemeNode`
        A list of all the nodes in the scheme.

    links : list of :class:`.SchemeLink`
        A list of all links in the scheme.

    annotations : list of :class:`BaseSchemeAnnotation`
        A list of all the annotations in the scheme.

    """

    # Signal emitted when a `node` is added to the scheme.
    node_added = Signal(SchemeNode)

    # Signal emitted when a `node` is removed from the scheme.
    node_removed = Signal(SchemeNode)

    # Signal emitted when a `link` is added to the scheme.
    link_added = Signal(SchemeLink)

    # Signal emitted when a `link` is removed from the scheme.
    link_removed = Signal(SchemeLink)

    # Signal emitted when a `annotation` is added to the scheme.
    annotation_added = Signal(BaseSchemeAnnotation)

    # Signal emitted when a `annotation` is removed from the scheme.
    annotation_removed = Signal(BaseSchemeAnnotation)

    # Signal emitted when the title of scheme changes.
    title_changed = Signal(unicode)

    # Signal emitted when the description of scheme changes.
    description_changed = Signal(unicode)

    node_state_changed = Signal()
    channel_state_changed = Signal()
    topology_changed = Signal()

    def __init__(self, parent=None, title=None, description=None):
        QObject.__init__(self, parent)

        self.__title = title or ""
        "Scheme title (empty string by default)."

        self.__description = description or ""
        "Scheme description (empty string by default)."

        self.__annotations = []
        self.__nodes = []
        self.__links = []

    @property
    def nodes(self):
        """
        A list of all nodes (:class:`.SchemeNode`) currently in the scheme.
        """
        return list(self.__nodes)

    @property
    def links(self):
        """
        A list of all links (:class:`.SchemeLink`) currently in the scheme.
        """
        return list(self.__links)

    @property
    def annotations(self):
        """
        A list of all annotations (:class:`.BaseSchemeAnnotation`) in the
        scheme.

        """
        return list(self.__annotations)

    def set_title(self, title):
        """
        Set the scheme title text.
        """
        if self.__title != title:
            self.__title = title
            self.title_changed.emit(title)

    def title(self):
        """
        The title (human readable string) of the scheme.
        """
        return self.__title

    title = Property(unicode, fget=title, fset=set_title)

    def set_description(self, description):
        """
        Set the scheme description text.
        """
        if self.__description != description:
            self.__description = description
            self.description_changed.emit(description)

    def description(self):
        """
        Scheme description text.
        """
        return self.__description

    description = Property(unicode, fget=description, fset=set_description)

    def add_node(self, node):
        """
        Add a node to the scheme. An error is raised if the node is
        already in the scheme.

        Parameters
        ----------
        node : :class:`.SchemeNode`
            Node instance to add to the scheme.

        """
        check_arg(node not in self.__nodes, "Node already in scheme.")
        check_type(node, SchemeNode)

        self.__nodes.append(node)
        log.info("Added node %r to scheme %r." % (node.title, self.title))
        self.node_added.emit(node)

    def new_node(self,
                 description,
                 title=None,
                 position=None,
                 properties=None):
        """
        Create a new :class:`.SchemeNode` and add it to the scheme.

        Same as::

            scheme.add_node(SchemeNode(description, title, position,
                                       properties))

        Parameters
        ----------
        description : :class:`WidgetDescription`
            The new node's description.
        title : str, optional
            Optional new nodes title. By default `description.name` is used.
        position : `(x, y)` tuple of floats, optional
            Optional position in a 2D space.
        properties : dict, optional
            A dictionary of optional extra properties.

        See also
        --------
        .SchemeNode, Scheme.add_node

        """
        if isinstance(description, WidgetDescription):
            node = SchemeNode(description,
                              title=title,
                              position=position,
                              properties=properties)
        else:
            raise TypeError("Expected %r, got %r." % \
                            (WidgetDescription, type(description)))

        self.add_node(node)
        return node

    def remove_node(self, node):
        """
        Remove a `node` from the scheme. All links into and out of the
        `node` are also removed. If the node in not in the scheme an error
        is raised.

        Parameters
        ----------
        node : :class:`.SchemeNode`
            Node instance to remove.

        """
        check_arg(node in self.__nodes, "Node is not in the scheme.")

        self.__remove_node_links(node)
        self.__nodes.remove(node)
        log.info("Removed node %r from scheme %r." % (node.title, self.title))
        self.node_removed.emit(node)
        return node

    def __remove_node_links(self, node):
        """
        Remove all links for node.
        """
        links_in, links_out = [], []
        for link in self.__links:
            if link.source_node is node:
                links_out.append(link)
            elif link.sink_node is node:
                links_in.append(link)

        for link in links_out + links_in:
            self.remove_link(link)

    def add_link(self, link):
        """
        Add a `link` to the scheme.

        Parameters
        ----------
        link : :class:`.SchemeLink`
            An initialized link instance to add to the scheme.

        """
        check_type(link, SchemeLink)

        self.check_connect(link)
        self.__links.append(link)

        log.info("Added link %r (%r) -> %r (%r) to scheme %r." % \
                 (link.source_node.title, link.source_channel.name,
                  link.sink_node.title, link.sink_channel.name,
                  self.title)
                 )

        self.link_added.emit(link)

    def new_link(self, source_node, source_channel, sink_node, sink_channel):
        """
        Create a new :class:`.SchemeLink` from arguments and add it to
        the scheme. The new link is returned.

        Parameters
        ----------
        source_node : :class:`.SchemeNode`
            Source node of the new link.
        source_channel : :class:`.OutputSignal`
            Source channel of the new node. The instance must be from
            ``source_node.output_channels()``
        sink_node : :class:`.SchemeNode`
            Sink node of the new link.
        sink_channel : :class:`.InputSignal`
            Sink channel of the new node. The instance must be from
            ``sink_node.input_channels()``

        See also
        --------
        .SchemeLink, Scheme.add_link

        """
        link = SchemeLink(source_node, source_channel, sink_node, sink_channel)
        self.add_link(link)
        return link

    def remove_link(self, link):
        """
        Remove a link from the scheme.

        Parameters
        ----------
        link : :class:`.SchemeLink`
            Link instance to remove.

        """
        check_arg(link in self.__links, "Link is not in the scheme.")

        self.__links.remove(link)
        log.info("Removed link %r (%r) -> %r (%r) from scheme %r." % \
                 (link.source_node.title, link.source_channel.name,
                  link.sink_node.title, link.sink_channel.name,
                  self.title)
                 )
        self.link_removed.emit(link)

    def check_connect(self, link):
        """
        Check if the `link` can be added to the scheme and raise an
        appropriate exception.

        Can raise:
            - :class:`TypeError` if `link` is not an instance of
              :class:`.SchemeLink`
            - :class:`.SchemeCycleError` if the `link` would introduce a cycle
            - :class:`.IncompatibleChannelTypeError` if the channel types are
              not compatible
            - :class:`.SinkChannelError` if a sink channel has a `Single` flag
              specification and the channel is already connected.
            - :class:`.DuplicatedLinkError` if a `link` duplicates an already
              present link.

        """
        check_type(link, SchemeLink)

        if self.creates_cycle(link):
            raise SchemeCycleError("Cannot create cycles in the scheme")

        if not self.compatible_channels(link):
            raise IncompatibleChannelTypeError(
                    "Cannot connect %r to %r." \
                    % (link.source_channel.type, link.sink_channel.type)
                )

        links = self.find_links(source_node=link.source_node,
                                source_channel=link.source_channel,
                                sink_node=link.sink_node,
                                sink_channel=link.sink_channel)

        if links:
            raise DuplicatedLinkError(
                    "A link from %r (%r) -> %r (%r) already exists" \
                    % (link.source_node.title, link.source_channel.name,
                       link.sink_node.title, link.sink_channel.name)
                )

        if link.sink_channel.single:
            links = self.find_links(sink_node=link.sink_node,
                                    sink_channel=link.sink_channel)
            if links:
                raise SinkChannelError("%r is already connected." %
                                       link.sink_channel.name)

    def creates_cycle(self, link):
        """
        Return `True` if `link` would introduce a cycle in the scheme.

        Parameters
        ----------
        link : :class:`.SchemeLink`

        """
        check_type(link, SchemeLink)
        source_node, sink_node = link.source_node, link.sink_node
        upstream = self.upstream_nodes(source_node)
        upstream.add(source_node)
        return sink_node in upstream

    def compatible_channels(self, link):
        """
        Return `True` if the channels in `link` have compatible types.

        Parameters
        ----------
        link : :class:`.SchemeLink`

        """
        check_type(link, SchemeLink)
        return compatible_channels(link.source_channel, link.sink_channel)

    def can_connect(self, link):
        """
        Return `True` if `link` can be added to the scheme.

        See also
        --------
        Scheme.check_connect

        """
        check_type(link, SchemeLink)
        try:
            self.check_connect(link)
            return True
        except (SchemeCycleError, IncompatibleChannelTypeError,
                SinkChannelError, DuplicatedLinkError):
            return False

    def upstream_nodes(self, start_node):
        """
        Return a set of all nodes upstream from `start_node` (i.e.
        all ancestor nodes).

        Parameters
        ----------
        start_node : :class:`.SchemeNode`

        """
        visited = set()
        queue = deque([start_node])
        while queue:
            node = queue.popleft()
            snodes = [link.source_node for link in self.input_links(node)]
            for source_node in snodes:
                if source_node not in visited:
                    queue.append(source_node)

            visited.add(node)
        visited.remove(start_node)
        return visited

    def downstream_nodes(self, start_node):
        """
        Return a set of all nodes downstream from `start_node`.

        Parameters
        ----------
        start_node : :class:`.SchemeNode`

        """
        visited = set()
        queue = deque([start_node])
        while queue:
            node = queue.popleft()
            snodes = [link.sink_node for link in self.output_links(node)]
            for source_node in snodes:
                if source_node not in visited:
                    queue.append(source_node)

            visited.add(node)
        visited.remove(start_node)
        return visited

    def is_ancestor(self, node, child):
        """
        Return True if `node` is an ancestor node of `child` (is upstream
        of the child in the workflow). Both nodes must be in the scheme.

        Parameters
        ----------
        node : :class:`.SchemeNode`
        child : :class:`.SchemeNode`

        """
        return child in self.downstream_nodes(node)

    def children(self, node):
        """
        Return a set of all children of `node`.
        """
        return set(link.sink_node for link in self.output_links(node))

    def parents(self, node):
        """
        Return a set of all parents of `node`.
        """
        return set(link.source_node for link in self.input_links(node))

    def input_links(self, node):
        """
        Return a list of all input links (:class:`.SchemeLink`) connected
        to the `node` instance.

        """
        return self.find_links(sink_node=node)

    def output_links(self, node):
        """
        Return a list of all output links (:class:`.SchemeLink`) connected
        to the `node` instance.

        """
        return self.find_links(source_node=node)

    def find_links(self,
                   source_node=None,
                   source_channel=None,
                   sink_node=None,
                   sink_channel=None):
        # TODO: Speedup - keep index of links by nodes and channels
        result = []
        match = lambda query, value: (query is None or value == query)
        for link in self.__links:
            if match(source_node, link.source_node) and \
                    match(sink_node, link.sink_node) and \
                    match(source_channel, link.source_channel) and \
                    match(sink_channel, link.sink_channel):
                result.append(link)

        return result

    def propose_links(self, source_node, sink_node):
        """
        Return a list of ordered (:class:`OutputSignal`,
        :class:`InputSignal`, weight) tuples that could be added to
        the scheme between `source_node` and `sink_node`.

        .. note:: This can depend on the links already in the scheme.

        """
        if source_node is sink_node or \
                self.is_ancestor(sink_node, source_node):
            # Cyclic connections are not possible.
            return []

        outputs = source_node.output_channels()
        inputs = sink_node.input_channels()

        # Get existing links to sink channels that are Single.
        links = self.find_links(None, None, sink_node)
        already_connected_sinks = [link.sink_channel for link in links \
                                   if link.sink_channel.single]

        def weight(out_c, in_c):
            if out_c.explicit or in_c.explicit:
                # Zero weight for explicit links
                weight = 0
            else:
                check = [
                    not out_c.dynamic,  # Dynamic signals are last
                    in_c not in already_connected_sinks,
                    bool(in_c.default),
                    bool(out_c.default)
                ]
                weights = [2**i for i in range(len(check), 0, -1)]
                weight = sum([w for w, c in zip(weights, check) if c])
            return weight

        proposed_links = []
        for out_c in outputs:
            for in_c in inputs:
                if compatible_channels(out_c, in_c):
                    proposed_links.append((out_c, in_c, weight(out_c, in_c)))

        return sorted(proposed_links, key=itemgetter(-1), reverse=True)

    def add_annotation(self, annotation):
        """
        Add an annotation (:class:`BaseSchemeAnnotation` subclass) instance
        to the scheme.

        """
        check_arg(annotation not in self.__annotations,
                  "Cannot add the same annotation multiple times.")
        check_type(annotation, BaseSchemeAnnotation)

        self.__annotations.append(annotation)
        self.annotation_added.emit(annotation)

    def remove_annotation(self, annotation):
        """
        Remove the `annotation` instance from the scheme.
        """
        check_arg(annotation in self.__annotations,
                  "Annotation is not in the scheme.")
        self.__annotations.remove(annotation)
        self.annotation_removed.emit(annotation)

    def clear(self):
        """
        Remove all nodes, links, and annotation items from the scheme.
        """
        def is_terminal(node):
            return not bool(self.find_links(source_node=node))

        while self.nodes:
            terminal_nodes = filter(is_terminal, self.nodes)
            for node in terminal_nodes:
                self.remove_node(node)

        for annotation in self.annotations:
            self.remove_annotation(annotation)

        assert (not (self.nodes or self.links or self.annotations))

    def save_to(self, stream, pretty=True, pickle_fallback=False):
        """
        Save the scheme as an xml formated file to `stream`

        See also
        --------
        .scheme_to_ows_stream

        """
        if isinstance(stream, basestring):
            stream = open(stream, "wb")

        scheme_to_ows_stream(self,
                             stream,
                             pretty,
                             pickle_fallback=pickle_fallback)

    def load_from(self, stream):
        """
        Load the scheme from xml formated stream.
        """
        if self.__nodes or self.__links or self.__annotations:
            # TODO: should we clear the scheme and load it.
            raise ValueError("Scheme is not empty.")

        if isinstance(stream, basestring):
            stream = open(stream, "rb")

        parse_scheme(self, stream)
예제 #20
0
파일: dock.py 프로젝트: yisuax11/orange2
class CollapsibleDockWidget(QDockWidget):
    """
    This :class:`QDockWidget` subclass overrides the `close` header
    button to instead collapse to a smaller size. The contents contents
    to show when in each state can be set using the ``setExpandedWidget``
    and ``setCollapsedWidget``.

    .. note:: Do  not use the base class ``QDockWidget.setWidget`` method
              to set the docks contents. Use set[Expanded|Collapsed]Widget
              instead.

    """

    #: Emitted when the dock widget's expanded state changes.
    expandedChanged = Signal(bool)

    def __init__(self, *args, **kwargs):
        QDockWidget.__init__(self, *args, **kwargs)

        self.__expandedWidget = None
        self.__collapsedWidget = None
        self.__expanded = True

        self.__trueMinimumWidth = -1

        self.setFeatures(QDockWidget.DockWidgetClosable | \
                         QDockWidget.DockWidgetMovable)
        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.featuresChanged.connect(self.__onFeaturesChanged)
        self.dockLocationChanged.connect(self.__onDockLocationChanged)

        # Use the toolbar horizontal extension button icon as the default
        # for the expand/collapse button
        pm = self.style().standardPixmap(
            QStyle.SP_ToolBarHorizontalExtensionButton
        )

        # Rotate the icon
        transform = QTransform()
        transform.rotate(180)

        pm_rev = pm.transformed(transform)

        self.__iconRight = QIcon(pm)
        self.__iconLeft = QIcon(pm_rev)

        close = self.findChild(QAbstractButton,
                               name="qt_dockwidget_closebutton")

        close.installEventFilter(self)
        self.__closeButton = close

        self.__stack = AnimatedStackedWidget()

        self.__stack.setSizePolicy(QSizePolicy.Fixed,
                                   QSizePolicy.Expanding)

        self.__stack.transitionStarted.connect(self.__onTransitionStarted)
        self.__stack.transitionFinished.connect(self.__onTransitionFinished)

        QDockWidget.setWidget(self, self.__stack)

        self.__closeButton.setIcon(self.__iconLeft)

    def setExpanded(self, state):
        """
        Set the widgets `expanded` state.
        """
        if self.__expanded != state:
            self.__expanded = state
            if state and self.__expandedWidget is not None:
                log.debug("Dock expanding.")
                self.__stack.setCurrentWidget(self.__expandedWidget)
            elif not state and self.__collapsedWidget is not None:
                log.debug("Dock collapsing.")
                self.__stack.setCurrentWidget(self.__collapsedWidget)
            self.__fixIcon()

            self.expandedChanged.emit(state)

    def expanded(self):
        """
        Is the dock widget in expanded state. If `True` the
        ``expandedWidget`` will be shown, and ``collapsedWidget`` otherwise.

        """
        return self.__expanded

    expanded_ = Property(bool, fset=setExpanded, fget=expanded)

    def setWidget(self, w):
        raise NotImplementedError(
                "Please use the 'setExpandedWidget'/'setCollapsedWidget' "
                "methods to set the contents of the dock widget."
              )

    def setExpandedWidget(self, widget):
        """
        Set the widget with contents to show while expanded.
        """
        if widget is self.__expandedWidget:
            return

        if self.__expandedWidget is not None:
            self.__stack.removeWidget(self.__expandedWidget)

        self.__stack.insertWidget(0, widget)
        self.__expandedWidget = widget

        if self.__expanded:
            self.__stack.setCurrentWidget(widget)
            self.updateGeometry()

    def expandedWidget(self):
        """
        Return the widget previously set with ``setExpandedWidget``,
        or ``None`` if no widget has been set.

        """
        return self.__expandedWidget

    def setCollapsedWidget(self, widget):
        """
        Set the widget with contents to show while collapsed.
        """
        if widget is self.__collapsedWidget:
            return

        if self.__collapsedWidget is not None:
            self.__stack.removeWidget(self.__collapsedWidget)

        self.__stack.insertWidget(1, widget)
        self.__collapsedWidget = widget

        if not self.__expanded:
            self.__stack.setCurrentWidget(widget)
            self.updateGeometry()

    def collapsedWidget(self):
        """
        Return the widget previously set with ``setCollapsedWidget``,
        or ``None`` if no widget has been set.

        """
        return self.__collapsedWidget

    def setAnimationEnabled(self, animationEnabled):
        """
        Enable/disable the transition animation.
        """
        self.__stack.setAnimationEnabled(animationEnabled)

    def animationEnabled(self):
        """
        Is transition animation enabled.
        """
        return self.__stack.animationEnabled()

    def currentWidget(self):
        """
        Return the current shown widget depending on the `expanded` state.
        """
        if self.__expanded:
            return self.__expandedWidget
        else:
            return self.__collapsedWidget

    def expand(self):
        """
        Expand the dock (same as ``setExpanded(True)``)
        """
        self.setExpanded(True)

    def collapse(self):
        """
        Collapse the dock (same as ``setExpanded(False)``)
        """
        self.setExpanded(False)

    def eventFilter(self, obj, event):
        if obj is self.__closeButton:
            etype = event.type()
            if etype == QEvent.MouseButtonPress:
                self.setExpanded(not self.__expanded)
                return True
            elif etype == QEvent.MouseButtonDblClick or \
                    etype == QEvent.MouseButtonRelease:
                return True
            # TODO: which other events can trigger the button (is the button
            # focusable).

        return QDockWidget.eventFilter(self, obj, event)

    def event(self, event):
        if event.type() == QEvent.LayoutRequest:
            self.__fixMinimumWidth()

        return QDockWidget.event(self, event)

    def __onFeaturesChanged(self, features):
        pass

    def __onDockLocationChanged(self, area):
        if area == Qt.LeftDockWidgetArea:
            self.setLayoutDirection(Qt.LeftToRight)
        else:
            self.setLayoutDirection(Qt.RightToLeft)

        self.__stack.setLayoutDirection(self.parentWidget().layoutDirection())
        self.__fixIcon()

    def __onTransitionStarted(self):
        log.debug("Dock transition started.")

    def __onTransitionFinished(self):
        log.debug("Dock transition finished (new width %i)",
                  self.size().width())

    def __fixMinimumWidth(self):
        # A workaround for forcing the QDockWidget layout to disregard the
        # default minimumSize which can be to wide for us (overriding the
        # minimumSizeHint or setting the minimum size directly does not
        # seem to have an effect (Qt 4.8.3).
        size = self.__stack.sizeHint()
        if size.isValid() and not size.isEmpty():
            left, _, right, _ = self.getContentsMargins()
            width = size.width() + left + right

            if width < self.minimumSizeHint().width():
                if not self.__hasFixedWidth():
                    log.debug("Overriding default minimum size "
                              "(setFixedWidth(%i))", width)
                    self.__trueMinimumWidth = self.minimumSizeHint().width()
                self.setFixedWidth(width)
            else:
                if self.__hasFixedWidth():
                    if width >= self.__trueMinimumWidth:
                        # Unset the fixed size.
                        log.debug("Restoring default minimum size "
                                  "(setFixedWidth(%i))", QWIDGETSIZE_MAX)
                        self.__trueMinimumWidth = -1
                        self.setFixedWidth(QWIDGETSIZE_MAX)
                        self.updateGeometry()
                    else:
                        self.setFixedWidth(width)

    def __hasFixedWidth(self):
        return self.__trueMinimumWidth >= 0

    def __fixIcon(self):
        """Fix the dock close icon.
        """
        direction = self.layoutDirection()
        if direction == Qt.LeftToRight:
            if self.__expanded:
                icon = self.__iconLeft
            else:
                icon = self.__iconRight
        else:
            if self.__expanded:
                icon = self.__iconRight
            else:
                icon = self.__iconLeft

        self.__closeButton.setIcon(icon)
예제 #21
0
class SchemeLink(QObject):
    """
    A instantiation of a link between two :class:`.SchemeNode` instances
    in a :class:`.Scheme`.

    Parameters
    ----------
    source_node : :class:`.SchemeNode`
        Source node.
    source_channel : :class:`OutputSignal`
        The source widget's signal.
    sink_node : :class:`.SchemeNode`
        The sink node.
    sink_channel : :class:`InputSignal`
        The sink widget's input signal.
    properties : `dict`
        Additional link properties.

    """

    #: The link enabled state has changed
    enabled_changed = Signal(bool)

    #: The link dynamic enabled state has changed.
    dynamic_enabled_changed = Signal(bool)

    def __init__(self,
                 source_node,
                 source_channel,
                 sink_node,
                 sink_channel,
                 enabled=True,
                 properties=None,
                 parent=None):
        QObject.__init__(self, parent)
        self.source_node = source_node

        if isinstance(source_channel, basestring):
            source_channel = source_node.output_channel(source_channel)
        elif source_channel not in source_node.output_channels():
            raise ValueError("%r not in in nodes output channels." \
                             % source_channel)

        self.source_channel = source_channel

        self.sink_node = sink_node

        if isinstance(sink_channel, basestring):
            sink_channel = sink_node.input_channel(sink_channel)
        elif sink_channel not in sink_node.input_channels():
            raise ValueError("%r not in in nodes input channels." \
                             % source_channel)

        self.sink_channel = sink_channel

        if not compatible_channels(source_channel, sink_channel):
            raise IncompatibleChannelTypeError(
                    "Cannot connect %r to %r" \
                    % (source_channel.type, sink_channel.type)
                )

        self.__enabled = enabled
        self.__dynamic_enabled = False
        self.__tool_tip = ""
        self.properties = properties or {}

    def source_type(self):
        """
        Return the type of the source channel.
        """
        return name_lookup(self.source_channel.type)

    def sink_type(self):
        """
        Return the type of the sink channel.
        """
        return name_lookup(self.sink_channel.type)

    def is_dynamic(self):
        """
        Is this link dynamic.
        """
        return self.source_channel.dynamic and \
            issubclass(self.sink_type(), self.source_type()) and \
            not (self.sink_type() is self.source_type())

    def set_enabled(self, enabled):
        """
        Enable/disable the link.
        """
        if self.__enabled != enabled:
            self.__enabled = enabled
            self.enabled_changed.emit(enabled)

    def enabled(self):
        """
        Is this link enabled.
        """
        return self.__enabled

    enabled = Property(bool, fget=enabled, fset=set_enabled)

    def set_dynamic_enabled(self, enabled):
        """
        Enable/disable the dynamic link. Has no effect if the link
        is not dynamic.

        """
        if self.is_dynamic() and self.__dynamic_enabled != enabled:
            self.__dynamic_enabled = enabled
            self.dynamic_enabled_changed.emit(enabled)

    def dynamic_enabled(self):
        """
        Is this a dynamic link and is `dynamic_enabled` set to `True`
        """
        return self.is_dynamic() and self.__dynamic_enabled

    dynamic_enabled = Property(bool,
                               fget=dynamic_enabled,
                               fset=set_dynamic_enabled)

    def set_tool_tip(self, tool_tip):
        """
        Set the link tool tip.
        """
        if self.__tool_tip != tool_tip:
            self.__tool_tip = tool_tip

    def tool_tip(self):
        """
        Link tool tip.
        """
        return self.__tool_tip

    tool_tip = Property(str, fget=tool_tip, fset=set_tool_tip)

    def __str__(self):
        return u"{0}(({1}, {2}) -> ({3}, {4}))".format(
            type(self).__name__, self.source_node.title,
            self.source_channel.name, self.sink_node.title,
            self.sink_channel.name)
예제 #22
0
class ControlPointRect(QGraphicsObject):
    Free = 0
    KeepAspectRatio = 1
    KeepCenter = 2

    rectChanged = Signal(QRectF)
    rectEdited = Signal(QRectF)

    def __init__(self, parent=None, rect=None, constraints=0, **kwargs):
        QGraphicsObject.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemHasNoContents)
        self.setFlag(QGraphicsItem.ItemIsFocusable)

        self.__rect = rect if rect is not None else QRectF()
        self.__margins = QMargins()
        points = \
            [ControlPoint(self, ControlPoint.Left),
             ControlPoint(self, ControlPoint.Top),
             ControlPoint(self, ControlPoint.TopLeft),
             ControlPoint(self, ControlPoint.Right),
             ControlPoint(self, ControlPoint.TopRight),
             ControlPoint(self, ControlPoint.Bottom),
             ControlPoint(self, ControlPoint.BottomLeft),
             ControlPoint(self, ControlPoint.BottomRight)
             ]
        assert(points == sorted(points, key=lambda p: p.anchor()))

        self.__points = dict((p.anchor(), p) for p in points)

        if self.scene():
            self.__installFilter()

        for p in points:
            p.setFlag(QGraphicsItem.ItemIsFocusable)
            p.setFocusProxy(self)

        self.controlPoint(ControlPoint.Top).setConstraint(Qt.Vertical)
        self.controlPoint(ControlPoint.Bottom).setConstraint(Qt.Vertical)
        self.controlPoint(ControlPoint.Left).setConstraint(Qt.Horizontal)
        self.controlPoint(ControlPoint.Right).setConstraint(Qt.Horizontal)

        self.__constraints = constraints
        self.__activeControl = None

        self.__pointsLayout()

    def controlPoint(self, anchor):
        """
        Return the anchor point (:class:`ControlPoint`) at anchor position
        or `None` if an anchor point is not set.

        """
        return self.__points.get(anchor)

    def setRect(self, rect):
        """
        Set the control point rectangle (:class:`QRectF`)
        """
        if self.__rect != rect:
            self.__rect = QRectF(rect)
            self.__pointsLayout()
            self.prepareGeometryChange()
            self.rectChanged.emit(rect.normalized())

    def rect(self):
        """
        Return the control point rectangle.
        """
        # Return the rect normalized. During the control point move the
        # rect can change to an invalid size, but the layout must still
        # know to which point does an unnormalized rect side belong,
        # so __rect is left unnormalized.
        # NOTE: This means all signal emits (rectChanged/Edited) must
        #       also emit normalized rects
        return self.__rect.normalized()

    rect_ = Property(QRectF, fget=rect, fset=setRect, user=True)

    def setControlMargins(self, *margins):
        """Set the controls points on the margins around `rect`
        """
        if len(margins) > 1:
            margins = QMargins(*margins)
        else:
            margins = margins[0]
            if isinstance(margins, int):
                margins = QMargins(margins, margins, margins, margins)

        if self.__margins != margins:
            self.__margins = margins
            self.__pointsLayout()

    def controlMargins(self):
        return self.__margins

    def setConstraints(self, constraints):
        raise NotImplementedError

    def isControlActive(self):
        """Return the state of the control. True if the control is
        active (user is dragging one of the points) False otherwise.

        """
        return self.__activeControl is not None

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSceneHasChanged and self.scene():
            self.__installFilter()

        return QGraphicsObject.itemChange(self, change, value)

    def sceneEventFilter(self, obj, event):
        try:
            obj = toGraphicsObjectIfPossible(obj)
            if isinstance(obj, ControlPoint):
                etype = event.type()
                if etype == QEvent.GraphicsSceneMousePress and \
                        event.button() == Qt.LeftButton:
                    self.__setActiveControl(obj)

                elif etype == QEvent.GraphicsSceneMouseRelease and \
                        event.button() == Qt.LeftButton:
                    self.__setActiveControl(None)

        except Exception:
            log.error("Error in 'ControlPointRect.sceneEventFilter'",
                      exc_info=True)

        return QGraphicsObject.sceneEventFilter(self, obj, event)

    def __installFilter(self):
        # Install filters on the control points.
        try:
            for p in self.__points.values():
                p.installSceneEventFilter(self)
        except Exception:
            log.error("Error in ControlPointRect.__installFilter",
                      exc_info=True)

    def __pointsLayout(self):
        """Layout the control points
        """
        rect = self.__rect
        margins = self.__margins
        rect = rect.adjusted(-margins.left(), -margins.top(),
                             margins.right(), margins.bottom())
        center = rect.center()
        cx, cy = center.x(), center.y()
        left, top, right, bottom = \
                rect.left(), rect.top(), rect.right(), rect.bottom()

        self.controlPoint(ControlPoint.Left).setPos(left, cy)
        self.controlPoint(ControlPoint.Right).setPos(right, cy)
        self.controlPoint(ControlPoint.Top).setPos(cx, top)
        self.controlPoint(ControlPoint.Bottom).setPos(cx, bottom)

        self.controlPoint(ControlPoint.TopLeft).setPos(left, top)
        self.controlPoint(ControlPoint.TopRight).setPos(right, top)
        self.controlPoint(ControlPoint.BottomLeft).setPos(left, bottom)
        self.controlPoint(ControlPoint.BottomRight).setPos(right, bottom)

    def __setActiveControl(self, control):
        if self.__activeControl != control:
            if self.__activeControl is not None:
                self.__activeControl.positionChanged[QPointF].disconnect(
                    self.__activeControlMoved
                )

            self.__activeControl = control

            if control is not None:
                control.positionChanged[QPointF].connect(
                    self.__activeControlMoved
                )

    def __activeControlMoved(self, pos):
        # The active control point has moved, update the control
        # rectangle
        control = self.__activeControl
        pos = control.pos()
        rect = QRectF(self.__rect)
        margins = self.__margins

        # TODO: keyboard modifiers and constraints.

        anchor = control.anchor()
        if anchor & ControlPoint.Top:
            rect.setTop(pos.y() + margins.top())
        elif anchor & ControlPoint.Bottom:
            rect.setBottom(pos.y() - margins.bottom())

        if anchor & ControlPoint.Left:
            rect.setLeft(pos.x() + margins.left())
        elif anchor & ControlPoint.Right:
            rect.setRight(pos.x() - margins.right())

        changed = self.__rect != rect

        self.blockSignals(True)
        self.setRect(rect)
        self.blockSignals(False)

        if changed:
            self.rectEdited.emit(rect.normalized())

    def boundingRect(self):
        return QRectF()
예제 #23
0
class DropShadowFrame(QWidget):
    """
    A widget drawing a drop shadow effect around the geometry of
    another widget (works similar to :class:`QFocusFrame`).

    Parameters
    ----------
    parent : :class:`QObject`
        Parent object.
    color : :class:`QColor`
        The color of the drop shadow.
    radius : float
        Shadow radius.

    """
    def __init__(self, parent=None, color=None, radius=5,
                 **kwargs):
        QWidget.__init__(self, parent, **kwargs)
        self.setAttribute(Qt.WA_TransparentForMouseEvents, True)
        self.setAttribute(Qt.WA_NoChildEventsForParent, True)
        self.setFocusPolicy(Qt.NoFocus)

        if color is None:
            color = self.palette().color(QPalette.Dark)

        self.__color = color
        self.__radius = radius

        self.__widget = None
        self.__widgetParent = None
        self.__updatePixmap()

    def setColor(self, color):
        """
        Set the color of the shadow.
        """
        if not isinstance(color, QColor):
            color = QColor(color)

        if self.__color != color:
            self.__color = QColor(color)
            self.__updatePixmap()

    def color(self):
        """
        Return the color of the drop shadow.
        """
        return QColor(self.__color)

    color_ = Property(QColor, fget=color, fset=setColor, designable=True,
                      doc="Drop shadow color")

    def setRadius(self, radius):
        """
        Set the drop shadow's blur radius.
        """
        if self.__radius != radius:
            self.__radius = radius
            self.__updateGeometry()
            self.__updatePixmap()

    def radius(self):
        """
        Return the shadow blur radius.
        """
        return self.__radius

    radius_ = Property(int, fget=radius, fset=setRadius, designable=True,
                       doc="Drop shadow blur radius.")

    def setWidget(self, widget):
        """
        Set the widget around which to show the shadow.
        """
        if self.__widget:
            self.__widget.removeEventFilter(self)

        self.__widget = widget

        if self.__widget:
            self.__widget.installEventFilter(self)
            # Find the parent for the frame
            # This is the top level window a toolbar or a viewport
            # of a scroll area
            parent = widget.parentWidget()
            while not (isinstance(parent, (QAbstractScrollArea, QToolBar)) or \
                       parent.isWindow()):
                parent = parent.parentWidget()

            if isinstance(parent, QAbstractScrollArea):
                parent = parent.viewport()

            self.__widgetParent = parent
            self.setParent(parent)
            self.stackUnder(widget)
            self.__updateGeometry()
            self.setVisible(widget.isVisible())

    def widget(self):
        """
        Return the widget that was set by `setWidget`.
        """
        return self.__widget

    def paintEvent(self, event):
        # TODO: Use QPainter.drawPixmapFragments on Qt 4.7
        opt = QStyleOption()
        opt.initFrom(self)

        pixmap = self.__shadowPixmap

        shadow_rect = QRectF(opt.rect)
        widget_rect = QRectF(self.widget().geometry())
        widget_rect.moveTo(self.radius_, self.radius_)

        left = top = right = bottom = self.radius_
        pixmap_rect = QRectF(QPointF(0, 0), QSizeF(pixmap.size()))

        # Shadow casting rectangle in the source pixmap.
        pixmap_shadow_rect = pixmap_rect.adjusted(left, top, -right, -bottom)
        source_rects = self.__shadowPixmapFragments(pixmap_rect,
                                                   pixmap_shadow_rect)
        target_rects = self.__shadowPixmapFragments(shadow_rect, widget_rect)

        painter = QPainter(self)
        for source, target in zip(source_rects, target_rects):
            painter.drawPixmap(target, pixmap, source)
        painter.end()

    def eventFilter(self, obj, event):
        etype = event.type()
        if etype == QEvent.Move or etype == QEvent.Resize:
            self.__updateGeometry()
        elif etype == QEvent.Show:
            self.__updateGeometry()
            self.show()
        elif etype == QEvent.Hide:
            self.hide()
        return QWidget.eventFilter(self, obj, event)

    def __updateGeometry(self):
        """
        Update the shadow geometry to fit the widget's changed
        geometry.

        """
        widget = self.__widget
        parent = self.__widgetParent
        radius = self.radius_
        pos = widget.pos()
        if parent != widget.parentWidget():
            pos = widget.parentWidget().mapTo(parent, pos)

        geom = QRect(pos, widget.size())
        geom.adjust(-radius, -radius, radius, radius)
        if geom != self.geometry():
            self.setGeometry(geom)

        # Set the widget mask (punch a hole through to the `widget` instance.
        rect = self.rect()

        mask = QRegion(rect)
        transparent = QRegion(rect.adjusted(radius, radius, -radius, -radius))

        mask = mask.subtracted(transparent)
        self.setMask(mask)

    def __updatePixmap(self):
        """
        Update the cached shadow pixmap.
        """
        rect_size = QSize(50, 50)
        left = top = right = bottom = self.radius_

        # Size of the pixmap.
        pixmap_size = QSize(rect_size.width() + left + right,
                            rect_size.height() + top + bottom)
        shadow_rect = QRect(QPoint(left, top), rect_size)
        pixmap = QPixmap(pixmap_size)
        pixmap.fill(QColor(0, 0, 0, 0))
        rect_fill_color = self.palette().color(QPalette.Window)

        pixmap = render_drop_shadow_frame(
                      pixmap,
                      QRectF(shadow_rect),
                      shadow_color=self.color_,
                      offset=QPointF(0, 0),
                      radius=self.radius_,
                      rect_fill_color=rect_fill_color
                      )

        self.__shadowPixmap = pixmap
        self.update()

    def __shadowPixmapFragments(self, pixmap_rect, shadow_rect):
        """
        Return a list of 8 QRectF fragments for drawing a shadow.
        """
        s_left, s_top, s_right, s_bottom = \
            shadow_rect.left(), shadow_rect.top(), \
            shadow_rect.right(), shadow_rect.bottom()
        s_width, s_height = shadow_rect.width(), shadow_rect.height()
        p_width, p_height = pixmap_rect.width(), pixmap_rect.height()

        top_left = QRectF(0.0, 0.0, s_left, s_top)
        top = QRectF(s_left, 0.0, s_width, s_top)
        top_right = QRectF(s_right, 0.0, p_width - s_width, s_top)
        right = QRectF(s_right, s_top, p_width - s_right, s_height)
        right_bottom = QRectF(shadow_rect.bottomRight(),
                              pixmap_rect.bottomRight())
        bottom = QRectF(shadow_rect.bottomLeft(),
                        pixmap_rect.bottomRight() - \
                        QPointF(p_width - s_right, 0.0))
        bottom_left = QRectF(shadow_rect.bottomLeft() - QPointF(s_left, 0.0),
                             pixmap_rect.bottomLeft() + QPointF(s_left, 0.0))
        left = QRectF(pixmap_rect.topLeft() + QPointF(0.0, s_top),
                      shadow_rect.bottomLeft())
        return [top_left, top, top_right, right, right_bottom,
                bottom, bottom_left, left]
예제 #24
0
class CrossFadePixmapWidget(QWidget):
    """
    A widget for cross fading between two pixmaps.
    """
    def __init__(self, parent=None, pixmap1=None, pixmap2=None):
        QWidget.__init__(self, parent)
        self.setPixmap(pixmap1)
        self.setPixmap2(pixmap2)
        self.blendingFactor_ = 0.0
        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

    def setPixmap(self, pixmap):
        """
        Set pixmap 1
        """
        self.pixmap1 = pixmap
        self.updateGeometry()

    def setPixmap2(self, pixmap):
        """
        Set pixmap 2
        """
        self.pixmap2 = pixmap
        self.updateGeometry()

    def setBlendingFactor(self, factor):
        """
        Set the blending factor between the two pixmaps.
        """
        self.__blendingFactor = factor
        self.updateGeometry()

    def blendingFactor(self):
        """
        Pixmap blending factor between 0.0 and 1.0
        """
        return self.__blendingFactor

    blendingFactor_ = Property(float, fget=blendingFactor,
                               fset=setBlendingFactor)

    def sizeHint(self):
        """
        Return an interpolated size between pixmap1.size()
        and pixmap2.size()

        """
        if self.pixmap1 and self.pixmap2:
            size1 = self.pixmap1.size()
            size2 = self.pixmap2.size()
            return size1 + self.blendingFactor_ * (size2 - size1)
        else:
            return QWidget.sizeHint(self)

    def paintEvent(self, event):
        """
        Paint the interpolated pixmap image.
        """
        p = QPainter(self)
        p.setClipRect(event.rect())
        factor = self.blendingFactor_ ** 2
        if self.pixmap1 and 1. - factor:
            p.setOpacity(1. - factor)
            p.drawPixmap(QPoint(0, 0), self.pixmap1)
        if self.pixmap2 and factor:
            p.setOpacity(factor)
            p.drawPixmap(QPoint(0, 0), self.pixmap2)
예제 #25
0
class MenuPage(ToolTree):
    """
    A menu page in a :class:`QuickMenu` widget, showing a list of actions.
    Shown actions can be disabled by setting a filtering function using the
    :func:`setFilterFunc`.

    """
    def __init__(self, parent=None, title=None, icon=None, **kwargs):
        ToolTree.__init__(self, parent, **kwargs)

        if title is None:
            title = ""

        if icon is None:
            icon = QIcon()

        self.__title = title
        self.__icon = icon
        self.__sizeHint = None

        self.view().setItemDelegate(_MenuItemDelegate(self.view()))
        self.view().entered.connect(self.__onEntered)
        self.view().viewport().setMouseTracking(True)

        # Make sure the initial model is wrapped in a ItemDisableFilter.
        self.setModel(self.model())

    def setTitle(self, title):
        """
        Set the title of the page.
        """
        if self.__title != title:
            self.__title = title
            self.update()

    def title(self):
        """
        Return the title of this page.
        """
        return self.__title

    title_ = Property(unicode,
                      fget=title,
                      fset=setTitle,
                      doc="Title of the page.")

    def setIcon(self, icon):
        """
        Set icon for this menu page.
        """
        if self.__icon != icon:
            self.__icon = icon
            self.update()

    def icon(self):
        """
        Return the icon of this manu page.
        """
        return self.__icon

    icon_ = Property(QIcon, fget=icon, fset=setIcon, doc="Page icon")

    def setFilterFunc(self, func):
        """
        Set the filtering function. `func` should a function taking a single
        :class:`QModelIndex` argument and returning True if the item at index
        should be disabled and False otherwise. To disable filtering `func` can
        be set to ``None``.

        """
        proxyModel = self.view().model()
        proxyModel.setFilterFunc(func)

    def setModel(self, model):
        """
        Reimplemented from :func:`ToolTree.setModel`.
        """
        proxyModel = ItemDisableFilter(self)
        proxyModel.setSourceModel(model)
        ToolTree.setModel(self, proxyModel)

        self.__invalidateSizeHint()

    def setRootIndex(self, index):
        """
        Reimplemented from :func:`ToolTree.setRootIndex`
        """
        proxyModel = self.view().model()
        mappedIndex = proxyModel.mapFromSource(index)
        ToolTree.setRootIndex(self, mappedIndex)

        self.__invalidateSizeHint()

    def rootIndex(self):
        """
        Reimplemented from :func:`ToolTree.rootIndex`
        """
        proxyModel = self.view().model()
        return proxyModel.mapToSource(ToolTree.rootIndex(self))

    def sizeHint(self):
        """
        Reimplemented from :func:`QWidget.sizeHint`.
        """
        if self.__sizeHint is None:
            view = self.view()
            model = view.model()

            # This will not work for nested items (tree).
            count = model.rowCount(view.rootIndex())

            # 'sizeHintForColumn' is the reason for size hint caching
            # since it must traverse all items in the column.
            width = view.sizeHintForColumn(0)

            if count:
                height = view.sizeHintForRow(0)
                height = height * count
            else:
                height = 0
            self.__sizeHint = QSize(width, height)

        return self.__sizeHint

    def __invalidateSizeHint(self):
        self.__sizeHint = None
        self.updateGeometry()

    def __onEntered(self, index):
        if not index.isValid():
            return

        if self.view().state() != QTreeView.NoState:
            # The item view can emit an 'entered' signal while the model/view
            # is being changed (rows removed). When this happens, setting the
            # current item can segfault (in QTreeView::scrollTo).
            return

        if index.flags() & Qt.ItemIsEnabled:
            self.view().selectionModel().setCurrentIndex(
                index, QItemSelectionModel.ClearAndSelect)
예제 #26
0
class FramelessWindow(QWidget):
    """
    A basic frameless window widget with rounded corners (if supported by
    the windowing system).

    """
    def __init__(self, parent=None, **kwargs):
        QWidget.__init__(self, parent, **kwargs)
        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)

        self.__radius = 6
        self.__isTransparencySupported = is_transparency_supported()
        self.setAttribute(Qt.WA_TranslucentBackground,
                          self.__isTransparencySupported)

    def setRadius(self, radius):
        """
        Set the window rounded border radius.
        """
        if self.__radius != radius:
            self.__radius = radius
            if not self.__isTransparencySupported:
                self.__updateMask()
            self.update()

    def radius(self):
        """
        Return the border radius.
        """
        return self.__radius

    radius_ = Property(int,
                       fget=radius,
                       fset=setRadius,
                       designable=True,
                       doc="Window border radius")

    def resizeEvent(self, event):
        QWidget.resizeEvent(self, event)
        if not self.__isTransparencySupported:
            self.__updateMask()

    def __updateMask(self):
        opt = QStyleOption()
        opt.init(self)
        rect = opt.rect

        size = rect.size()
        mask = QBitmap(size)

        p = QPainter(mask)
        p.setRenderHint(QPainter.Antialiasing)
        p.setBrush(Qt.black)
        p.setPen(Qt.NoPen)
        p.drawRoundedRect(rect, self.__radius, self.__radius)
        p.end()

        self.setMask(mask)

    def paintEvent(self, event):
        if self.__isTransparencySupported:
            opt = QStyleOption()
            opt.init(self)
            rect = opt.rect

            p = QPainter(self)
            p.setRenderHint(QPainter.Antialiasing, True)
            p.setBrush(opt.palette.brush(QPalette.Window))
            p.setPen(Qt.NoPen)
            p.drawRoundedRect(rect, self.__radius, self.__radius)
            p.end()
        else:
            StyledWidget_paintEvent(self, event)
예제 #27
0
파일: toolbox.py 프로젝트: yisuax11/orange2
class ToolBoxTabButton(QToolButton):
    """
    A tab button for an item in a :class:`ToolBox`.
    """
    def setNativeStyling(self, state):
        """
        Render tab buttons as native (or css styled) :class:`QToolButtons`.
        If set to `False` (default) the button is pained using a custom
        paint routine.

        """
        self.__nativeStyling = state
        self.update()

    def nativeStyling(self):
        """
        Use :class:`QStyle`'s to paint the class:`QToolButton` look.
        """
        return self.__nativeStyling

    nativeStyling_ = Property(bool,
                              fget=nativeStyling,
                              fset=setNativeStyling,
                              designable=True)

    def __init__(self, *args, **kwargs):
        self.__nativeStyling = False
        self.position = QStyleOptionToolBoxV2.OnlyOneTab
        self.selected = QStyleOptionToolBoxV2.NotAdjacent

        QToolButton.__init__(self, *args, **kwargs)

    def paintEvent(self, event):
        if self.__nativeStyling:
            QToolButton.paintEvent(self, event)
        else:
            self.__paintEventNoStyle()

    def __paintEventNoStyle(self):
        p = QPainter(self)
        opt = QStyleOptionToolButton()
        self.initStyleOption(opt)

        fm = QFontMetrics(opt.font)
        palette = opt.palette

        # highlight brush is used as the background for the icon and background
        # when the tab is expanded and as mouse hover color (lighter).
        brush_highlight = palette.highlight()
        if opt.state & QStyle.State_Sunken:
            # State 'down' pressed during a mouse press (slightly darker).
            background_brush = brush_darker(brush_highlight, 110)
        elif opt.state & QStyle.State_MouseOver:
            background_brush = brush_darker(brush_highlight, 95)
        elif opt.state & QStyle.State_On:
            background_brush = brush_highlight
        else:
            # The default button brush.
            background_brush = palette.button()

        rect = opt.rect
        icon = opt.icon
        icon_size = opt.iconSize

        # TODO: add shift for pressed as set by the style (PM_ButtonShift...)

        pm = None
        if not icon.isNull():
            if opt.state & QStyle.State_Enabled:
                mode = QIcon.Normal
            else:
                mode = QIcon.Disabled

            pm = opt.icon.pixmap(
                rect.size().boundedTo(icon_size), mode,
                QIcon.On if opt.state & QStyle.State_On else QIcon.Off)

        icon_area_rect = QRect(rect)
        icon_area_rect.setRight(int(icon_area_rect.height() * 1.26))

        text_rect = QRect(rect)
        text_rect.setLeft(icon_area_rect.right() + 10)

        # Background  (TODO: Should the tab button have native
        # toolbutton shape, drawn using PE_PanelButtonTool or even
        # QToolBox tab shape)

        # Default outline pen
        pen = QPen(palette.color(QPalette.Mid))

        p.save()
        p.setPen(Qt.NoPen)
        p.setBrush(QBrush(background_brush))
        p.drawRect(rect)

        # Draw the background behind the icon if the background_brush
        # is different.
        if not opt.state & QStyle.State_On:
            p.setBrush(brush_highlight)
            p.drawRect(icon_area_rect)
            # Line between the icon and text
            p.setPen(pen)
            p.drawLine(icon_area_rect.topRight(), icon_area_rect.bottomRight())

        if opt.state & QStyle.State_HasFocus:
            # Set the focus frame pen and draw the border
            pen = QPen(QColor(FOCUS_OUTLINE_COLOR))
            p.setPen(pen)
            p.setBrush(Qt.NoBrush)
            # Adjust for pen
            rect = rect.adjusted(0, 0, -1, -1)
            p.drawRect(rect)

        else:
            p.setPen(pen)
            # Draw the top/bottom border
            if self.position == QStyleOptionToolBoxV2.OnlyOneTab or \
                    self.position == QStyleOptionToolBoxV2.Beginning or \
                    self.selected & \
                        QStyleOptionToolBoxV2.PreviousIsSelected:

                p.drawLine(rect.topLeft(), rect.topRight())

            p.drawLine(rect.bottomLeft(), rect.bottomRight())

        p.restore()

        p.save()
        text = fm.elidedText(opt.text, Qt.ElideRight, text_rect.width())
        p.setPen(QPen(palette.color(QPalette.ButtonText)))
        p.setFont(opt.font)

        p.drawText(text_rect,
                   int(Qt.AlignVCenter | Qt.AlignLeft) | \
                   int(Qt.TextSingleLine),
                   text)
        if pm:
            pm_rect = QRect(QPoint(0, 0), pm.size())
            centered_rect = QRect(pm_rect)
            centered_rect.moveCenter(icon_area_rect.center())
            p.drawPixmap(centered_rect, pm, pm_rect)
        p.restore()
예제 #28
0
파일: link.py 프로젝트: waqarini/orange3
class SchemeLink(QObject):
    """
    A instantiation of a link between two :class:`.SchemeNode` instances
    in a :class:`.Scheme`.

    Parameters
    ----------
    source_node : :class:`.SchemeNode`
        Source node.
    source_channel : :class:`OutputSignal`
        The source widget's signal.
    sink_node : :class:`.SchemeNode`
        The sink node.
    sink_channel : :class:`InputSignal`
        The sink widget's input signal.
    properties : `dict`
        Additional link properties.

    """

    #: The link enabled state has changed
    enabled_changed = Signal(bool)

    #: The link dynamic enabled state has changed.
    dynamic_enabled_changed = Signal(bool)

    #: Runtime link state has changed
    state_changed = Signal(int)

    class State(enum.IntEnum):
        """
        Flags indicating the runtime state of a link
        """
        #: A link is empty when it has no value on it
        Empty = 0
        #: A link is active when the source node provides a value on output
        Active = 1
        #: A link is pending when it's sink node has not yet been notified
        #: of a change (note that Empty|Pending is a valid state)
        Pending = 2

    Empty, Active, Pending = State

    def __init__(self,
                 source_node,
                 source_channel,
                 sink_node,
                 sink_channel,
                 enabled=True,
                 properties=None,
                 parent=None):
        QObject.__init__(self, parent)
        self.source_node = source_node

        if isinstance(source_channel, str):
            source_channel = source_node.output_channel(source_channel)
        elif source_channel not in source_node.output_channels():
            raise ValueError("%r not in in nodes output channels." \
                             % source_channel)

        self.source_channel = source_channel

        self.sink_node = sink_node

        if isinstance(sink_channel, str):
            sink_channel = sink_node.input_channel(sink_channel)
        elif sink_channel not in sink_node.input_channels():
            raise ValueError("%r not in in nodes input channels." \
                             % source_channel)

        self.sink_channel = sink_channel

        if not compatible_channels(source_channel, sink_channel):
            raise IncompatibleChannelTypeError(
                    "Cannot connect %r to %r" \
                    % (source_channel.type, sink_channel.type)
                )

        self.__enabled = enabled
        self.__dynamic_enabled = False
        self.__state = SchemeLink.Empty
        self.__tool_tip = ""
        self.properties = properties or {}

    def source_type(self):
        """
        Return the type of the source channel.
        """
        return name_lookup(self.source_channel.type)

    def sink_type(self):
        """
        Return the type of the sink channel.
        """
        return name_lookup(self.sink_channel.type)

    def is_dynamic(self):
        """
        Is this link dynamic.
        """
        return self.source_channel.dynamic and \
            issubclass(self.sink_type(), self.source_type()) and \
            not (self.sink_type() is self.source_type())

    def set_enabled(self, enabled):
        """
        Enable/disable the link.
        """
        if self.__enabled != enabled:
            self.__enabled = enabled
            self.enabled_changed.emit(enabled)

    def enabled(self):
        """
        Is this link enabled.
        """
        return self.__enabled

    enabled = Property(bool, fget=enabled, fset=set_enabled)

    def set_dynamic_enabled(self, enabled):
        """
        Enable/disable the dynamic link. Has no effect if the link
        is not dynamic.

        """
        if self.is_dynamic() and self.__dynamic_enabled != enabled:
            self.__dynamic_enabled = enabled
            self.dynamic_enabled_changed.emit(enabled)

    def dynamic_enabled(self):
        """
        Is this a dynamic link and is `dynamic_enabled` set to `True`
        """
        return self.is_dynamic() and self.__dynamic_enabled

    dynamic_enabled = Property(bool,
                               fget=dynamic_enabled,
                               fset=set_dynamic_enabled)

    def set_runtime_state(self, state):
        """
        Set the link's runtime state.

        Parameters
        ----------
        state : SchemeLink.State
        """
        if self.__state != state:
            self.__state = state
            self.state_changed.emit(state)

    def runtime_state(self):
        """
        Returns
        -------
        state : SchemeLink.State
        """
        return self.__state

    def set_tool_tip(self, tool_tip):
        """
        Set the link tool tip.
        """
        if self.__tool_tip != tool_tip:
            self.__tool_tip = tool_tip

    def tool_tip(self):
        """
        Link tool tip.
        """
        return self.__tool_tip

    tool_tip = Property(str, fget=tool_tip, fset=set_tool_tip)

    def __str__(self):
        return "{0}(({1}, {2}) -> ({3}, {4}))".format(
            type(self).__name__, self.source_node.title,
            self.source_channel.name, self.sink_node.title,
            self.sink_channel.name)