Example #1
0
    def drawBitmap(self, bmp, opt, painter):
        """ Draw the bitmap for the button.

        The bitmap will be drawn with the foreground color set by
        the style sheet and the style option.

        Parameters
        ----------
        bmp : QBitmap
            The bitmap to draw.

        opt : QStyleOption
            The style option to use for drawing.

        painter : QPainter
            The painter to use for drawing.

        """
        # hack to get the current stylesheet foreground color
        hint = QStyle.SH_GroupBox_TextLabelColor
        fg = self.style().styleHint(hint, opt, self)
        # mask signed to unsigned which 'fromRgba' requires
        painter.setPen(QColor.fromRgba(0xffffffff & fg))
        size = self.size()
        im_size = bmp.size()
        x = size.width() / 2 - im_size.width() / 2
        y = size.height() / 2 - im_size.height() / 2
        source = QRect(QPoint(0, 0), im_size)
        dest = QRect(QPoint(x, y), im_size)
        painter.drawPixmap(dest, bmp, source)
Example #2
0
    def mouseReleaseEvent(self, event):
        """ Handle the mouse release event for the widget.

        """
        event.ignore()
        if event.button() == Qt.LeftButton:
            self._press_pos = QPoint()
            event.accept()
Example #3
0
    def __init__(self, parent=None):
        """ Initialize a QDockBarItemHandle.

        Parameters
        ----------
        parent : QWidget, optional
            The parent widget of the handle.

        """
        super(QDockBarItemHandle, self).__init__(parent)
        policy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.setSizePolicy(policy)
        self._press_pos = QPoint()
        self._size_hint = QSize(5, 5)
Example #4
0
    def __init__(self):
        """ Initialize a QGuideRose.

        """
        super(QGuideRose, self).__init__()
        # On Mac, setting the translucent background does not cause the
        # frame shadow to be hidden; it must be explicitly hidden. Mac
        # also requires the window to be a tooltip in order to be raised
        # above the rubber band in the Z-order. On Windows, the tooltip
        # leaves a dropshadow on Qt >= 4.8 whereas tool does not.
        if sys.platform == 'darwin':
            self.setAttribute(Qt.WA_MacNoShadow, True)
            flags = Qt.ToolTip
        else:
            flags = Qt.Tool
        self.setAttribute(Qt.WA_TranslucentBackground, True)
        flags |= Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint
        self.setWindowFlags(flags)
        self._mode = self.Mode.NoMode
        self._center_point = QPoint()
        self._border_guide = BorderGuide()
        self._compass_guide = CompassGuide()
        self._compass_ex_guide = CompassExGuide()
        self._vsplit_guide = SplitVerticalGuide()
        self._hsplit_guide = SplitHorizontalGuide()
        self._area_guide = AreaCenterGuide()
Example #5
0
    def _onHandleMoved(self, delta):
        """ Handle the 'handleMoved' signal on the item handle.

        This handler resizes the item by the delta and then updates
        the internal user size. The resize is bounded by the limits
        of the widget and the parent dock area size.

        Resizing is disabled if an animation is running.

        """
        animation = self._animation
        if animation and animation.state() == animation.Running:
            return
        p = self.position()
        if p == QDockBar.North:
            delta = QSize(0, delta.y())
        elif p == QDockBar.East:
            delta = QSize(-delta.x(), 0)
        elif p == QDockBar.South:
            delta = QSize(0, -delta.y())
        else:
            delta = QSize(delta.x(), 0)
        user_size = self.size() + delta
        user_size = user_size.expandedTo(self.minimumSize())
        parent = self.parent()
        if parent is not None:
            user_size = user_size.boundedTo(parent.size())
        self._user_size = user_size
        if p == QDockBar.East or p == QDockBar.South:
            d = user_size - self.size()
            p = self.pos() - QPoint(d.width(), d.height())
            self.setGeometry(QRect(p, user_size))
        else:
            self.resize(user_size)
Example #6
0
    def mouseMoveEvent(self, event):
        """ Handle the mouse move event for the tab bar.

        This handler will undock the tab if the mouse is held and the
        drag leaves the boundary of the container by the application
        drag distance amount.

        """
        super(QDockTabBar, self).mouseMoveEvent(event)
        if not self._has_mouse:
            return
        pos = event.pos()
        if self.rect().contains(pos):
            return
        x = max(0, min(pos.x(), self.width()))
        y = max(0, min(pos.y(), self.height()))
        dist = (QPoint(x, y) - pos).manhattanLength()
        if dist > QApplication.startDragDistance():
            # Fake a mouse release event so that the tab resets its
            # internal state and finalizes the animation for the tab.
            # The button must be Qt.LeftButton, not event.button().
            btn = Qt.LeftButton
            mod = event.modifiers()
            evt = QMouseEvent(QEvent.MouseButtonRelease, pos, btn, btn, mod)
            QApplication.sendEvent(self, evt)
            container = self.parent().widget(self.currentIndex())
            container.untab(event.globalPos())
            self._has_mouse = False
