Beispiel #1
0
def ensure_on_screen(rect):
    """ Ensure that the given rect is contained on screen.

    If the origin of the rect is not contained within the closest
    desktop screen, the rect will be moved so that it is fully on the
    closest screen. If the rect is larger than the closest screen, the
    origin will never be less than the screen origin.

    Parameters
    ----------
    rect : QRect
        The geometry rect of interest.

    Returns
    -------
    result : QRect
        The potentially adjusted QRect which fits on the screen.

    """
    d = QApplication.desktop()
    pos = rect.topLeft()
    drect = d.screenGeometry(pos)
    if not drect.contains(pos):
        x = pos.x()
        if x < drect.x() or x > drect.right():
            dw = drect.width() - rect.width()
            x = max(drect.x(), drect.x() + dw)
        y = pos.y()
        if x < drect.top() or y > drect.bottom():
            dh = drect.height() - rect.height()
            y = max(drect.y(), drect.y() + dh)
        rect = QRect(x, y, rect.width(), rect.height())
    return rect
Beispiel #2
0
def ensure_on_screen(rect):
    """ Ensure that the given rect is contained on screen.

    If the origin of the rect is not contained within the closest
    desktop screen, the rect will be moved so that it is fully on the
    closest screen. If the rect is larger than the closest screen, the
    origin will never be less than the screen origin.

    Parameters
    ----------
    rect : QRect
        The geometry rect of interest.

    Returns
    -------
    result : QRect
        The potentially adjusted QRect which fits on the screen.

    """
    d = QApplication.desktop()
    pos = rect.topLeft()
    drect = d.screenGeometry(pos)
    if not drect.contains(pos):
        x = pos.x()
        if x < drect.x() or x > drect.right():
            dw = drect.width() - rect.width()
            x = max(drect.x(), drect.x() + dw)
        y = pos.y()
        if x < drect.top() or y > drect.bottom():
            dh = drect.height() - rect.height()
            y = max(drect.y(), drect.y() + dh)
        rect = QRect(x, y, rect.width(), rect.height())
    return rect
Beispiel #3
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)
Beispiel #4
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)
Beispiel #5
0
    def layout(self, pos):
        """ Layout the guides for the given position.

        Parameters
        ----------
        pos : QPoint
            The center point of the guide.

        """
        x = pos.x()
        y = pos.y()
        self._guide.rect = QRect(x - 15, y - 15, 31, 31)
        self._box.rect = QRect(x - 20, y - 20, 41, 41)
Beispiel #6
0
    def paint(self, painter, option, index):
        self.initStyleOption(option, index)
        painter.save()
        left_width = int(option.rect.width() * self.model.cellFrac(index))
        right_width = option.rect.width() - left_width
        left_rect = QRect(option.rect.left(), option.rect.top(), left_width,
                          option.rect.height())
        right_rect = QRect(option.rect.left(), option.rect.top(), right_width,
                           option.rect.height())

        left_brush = QBrush(self.model.cellColor(index))
        painter.fillRect(left_rect, left_brush)
        painter.fillRect(right_rect, Qt.NoBrush)
        painter.restore()
        option.backgroundBrush = QBrush(Qt.NoBrush)
        super().paint(painter, option, index)
Beispiel #7
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())
Beispiel #8
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)
Beispiel #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()
Beispiel #10
0
    def paintEvent(self, event):
        self.proxy.ais_context.UpdateCurrentViewer()
        # important to allow overpainting of the OCC OpenGL context in Qt

        if self._drawbox:
            painter = QPainter(self)
            painter.setPen(self._select_pen)
            painter.drawRect(QRect(*self._drawbox))
Beispiel #11
0
 def drawBox(self):
     if self._drawbox:
         self.makeCurrent()
         painter = QPainter(self)
         painter.setPen(self._select_pen)
         painter.drawRect(QRect(*self._drawbox))
         painter.end()
         self.doneCurrent()
Beispiel #12
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.

        """
        cmargins = self.layout().contentsMargins()
        if self.isMaximized():
            return QRect(0, 0, self.width(), cmargins.top())
        rmargins = self.ResizeMargins
        width = self.width() - (cmargins.left() + cmargins.right())
        height = cmargins.top() - rmargins.top()
        return QRect(cmargins.left(), rmargins.top(), width, height)
Beispiel #13
0
    def setGeometry(self, rect):
        """ Set the geometry for the items in the layout.

        """
        super(QDockItemLayout, self).setGeometry(rect)
        title = self._title_bar
        widget = self._dock_widget
        title_rect = QRect(rect)
        widget_rect = QRect(rect)
        if title is not None and not title.isHidden():
            msh = title.minimumSizeHint()
            title_rect.setHeight(msh.height())
            widget_rect.setTop(title_rect.bottom() + 1)
            title.setGeometry(title_rect)
        if widget is not None and not widget.isHidden():
            widget.setGeometry(widget_rect)
Beispiel #14
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())
Beispiel #15
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 should be returned if title
            bar should not be active.

        """
        return QRect()
