예제 #1
0
def unplug_container(area, container):
    """ Unplug a container from a dock area.

    Parameters
    ----------
    area : QDockArea
        The dock area in which the container lives.

    container : QDockContainer
        The container to remove from the dock area.

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

    """
    root = area.centralWidget()
    if root is None:
        return False
    if root is container:
        root.hide()
        root.setParent(None)
        area.setCentralWidget(None)
        QApplication.sendEvent(area, QEvent(DockAreaContentsChanged))
        return True
    success, replace = _unplug(root, container)
    if not success:
        return False
    if replace is not None:
        area.setCentralWidget(replace)
    QApplication.sendEvent(area, QEvent(DockAreaContentsChanged))
    return True
예제 #2
0
    def removeContainer(self, container):
        """ Remove a container from its dock bar.

        Parameters
        ----------
        container : QDockContainer
            The container to remove from the dock bars.

        """
        button = self._widgets.pop(container, None)
        if button is not None:
            container.setParent(None)
            container.setPinned(False, quiet=True)
            container.frame_state.in_dock_bar = False
            container.alerted.disconnect(button.onAlerted)

            item = self._widgets.pop(button)
            item.setParent(None)
            self._untrackForResize(item)

            dock_bar = self._getDockBar(button.position())
            button.toggled.disconnect(self._onButtonToggled)
            button.setParent(None)
            if dock_bar.isEmpty():
                self._dock_bars[dock_bar.position()] = None
                dock_bar.setParent(None)

            event = QEvent(DockAreaContentsChanged)
            QApplication.sendEvent(self.parent(), event)
예제 #3
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
예제 #4
0
    def setCloseButtonVisible(self, index, visible):
        """ Set the close button visibility for the given tab index.

        Parameters
        ----------
        index : int
            The index of the tab to set the close button visibility.

        visible : bool
            Whether or not the close button should be visible.

        """
        if index < 0 or index >= self.count():
            return
        button = self.tabButton(index, QTabBar.RightSide)
        if button is not None:
            if button.isVisibleTo(self) != visible:
                # The public QTabBar api does not provide a way to
                # trigger the 'layoutTabs' method of QTabBarPrivate
                # and there are certain operations (such as modifying
                # a tab close button) which need to have that happen.
                # A workaround is to send a dummy resize event.
                button.setVisible(visible)
                if not visible:
                    button.resize(0, 0)
                else:
                    button.resize(button.sizeHint())
                size = self.size()
                event = QResizeEvent(size, size)
                QApplication.sendEvent(self, event)
                self.update()
예제 #5
0
def plug_frame(area, widget, frame, guide):
    """ Plug a dock frame into a dock area.

    Parameters
    ----------
    area : QDockArea
        The dock area which owns the layout into which the
        container is being plugged.

    widget : QWidget
        The widget under the mouse. This should be one of
        QDockSplitterHandle, QDockTabWidget, or QDockContainer.

    frame : QDockContainer or QDockWindow
        The dock container or window to be plugged into the area.
        This should already be unplugged from any other layout.

    guide : QGuideRose.Guide
        The guide rose guide which indicates how to perform
        the plugging.

    Returns
    -------
    result : bool
        True if the plugging was successful, False otherwise.

    """
    if not isinstance(frame, (QDockContainer, QDockWindow)):
        return False
    res = _PLUG_HANDLERS[guide](area, widget, frame, guide)
    if res:
        QApplication.sendEvent(area, QEvent(DockAreaContentsChanged))
    return res
예제 #6
0
    def removeContainer(self, container):
        """ Remove a container from its dock bar.

        Parameters
        ----------
        container : QDockContainer
            The container to remove from the dock bars.

        """
        button = self._widgets.pop(container, None)
        if button is not None:
            container.setParent(None)
            container.setPinned(False, quiet=True)
            container.frame_state.in_dock_bar = False
            container.alerted.disconnect(button.onAlerted)

            item = self._widgets.pop(button)
            item.setParent(None)
            self._untrackForResize(item)

            dock_bar = self._getDockBar(button.position())
            button.toggled.disconnect(self._onButtonToggled)
            button.setParent(None)
            if dock_bar.isEmpty():
                self._dock_bars[dock_bar.position()] = None
                dock_bar.setParent(None)

            event = QEvent(DockAreaContentsChanged)
            QApplication.sendEvent(self.parent(), event)
