Esempio n. 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
Esempio n. 2
0
class ColorButton(QPushButton):
    """
    Color choosing push button
    """

    colorChanged = Signal(QColor)

    def __init__(self, parent=None):
        QPushButton.__init__(self, parent)
        self.setFixedSize(20, 20)
        self.setIconSize(QSize(12, 12))
        self.clicked.connect(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.colorChanged.emit(self._color)
            pixmap = QPixmap(self.iconSize())
            pixmap.fill(color)
            self.setIcon(QIcon(pixmap))

    color = Property("QColor", get_color, set_color)
Esempio n. 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)
Esempio n. 4
0
def Q_ENUMS_mock(classvars, enumclass):  #do not use, PySide2 workaround

    values = [
        a for a in dir(enumclass)
        if not a.startswith('__') and not callable(getattr(enumclass, a))
    ]

    for v in values:
        classvars[f'{v}'] = Property(int,
                                     ConstGetter(getattr(enumclass, v)),
                                     constant=True)
Esempio n. 5
0
def makeProperty(name, propertyType=str, **kwargs):
    storageVarName = "_" + name

    def g(self):
        return eval("self." + storageVarName)

    def s(self, val):
        exec("self." + storageVarName + "=val")
        if "notify" in kwargs.keys():
            kwargs["notify"].emit()

    return Property(propertyType, g, s)
Esempio n. 6
0
def ConstProperty(classvars, typename, name):
    '''
        This function adds a QProperty named 'name' to a class's vars() dictionary.
        It create the getter.

        *Important* a member variable named '_name' will be expected by the getter.

        A QProperty is exposed to QML.
    '''
    goc_member_variable(classvars, name)
    classvars[f'{name}'] = Property(typename,
                                    select_getter(typename, name),
                                    constant=True)
Esempio n. 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 six.text_type(self.__placeholderText)

    placeholderText_ = Property(six.text_type,
                                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)
Esempio n. 8
0
class REButton(CodeButton):

    runInThread = Property(bool, designable=False)  # Disabling property

    def __init__(self, parent):
        super().__init__(parent)
        self._plots = "[]"
        self._plotFields = "[[]]"
        self._plotKwargsList = "[{}]"
        self._RunEngine = "RE"

    plots = makeProperty("plots")
    plotFields = makeProperty("plotFields")
    plotKwargsList = makeProperty("plotKwargsList")
    RunEngine = makeProperty("RunEngine")
Esempio n. 9
0
def RWProperty(classvars, typename, name):
    '''
        This function adds a QProperty named 'name' to a class's vars() dictionary.
        It create the getter, setter, and signal named 'nameChanged'.

        *Important* a member variable named '_name' will be expected by the getter and setter.

        A QProperty is exposed to QML.
    '''
    goc_member_variable(classvars, name)
    notify = classvars[f'{name}Changed'] = Signal()
    classvars[f'{name}'] = Property(typename,
                                    select_getter(typename, name),
                                    Setter(name),
                                    notify=notify)
Esempio n. 10
0
class Transform(Product.Product):
    def __init__(self, parent=None):
        super(Transform, self).__init__(parent)
        self._localTransform = QMatrix4x4()
        self._parentTransform = None

    Q_CLASSINFO('DefaultProperty', 'parentTransform')

    localTransformChanged = Signal()

    def local_transform(self):
        return self._localTransform

    localTransform = Property(QMatrix4x4,
                              local_transform,
                              notify=localTransformChanged)

    def set_local_transform(self, matrix4x4):
        if matrix4x4 is None:
            matrix4x4 = QMatrix4x4()
        Product.assign_input(self, "localTransform", matrix4x4)

    Product.InputProperty(vars(), Product.Product, 'parentTransform')

    @Slot(result=QMatrix4x4)
    def worldTransform(self, update=False):
        if update:
            self.update()
        assert not self.dirty
        return self.localTransform if self.parentTransform is None \
               else self.parentTransform.worldTransform() * self.localTransform

    @Slot(QVector3D, float, result=QQuaternion)
    def qFromAA(self, axis, angle_rad):
        return QQuaternion.fromAxisAndAngle(axis, math.degrees(angle_rad))

    @Slot(float, float, float, result=QQuaternion)
    def qFromEuler(self, roll, pitch, yaw):
        return QQuaternion.fromEulerAngles(math.degrees(roll),
                                           math.degrees(pitch),
                                           math.degrees(yaw))

    @Slot(QQuaternion, QVector3D, result=QMatrix4x4)
    def mFromTQ(self, t=QVector3D(), q=QQuaternion()):
        m = QMatrix4x4()
        m.rotate(q)
        m.translate(t)
        return m
