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)
def mouseReleaseEvent(self, event): """ Handle the mouse release event for the widget. """ event.ignore() if event.button() == Qt.LeftButton: self._press_pos = QPoint() event.accept()
def __init__(self, parent=None): """ Initialize a QDockBarItemHandle. Parameters ---------- parent : QWidget, optional The parent widget of the handle. """ super(QDockBarItemHandle, self).__init__(parent) policy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.setSizePolicy(policy) self._press_pos = QPoint() self._size_hint = QSize(5, 5)
def __init__(self): """ Initialize a QGuideRose. """ super(QGuideRose, self).__init__() # On Mac, setting the translucent background does not cause the # frame shadow to be hidden; it must be explicitly hidden. Mac # also requires the window to be a tooltip in order to be raised # above the rubber band in the Z-order. On Windows, the tooltip # leaves a dropshadow on Qt >= 4.8 whereas tool does not. if sys.platform == 'darwin': self.setAttribute(Qt.WA_MacNoShadow, True) flags = Qt.ToolTip else: flags = Qt.Tool self.setAttribute(Qt.WA_TranslucentBackground, True) flags |= Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint self.setWindowFlags(flags) self._mode = self.Mode.NoMode self._center_point = QPoint() self._border_guide = BorderGuide() self._compass_guide = CompassGuide() self._compass_ex_guide = CompassExGuide() self._vsplit_guide = SplitVerticalGuide() self._hsplit_guide = SplitHorizontalGuide() self._area_guide = AreaCenterGuide()
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)
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 do_drag(self, widget): """Perform the drag operation for the widget. Parameters ---------- widget: QWidget A reference to the viewport widget. """ drag_data = self.declaration.drag_start() if drag_data is None: return # widget = self.widget qdrag = QDrag(widget) qdrag.setMimeData(drag_data.mime_data.q_data()) if drag_data.image is not None: qimg = get_cached_qimage(drag_data.image) qdrag.setPixmap(QPixmap.fromImage(qimg)) # else: # if __version_info__ < (5, ): # qdrag.setPixmap(QPixmap.grabWidget(self.widget)) # else: # qdrag.setPixmap(widget.grab()) if drag_data.hotspot: qdrag.setHotSpot(QPoint(*drag_data.hotspot)) else: cursor_position = widget.mapFromGlobal(QCursor.pos()) qdrag.setHotSpot(cursor_position) default = Qt.DropAction(drag_data.default_drop_action) supported = Qt.DropActions(drag_data.supported_actions) qresult = qdrag.exec_(supported, default) self.declaration.drag_end(drag_data, DropAction(int(qresult)))
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 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()
class QDockBarItemHandle(QFrame): """ A frame which provides a resize border for a QDockBarItem. """ #: A signal emitted when the handle is moved. The payload is a #: QPoint which represents the delta drag distance. handleMoved = Signal(QPoint) def __init__(self, parent=None): """ Initialize a QDockBarItemHandle. Parameters ---------- parent : QWidget, optional The parent widget of the handle. """ super(QDockBarItemHandle, self).__init__(parent) policy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.setSizePolicy(policy) self._press_pos = QPoint() self._size_hint = QSize(5, 5) def sizeHint(self): """ Get the size hint for the widget. """ return self._size_hint def mousePressEvent(self, event): """ Handle the mouse press event for the widget. """ event.ignore() if event.button() == Qt.LeftButton: self._press_pos = event.pos() event.accept() def mouseReleaseEvent(self, event): """ Handle the mouse release event for the widget. """ event.ignore() if event.button() == Qt.LeftButton: self._press_pos = QPoint() event.accept() def mouseMoveEvent(self, event): """ Handle the mouse move event for the widget. """ event.ignore() if not self._press_pos.isNull(): self.handleMoved.emit(event.pos() - self._press_pos) event.accept()
def _computePressPos(container, coeff): """ Compute the press position for a title bar. Parameters ---------- container : QDockContainer The dock container which owns the title bar of interest. coeff : float A floating point value between 0.0 and 1.0 which is the proportional x-offset of the mouse press in the title bar. """ margins = container.layout().contentsMargins() button_width = 50 # general approximation max_x = container.width() - margins.right() - button_width test_x = int(coeff * container.width()) new_x = max(margins.left() + 5, min(test_x, max_x)) title_bar = container.dockItem().titleBarWidget() title_height = title_bar.height() / 2 mid_title = title_bar.mapTo(container, QPoint(0, title_height)) return QPoint(new_x, mid_title.y())
def untab(self, pos): """ Unplug the container from a tab control. This method is invoked by the QDockTabBar when the container should be torn out. It synthesizes the appropriate internal state so that the item can continue to be dock dragged. This method should not be called by user code. Parameters ---------- pos : QPoint The global mouse position. Returns ------- result : bool True on success, False otherwise. """ if not self.unplug(): return self.postUndockedEvent() state = self.frame_state state.mouse_title = True state.dragging = True state.frame_was_maximized = False self.float() self.raiseFrame() title_bar = self.dockItem().titleBarWidget() title_pos = QPoint(title_bar.width() / 2, title_bar.height() / 2) margins = self.layout().contentsMargins() offset = QPoint(margins.left(), margins.top()) state.press_pos = title_bar.mapTo(self, title_pos) + offset state.start_pos = pos - state.press_pos self.move(state.start_pos) self.show() self.grabMouse() self.activateWindow() self.raise_()
def _closestDockBar(container): """ Get the closest dock bar position for the container. This function computes the closest dock bar position by ranking the edges of the container by their distance to the respective dock area edge. Ties are broken first by relative edge weight and then by an ordered heuristic of East, West, South, then North. Returns ------- result : QDockBar.Position The closet dock bar position. """ area = container.parentDockArea() if area is None: return QDockBar.North pane = area.centralPane() c_width = container.width() c_height = container.height() p_width = pane.width() p_height = pane.height() p1 = container.mapTo(pane, QPoint(0, 0)) p2 = container.mapTo(pane, QPoint(c_width, c_height)) edge_ranks = (p1.y(), p_width - p2.x(), p_height - p2.y(), p1.x()) f_width = 1.0 - float(c_width) / p_width f_height = 1.0 - float(c_height) / p_height edge_weights = (f_width, f_height) * 2 tie_breakers = (3, 0, 2, 1) borders = (QDockBar.North, QDockBar.East, QDockBar.South, QDockBar.West) values = zip(edge_ranks, edge_weights, tie_breakers, borders) return sorted(values)[0][3]
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())
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())
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
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()
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()
def _resizeBorderTest(self, pos): """ Hit test the frame for resizing. Parameters ---------- pos : QPoint The point of interest, expressed in local coordinates. Returns ------- result : tuple A 2-tuple of (int, QPoint) representing the resize border and offset for the border. """ rect = self.rect() if not rect.contains(pos): return (self.NoBorder, QPoint()) x = pos.x() y = pos.y() width = rect.width() height = rect.height() margins = self.resizeMargins() extra = self.ResizeCornerExtra if x < margins.left(): if y < margins.top() + extra: mode = self.NorthWestBorder offset = QPoint(x, y) elif y > height - (margins.bottom() + extra): mode = self.SouthWestBorder offset = QPoint(x, height - y) else: mode = self.WestBorder offset = QPoint(x, 0) elif y < margins.top(): if x < margins.left() + extra: mode = self.NorthWestBorder offset = QPoint(x, y) elif x > width - (margins.right() + extra): mode = self.NorthEastBorder offset = QPoint(width - x, y) else: mode = self.NorthBorder offset = QPoint(0, y) elif x > width - margins.right(): if y < margins.top() + extra: mode = self.NorthEastBorder offset = QPoint(width - x, y) elif y > height - (margins.bottom() + extra): mode = self.SouthEastBorder offset = QPoint(width - x, height - y) else: mode = self.EastBorder offset = QPoint(width - x, 0) elif y > height - margins.bottom(): if x < margins.left() + extra: mode = self.SouthWestBorder offset = QPoint(x, height - y) elif x > width - (margins.right() + extra): mode = self.SouthEastBorder offset = QPoint(width - x, height - y) else: mode = self.SouthBorder offset = QPoint(0, height - y) else: mode = self.NoBorder offset = QPoint() return mode, offset
def pixel_density(self): tr = self.widget.transform().inverted()[0] p = tr.map(QPoint(1, 1)) - tr.map(QPoint(0, 0)) return Point(p.x(), p.y())
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