Example #7
0
    def do_drag(self, widget):
        """Perform the drag operation for the widget.

        Parameters
        ----------
        widget: QWidget
            A reference to the viewport widget.

        """
        drag_data = self.declaration.drag_start()
        if drag_data is None:
            return
        # widget = self.widget
        qdrag = QDrag(widget)
        qdrag.setMimeData(drag_data.mime_data.q_data())
        if drag_data.image is not None:
            qimg = get_cached_qimage(drag_data.image)
            qdrag.setPixmap(QPixmap.fromImage(qimg))
        # else:
        # if __version_info__ < (5, ):
        # qdrag.setPixmap(QPixmap.grabWidget(self.widget))
        # else:
        # qdrag.setPixmap(widget.grab())
        if drag_data.hotspot:
            qdrag.setHotSpot(QPoint(*drag_data.hotspot))
        else:
            cursor_position = widget.mapFromGlobal(QCursor.pos())
            qdrag.setHotSpot(cursor_position)
        default = Qt.DropAction(drag_data.default_drop_action)
        supported = Qt.DropActions(drag_data.supported_actions)
        qresult = qdrag.exec_(supported, default)
        self.declaration.drag_end(drag_data, DropAction(int(qresult)))
Example #8
0
    def close_window(self, window, event):
        """ Handle a close request for a QDockWindow.

        This method is called by the framework at the appropriate times
        and should not be called directly by user code.

        Parameters
        ----------
        window : QDockWindow
            The dock window to close.

        event : QCloseEvent
            The close event passed to the event handler.

        """
        area = window.dockArea()
        if area is not None:
            containers = list(iter_containers(area))
            geometries = {}
            for container in containers:
                pos = container.mapToGlobal(QPoint(0, 0))
                size = container.size()
                geometries[container] = QRect(pos, size)
            for container, ignored in area.dockBarContainers():
                containers.append(container)
                size = container.sizeHint()
                geometries[container] = QRect(window.pos(), size)
            for container in containers:
                if not container.close():
                    container.unplug()
                    container.float()
                    container.setGeometry(geometries[container])
                    container.show()
        self._free_window(window)
Example #9
0
    def mouse_over_widget(self, widget, pos, empty=False):
        """ Update the overlays based on the mouse position.

        This handler should be invoked when the mouse hovers over a
        single widget (such as a floating dock container) as opposed to
        an area of docked widgets. The guide rose will be displayed in
        the center of the widget with no border guides.

        Parameters
        ----------
        widget : QWidget
            The widget under the mouse.

        pos : QPoint
            The hover position, expressed in the local coordinates of
            the widget.

        empty : bool, optional
            Whether the widget represents an empty widget. If this is
            True, a single center guide will be shown instead of the
            guide rose.

        """
        Mode = QGuideRose.Mode
        rose = self._rose
        target_mode = Mode.AreaCenter if empty else Mode.CompassEx
        self._target_rose_mode = target_mode
        if rose.mode() != target_mode:
            rose.setMode(Mode.NoMode)
            self._rose_timer.start(self.rose_delay)
            self._band_timer.start(self.band_delay)
        origin = widget.mapToGlobal(QPoint(0, 0))
        geo = QRect(origin, widget.size())
        dirty = rose.geometry() != geo
        if dirty:
            rose.hide()
            rose.setMode(Mode.NoMode)
            rose.setGeometry(geo)
        guide = rose.guideAt(pos, target_mode)
        if dirty or guide != self._last_guide:
            self._last_guide = guide
            self._target_band_geo = self._band_geometry(widget, guide)
            self._band_timer.start(self.band_delay)
        rose.setCenterPoint(QPoint(geo.width() / 2, geo.height() / 2))
        rose.mouseOver(pos)
        rose.show()
Example #10
0
    def mouseReleaseEvent(self, event):
        """ Handle the mouse release event for the widget.

        """
        event.ignore()
        if event.button() == Qt.LeftButton:
            self._press_pos = QPoint()
            event.accept()
Example #11
0
class QDockBarItemHandle(QFrame):
    """ A frame which provides a resize border for a QDockBarItem.

    """

    #: A signal emitted when the handle is moved. The payload is a
    #: QPoint which represents the delta drag distance.
    handleMoved = Signal(QPoint)

    def __init__(self, parent=None):
        """ Initialize a QDockBarItemHandle.

        Parameters
        ----------
        parent : QWidget, optional
            The parent widget of the handle.

        """
        super(QDockBarItemHandle, self).__init__(parent)
        policy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.setSizePolicy(policy)
        self._press_pos = QPoint()
        self._size_hint = QSize(5, 5)

    def sizeHint(self):
        """ Get the size hint for the widget.

        """
        return self._size_hint

    def mousePressEvent(self, event):
        """ Handle the mouse press event for the widget.

        """
        event.ignore()
        if event.button() == Qt.LeftButton:
            self._press_pos = event.pos()
            event.accept()

    def mouseReleaseEvent(self, event):
        """ Handle the mouse release event for the widget.

        """
        event.ignore()
        if event.button() == Qt.LeftButton:
            self._press_pos = QPoint()
            event.accept()

    def mouseMoveEvent(self, event):
        """ Handle the mouse move event for the widget.

        """
        event.ignore()
        if not self._press_pos.isNull():
            self.handleMoved.emit(event.pos() - self._press_pos)
            event.accept()