Esempio n. 11
0
def ROProperty(classvars, typename, name):
    '''
        This function adds a QProperty named 'name' to a class's vars() dictionary.
        It creates the getter, and signal named 'nameChanged'. It also creates
        a set_name() setter outside of the Qt property system.

        *Important* a member variable named '_name' will be expected by the getter.

        A QProperty is exposed to QML.
    '''
    goc_member_variable(classvars, name)
    notify = classvars[f'{name}Changed'] = Signal()
    classvars[f'{name}'] = Property(typename,
                                    select_getter(typename, name),
                                    notify=notify)
    classvars[f'set_{name}'] = Setter(name)
Esempio n. 12
0
def InputProperty(classvars, typename, name, callback=None):
    '''
        This function adds a QProperty named 'name' to a class's vars() dictionary.
        It create the getter, setter, and signal named 'nameChanged'.

        *Important* a member variable named '_name' will be expected by the getter and setter.

        'callback' will be called if
        (and only if) a new value is set. see InputSetter for more information on 'callback'
        A QProperty is exposed to QML.
        An InputProperty is a property that turns a product dirty when needed.
        It can be a primitive type (e.g. int, string, bool, etc) or a Product,
        or a collection containing products
    '''
    goc_member_variable(classvars, name)
    notify = classvars[f'{name}Changed'] = Signal()
    classvars[f'{name}'] = Property(typename,
                                    select_getter(typename, name),
                                    InputSetter(classvars, name, callback),
                                    notify=notify)