예제 #7
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
예제 #8
0
    def mousePressEvent(self, event):
        """ Handle the mouse press event for the tab bar.

        This handler will set the internal '_has_mouse' flag if the
        left mouse button is pressed on a tab.

        """
        super(QDockTabBar, self).mousePressEvent(event)
        self._has_mouse = False
        if event.button() == Qt.LeftButton:
            if self.tabAt(event.pos()) != -1:
                self._has_mouse = True
        elif event.button() == Qt.RightButton:
            index = self.tabAt(event.pos())
            if index != -1:
                button = self.tabButton(index, QTabBar.RightSide)
                if button.geometry().contains(event.pos()):
                    return
                item = self.parent().widget(index).dockItem()
                item.titleBarRightClicked.emit(event.globalPos())
                # Emitting the clicked signal may have caused a popup
                # menu to open, which will have grabbed the mouse. When
                # this happens, the hover leave event is not sent and
                # the tab bar will be stuck in the hovered paint state.
                # Manual checking as 'underMouse' yields False negatives.
                p = self.mapFromGlobal(QCursor.pos())
                if not self.rect().contains(p):
                    QApplication.sendEvent(self, QEvent(QEvent.HoverLeave))
예제 #9
0
def plug_frame(area, widget, frame, guide):
    """ Plug a dock frame into a dock area.

    Parameters
    ----------
    area : QDockArea
        The dock area which owns the layout into which the
        container is being plugged.

    widget : QWidget
        The widget under the mouse. This should be one of
        QDockSplitterHandle, QDockTabWidget, or QDockContainer.

    frame : QDockContainer or QDockWindow
        The dock container or window to be plugged into the area.

    guide : QGuideRose.Guide
        The guide rose guide which indicates how to perform
        the plugging.

    Returns
    -------
    result : bool
        True if the plugging was successful, False otherwise.

    """
    if not isinstance(frame, (QDockContainer, QDockWindow)):
        return False
    res = _PLUG_HANDLERS[guide](area, widget, frame, guide)
    if res:
        QApplication.sendEvent(area, QEvent(DockAreaContentsChanged))
    return res
예제 #10
0
def unplug_container(area, container):
    """ Unplug a container from a dock area.

    Parameters
    ----------
    area : QDockArea
        The dock area in which the container lives.

    container : QDockContainer
        The container to remove from the dock area.

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

    """
    root = area.layoutWidget()
    if root is None:
        return False
    if root is container:
        root.hide()
        root.setParent(None)
        area.setLayoutWidget(None)
        QApplication.sendEvent(area, QEvent(DockAreaContentsChanged))
        return True
    success, replace = _unplug(root, container)
    if not success:
        return False
    if replace is not None:
        area.setLayoutWidget(replace)
    QApplication.sendEvent(area, QEvent(DockAreaContentsChanged))
    return True
예제 #11
0
    def mousePressEvent(self, event):
        """ Handle the mouse press event for the tab bar.

        This handler will set the internal '_has_mouse' flag if the
        left mouse button is pressed on a tab.

        """
        super(QDockTabBar, self).mousePressEvent(event)
        self._has_mouse = False
        if event.button() == Qt.LeftButton:
            index = self.tabAt(event.pos())
            if index != -1:
                self._has_mouse = True
                data = self._tab_data[index]
                container = data.container
                if container is not None:
                    # likey a no-op, but just in case
                    container.dockItem().clearAlert()
        elif event.button() == Qt.RightButton:
            index = self.tabAt(event.pos())
            if index != -1:
                button = self.tabButton(index, QTabBar.RightSide)
                if button.geometry().contains(event.pos()):
                    return
                item = self.parent().widget(index).dockItem()
                item.titleBarRightClicked.emit(event.globalPos())
                # Emitting the clicked signal may have caused a popup
                # menu to open, which will have grabbed the mouse. When
                # this happens, the hover leave event is not sent and
                # the tab bar will be stuck in the hovered paint state.
                # Manual checking as 'underMouse' yields False negatives.
                p = self.mapFromGlobal(QCursor.pos())
                if not self.rect().contains(p):
                    QApplication.sendEvent(self, QEvent(QEvent.HoverLeave))
