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
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)
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
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()
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
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))
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
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
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))
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)
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)
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)
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)
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)
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
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
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)
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()
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)
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'')
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()
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()
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()
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
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()
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