Esempio n. 13
0
class GraphicsWidgetView(QGraphicsView):
    """
    A Graphics view with a single central QGraphicsWidget which is resized
    fo fit into the view.
    """
    __centralWidget: Optional[QGraphicsWidget] = None
    __fitInView = True
    __aspectMode = Qt.KeepAspectRatio
    __widgetResizable = False
    __zoomFactor = 100

    def __init__(self, *args, widgetResizable=False, **kwargs):
        super().__init__(*args, **kwargs)
        self.__widgetResizable = widgetResizable
        self.__zoomFactor = 100

        zoomin = QAction(
            "Zoom in",
            self,
            objectName="zoom-in-action",
            shortcut=QKeySequence.ZoomIn,
        )
        zoomout = QAction(
            "Zoom out",
            self,
            objectName="zoom-out-action",
            shortcut=QKeySequence.ZoomOut,
        )
        zoomreset = QAction(
            "Actual Size",
            self,
            objectName="zoom-reset-action",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),
        )
        fit = QAction(
            "Zoom to fit",
            self,
            objectName="zoom-to-fit-action",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_9),
            checkable=True,
        )

        if hasattr(QAction, "setShortcutVisibleInContextMenu"):  # Qt 5.10
            for a in [zoomin, zoomout, zoomreset, fit]:
                a.setShortcutVisibleInContextMenu(True)

        @zoomin.triggered.connect
        def _():
            self.setZoomFactor(self.__zoomFactor + 10)

        @zoomout.triggered.connect
        def _():
            self.setZoomFactor(self.__zoomFactor - 10)

        @zoomreset.triggered.connect
        def _():
            self.__zoomFactor = -1
            self.setZoomFactor(100.)

        @fit.toggled.connect
        def _(state):
            self.setFitInView(state)

        self.addActions([zoomin, zoomout, zoomreset, fit])
        self._actions = SimpleNamespace(zoomin=zoomin,
                                        zoomout=zoomout,
                                        zoomreset=zoomreset,
                                        fit=fit)

    def viewActions(self) -> List[QAction]:
        return [
            self._actions.zoomout, self._actions.zoomin,
            self._actions.zoomreset, self._actions.fit
        ]

    def setZoomFactor(self, factor: float) -> None:
        """
        Set the zoom level `factor`

        Parameters
        ----------
        factor:
            Zoom level where 100 is default 50 is half the size and 200 is
            twice the size
        """
        if self.__zoomFactor != factor or self.__fitInView:
            self.__fitInView = False
            self._actions.fit.setChecked(False)
            self.__zoomFactor = factor
            self.setTransform(
                QTransform.fromScale(*(self.__zoomFactor / 100, ) * 2))
            self._actions.zoomout.setEnabled(factor >= 20)
            self._actions.zoomin.setEnabled(factor <= 300)
            self.zoomFactorChanged.emit(factor)
            if self.__widgetResizable:
                self._resizeToFit()

    def zoomFactor(self) -> float:
        """
        Returns
        -------
        factor: float
            The zoom factor.
        """
        return self.__zoomFactor

    zoomFactorChanged = Signal(int)
    zoomFactor_ = Property(int,
                           zoomFactor,
                           setZoomFactor,
                           notify=zoomFactorChanged)

    def viewportEvent(self, event: QEvent) -> bool:
        if event.type() == QEvent.Resize:
            self._layout()
        return super().viewportEvent(event)

    def setCentralWidget(self, widget: Optional[QGraphicsWidget]) -> None:
        """
        Set the central widget. Previous widget (if set) is unset.
        The widget needs to be in this view's `scene()`
        """
        if self.__centralWidget is not None:
            self.__centralWidget.removeEventFilter(self)
            self.__centralWidget.destroyed.disconnect(
                self.__on_centralWidgetDestroyed)
        self.__centralWidget = widget
        if widget is not None:
            widget.installEventFilter(self)
            widget.destroyed.connect(self.__on_centralWidgetDestroyed)
            self._layout()

    def centralWidget(self) -> Optional[QGraphicsWidget]:
        """Return the central widget."""
        return self.__centralWidget

    @Slot(QObject)
    def __on_centralWidgetDestroyed(self):
        self.__centralWidget = None

    def widgetResizable(self) -> bool:
        """
        Should the central widget be resized (via .resize()) to match the view.
        or should the view's scale be updated instead.
        """
        return self.__widgetResizable

    def setWidgetResizable(self, resizable: bool) -> None:
        """
        Parameters
        ----------
        resizable: bool
        """
        if self.__widgetResizable != resizable:
            self.__widgetResizable = resizable
            QApplication.postEvent(self, QEvent(QEvent.LayoutRequest))

    def setFitInView(self, enabled: bool) -> None:
        if self.__fitInView != enabled:
            self.__fitInView = enabled
            self._actions.fit.setChecked(enabled)
            if enabled:
                if self.__widgetResizable:
                    self._resizeToFit()
                else:
                    self._scaleToFit()

    def setAspectMode(self, mode: Qt.AspectRatioMode) -> None:
        if self.__aspectMode != mode:
            self.__aspectMode = mode
            if self.__fitInView:
                self._scaleToFit()
            elif self.__widgetResizable:
                self._resizeToFit()

    def eventFilter(self, recv: QObject, event: QEvent) -> bool:
        if event.type() == QEvent.LayoutRequest \
                and recv is self.__centralWidget:
            self._layout()
        return super().eventFilter(recv, event)

    def _layout(self) -> None:
        widget = self.__centralWidget
        if widget is None:
            return
        if self.__widgetResizable:
            self._resizeToFit()
        else:
            self._scaleToFit()

    def _resizeToFit(self):
        widget = self.__centralWidget
        size = self.__viewportContentSize()
        vprect = self.viewport().geometry()
        vprect.setSize(size)
        margins = self.viewportMargins()
        vprect = vprect.marginsRemoved(margins)

        viewrect = self.mapToScene(vprect).boundingRect()
        targetsize = viewrect.size()
        maxsize = widget.maximumSize()
        minsize = widget.minimumSize()
        targetsize = targetsize.expandedTo(minsize).boundedTo(maxsize)
        sh = widget.effectiveSizeHint(Qt.PreferredSize)
        policy = widget.sizePolicy()
        vpolicy = policy.verticalPolicy()
        hpolicy = policy.horizontalPolicy()

        if not self.__fitInView:
            widget.resize(sh.expandedTo(minsize).boundedTo(maxsize))
            return

        width = adjusted_size(sh.width(), targetsize.width(), minsize.width(),
                              maxsize.width(), hpolicy)
        height = adjusted_size(sh.height(), targetsize.height(),
                               minsize.height(), maxsize.height(), vpolicy)

        if policy.hasHeightForWidth():
            constr = QSizeF(width, -1)
            height = adjusted_size(
                widget.effectiveSizeHint(Qt.PreferredSize, constr).height(),
                targetsize.height(),
                widget.effectiveSizeHint(Qt.MinimumSize, constr).height(),
                widget.effectiveSizeHint(Qt.MaximumSize, constr).height(),
                QSizePolicy.Fixed)
        widget.resize(QSizeF(width, height))

    def _scaleToFit(self):
        widget = self.__centralWidget
        if widget is None or not self.__fitInView:
            return
        vpsize = self.__viewportContentSize()
        size = widget.size()
        if not size.isEmpty():
            sc = scaled(size, vpsize, self.__aspectMode)
            sx = sc.width() / size.width()
            sy = sc.height() / size.height()
            self.setTransform(QTransform().scale(sx, sy))

    def __viewportContentSize(self):
        msize = self.maximumViewportSize()
        vsbar = self.verticalScrollBar()
        hsbar = self.horizontalScrollBar()
        vsbpolicy = self.verticalScrollBarPolicy()
        hsbpolicy = self.horizontalScrollBarPolicy()
        htransient = hsbar.style().styleHint(QStyle.SH_ScrollBar_Transient,
                                             None, hsbar)
        vtransient = vsbar.style().styleHint(QStyle.SH_ScrollBar_Transient,
                                             None, vsbar)
        # always reserve room for scroll bars when they are possible
        if vsbpolicy == Qt.ScrollBarAsNeeded and not vtransient:
            msize.setWidth(msize.width() - vsbar.sizeHint().width())
        if hsbpolicy == Qt.ScrollBarAsNeeded and not htransient:
            msize.setHeight(msize.height() - hsbar.sizeHint().height())
        return msize