예제 #12
0
    def setCloseButtonVisible(self, index, visible):
        """ Set the close button visibility for the given tab index.

        Parameters
        ----------
        index : int
            The index of the tab to set the close button visibility.

        visible : bool
            Whether or not the close button should be visible.

        """
        if index < 0 or index >= self.count():
            return
        button = self.tabButton(index, QTabBar.RightSide)
        if button is not None:
            if button.isVisibleTo(self) != visible:
                # The public QTabBar api does not provide a way to
                # trigger the 'layoutTabs' method of QTabBarPrivate
                # and there are certain operations (such as modifying
                # a tab close button) which need to have that happen.
                # A workaround is to send a dummy resize event.
                button.setVisible(visible)
                if not visible:
                    button.resize(0, 0)
                else:
                    button.resize(button.sizeHint())
                size = self.size()
                event = QResizeEvent(size, size)
                QApplication.sendEvent(self, event)
                self.update()
예제 #13
0
    def postUndockedEvent(self):
        """ Post a DockItemUndocked event to the root dock area.

        """
        root_area = self.manager().dock_area()
        if root_area.dockEventsEnabled():
            event = QDockItemEvent(DockItemUndocked, self.objectName())
            QApplication.postEvent(root_area, event)
예제 #14
0
    def postUndockedEvent(self):
        """ Post a DockItemUndocked event to the root dock area.

        """
        root_area = self.manager().dock_area()
        if root_area.dockEventsEnabled():
            event = QDockItemEvent(DockItemUndocked, self.objectName())
            QApplication.postEvent(root_area, event)
예제 #15
0
    def _onSlideOutFinished(self):
        """ Handle the 'finished' signal from a slide out animation.

        """
        item = self.sender().targetObject()
        item.setAnimation(None)
        container = item.widget()
        area = container.manager().dock_area()
        if area.dockEventsEnabled():
            event = QDockItemEvent(DockItemExtended, container.objectName())
            QApplication.postEvent(area, event)
예제 #16
0
    def _onSlideOutFinished(self):
        """ Handle the 'finished' signal from a slide out animation.

        """
        item = self.sender().targetObject()
        item.setAnimation(None)
        container = item.widget()
        area = container.manager().dock_area()
        if area.dockEventsEnabled():
            event = QDockItemEvent(DockItemExtended, container.objectName())
            QApplication.postEvent(area, event)
예제 #17
0
    def post_docked_event(self, container):
        """ Post the docked event for the given container.

        Parameters
        ----------
        container : QDockContainer
            The dock container which was undocked.

        """
        root_area = container.manager().dock_area()
        if root_area.dockEventsEnabled():
            event = QDockItemEvent(DockItemDocked, container.objectName())
            QApplication.postEvent(root_area, event)
예제 #18
0
    def _onSlideInFinished(self):
        """ Handle the 'finished' signal from a slide in animation.

        """
        item = self.sender().targetObject()
        item.setAnimation(None)
        item.hide()
        self._untrackForResize(item)
        container = item.widget()
        area = container.manager().dock_area()
        if area.dockEventsEnabled():
            event = QDockItemEvent(DockItemRetracted, container.objectName())
            QApplication.postEvent(area, event)
예제 #19
0
    def closeEvent(self, event):
        """ Handle the close event for the dock item.

        This handler will reject the event if the item is not closable.

        """
        event.ignore()
        if self._closable:
            event.accept()
            area = self.rootDockArea()
            if area is not None and area.dockEventsEnabled():
                event = QDockItemEvent(DockItemClosed, self.objectName())
                QApplication.postEvent(area, event)
예제 #20
0
    def _onSlideInFinished(self):
        """ Handle the 'finished' signal from a slide in animation.

        """
        item = self.sender().targetObject()
        item.setAnimation(None)
        item.hide()
        self._untrackForResize(item)
        container = item.widget()
        area = container.manager().dock_area()
        if area.dockEventsEnabled():
            event = QDockItemEvent(DockItemRetracted, container.objectName())
            QApplication.postEvent(area, event)