Example #12
0
class QDockBarItemHandle(QFrame):
    """ A frame which provides a resize border for a QDockBarItem.

    """
    #: A signal emitted when the handle is moved. The payload is a
    #: QPoint which represents the delta drag distance.
    handleMoved = Signal(QPoint)

    def __init__(self, parent=None):
        """ Initialize a QDockBarItemHandle.

        Parameters
        ----------
        parent : QWidget, optional
            The parent widget of the handle.

        """
        super(QDockBarItemHandle, self).__init__(parent)
        policy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.setSizePolicy(policy)
        self._press_pos = QPoint()
        self._size_hint = QSize(5, 5)

    def sizeHint(self):
        """ Get the size hint for the widget.

        """
        return self._size_hint

    def mousePressEvent(self, event):
        """ Handle the mouse press event for the widget.

        """
        event.ignore()
        if event.button() == Qt.LeftButton:
            self._press_pos = event.pos()
            event.accept()

    def mouseReleaseEvent(self, event):
        """ Handle the mouse release event for the widget.

        """
        event.ignore()
        if event.button() == Qt.LeftButton:
            self._press_pos = QPoint()
            event.accept()

    def mouseMoveEvent(self, event):
        """ Handle the mouse move event for the widget.

        """
        event.ignore()
        if not self._press_pos.isNull():
            self.handleMoved.emit(event.pos() - self._press_pos)
            event.accept()
Example #13
0
def _computePressPos(container, coeff):
    """ Compute the press position for a title bar.

    Parameters
    ----------
    container : QDockContainer
        The dock container which owns the title bar of interest.

    coeff : float
        A floating point value between 0.0 and 1.0 which is the
        proportional x-offset of the mouse press in the title bar.

    """
    margins = container.layout().contentsMargins()
    button_width = 50  # general approximation
    max_x = container.width() - margins.right() - button_width
    test_x = int(coeff * container.width())
    new_x = max(margins.left() + 5, min(test_x, max_x))
    title_bar = container.dockItem().titleBarWidget()
    title_height = title_bar.height() / 2
    mid_title = title_bar.mapTo(container, QPoint(0, title_height))
    return QPoint(new_x, mid_title.y())
Example #14
0
    def untab(self, pos):
        """ Unplug the container from a tab control.

        This method is invoked by the QDockTabBar when the container
        should be torn out. It synthesizes the appropriate internal
        state so that the item can continue to be dock dragged. This
        method should not be called by user code.

        Parameters
        ----------
        pos : QPoint
            The global mouse position.

        Returns
        -------
        result : bool
            True on success, False otherwise.

        """
        if not self.unplug():
            return
        self.postUndockedEvent()
        state = self.frame_state
        state.mouse_title = True
        state.dragging = True
        state.frame_was_maximized = False
        self.float()
        self.raiseFrame()
        title_bar = self.dockItem().titleBarWidget()
        title_pos = QPoint(title_bar.width() / 2, title_bar.height() / 2)
        margins = self.layout().contentsMargins()
        offset = QPoint(margins.left(), margins.top())
        state.press_pos = title_bar.mapTo(self, title_pos) + offset
        state.start_pos = pos - state.press_pos
        self.move(state.start_pos)
        self.show()
        self.grabMouse()
        self.activateWindow()
        self.raise_()
Example #15
0
    def __init__(self, parent=None):
        """ Initialize a QDockBarItemHandle.

        Parameters
        ----------
        parent : QWidget, optional
            The parent widget of the handle.

        """
        super(QDockBarItemHandle, self).__init__(parent)
        policy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.setSizePolicy(policy)
        self._press_pos = QPoint()
        self._size_hint = QSize(5, 5)
Example #16
0
def _closestDockBar(container):
    """ Get the closest dock bar position for the container.

    This function computes the closest dock bar position by ranking
    the edges of the container by their distance to the respective
    dock area edge. Ties are broken first by relative edge weight and
    then by an ordered heuristic of East, West, South, then North.

    Returns
    -------
    result : QDockBar.Position
        The closet dock bar position.

    """
    area = container.parentDockArea()
    if area is None:
        return QDockBar.North

    pane = area.centralPane()
    c_width = container.width()
    c_height = container.height()
    p_width = pane.width()
    p_height = pane.height()

    p1 = container.mapTo(pane, QPoint(0, 0))
    p2 = container.mapTo(pane, QPoint(c_width, c_height))
    edge_ranks = (p1.y(), p_width - p2.x(), p_height - p2.y(), p1.x())

    f_width = 1.0 - float(c_width) / p_width
    f_height = 1.0 - float(c_height) / p_height
    edge_weights = (f_width, f_height) * 2

    tie_breakers = (3, 0, 2, 1)
    borders = (QDockBar.North, QDockBar.East, QDockBar.South, QDockBar.West)
    values = zip(edge_ranks, edge_weights, tie_breakers, borders)
    return sorted(values)[0][3]