Beispiel #16
0
    def layout(self, pos):
        """ Layout the guides for the extended compass.

        Parameters
        ----------
        pos : QPoint
            The center point of the compass.

        """
        x = pos.x()
        y = pos.y()
        Guide = QGuideRose.Guide
        guides = self._guides
        guides[Guide.CompassNorth].rect = QRect(x - 15, y - 64, 31, 31)
        guides[Guide.CompassEast].rect = QRect(x + 34, y - 15, 31, 31)
        guides[Guide.CompassSouth].rect = QRect(x - 15, y + 34, 31, 31)
        guides[Guide.CompassWest].rect = QRect(x - 64, y - 15, 31, 31)
        guides[Guide.CompassCenter].rect = QRect(x - 15, y - 15, 31, 31)
        guides[Guide.CompassExNorth].rect = QRect(x - 15, y - 29, 31, 10)
        guides[Guide.CompassExEast].rect = QRect(x + 20, y - 15, 10, 31)
        guides[Guide.CompassExSouth].rect = QRect(x - 15, y + 20, 31, 10)
        guides[Guide.CompassExWest].rect = QRect(x - 29, y - 15, 10, 31)
        self._box.rect = QRect(x - 69, y - 69, 139, 139)
Beispiel #17
0
    def _updateButtonGeometry(self):
        """ Update the geometry of the window buttons.

        This method will set the geometry of the window buttons
        according to the current window size.

        """
        title_buttons = self._title_buttons
        size = title_buttons.minimumSizeHint()
        margins = self.layout().contentsMargins()
        offset = max(self.MinButtonOffset, margins.right())
        x = self.width() - size.width() - offset
        rect = QRect(x, 1, size.width(), size.height())
        title_buttons.setGeometry(rect)
Beispiel #18
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
Beispiel #19
0
    def init_floating_frame(self, frame, layout):
        """ Initialize a floating frame.

        This initializer sets up the geometry, maximized state, and
        linked state for the floating frame.

        Parameters
        ----------
        frame : QDockFrame
            The floating dock frame of interest.

        layout : ItemLayout or AreaLayout
            The layout describing the floating state of the frame.

        """
        rect = QRect(*layout.geometry)
        if rect.isValid():
            rect = ensure_on_screen(rect)
            frame.setGeometry(rect)
        frame.show()
        if layout.linked:
            frame.setLinked(True)
        if layout.maximized:
            frame.showMaximized()
Beispiel #20
0
    def init_floating_frame(self, frame, layout):
        """ Initialize a floating frame.

        This initializer sets up the geometry, maximized state, and
        linked state for the floating frame.

        Parameters
        ----------
        frame : QDockFrame
            The floating dock frame of interest.

        layout : ItemLayout or AreaLayout
            The layout describing the floating state of the frame.

        """
        rect = QRect(*layout.geometry)
        if rect.isValid():
            rect = ensure_on_screen(rect)
            frame.setGeometry(rect)
        frame.show()
        if layout.linked:
            frame.setLinked(True)
        if layout.maximized:
            frame.showMaximized()
Beispiel #21
0
    def setGeometry(self, rect):
        """ Set the geometry for the items in the layout.

        """
        super(QDockItemLayout, self).setGeometry(rect)
        title = self._title_bar
        widget = self._dock_widget
        title_rect = QRect(rect)
        widget_rect = QRect(rect)
        if title is not None and not title.isHidden():
            msh = title.minimumSizeHint()
            title_rect.setHeight(msh.height())
            widget_rect.setTop(title_rect.bottom() + 1)
            title.setGeometry(title_rect)
        if widget is not None and not widget.isHidden():
            widget.setGeometry(widget_rect)
Beispiel #22
0
    def layout(self, pos):
        """ Layout the guides for the given position.

        Parameters
        ----------
        pos : QPoint
            The center point of the compass.

        """
        x = pos.x()
        y = pos.y()
        Guide = QGuideRose.Guide
        guides = self._guides
        guides[Guide.CompassNorth].rect = QRect(x - 15, y - 50, 31, 31)
        guides[Guide.CompassEast].rect = QRect(x + 20, y - 15, 31, 31)
        guides[Guide.CompassSouth].rect = QRect(x - 15, y + 20, 31, 31)
        guides[Guide.CompassWest].rect = QRect(x - 50, y - 15, 31, 31)
        guides[Guide.CompassCenter].rect = QRect(x - 15, y - 15, 31, 31)
        self._box.rect = QRect(x - 55, y - 55, 111, 111)