예제 #21
0
    def closeEvent(self, event):
        """ Handle the close event for the dock item.

        This handler will reject the event if the item is not closable.

        """
        event.ignore()
        if self._closable:
            event.accept()
            area = self.rootDockArea()
            if area is not None and area.dockEventsEnabled():
                event = QDockItemEvent(DockItemClosed, self.objectName())
                QApplication.postEvent(area, event)
예제 #22
0
    def _onVisibilityTimer(self):
        """ Handle the visibility timer timeout.

        This handler will post the dock item visibility event to the
        root dock area.

        """
        area = self.rootDockArea()
        if area is not None and area.dockEventsEnabled():
            timer, visible = self._vis_changed
            evt_type = DockItemShown if visible else DockItemHidden
            event = QDockItemEvent(evt_type, self.objectName())
            QApplication.postEvent(area, event)
            self._vis_changed = None
예제 #23
0
    def _onVisibilityTimer(self):
        """ Handle the visibility timer timeout.

        This handler will post the dock item visibility event to the
        root dock area.

        """
        area = self.rootDockArea()
        if area is not None and area.dockEventsEnabled():
            timer, visible = self._vis_changed
            evt_type = DockItemShown if visible else DockItemHidden
            event = QDockItemEvent(evt_type, self.objectName())
            QApplication.postEvent(area, event)
            self._vis_changed = None
예제 #24
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
예제 #25
0
    def _onCurrentChanged(self, index):
        """ Handle the 'currentChanged' signal for the tab widget.

        """
        # These checks protect against the signal firing during close.
        container = self.widget(index)
        if container is None:
            return
        manager = container.manager()
        if manager is None:
            return
        area = manager.dock_area()
        if area is None:
            return
        if area.dockEventsEnabled():
            event = QDockItemEvent(DockTabSelected, container.objectName())
            QApplication.postEvent(area, event)
예제 #26
0
    def addContainer(self, container, position, index=-1):
        """ Add a container to the specified dock bar.

        Parameters
        ----------
        container : QDockContainer
            The container to add to the dock bar. It should already be
            unplugged from a layout or other dock manager before being
            added to this manager.

        position : QDockBar.Position
            The position of the dock bar to which the container should
            be added.

        index : int, optional
            The index at which to insert the item. The default is -1
            and will append the item to the dock bar.

        """
        assert isinstance(position, QDockBar.Position)
        self.removeContainer(container)
        container.setPinned(True, quiet=True)
        container.frame_state.in_dock_bar = True

        button = QDockBarButton()
        button.setText(container.title())
        button.setIcon(container.icon())
        button.toggled.connect(self._onButtonToggled)
        button.pressed.connect(self._onButtonPressed)
        container.alerted.connect(button.onAlerted)

        dock_bar = self._getDockBar(position)
        dock_bar.insertButton(index, button)

        item = QDockBarItem(self.parent().centralPane(), position=position)
        item.hide()
        item.setWidget(container)
        container.show()

        self._widgets[button] = item
        self._widgets[container] = button

        event = QEvent(DockAreaContentsChanged)
        QApplication.sendEvent(self.parent(), event)
예제 #27
0
    def addContainer(self, container, position, index=-1):
        """ Add a container to the specified dock bar.

        Parameters
        ----------
        container : QDockContainer
            The container to add to the dock bar. It should already be
            unplugged from a layout or other dock manager before being
            added to this manager.

        position : QDockBar.Position
            The position of the dock bar to which the container should
            be added.

        index : int, optional
            The index at which to insert the item. The default is -1
            and will append the item to the dock bar.

        """
        assert isinstance(position, QDockBar.Position)
        self.removeContainer(container)
        container.setPinned(True, quiet=True)
        container.frame_state.in_dock_bar = True

        button = QDockBarButton()
        button.setText(container.title())
        button.setIcon(container.icon())
        button.toggled.connect(self._onButtonToggled)
        button.pressed.connect(self._onButtonPressed)
        container.alerted.connect(button.onAlerted)

        dock_bar = self._getDockBar(position)
        dock_bar.insertButton(index, button)

        item = QDockBarItem(self.parent().centralPane(), position=position)
        item.hide()
        item.setWidget(container)
        container.show()

        self._widgets[button] = item
        self._widgets[container] = button

        event = QEvent(DockAreaContentsChanged)
        QApplication.sendEvent(self.parent(), event)
 def snapshot(self):
     """ Take a snapshot of the window and close it.
     
     """
     widget = self.view.proxy.widget
     framesize =  widget.window().frameSize()
     QPixmap.grabWindow(QApplication.desktop().winId(), widget.x(),
                        widget.y(), framesize.width(),
                        framesize.height() ).save(self.path)
     widget.close()