Example #17
0
    def titleBarGeometry(self):
        """ Get the geometry rect for the title bar.

        Returns
        -------
        result : QRect
            The geometry rect for the title bar, expressed in frame
            coordinates. An invalid rect is returned if title bar
            should not be active.

        """
        title_bar = self.dockItem().titleBarWidget()
        if title_bar.isHidden():
            return QRect()
        pt = title_bar.mapTo(self, QPoint(0, 0))
        return QRect(pt, title_bar.size())
Example #18
0
    def dockBarGeometry(self, position):
        """ Get the geometry of the dock bar at the given position.

        Parameters
        ----------
        position : QDockBar.Position
            The enum value specifying the dock bar of interest.

        Returns
        -------
        result : QRect
            The geometry of the given dock bar expressed in area
            coordinates. If no dock bar exists at the given position,
            an invalid QRect will be returned.

        """
        bar = self._getDockBar(position, create=False)
        if bar is None:
            return QRect()
        pos = bar.mapTo(self.parent(), QPoint(0, 0))
        return QRect(pos, bar.size())
Example #19
0
    def _animationGeo(self, item):
        """ Get the animation geometry for the given item.

        Parameters
        ----------
        item : QDockBarItem
            The dock bar item to be animated.

        Returns
        -------
        result : tuple
            A 2-tuple of QRect objects representing the start and end
            geometries for the animation assuming a slide out effect.

        """
        pane = self.parent().centralPane()
        hint = item.sizeHint().boundedTo(pane.size())
        position = item.position()
        if position == QDockBar.North:
            start_pos = QPoint(0, -hint.height())
            end_pos = QPoint(0, 0)
            size = QSize(pane.width(), hint.height())
        elif position == QDockBar.East:
            start_pos = QPoint(pane.width(), 0)
            end_pos = QPoint(pane.width() - hint.width(), 0)
            size = QSize(hint.width(), pane.height())
        elif position == QDockBar.South:
            start_pos = QPoint(0, pane.height())
            end_pos = QPoint(0, pane.height() - hint.height())
            size = QSize(pane.width(), hint.height())
        else:
            start_pos = QPoint(-hint.width(), 0)
            end_pos = QPoint(0, 0)
            size = QSize(hint.width(), pane.height())
        start_geo = QRect(start_pos, size)
        end_geo = QRect(end_pos, size)
        return start_geo, end_geo
Example #20
0
    def mouse_over_area(self, area, widget, pos):
        """ Update the overlays based on the mouse position.

        Parameters
        ----------
        area : QDockArea
            The dock area which contains the dock items onto which
            the overlay will be displayed.

        widget : QWidget
            The dock widget in the area which is under the mouse, or
            None if there is no relevant widget.

        pos : QPoint
            The hover position, expressed in the local coordinates of
            the overlayed dock area.

        """
        Mode = QGuideRose.Mode
        Guide = QGuideRose.Guide
        pane = area.centralPane()
        pos = pane.mapFrom(area, pos)

        if widget is None:
            if area.centralWidget() is None:
                self.mouse_over_widget(pane, pos, empty=True)
            return

        # Compute the target mode for the guide rose based on the dock
        # widget which lies under the mouse position.
        target_mode = Mode.Border
        if isinstance(widget, QDockContainer):
            target_mode |= Mode.CompassEx
        elif isinstance(widget, QDockTabWidget):
            target_mode |= Mode.Compass
        elif isinstance(widget, QDockSplitterHandle):
            if widget.orientation() == Qt.Horizontal:
                target_mode |= Mode.SplitHorizontal
            else:
                target_mode |= Mode.SplitVertical

        # Get the local area coordinates for the center of the widget.
        center = widget.mapTo(pane, QPoint(0, 0))
        center += QPoint(widget.width() / 2, widget.height() / 2)

        # Update the state of the rose. If it is to be hidden, it is
        # done so immediately. If the target mode is different from
        # the current mode, the rose is hidden and the state changes
        # are collapsed on a timer.
        rose = self._rose
        self._hover_pos = pos
        self._show_band = True
        self._target_rose_mode = target_mode
        if target_mode != rose.mode():
            rose.setMode(Mode.Border)
            self._rose_timer.start(self.rose_delay)
            self._show_band = False

        # Update the geometry of the rose if needed. This ensures that
        # the rose does not change geometry while visible.
        origin = pane.mapToGlobal(QPoint(0, 0))
        geo = QRect(origin, pane.size())
        dirty = rose.geometry() != geo
        if dirty:
            rose.hide()
            rose.setMode(Mode.NoMode)
            rose.setGeometry(geo)

        # Hit test the rose and update the target geometry for the
        # rubber band if the target guide has changed.
        rose.setCenterPoint(center)
        guide = rose.guideAt(pos, target_mode)
        if dirty or guide != self._last_guide:
            self._last_guide = guide
            if guide >= Guide.BorderNorth and guide <= Guide.BorderWest:
                band_geo = self._band_geometry(pane, guide)
            elif guide >= Guide.BorderExNorth and guide <= Guide.BorderExWest:
                band_geo = self._band_geometry(area, guide)
            else:
                band_geo = self._band_geometry(widget, guide)
            self._target_band_geo = band_geo
            self._band_timer.start(self.band_delay)

        # Finally, make the rose visible and issue a mouseover command
        # so that the guides are highlighted.
        rose.mouseOver(pos)
        rose.show()