Esempio n. 14
0
class SchemeArrowAnnotation(BaseSchemeAnnotation):
    """
    An arrow annotation in the scheme.
    """

    color_changed = Signal(six.text_type)

    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, six.string_types)
        color = six.text_type(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(six.text_type, fget=color, fset=set_color)
Esempio n. 15
0
class SchemeTextAnnotation(BaseSchemeAnnotation):
    """
    Text annotation in the scheme.
    """

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

    # 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, six.string_types)
        text = six.text_type(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(six.text_type, fget=font, fset=set_font)
Esempio n. 16
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.initFrom(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.initFrom(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)
Esempio n. 17
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)
Esempio n. 18
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)
Esempio n. 19
0
class OpenWindowButton(CodeButton):

    runInThread = Property(bool, designable=False)  # Disabling property

    def deleteSubWindow(self):
        if hasattr(self, "subWindow"):
            deleteWidgetAndChildren(self.subWindow)
        else:
            logger.info("no existing subwindow")

    def __init__(self, parent=None):
        super().__init__(parent)
        self._fileName = QUrl()
        self._macros = []
        self._useRelativePath = True
        self._useThreading = False

    def default_code(self):
        return """
			from PyQt5.QtWidgets import QDialog, QFrame, QWidget, QApplication
			from PyQt5 import uic, QtCore
			from PyQt5.QtCore import QDir
			from PyQt5.Qt import Qt
			from bsstudio.functions import openFileAsString
			from bsstudio.widgets.embedframe import absPath, EmbedFrame
			import time
			import io
			ui = self.ui
			#if hasattr(self, "subWindow"):
			#	print("already has subwindow")
			#	self.subWindow.close()
			#self.deleteSubWindow()
			#self.subWindow = QDialog(self)
			self.subWindow = EmbedFrame(self)
			self.subWindow.setWindowFlags(Qt.Window)
			#self.subWindow = QDialog(self)
			self.subWindow.setAttribute(QtCore.Qt.WA_DeleteOnClose)

			self.subWindow.isTopLevel = True
			filename = self.fileName.toLocalFile()
			if not QDir.isAbsolutePath(filename):
				filename = absPath(self.windowFileName(), filename)
			self.subWindow.uiFilePath = filename
			fileContents = openFileAsString(filename, self.macros)
			fileObject = io.StringIO(fileContents)
			uic.loadUi(fileObject, self.subWindow)
			#self.resize(self.subWindow.size())
			self.subWindow.show()
			self.subWindow.update()
			self.subWindow.repaint()
			#QApplication.instance().processEvents()
			self.resumeChildren()
			"""[1:]

    def resumeWidget(self):
        self._paused = False

    @Property("QUrl")
    def fileName(self):
        return self._fileName

    @fileName.setter
    def fileName(self, val):
        path = convertPath(self, val, toRelative=self._useRelativePath)
        if path is not None:
            self._fileName = path

    @Property(bool)
    def useRelativePath(self):
        return self._useRelativePath

    @useRelativePath.setter
    def useRelativePath(self, val):
        self._useRelativePath = val
        self.fileName = self._fileName

    #fileName = makeProperty("fileName", QUrl)
    macros = makeProperty("macros", "QStringList")
Esempio n. 20
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
        ]