예제 #29
0
    def clearAlert(self):
        """ Clear the current alert level, if any.

        """
        if self._alert_data is not None:
            self._alert_data.timer.stop()
            self._alert_data = None
            app = QApplication.instance()
            app.focusChanged.disconnect(self._onAppFocusChanged)
            self.alerted.emit('')
 def setUp(self):
     qt_app = QApplication.instance()
     if qt_app is None:
         qt_app = QApplication([])
     self.qt_app = qt_app
     enaml_app = QtApplication.instance()
     if enaml_app is None:
         enaml_app = QtApplication()
     self.enaml_app = enaml_app
     self.event_loop_helper = EventLoopHelper(qt_app=self.qt_app)
예제 #31
0
    def clearAlert(self):
        """ Clear the current alert level, if any.

        """
        if self._alert_data is not None:
            self._alert_data.timer.stop()
            self._alert_data = None
            app = QApplication.instance()
            app.focusChanged.disconnect(self._onAppFocusChanged)
            self.alerted.emit(u'')
예제 #32
0
    def snapshot(self):
        """ Take a snapshot of the window and close it.

        """
        widget = self.view.proxy.widget
        framesize =  widget.window().frameSize()
        QPixmap.grabWindow(QApplication.desktop().winId(), widget.x(),
                           widget.y(), framesize.width(),
                           framesize.height() ).save(self.path)
        widget.close()
예제 #33
0
    def hoverMoveEvent(self, event):
        """ Handle the hover move event for the frame.

        """
        event.ignore()
        if not self.isWindow() or self.isMaximized():
            return
        if QApplication.mouseButtons() != Qt.NoButton:
            return
        state = self.frame_state
        if state.mouse_title:
            return
        if state.resize_border != self.NoBorder:
            return
        self._refreshCursor(event.pos())
        event.accept()
예제 #34
0
    def alert(self, level, on=250, off=250, repeat=4, persist=False):
        """ Set the alert level on the dock item.

        This will override any currently applied alert level.

        Parameters
        ----------
        level : str
            The alert level token to apply to the dock item.

        on : int
            The duration of the 'on' cycle, in ms. A value of -1 means
            always on.

        off : int
            The duration of the 'off' cycle, in ms. If 'on' is -1, this
            value is ignored.

        repeat : int
            The number of times to repeat the on-off cycle. If 'on' is
            -1, this value is ignored.

        persist : bool
            Whether to leave the alert in the 'on' state when the cycles
            finish. If 'on' is -1, this value is ignored.

        """
        if self._alert_data is not None:
            self.clearAlert()
        app = QApplication.instance()
        app.focusChanged.connect(self._onAppFocusChanged)
        timer = QTimer()
        timer.setSingleShot(True)
        timer.timeout.connect(self._onAlertTimer)
        on, off, repeat = max(-1, on), max(0, off), max(1, repeat)
        self._alert_data = _AlertData(timer, level, on, off, repeat, persist)
        if on < 0:
            self.alerted.emit(level)
        else:
            self._onAlertTimer()