Example #21
0
class DockOverlay(Atom):
    """ An object which manages the overlays for dock widgets.

    This manager handles the state transitions for the overlays. The
    transitions are performed on a slightly-delayed timer to provide
    a more fluid user interaction experience.

    """
    # PySide requires weakrefs for using bound methods as slots.
    # PyQt doesn't, but executes unsafe code if not using weakrefs.
    __slots__ = '__weakref__'

    #: The size of the rubber band when docking on the border, in px.
    border_size = Int(60)

    #: The delay to use when triggering the rose timer, in ms.
    rose_delay = Int(30)

    #: The delay to use when triggering the band timer, in ms.
    band_delay = Int(50)

    #: The target opacity to use when making the band visible.
    band_target_opacity = Float(1.0)

    #: The duration of the band visibilty animation, in ms.
    band_vis_duration = Int(100)

    #: the duration of the band geometry animation, in ms.
    band_geo_duration = Int(100)

    #: The overlayed guide rose.
    _rose = Typed(QGuideRose, ())

    #: The overlayed rubber band.
    _band = Typed(QDockRubberBand, ())

    #: The property animator for the rubber band geometry.
    _geo_animator = Typed(QPropertyAnimation)

    #: The property animator for the rubber band visibility.
    _vis_animator = Typed(QPropertyAnimation)

    #: The target mode to apply to the rose on timeout.
    _target_rose_mode = Int(QGuideRose.Mode.NoMode)

    #: The target geometry to apply to rubber band on timeout.
    _target_band_geo = Typed(QRect, factory=lambda: QRect())

    #: The value of the last guide which was hit in the rose.
    _last_guide = Int(-1)

    #: A flag indicating whether it is safe to show the band.
    _show_band = Bool(False)

    #: The hover position of the mouse to use for state changes.
    _hover_pos = Typed(QPoint, factory=lambda: QPoint())

    #: The timer for changing the state of the rose.
    _rose_timer = Typed(QTimer)

    #: The timer for changing the state of the band.
    _band_timer = Typed(QTimer)

    def __init__(self, parent=None):
        """ Initialize a DockOverlay.

        Parameters
        ----------
        parent : QWidget, optional
            The parent of the overlay. This will be used as the parent
            widget for the dock rubber band. The overlay guides do not
            have a parent.

        """
        self._band = QDockRubberBand(parent)

    #--------------------------------------------------------------------------
    # Default Value Methods
    #--------------------------------------------------------------------------
    def _default__rose_timer(self):
        """ Create the default timer for the rose state changes.

        """
        timer = QTimer()
        timer.setSingleShot(True)
        timer.timeout.connect(self._on_rose_timer)
        return timer

    def _default__band_timer(self):
        """ Create the default timer for the band state changes.

        """
        timer = QTimer()
        timer.setSingleShot(True)
        timer.timeout.connect(self._on_band_timer)
        return timer

    def _default__geo_animator(self):
        """ Create the default property animator for the rubber band.

        """
        p = QPropertyAnimation(self._band, b'geometry')
        p.setDuration(self.band_geo_duration)
        return p

    def _default__vis_animator(self):
        """ Create the default property animator for the rubber band.

        """
        p = QPropertyAnimation(self._band, b'windowOpacity')
        p.setDuration(self.band_vis_duration)
        p.finished.connect(self._on_vis_finished)
        return p

    #--------------------------------------------------------------------------
    # Timer Handlers
    #--------------------------------------------------------------------------
    def _on_rose_timer(self):
        """ Handle the timeout event for the internal rose timer.

        This handler transitions the rose to its new state and updates
        the position of the rubber band.

        """
        rose = self._rose
        rose.setMode(self._target_rose_mode)
        rose.mouseOver(self._hover_pos)
        self._show_band = True
        self._update_band_state()

    def _on_band_timer(self):
        """ Handle the timeout event for the internal band timer.

        This handler updates the position of the rubber band.

        """
        self._update_band_state()

    #--------------------------------------------------------------------------
    # Animation Handlers
    #--------------------------------------------------------------------------
    def _on_vis_finished(self):
        """ Handle the 'finished' signal from the visibility animator.

        This handle will hide the rubber band when its opacity is 0.

        """
        band = self._band
        if band.windowOpacity() == 0.0:
            band.hide()

    #--------------------------------------------------------------------------
    # Private API
    #--------------------------------------------------------------------------
    def _update_band_state(self):
        """ Refresh the geometry and visible state of the rubber band.

        The state will be updated using animated properties to provide
        a nice fluid user experience.

        """
        # A valid geometry indicates that the rubber should be shown on
        # the screen. An invalid geometry means it should be hidden. If
        # the validity is changed during animation, the animators are
        # restarted using the current state as their starting point.
        band = self._band
        geo = self._target_band_geo
        if geo.isValid() and self._show_band:
            # If the band is already hidden, the geometry animation can
            # be bypassed since the band can be located anywhere.
            if band.isHidden():
                band.setGeometry(geo)
                self._start_vis_animator(self.band_target_opacity)
                self._rose.raise_()
            else:
                self._start_vis_animator(self.band_target_opacity)
                self._start_geo_animator(geo)
        else:
            self._start_vis_animator(0.0)

    def _start_vis_animator(self, opacity):
        """ (Re)start the visibility animator.

        Parameters
        ----------
        opacity : float
            The target opacity of the target object.

        """
        animator = self._vis_animator
        if animator.state() == animator.Running:
            animator.stop()
        target = animator.targetObject()
        if target.isHidden() and opacity != 0.0:
            target.setWindowOpacity(0.0)
            target.show()
        animator.setStartValue(target.windowOpacity())
        animator.setEndValue(opacity)
        animator.start()

    def _start_geo_animator(self, geo):
        """ (Re)start the visibility animator.

        Parameters
        ----------
        geo : QRect
            The target geometry for the target object.

        """
        animator = self._geo_animator
        if animator.state() == animator.Running:
            animator.stop()
        target = animator.targetObject()
        animator.setStartValue(target.geometry())
        animator.setEndValue(geo)
        animator.start()

    def _band_geometry(self, widget, guide):
        """ Compute the geometry for an overlay rubber band.

        Parameters
        ----------
        widget : QWidget
            The widget to which the band geometry should be fit.

        guide : Guide
            The rose guide under the mouse. This determines how the
            geometry of the band will be fit to the widget.

        """
        Guide = QGuideRose.Guide
        if guide == Guide.NoGuide:
            return QRect()

        # border hits
        border_size = self.border_size
        rect = widget.contentsRect()
        if guide == Guide.BorderNorth:
            rect.setHeight(border_size)
        elif guide == Guide.BorderEast:
            rect.setLeft(rect.right() + 1 - border_size)
        elif guide == Guide.BorderSouth:
            rect.setTop(rect.bottom() + 1 - border_size)
        elif guide == Guide.BorderWest:
            rect.setWidth(border_size)
        # For the next 4 conditions `widget` will be a QDockArea
        elif guide == Guide.BorderExNorth:
            bar_rect = widget.dockBarGeometry(QDockBar.North)
            if bar_rect.isValid():
                rect = bar_rect
            else:
                rect.setHeight(border_size / 2)
        elif guide == Guide.BorderExEast:
            bar_rect = widget.dockBarGeometry(QDockBar.East)
            if bar_rect.isValid():
                rect = bar_rect
            else:
                rect.setLeft(rect.right() + 1 - border_size / 2)
        elif guide == Guide.BorderExSouth:
            bar_rect = widget.dockBarGeometry(QDockBar.South)
            if bar_rect.isValid():
                rect = bar_rect
            else:
                rect.setTop(rect.bottom() + 1 - border_size / 2)
        elif guide == Guide.BorderExWest:
            bar_rect = widget.dockBarGeometry(QDockBar.West)
            if bar_rect.isValid():
                rect = bar_rect
            else:
                rect.setWidth(border_size / 2)

        # compass hits
        elif guide == Guide.CompassNorth:
            rect.setHeight(rect.height() / 3)
        elif guide == Guide.CompassEast:
            rect.setLeft(2 * rect.width() / 3)
        elif guide == Guide.CompassSouth:
            rect.setTop(2 * rect.height() / 3)
        elif guide == Guide.CompassWest:
            rect.setWidth(rect.width() / 3)
        elif guide == Guide.CompassCenter:
            pass  # nothing to do
        elif guide == Guide.CompassExNorth:
            pass  # nothing to do
        elif guide == Guide.CompassExEast:
            pass  # nothing to do
        elif guide == Guide.CompassExSouth:
            pass  # nothing to do
        elif guide == Guide.CompassExWest:
            pass  # nothing to do

        # splitter handle hits
        elif guide == Guide.SplitHorizontal:
            wo, r = divmod(border_size - rect.width(), 2)
            rect.setWidth(2 * (wo + r) + rect.width())
            rect.moveLeft(rect.x() - (wo + r))
        elif guide == Guide.SplitVertical:
            ho, r = divmod(border_size - widget.height(), 2)
            rect.setHeight(2 * (ho + r) + rect.height())
            rect.moveTop(rect.y() - (ho + r))

        # single center
        elif guide == Guide.AreaCenter:
            pass  # nothing to do

        # default no-op
        else:
            return QRect()

        pt = widget.mapToGlobal(rect.topLeft())
        return QRect(pt, rect.size())

    #--------------------------------------------------------------------------
    # Public API
    #--------------------------------------------------------------------------
    def guide_at(self, pos):
        """ Get the dock guide for a given position.

        Parameters
        ----------
        pos : QPoint
            The position of interest, expressed in global coordinates.

        Returns
        -------
        result : Guide
            The guide enum which lies under the given point.

        """
        rose = self._rose
        pos = rose.mapFromGlobal(pos)
        return rose.guideAt(pos)

    def hide(self):
        """ Hide the overlay.

        This method will stop the timers and set the visibility of the
        guide rose and the rubber band to False.

        """
        self._rose_timer.stop()
        self._band_timer.stop()
        self._rose.hide()
        self._band.hide()

    def mouse_over_widget(self, widget, pos, empty=False):
        """ Update the overlays based on the mouse position.

        This handler should be invoked when the mouse hovers over a
        single widget (such as a floating dock container) as opposed to
        an area of docked widgets. The guide rose will be displayed in
        the center of the widget with no border guides.

        Parameters
        ----------
        widget : QWidget
            The widget under the mouse.

        pos : QPoint
            The hover position, expressed in the local coordinates of
            the widget.

        empty : bool, optional
            Whether the widget represents an empty widget. If this is
            True, a single center guide will be shown instead of the
            guide rose.

        """
        Mode = QGuideRose.Mode
        rose = self._rose
        target_mode = Mode.AreaCenter if empty else Mode.CompassEx
        self._target_rose_mode = target_mode
        if rose.mode() != target_mode:
            rose.setMode(Mode.NoMode)
            self._rose_timer.start(self.rose_delay)
            self._band_timer.start(self.band_delay)
        origin = widget.mapToGlobal(QPoint(0, 0))
        geo = QRect(origin, widget.size())
        dirty = rose.geometry() != geo
        if dirty:
            rose.hide()
            rose.setMode(Mode.NoMode)
            rose.setGeometry(geo)
        guide = rose.guideAt(pos, target_mode)
        if dirty or guide != self._last_guide:
            self._last_guide = guide
            self._target_band_geo = self._band_geometry(widget, guide)
            self._band_timer.start(self.band_delay)
        rose.setCenterPoint(QPoint(geo.width() / 2, geo.height() / 2))
        rose.mouseOver(pos)
        rose.show()

    def mouse_over_area(self, area, widget, pos):
        """ Update the overlays based on the mouse position.

        Parameters
        ----------
        area : QDockArea
            The dock area which contains the dock items onto which
            the overlay will be displayed.

        widget : QWidget
            The dock widget in the area which is under the mouse, or
            None if there is no relevant widget.

        pos : QPoint
            The hover position, expressed in the local coordinates of
            the overlayed dock area.

        """
        Mode = QGuideRose.Mode
        Guide = QGuideRose.Guide
        pane = area.centralPane()
        pos = pane.mapFrom(area, pos)

        if widget is None:
            if area.centralWidget() is None:
                self.mouse_over_widget(pane, pos, empty=True)
            return

        # Compute the target mode for the guide rose based on the dock
        # widget which lies under the mouse position.
        target_mode = Mode.Border
        if isinstance(widget, QDockContainer):
            target_mode |= Mode.CompassEx
        elif isinstance(widget, QDockTabWidget):
            target_mode |= Mode.Compass
        elif isinstance(widget, QDockSplitterHandle):
            if widget.orientation() == Qt.Horizontal:
                target_mode |= Mode.SplitHorizontal
            else:
                target_mode |= Mode.SplitVertical

        # Get the local area coordinates for the center of the widget.
        center = widget.mapTo(pane, QPoint(0, 0))
        center += QPoint(widget.width() / 2, widget.height() / 2)

        # Update the state of the rose. If it is to be hidden, it is
        # done so immediately. If the target mode is different from
        # the current mode, the rose is hidden and the state changes
        # are collapsed on a timer.
        rose = self._rose
        self._hover_pos = pos
        self._show_band = True
        self._target_rose_mode = target_mode
        if target_mode != rose.mode():
            rose.setMode(Mode.Border)
            self._rose_timer.start(self.rose_delay)
            self._show_band = False

        # Update the geometry of the rose if needed. This ensures that
        # the rose does not change geometry while visible.
        origin = pane.mapToGlobal(QPoint(0, 0))
        geo = QRect(origin, pane.size())
        dirty = rose.geometry() != geo
        if dirty:
            rose.hide()
            rose.setMode(Mode.NoMode)
            rose.setGeometry(geo)

        # Hit test the rose and update the target geometry for the
        # rubber band if the target guide has changed.
        rose.setCenterPoint(center)
        guide = rose.guideAt(pos, target_mode)
        if dirty or guide != self._last_guide:
            self._last_guide = guide
            if guide >= Guide.BorderNorth and guide <= Guide.BorderWest:
                band_geo = self._band_geometry(pane, guide)
            elif guide >= Guide.BorderExNorth and guide <= Guide.BorderExWest:
                band_geo = self._band_geometry(area, guide)
            else:
                band_geo = self._band_geometry(widget, guide)
            self._target_band_geo = band_geo
            self._band_timer.start(self.band_delay)

        # Finally, make the rose visible and issue a mouseover command
        # so that the guides are highlighted.
        rose.mouseOver(pos)
        rose.show()