Esempio n. 21
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(six.text_type, 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
            )
Esempio n. 22
0
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 = QStyleOptionToolBox.OnlyOneTab
        self.selected = QStyleOptionToolBox.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()
        foregroundrole = QPalette.ButtonText
        if opt.state & QStyle.State_Sunken:
            # State 'down' pressed during a mouse press (slightly darker).
            background_brush = brush_darker(brush_highlight, 110)
            foregroundrole = QPalette.HighlightedText
        elif opt.state & QStyle.State_MouseOver:
            background_brush = brush_darker(brush_highlight, 95)
            foregroundrole = QPalette.HighlightedText
        elif opt.state & QStyle.State_On:
            background_brush = brush_highlight
            foregroundrole = QPalette.HighlightedText
        else:
            # The default button brush.
            background_brush = palette.button()

        rect = opt.rect

        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 == QStyleOptionToolBox.OnlyOneTab or \
                    self.position == QStyleOptionToolBox.Beginning or \
                    self.selected & \
                        QStyleOptionToolBox.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(foregroundrole)))
        p.setFont(opt.font)

        p.drawText(text_rect,
                   int(Qt.AlignVCenter | Qt.AlignLeft) | \
                   int(Qt.TextSingleLine),
                   text)

        if not opt.icon.isNull():
            if opt.state & QStyle.State_Enabled:
                mode = QIcon.Normal
            else:
                mode = QIcon.Disabled
            if opt.state & QStyle.State_On:
                state = QIcon.On
            else:
                state = QIcon.Off
            icon_area_rect = icon_area_rect
            icon_rect = QRect(QPoint(0, 0), opt.iconSize)
            icon_rect.moveCenter(icon_area_rect.center())
            opt.icon.paint(p, icon_rect, Qt.AlignCenter, mode, state)
        p.restore()
Esempio n. 23
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.dataChanged.disconnect(self.__on_dataChanged)
            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.dataChanged.connect(self.__on_dataChanged)
            self.__model.rowsInserted.connect(self.__on_rowsInserted)
            self.__model.rowsRemoved.connect(self.__on_rowsRemoved)

        self.__initFromModel(self.__model)

    def __initFromModel(self, model):
        for row in range(model.rowCount()):
            self.__insertItem(model.index(row, 0), self.count())

    def __insertItem(self, item, index):
        """
        Insert category item  (`QModelIndex`) at index.
        """
        grid = WidgetToolGrid()
        grid.setModel(item.model(), item)
        grid.actionTriggered.connect(self.triggered)
        grid.actionHovered.connect(self.hovered)

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

        text = item_text(item)
        icon = item_icon(item)
        tooltip = item_tooltip(item)

        # 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
        brush = qtcompat.qunwrap(item.data(Qt.BackgroundRole))
        if not isinstance(brush, QBrush):
            brush = qtcompat.qunwrap(item.data(QtWidgetRegistry.BACKGROUND_ROLE))
            if not isinstance(brush, QBrush):
                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_dataChanged(self, topLeft, bottomRight):
        parent = topLeft.parent()
        if not parent.isValid():
            for row in range(topLeft.row(), bottomRight.row() + 1):
                item = topLeft.sibling(row, topLeft.column())
                button = self.tabButton(row)
                button.setIcon(item_icon(item))
                button.setText(item_text(item))
                button.setToolTip(item_tooltip(item))

    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.isValid():
            for i in range(start, end + 1):
                item = self.__model.index(i, 0)
                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.isValid():
            for i in range(end, start - 1, -1):
                self.removeItem(i)