Beispiel #23
0
class GuideImage(Atom):
    """ A class which manages the painting of a guide image.

    """
    #: The default alpha value for guide transparency.
    TRANSPARENT = 0.60

    #: The default alpha value for no guide transparency.
    OPAQUE = 1.0

    #: The QImage to use when painting the guide.
    image = Typed(QImage, factory=lambda: QImage())

    #: The QRect specifying where to draw the image.
    rect = Typed(QRect, factory=lambda: QRect())

    #: The opacity to use when drawing the image.
    opacity = Float(TRANSPARENT)

    #: A cache of QImage instances for the loaded guide images.
    _images = {}

    @classmethod
    def load_image(cls, name):
        """ Load the guide image for the given name into a QImage.

        This function is hard-coded to return the named .png image from
        the ./dockguides directory located alongside this file. It is not
        a generic image loading routine.

        """
        image = cls._images.get(name)
        if image is None:
            image = QImage(':dock_images/%s.png' % name)
            cls._images[name] = image
        return image

    def __init__(self, name):
        """ Initialize a GuideImage.

        Parameters
        ----------
        name : string
            The name of the image to load for the guide.

        """
        self.image = self.load_image(name)

    def opacify(self):
        """ Make the guide image opaque.

        """
        self.opacity = self.OPAQUE

    def transparentize(self):
        """ Make the guide image transparent.

        """
        self.opacity = self.TRANSPARENT

    def contains(self, point):
        """ Test whether the image contains a point.

        Parameters
        ----------
        rect : QPoint
            The rect to test for containment.

        Returns
        -------
        result : bool
            True if the image contains the point, False otherwise.

        """
        return self.rect.contains(point)

    def paint(self, painter):
        """ Paint the image using the given painter.

        Parameters
        ----------
        painter : QPainter
            An active QPainter to use for drawing the image. If the
            image is a null image, painting will be skipped.

        """
        image = self.image
        if image.isNull():
            return
        painter.save()
        painter.setOpacity(self.opacity)
        painter.drawImage(self.rect, image)
        painter.restore()
Beispiel #24
0
    def apply_layout(self, layout):
        """ Apply a layout to the dock area.

        Parameters
        ----------
        layout : docklayout
            The docklayout to apply to the managed area.

        """
        # Remove the layout widget before resetting the handlers. This
        # prevents a re-used container from being hidden by the call to
        # setLayoutWidget after it has already been reset. The reference
        # is held to the old widget so the containers are not destroyed
        # before they are reset.
        widget = self._dock_area.layoutWidget()
        self._dock_area.setLayoutWidget(None)
        containers = list(self._dock_containers())
        for container in containers:
            container.reset()
        for window in list(self._dock_windows()):
            window.close()

        # Emit a warning for an item referenced in the layout which
        # has not been added to the dock manager.
        names = set(container.objectName() for container in containers)
        filter_func = lambda item: isinstance(item, dockitem)
        for item in filter(filter_func, layout.traverse()):
            if item.name not in names:
                msg = "dock item '%s' was not found in the dock manager"
                warnings.warn(msg % item.name, stacklevel=2)

        # A convenience closure for populating a dock area.
        def popuplate_area(area, layout):
            widget = build_layout(layout.child, containers)
            area.setLayoutWidget(widget)
            if layout.maximized_item:
                maxed = self._find_container(layout.maximized_item)
                if maxed is not None:
                    maxed.showMaximized()

        # Setup the layout for the primary dock area widget.
        primary = layout.primary
        if primary is not None:
            if isinstance(primary, dockarea):
                popuplate_area(self._dock_area, primary)
            else:
                widget = build_layout(primary, containers)
                self._dock_area.setLayoutWidget(widget)

        # Setup the layout for the secondary floating dock area. This
        # classifies the secondary items according to their type as
        # each type has subtle differences in how they area handled.
        single_items = []
        single_areas = []
        multi_areas = []
        for secondary in layout.secondary:
            if isinstance(secondary, dockitem):
                single_items.append(secondary)
            elif isinstance(secondary.child, dockitem):
                single_areas.append(secondary)
            else:
                multi_areas.append(secondary)

        targets = []
        for item in single_items:
            target = self._find_container(item.name)
            if target is not None:
                target.float()
                targets.append((target, item))
        for item in single_areas:
            target = self._find_container(item.child.name)
            if target is not None:
                target.float()
                targets.append((target, item))
        for item in multi_areas:
            target = QDockWindow.create(self, self._dock_area)
            win_area = target.dockArea()
            popuplate_area(win_area, item)
            win_area.installEventFilter(self._area_filter)
            self._dock_frames.append(target)
            self._proximity_handler.addFrame(target)
            targets.append((target, item))

        for target, item in targets:
            rect = QRect(*item.geometry)
            if rect.isValid():
                rect = ensure_on_screen(rect)
                target.setGeometry(rect)
            target.show()
            if item.linked:
                target.setLinked(True)
            if item.maximized:
                target.showMaximized()