Example #22
0
    def _resizeBorderTest(self, pos):
        """ Hit test the frame for resizing.

        Parameters
        ----------
        pos : QPoint
            The point of interest, expressed in local coordinates.

        Returns
        -------
        result : tuple
            A 2-tuple of (int, QPoint) representing the resize border
            and offset for the border.

        """
        rect = self.rect()
        if not rect.contains(pos):
            return (self.NoBorder, QPoint())
        x = pos.x()
        y = pos.y()
        width = rect.width()
        height = rect.height()
        margins = self.resizeMargins()
        extra = self.ResizeCornerExtra
        if x < margins.left():
            if y < margins.top() + extra:
                mode = self.NorthWestBorder
                offset = QPoint(x, y)
            elif y > height - (margins.bottom() + extra):
                mode = self.SouthWestBorder
                offset = QPoint(x, height - y)
            else:
                mode = self.WestBorder
                offset = QPoint(x, 0)
        elif y < margins.top():
            if x < margins.left() + extra:
                mode = self.NorthWestBorder
                offset = QPoint(x, y)
            elif x > width - (margins.right() + extra):
                mode = self.NorthEastBorder
                offset = QPoint(width - x, y)
            else:
                mode = self.NorthBorder
                offset = QPoint(0, y)
        elif x > width - margins.right():
            if y < margins.top() + extra:
                mode = self.NorthEastBorder
                offset = QPoint(width - x, y)
            elif y > height - (margins.bottom() + extra):
                mode = self.SouthEastBorder
                offset = QPoint(width - x, height - y)
            else:
                mode = self.EastBorder
                offset = QPoint(width - x, 0)
        elif y > height - margins.bottom():
            if x < margins.left() + extra:
                mode = self.SouthWestBorder
                offset = QPoint(x, height - y)
            elif x > width - (margins.right() + extra):
                mode = self.SouthEastBorder
                offset = QPoint(width - x, height - y)
            else:
                mode = self.SouthBorder
                offset = QPoint(0, height - y)
        else:
            mode = self.NoBorder
            offset = QPoint()
        return mode, offset