Esempio n. 24
0
 def pyqtProperty(*args, **kwargs):
     kwargs.pop('doc', None)
     return Property(*args, **kwargs)
Esempio n. 25
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, six.string_types):
            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, six.string_types):
            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
                )
Esempio n. 26
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()
Esempio n. 27
0
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")
        self.__contentsLayout = _ToolBoxLayout(
            sizeConstraint=_ToolBoxLayout.SetMinAndMaxSize, spacing=0)
        self.__contentsLayout.setContentsMargins(0, 0, 0, 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.Ignored, 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 = QStyleOptionToolBox.NextIsSelected
            if on:
                previous.selected |= flag
            else:
                previous.selected &= ~flag

            previous.update()

        if index < self.count() - 1:
            next = self.__pages[index + 1].button
            flag = QStyleOptionToolBox.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 = QStyleOptionToolBox

        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 = QStyleOptionToolBox.OnlyOneTab
        else:
            self.__pages[0].button.position = QStyleOptionToolBox.Beginning
            self.__pages[-1].button.position = QStyleOptionToolBox.End
            for p in self.__pages[1:-1]:
                p.button.position = QStyleOptionToolBox.Middle

        for p in self.__pages:
            p.button.update()
Esempio n. 28
0
class Product(QObject):
    '''
    classdocs
    '''
    productDirty = Signal()
    productClean = Signal()

    def __init__(self, parent=None):
        super(Product, self).__init__(parent)
        '''
        Constructor
        '''
        self._dirty = False
        self._dependsOn = []
        self._dependencies = []
        self._error = None
        self._producer = None
        self._autoUpdate = False

    @Property(QObject, notify=productClean)
    def bind(self):
        return self

    def set_dirty(self, d):
        if self._dirty != d:
            self._dirty = d
            self.dirtyChanged.emit()
        if self._dirty and self._autoUpdate:
            QTimer.singleShot(
                0, self.update
            )  # schedule an update as soon as we go back to event loop, but not before

    dirtyChanged = Signal()
    dirty = Property(bool, Getter('dirty'), set_dirty, dirtyChanged)

    RWProperty(vars(), bool, 'autoUpdate')

    def _update(self):
        '''
        update function to override
        '''
        pass

    @Slot()
    def update(self):

        if self.dirty:

            self._error = None

            for d in self._dependencies:
                if not d.update():
                    self._error = d._error
                    return False

            try:
                self._update()
            except Exception as e:
                self._error = e
                print(traceback.format_exc())

            self.makeClean()

        return self._error is None

    @Slot()
    def makeDirty(self):
        if not self.dirty:
            self.dirty = True
            self.productDirty.emit()

    @Slot()
    def makeClean(self):
        if self.dirty:
            self.dirty = False
            self.productClean.emit()

    def set_dependsOn(self, v):
        '''
            *Important" this property is meant to be used only from QML.
            Use add/remove_dependency() from python.
        '''
        old = self._dependsOn
        if assign_input(self, "dependsOn", v):
            for d in old:
                self.remove_dependency(d)
            for d in self._dependsOn:
                self.add_dependency(d)

    dependsOnChanged = Signal()
    dependsOn = Property(list, Getter('dependsOn'), set_dependsOn,
                         dependsOnChanged)

    def add_dependency(self, d):
        if d is not None:
            self._dependencies.append(d)
            d.productDirty.connect(self.makeDirty)
            self.makeDirty()

    def remove_dependency(self, d):
        if d is not None:
            self._dependencies.remove(d)
            d.productDirty.disconnect(self.makeDirty)
            self.makeDirty()

    def set_producer(self, producer):
        if self._producer is not None:
            raise RuntimeError(
                "Error: tried to call set a set_producer() twice on " +
                str(self) + ".")
        assert (issubclass(type(producer), Product))
        self._producer = producer
        self.add_dependency(producer)
        producer.productClean.connect(self.makeClean)
Esempio n. 29
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)
Esempio n. 30
0
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)