예제 #35
0
    def alert(self, level, on=250, off=250, repeat=4, persist=False):
        """ Set the alert level on the dock item.

        This will override any currently applied alert level.

        Parameters
        ----------
        level : unicode
            The alert level token to apply to the dock item.

        on : int
            The duration of the 'on' cycle, in ms. A value of -1 means
            always on.

        off : int
            The duration of the 'off' cycle, in ms. If 'on' is -1, this
            value is ignored.

        repeat : int
            The number of times to repeat the on-off cycle. If 'on' is
            -1, this value is ignored.

        persist : bool
            Whether to leave the alert in the 'on' state when the cycles
            finish. If 'on' is -1, this value is ignored.

        """
        if self._alert_data is not None:
            self.clearAlert()
        app = QApplication.instance()
        app.focusChanged.connect(self._onAppFocusChanged)
        timer = QTimer()
        timer.setSingleShot(True)
        timer.timeout.connect(self._onAlertTimer)
        on, off, repeat = max(-1, on), max(0, off), max(1, repeat)
        self._alert_data = _AlertData(timer, level, on, off, repeat, persist)
        if on < 0:
            self.alerted.emit(level)
        else:
            self._onAlertTimer()
예제 #36
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
예제 #37
0
class DockManager(Atom):
    """ A class which manages the docking behavior of a dock area.

    """
    #: The handler which holds the primary dock area.
    _dock_area = Typed(QDockArea)

    #: The overlay used when hovering over a dock area.
    _overlay = Typed(DockOverlay, ())

    #: The list of QDockFrame instances maintained by the manager. The
    #: QDockFrame class maintains this list in proper Z-order.
    _dock_frames = List()

    #: The set of QDockItem instances added to the manager.
    _dock_items = Typed(set, ())

    #: The distance to use for snapping floating dock frames.
    _snap_dist = Int(factory=lambda: QApplication.startDragDistance() * 2)

    #: A proximity handler which manages proximal floating frames.
    _proximity_handler = Typed(ProximityHandler, ())

    #: A container monitor which tracks toplevel container changes.
    _container_monitor = Typed(DockContainerMonitor)

    def _default__container_monitor(self):
        return DockContainerMonitor(self)

    def __init__(self, dock_area):
        """ Initialize a DockingManager.

        Parameters
        ----------
        dock_area : QDockArea
            The primary dock area to be managed. Docking will be
            restricted to this area and to windows spawned by the
            area.

        """
        assert dock_area is not None
        self._dock_area = dock_area
        self._overlay = DockOverlay(dock_area)

    #--------------------------------------------------------------------------
    # Public API
    #--------------------------------------------------------------------------
    def dock_area(self):
        """ Get the dock area to which the manager is attached.

        Returns
        -------
        result : QDockArea
            The dock area to which the manager is attached.

        """
        return self._dock_area

    def add_item(self, item):
        """ Add a dock item to the dock manager.

        If the item has already been added, this is a no-op.

        Parameters
        ----------
        items : QDockItem
            The item to be managed by this dock manager. It will be
            reparented to a dock container and made available to the
            the layout system.

        """
        if item in self._dock_items:
            return
        self._dock_items.add(item)
        item._manager = self
        container = QDockContainer(self, self._dock_area)
        container.setDockItem(item)
        container.setObjectName(item.objectName())
        monitor = self._container_monitor
        container.topLevelChanged.connect(monitor.onTopLevelChanged)
        self._dock_frames.append(container)

    def remove_item(self, item):
        """ Remove a dock item from the dock manager.

        If the item has not been added to the manager, this is a no-op.

        Parameters
        ----------
        items : QDockItem
            The item to remove from the dock manager. It will be hidden
            and unparented, but not destroyed.

        """
        if item not in self._dock_items:
            return
        item._manager = None
        for container in self.dock_containers():
            if container.dockItem() is item:
                if not container.isWindow():
                    container.unplug()
                container.hide()
                self._free_container(container)
                break

    def save_layout(self):
        """ Get the current layout of the dock area.

        Returns
        -------
        result : docklayout
            A docklayout instance which represents the current layout
            state.

        """
        items = [self._dock_area] + self.floating_frames()
        return DockLayout(*map(LayoutSaver(), items))

    def apply_layout(self, layout):
        """ Apply a layout to the dock area.

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

        """
        LayoutBuilder(self)(layout)

    def update_layout(self, ops):
        """ Update the layout for a list of layout operations.

        Parameters
        ----------
        ops : list
            A list of LayoutOp objects to use for updating the layout.

        """
        builder = LayoutBuilder(self)
        for op in ops:
            builder(op)

    def destroy(self):
        """ Destroy the dock manager.

        This method will free all of the resources held by the dock
        manager. The primary dock area and dock items will not be
        destroyed. After the method is called, the dock manager is
        invalid and should no longer be used.

        """
        for frame in self._dock_frames:
            if isinstance(frame, QDockContainer):
                frame.setDockItem(None)
                frame.setParent(None, Qt.Widget)
                frame.hide()
        for frame in self._dock_frames:
            if isinstance(frame, QDockWindow):
                frame.setParent(None, Qt.Widget)
                frame.hide()
        for item in self._dock_items:
            item._manager = None
        self._dock_area.setCentralWidget(None)
        self._dock_area.setMaximizedWidget(None)
        del self._dock_area
        del self._dock_frames
        del self._dock_items
        del self._proximity_handler
        del self._container_monitor
        del self._overlay

    #--------------------------------------------------------------------------
    # Framework API
    #--------------------------------------------------------------------------
    def dock_containers(self):
        """ Get an iterable of QDockContainer instances.

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

        Returns
        -------
        result : list
            A list of QDockContainer instances owned by this dock
            manager.

        """
        f = lambda w: isinstance(w, QDockContainer)
        return filter(f, self._dock_frames)

    def dock_windows(self):
        """ Get an iterable of QDockWindow instances.

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

        Returns
        -------
        result : list
            A list of QDockWindow instances owned by this dock manager.

        """
        f = lambda w: isinstance(w, QDockWindow)
        return filter(f, self._dock_frames)

    def floating_frames(self):
        """ Get an iterable of floating dock frames.

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

        Returns
        -------
        result : list
            A list toplevel QDockFrame instances.

        """
        f = lambda w: w.isWindow()
        return filter(f, self._dock_frames)

    def add_window(self, window):
        """ Add a floating QDockWindow to the dock manager.

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

        Parameters
        ----------
        window : QDockWindow
            A newly created dock window which should be tracked by
            the dock manager.

        """
        self._dock_frames.append(window)
        self._proximity_handler.addFrame(window)

    def close_container(self, container, event):
        """ Handle a close request for a QDockContainer.

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

        Parameters
        ----------
        window : QDockContainer
            The dock container to close.

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

        """
        item = container.dockItem()
        if item is None or item.close():
            if not container.isWindow():
                container.unplug()
            self._free_container(container)
        else:
            event.ignore()

    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)

    def raise_frame(self, frame):
        """ Raise a frame to the top of the Z-order.

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

        Parameters
        ----------
        frame : QDockFrame
            The frame to raise to the top of the Z-order.

        """
        frames = self._dock_frames
        handler = self._proximity_handler
        if handler.hasLinkedFrames(frame):
            linked = set(handler.linkedFrames(frame))
            ordered = [f for f in frames if f in linked]
            for other in ordered:
                frames.remove(other)
                frames.append(other)
                other.raise_()
            frame.raise_()
        frames.remove(frame)
        frames.append(frame)

    def frame_resized(self, frame):
        """ Handle the post-processing for a resized floating frame.

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

        Parameters
        ----------
        frame : QDockFrame
            The frame which has been resized.

        """
        # If the frame is linked, the resize may have changed the frame
        # geometry such that the existing links are no longer valid.
        # The links are refreshed and the link button state is updated.
        if frame.isLinked():
            handler = self._proximity_handler
            handler.updateLinks(frame)
            if not handler.hasLinkedFrames(frame):
                frame.setLinked(False)

    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 = filter(filt, (
                        o_x - f_x,
                        o_x - (f_x + f_w),
                        o_right - f_x,
                        o_right - (f_x + f_w),
                    ))
                    if dx:
                        f_x += min(dx)
                    dy = filter(filt, (
                        o_y - f_y,
                        o_y - (f_y + f_h),
                        o_bottom - f_y,
                        o_bottom - (f_y + f_h),
                    ))
                    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()

    def drag_release_frame(self, frame, pos):
        """ Handle the dock frame being released by the user.

        This method is called by the framework at the appropriate times
        and should not be called directly by user code. It will redock
        a floating dock item if it is released over a dock guide.

        Parameters
        ----------
        frame : QDockFrame
            The dock frame being dragged by the user.

        pos : QPoint
            The global coordinates of the mouse position.

        """
        # Docking is disallowed for frames which have linked proximal
        # frames, or if the target dock area has a maximized widget.
        # This prevents a situation where the docking logic would be
        # non-sensical and maintains a consistent user experience.
        overlay = self._overlay
        overlay.hide()
        guide = overlay.guide_at(pos)
        if guide == QGuideRose.Guide.NoGuide:
            return
        if self._proximity_handler.hasLinkedFrames(frame):
            return
        builder = LayoutBuilder(self)
        target = self._dock_target(frame, pos)
        if isinstance(target, QDockArea):
            if target.maximizedWidget() is not None:
                return
            with builder.drop_frame(frame):
                local = target.mapFromGlobal(pos)
                widget = layout_hit_test(target, local)
                plug_frame(target, widget, frame, guide)
        elif isinstance(target, QDockContainer):
            with builder.dock_context(target):
                with builder.drop_frame(frame):
                    area = target.parentDockArea()
                    if area is not None:
                        plug_frame(area, target, frame, guide)

    #--------------------------------------------------------------------------
    # Private API
    #--------------------------------------------------------------------------
    def _free_container(self, container):
        """ Free the resources attached to the container.

        Parameters
        ----------
        container : QDockContainer
            The container which should be cleaned up. It should be
            unplugged from any layout before being passed to this
            method.

        """
        item = container.dockItem()
        container.setParent(None)
        container.setDockItem(None)
        container._manager = None
        self._dock_items.discard(item)
        self._dock_frames.remove(container)
        self._proximity_handler.removeFrame(container)

    def _free_window(self, window):
        """ Free the resources attached to the window.

        Parameters
        ----------
        window : QDockWindow
            The Window which should be cleaned up.

        """
        window.setParent(None)
        window.setDockArea(None)
        window._manager = None
        self._dock_frames.remove(window)
        self._proximity_handler.removeFrame(window)

    def _iter_dock_targets(self, frame):
        """ Get an iterable of potential dock targets.

        Parameters
        ----------
        frame : QDockFrame
            The frame which is being docked, and therefore excluded
            from the target search.

        Returns
        -------
        result : generator
            A generator which yields the dock container and dock area
            instances which are potential dock targets.

        """
        for target in reversed(self._dock_frames):
            if target is not frame and target.isWindow():
                if isinstance(target, QDockContainer):
                    yield target
                elif isinstance(target, QDockWindow):
                    yield target.dockArea()
        yield self._dock_area

    def _dock_target(self, frame, pos):
        """ Get the dock target for the given frame and position.

        Parameters
        ----------
        frame : QDockFrame
            The dock frame which should be docked.

        pos : QPoint
            The global mouse position.

        Returns
        -------
        result : QDockArea, QDockContainer, or None
            The potential dock target for the frame and position.

        """
        for target in self._iter_dock_targets(frame):
            # Hit test the central pane instead of the entire dock area
            # so that mouse movement over the dock bars is ignored.
            if isinstance(target, QDockArea):
                pane = target.centralPane()
                local = pane.mapFromGlobal(pos)
                if pane.rect().contains(local):
                    return target
            else:
                local = target.mapFromGlobal(pos)
                if target.rect().contains(local):
                    return target

    def _update_drag_overlay(self, frame, pos):
        """ Update the overlay for a dragged frame.

        Parameters
        ----------
        frame : QDockFrame
            The dock frame being dragged by the user.

        pos : QPoint
            The global coordinates of the mouse position.

        """
        overlay = self._overlay
        target = self._dock_target(frame, pos)
        if isinstance(target, QDockContainer):
            local = target.mapFromGlobal(pos)
            overlay.mouse_over_widget(target, local)
        elif isinstance(target, QDockArea):
            # Disallow docking onto an area with a maximized widget.
            # This prevents a non-intuitive user experience.
            if target.maximizedWidget() is not None:
                overlay.hide()
                return
            local = target.mapFromGlobal(pos)
            widget = layout_hit_test(target, local)
            overlay.mouse_over_area(target, widget, local)
        else:
            overlay.hide()
예제 #38
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