Example #23
0
 def pixel_density(self):
     tr = self.widget.transform().inverted()[0]
     p = tr.map(QPoint(1, 1)) - tr.map(QPoint(0, 0))
     return Point(p.x(), p.y())
Example #24
0
    def titleBarMouseMoveEvent(self, event):
        """ Handle a mouse move event on the title bar.

        Returns
        -------
        result : bool
            True if the event is handled, False otherwise.

        """
        state = self.frame_state
        if state.press_pos is None:
            return False

        # If dragging and floating, move the container's position and
        # notify the manager of that the container was mouse moved. If
        # the container is maximized, it is first restored before.
        global_pos = event.globalPos()
        if state.dragging:
            if self.isWindow():
                target_pos = global_pos - state.press_pos
                self.manager().drag_move_frame(self, target_pos, global_pos)
            return True

        # Ensure the drag has crossed the app drag threshold.
        dist = (event.pos() - state.press_pos).manhattanLength()
        if dist <= QApplication.startDragDistance():
            return True

        # If the container is already floating, ensure that it is shown
        # normal size. The next move event will move the window.
        state.dragging = True
        if self.isWindow():
            state.frame_was_maximized = self.isMaximized()
            if state.frame_was_maximized:
                coeff = state.press_pos.x() / float(self.width())
                self.showNormal()
                state.press_pos = _computePressPos(self, coeff)
            return True

        # Restore a maximized dock item before unplugging.
        if state.item_is_maximized:
            bar = self.dockItem().titleBarWidget()
            coeff = state.press_pos.x() / float(bar.width())
            self.showNormal()
            state.press_pos = _computePressPos(self, coeff)

        # Unplug the container from the layout before floating so
        # that layout widgets can clean themselves up when empty.
        if not self.unplug():
            return False
        self.postUndockedEvent()

        # Make the container a toplevel frame, update it's Z-order,
        # and grab the mouse to continue processing drag events.
        self.float()
        self.raiseFrame()
        margins = self.layout().contentsMargins()
        state.press_pos += QPoint(0, margins.top())
        state.start_pos = global_pos - state.press_pos
        self.move(state.start_pos)
        self.show()
        self.grabMouse()
        self.activateWindow()
        self.raise_()
        return True