Beispiel #25
0
    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())
Beispiel #26
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()
Beispiel #27
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()
Beispiel #28
0
    def drag_move_frame(self, frame, target_pos, mouse_pos):
        """ Move the floating frame to the target position.

        This method is called by a floating frame in response to a user
        moving it by dragging on it's title bar. It takes into account
        neighboring windows and will snap the frame edge to another
        window if it comes close to the boundary. It also ensures that
        the guide overlays are shown at the proper position. This method
        should not be called by user code.

        Parameters
        ----------
        frame : QDockFrame
            The floating QDockFrame which should be moved.

        target_pos : QPoint
            The global position which is the target of the move.

        mouse_pos : QPoint
            The global mouse position.

        """
        # If the frame is linked, it and any of its linked frames are
        # moved the same amount with no snapping. An unlinked window
        # is free to move and will snap to any other floating window
        # that has an opposite edge lying within the snap distance.
        # The overlay is hidden when the frame has proximal frames
        # since such a frame is not allowed to be docked.
        show_drag_overlay = True
        handler = self._proximity_handler
        if frame.isLinked():
            delta = target_pos - frame.pos()
            frame.move(target_pos)
            if handler.hasLinkedFrames(frame):
                show_drag_overlay = False
                for other in handler.linkedFrames(frame):
                    other.move(other.pos() + delta)
        else:
            f_size = frame.frameGeometry().size()
            f_rect = QRect(target_pos, f_size)
            f_x = target_pos.x()
            f_y = target_pos.y()
            f_w = f_size.width()
            f_h = f_size.height()
            dist = self._snap_dist
            filt = lambda n: -dist < n < dist
            for other in handler.proximalFrames(f_rect, dist):
                if other is not frame:
                    o_geo = other.frameGeometry()
                    o_x = o_geo.left()
                    o_y = o_geo.top()
                    o_right = o_x + o_geo.width()
                    o_bottom = o_y + o_geo.height()
                    dx = [
                        c for c in (o_x - f_x, o_x - (f_x + f_w),
                                    o_right - f_x, o_right - (f_x + f_w))
                        if filt(c)
                    ]
                    if dx:
                        f_x += min(dx)
                    dy = [
                        c for c in (o_y - f_y, o_y - (f_y + f_h),
                                    o_bottom - f_y, o_bottom - (f_y + f_h))
                        if filt(c)
                    ]
                    if dy:
                        f_y += min(dy)
            frame.move(f_x, f_y)
        if show_drag_overlay:
            self._update_drag_overlay(frame, mouse_pos)
        else:
            self._overlay.hide()
Beispiel #29
0
    def layout(self, rect):
        """ Layout the guides for the given rect.

        Parameters
        ----------
        rect : QRect
            The rectangle in which to layout the border guides.

        """
        boxes = self._boxes
        guides = self._guides
        w = rect.width()
        h = rect.height()
        cx = rect.left() + w / 2
        cy = rect.top() + h / 2
        Guide = QGuideRose.Guide
        guides[Guide.BorderNorth].rect = QRect(cx - 15, 27, 31, 19)
        guides[Guide.BorderExNorth].rect = QRect(cx - 15, 15, 31, 10)
        boxes[Guide.BorderNorth].rect = QRect(cx - 20, 10, 41, 41)
        guides[Guide.BorderEast].rect = QRect(w - 45, cy - 15, 19, 31)
        guides[Guide.BorderExEast].rect = QRect(w - 24, cy - 15, 10, 31)
        boxes[Guide.BorderEast].rect = QRect(w - 50, cy - 20, 41, 41)
        guides[Guide.BorderSouth].rect = QRect(cx - 15, h - 45, 31, 19)
        guides[Guide.BorderExSouth].rect = QRect(cx - 15, h - 24, 31, 10)
        boxes[Guide.BorderSouth].rect = QRect(cx - 20, h - 50, 41, 41)
        guides[Guide.BorderWest].rect = QRect(27, cy - 15, 19, 31)
        guides[Guide.BorderExWest].rect = QRect(15, cy - 15, 10, 31)
        boxes[Guide.BorderWest].rect = QRect(10, cy - 20, 41, 41)