2019-11-12 16:45:42 +00:00
# -*- coding: utf-8 -*-
2012-03-02 02:55:32 +00:00
import weakref
2018-12-20 17:40:36 +00:00
import warnings
2021-07-23 03:57:50 +00:00
from time import perf_counter , perf_counter_ns
2018-12-20 17:40:36 +00:00
2021-01-16 22:45:19 +00:00
from . . Qt import QtCore , QtGui , QT_LIB , isQObjectAlive
2013-12-23 12:51:33 +00:00
from . . Point import Point
2012-05-11 22:05:41 +00:00
from . mouseEvents import *
2013-12-23 12:51:33 +00:00
from . . import debug as debug
2020-06-25 05:20:47 +00:00
from . . import getConfigOption
2021-07-23 03:57:50 +00:00
getMillis = lambda : perf_counter_ns ( ) / / 10 * * 6
2012-03-02 02:55:32 +00:00
2015-05-19 13:29:55 +00:00
2021-01-16 22:45:19 +00:00
if QT_LIB . startswith ( ' PyQt ' ) :
from . . Qt import sip
HAVE_SIP = True
2012-06-18 23:40:15 +00:00
else :
2012-03-02 02:55:32 +00:00
HAVE_SIP = False
__all__ = [ ' GraphicsScene ' ]
class GraphicsScene ( QtGui . QGraphicsScene ) :
"""
Extension of QGraphicsScene that implements a complete , parallel mouse event system .
( It would have been preferred to just alter the way QGraphicsScene creates and delivers
events , but this turned out to be impossible because the constructor for QGraphicsMouseEvent
is private )
2021-07-21 14:12:35 +00:00
* Generates MouseClicked events in addition to the usual press / move / release events .
( This works around a problem where it is impossible to have one item respond to a
drag if another is watching for a click . )
* Adjustable radius around click that will catch objects so you don ' t have to click *exactly* over small/thin objects
* Global context menu - - if an item implements a context menu , then its parent ( s ) may also add items to the menu .
* Allows items to decide _before_ a mouse click which item will be the recipient of mouse events .
This lets us indicate unambiguously to the user which item they are about to click / drag on
* Eats mouseMove events that occur too soon after a mouse press .
* Reimplements items ( ) and itemAt ( ) to circumvent PyQt bug
2018-04-25 20:19:50 +00:00
2020-11-06 22:06:53 +00:00
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
2018-04-25 20:19:50 +00:00
* * Signals * *
sigMouseClicked ( event ) Emitted when the mouse is clicked over the scene . Use ev . pos ( ) to
get the click position relative to the item that was clicked on ,
or ev . scenePos ( ) to get the click position in scene coordinates .
See : class : ` pyqtgraph . GraphicsScene . MouseClickEvent ` .
sigMouseMoved ( pos ) Emitted when the mouse cursor moves over the scene . The position
is given in scene coordinates .
sigMouseHover ( items ) Emitted when the mouse is moved over the scene . Items is a list
of items under the cursor .
2020-11-06 22:06:53 +00:00
sigItemAdded ( item ) Emitted when an item is added via addItem ( ) . The item is given .
sigItemRemoved ( item ) Emitted when an item is removed via removeItem ( ) . The item is given .
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
2012-03-02 02:55:32 +00:00
Mouse interaction is as follows :
2012-04-18 04:02:15 +00:00
2012-03-02 02:55:32 +00:00
1 ) Every time the mouse moves , the scene delivers both the standard hoverEnter / Move / LeaveEvents
as well as custom HoverEvents .
2 ) Items are sent HoverEvents in Z - order and each item may optionally call event . acceptClicks ( button ) ,
acceptDrags ( button ) or both . If this method call returns True , this informs the item that _if_
the user clicks / drags the specified mouse button , the item is guaranteed to be the
recipient of click / drag events ( the item may wish to change its appearance to indicate this ) .
2012-04-18 04:02:15 +00:00
If the call to acceptClicks / Drags returns False , then the item is guaranteed to * not * receive
2012-03-02 02:55:32 +00:00
the requested event ( because another item has already accepted it ) .
3 ) If the mouse is clicked , a mousePressEvent is generated as usual . If any items accept this press event , then
No click / drag events will be generated and mouse interaction proceeds as defined by Qt . This allows
items to function properly if they are expecting the usual press / move / release sequence of events .
( It is recommended that items do NOT accept press events , and instead use click / drag events )
2012-04-18 04:02:15 +00:00
Note : The default implementation of QGraphicsItem . mousePressEvent will * accept * the event if the
2012-03-02 02:55:32 +00:00
item is has its Selectable or Movable flags enabled . You may need to override this behavior .
2012-04-18 04:02:15 +00:00
4 ) If no item accepts the mousePressEvent , then the scene will begin delivering mouseDrag and / or mouseClick events .
2012-03-02 02:55:32 +00:00
If the mouse is moved a sufficient distance ( or moved slowly enough ) before the button is released ,
then a mouseDragEvent is generated .
If no drag events are generated before the button is released , then a mouseClickEvent is generated .
2012-04-18 04:02:15 +00:00
5 ) Click / drag events are delivered to the item that called acceptClicks / acceptDrags on the HoverEvent
2012-03-02 02:55:32 +00:00
in step 1. If no such items exist , then the scene attempts to deliver the events to items near the event .
ClickEvents may be delivered in this way even if no
item originally claimed it could accept the click . DragEvents may only be delivered this way if it is the initial
move in a drag .
"""
sigMouseHover = QtCore . Signal ( object ) ## emits a list of objects hovered over
sigMouseMoved = QtCore . Signal ( object ) ## emits position of mouse on every move
2012-12-22 22:04:07 +00:00
sigMouseClicked = QtCore . Signal ( object ) ## emitted when mouse is clicked. Check for event.isAccepted() to see whether the event has already been acted on.
2012-03-02 02:55:32 +00:00
2013-01-10 03:21:32 +00:00
sigPrepareForPaint = QtCore . Signal ( ) ## emitted immediately before the scene is about to be rendered
2020-11-06 22:06:53 +00:00
sigItemAdded = QtCore . Signal ( object ) ## emits the item object just added
sigItemRemoved = QtCore . Signal ( object ) ## emits the item object just removed
2012-03-23 06:41:10 +00:00
_addressCache = weakref . WeakValueDictionary ( )
2012-03-11 15:59:45 +00:00
ExportDirectory = None
2012-03-02 02:55:32 +00:00
@classmethod
def registerObject ( cls , obj ) :
2018-12-20 17:40:36 +00:00
warnings . warn (
" ' registerObject ' is deprecated and does nothing. " ,
DeprecationWarning , stacklevel = 2
)
2014-11-27 02:25:17 +00:00
def __init__ ( self , clickRadius = 2 , moveDistance = 5 , parent = None ) :
QtGui . QGraphicsScene . __init__ ( self , parent )
2012-03-02 02:55:32 +00:00
self . setClickRadius ( clickRadius )
self . setMoveDistance ( moveDistance )
2012-03-11 15:59:45 +00:00
self . exportDirectory = None
2012-03-02 02:55:32 +00:00
self . clickEvents = [ ]
self . dragButtons = [ ]
self . mouseGrabber = None
self . dragItem = None
self . lastDrag = None
self . hoverItems = weakref . WeakKeyDictionary ( )
self . lastHoverEvent = None
2016-02-04 07:13:58 +00:00
self . minDragTime = 0.5 # drags shorter than 0.5 sec are interpreted as clicks
2012-03-02 02:55:32 +00:00
2021-01-28 16:42:18 +00:00
self . contextMenu = [ QtGui . QAction ( QtCore . QCoreApplication . translate ( " GraphicsScene " , " Export... " ) , self ) ]
2012-03-11 15:59:45 +00:00
self . contextMenu [ 0 ] . triggered . connect ( self . showExportDialog )
self . exportDialog = None
2020-06-25 05:20:47 +00:00
self . _lastMoveEventTime = 0
2012-03-11 15:59:45 +00:00
2013-01-10 03:21:32 +00:00
def render ( self , * args ) :
self . prepareForPaint ( )
2013-02-10 18:56:42 +00:00
return QtGui . QGraphicsScene . render ( self , * args )
2013-01-10 03:21:32 +00:00
def prepareForPaint ( self ) :
""" Called before every render. This method will inform items that the scene is about to
be rendered by emitting sigPrepareForPaint .
This allows items to delay expensive processing until they know a paint will be required . """
self . sigPrepareForPaint . emit ( )
2012-03-02 02:55:32 +00:00
def setClickRadius ( self , r ) :
"""
Set the distance away from mouse clicks to search for interacting items .
When clicking , the scene searches first for items that directly intersect the click position
followed by any other items that are within a rectangle that extends r pixels away from the
click position .
"""
self . _clickRadius = r
def setMoveDistance ( self , d ) :
"""
Set the distance the mouse must move after a press before mouseMoveEvents will be delivered .
This ensures that clicks with a small amount of movement are recognized as clicks instead of
drags .
"""
self . _moveDistance = d
def mousePressEvent ( self , ev ) :
2021-01-17 04:26:32 +00:00
super ( ) . mousePressEvent ( ev )
2012-03-02 02:55:32 +00:00
if self . mouseGrabberItem ( ) is None : ## nobody claimed press; we are free to generate drag/click events
2014-08-15 16:45:35 +00:00
if self . lastHoverEvent is not None :
# If the mouse has moved since the last hover event, send a new one.
# This can happen if a context menu is open while the mouse is moving.
if ev . scenePos ( ) != self . lastHoverEvent . scenePos ( ) :
self . sendHoverEvents ( ev )
2012-03-02 02:55:32 +00:00
self . clickEvents . append ( MouseClickEvent ( ev ) )
2012-04-15 14:19:30 +00:00
## set focus on the topmost focusable item under this click
items = self . items ( ev . scenePos ( ) )
for i in items :
2021-06-11 02:25:05 +00:00
if i . isEnabled ( ) and i . isVisible ( ) and ( i . flags ( ) & i . GraphicsItemFlag . ItemIsFocusable ) :
2021-06-06 01:17:43 +00:00
i . setFocus ( QtCore . Qt . FocusReason . MouseFocusReason )
2012-04-15 14:19:30 +00:00
break
2020-06-25 05:20:47 +00:00
def _moveEventIsAllowed ( self ) :
# For ignoring events that are too close together
2021-06-11 02:25:05 +00:00
2020-06-25 05:20:47 +00:00
# Max number of events per second
rateLimit = getConfigOption ( ' mouseRateLimit ' )
if rateLimit < = 0 :
return True
2021-06-11 02:25:05 +00:00
2020-06-25 05:20:47 +00:00
# Delay between events (in milliseconds)
delay = 1000.0 / rateLimit
if getMillis ( ) - self . _lastMoveEventTime > = delay :
return True
return False
2012-03-02 02:55:32 +00:00
2020-06-25 05:20:47 +00:00
def mouseMoveEvent ( self , ev ) :
# ignore high frequency events
if self . _moveEventIsAllowed ( ) :
self . _lastMoveEventTime = getMillis ( )
self . sigMouseMoved . emit ( ev . scenePos ( ) )
# First allow QGraphicsScene to eliver hoverEvent/Move/Exit Events
2021-01-17 04:26:32 +00:00
super ( ) . mouseMoveEvent ( ev )
2020-06-25 05:20:47 +00:00
# Next Deliver our own Hover Events
self . sendHoverEvents ( ev )
2021-01-17 04:23:49 +00:00
if ev . buttons ( ) :
2020-06-25 05:20:47 +00:00
# button is pressed' send mouseMoveEvents and mouseDragEvents
2021-01-17 04:26:32 +00:00
super ( ) . mouseMoveEvent ( ev )
2020-06-25 05:20:47 +00:00
if self . mouseGrabberItem ( ) is None :
2021-07-23 03:57:50 +00:00
now = perf_counter ( )
2020-06-25 05:20:47 +00:00
init = False
## keep track of which buttons are involved in dragging
2021-06-06 01:17:43 +00:00
for btn in [ QtCore . Qt . MouseButton . LeftButton , QtCore . Qt . MouseButton . MiddleButton , QtCore . Qt . MouseButton . RightButton ] :
2021-01-17 04:23:49 +00:00
if not ( ev . buttons ( ) & btn ) :
2020-06-25 05:20:47 +00:00
continue
2021-01-17 04:23:49 +00:00
if btn not in self . dragButtons : ## see if we've dragged far enough yet
cev = [ e for e in self . clickEvents if e . button ( ) == btn ]
2020-06-25 05:20:47 +00:00
if cev :
cev = cev [ 0 ]
dist = Point ( ev . scenePos ( ) - cev . scenePos ( ) ) . length ( )
if dist == 0 or ( dist < self . _moveDistance and now - cev . time ( ) < self . minDragTime ) :
continue
init = init or ( len ( self . dragButtons ) == 0 ) ## If this is the first button to be dragged, then init=True
2021-01-17 04:23:49 +00:00
self . dragButtons . append ( btn )
2020-06-25 05:20:47 +00:00
## if we have dragged buttons, deliver a drag event
if len ( self . dragButtons ) > 0 :
if self . sendDragEvent ( ev , init = init ) :
ev . accept ( )
else :
2021-01-17 04:26:32 +00:00
super ( ) . mouseMoveEvent ( ev )
2020-06-25 05:20:47 +00:00
# if you do not accept event (which is ignored) then cursor will disappear
ev . accept ( )
2012-03-02 02:55:32 +00:00
def leaveEvent ( self , ev ) : ## inform items that mouse is gone
if len ( self . dragButtons ) == 0 :
self . sendHoverEvents ( ev , exitOnly = True )
def mouseReleaseEvent ( self , ev ) :
if self . mouseGrabberItem ( ) is None :
if ev . button ( ) in self . dragButtons :
if self . sendDragEvent ( ev , final = True ) :
#print "sent drag event"
ev . accept ( )
self . dragButtons . remove ( ev . button ( ) )
else :
2021-01-17 04:23:49 +00:00
cev = [ e for e in self . clickEvents if e . button ( ) == ev . button ( ) ]
2019-09-13 03:24:48 +00:00
if cev :
if self . sendClickEvent ( cev [ 0 ] ) :
#print "sent click event"
ev . accept ( )
self . clickEvents . remove ( cev [ 0 ] )
2012-03-02 02:55:32 +00:00
2021-01-17 04:23:49 +00:00
if not ev . buttons ( ) :
2012-03-02 02:55:32 +00:00
self . dragItem = None
self . dragButtons = [ ]
self . clickEvents = [ ]
self . lastDrag = None
2021-01-17 04:26:32 +00:00
super ( ) . mouseReleaseEvent ( ev )
2012-03-02 02:55:32 +00:00
self . sendHoverEvents ( ev ) ## let items prepare for next click/drag
def mouseDoubleClickEvent ( self , ev ) :
2021-01-17 04:26:32 +00:00
super ( ) . mouseDoubleClickEvent ( ev )
2012-03-02 02:55:32 +00:00
if self . mouseGrabberItem ( ) is None : ## nobody claimed press; we are free to generate drag/click events
self . clickEvents . append ( MouseClickEvent ( ev , double = True ) )
def sendHoverEvents ( self , ev , exitOnly = False ) :
## if exitOnly, then just inform all previously hovered items that the mouse has left.
if exitOnly :
acceptable = False
items = [ ]
event = HoverEvent ( None , acceptable )
else :
2021-01-17 04:23:49 +00:00
acceptable = not ev . buttons ( ) ## if we are in mid-drag, do not allow items to accept the hover event.
2012-03-02 02:55:32 +00:00
event = HoverEvent ( ev , acceptable )
2013-01-10 03:21:32 +00:00
items = self . itemsNearEvent ( event , hoverable = True )
2012-03-02 02:55:32 +00:00
self . sigMouseHover . emit ( items )
2012-05-11 22:05:41 +00:00
prevItems = list ( self . hoverItems . keys ( ) )
2012-03-02 02:55:32 +00:00
for item in items :
if hasattr ( item , ' hoverEvent ' ) :
event . currentItem = item
if item not in self . hoverItems :
self . hoverItems [ item ] = None
event . enter = True
else :
prevItems . remove ( item )
event . enter = False
try :
item . hoverEvent ( event )
except :
debug . printExc ( " Error sending hover event: " )
2016-05-31 01:00:19 +00:00
2012-03-02 02:55:32 +00:00
event . enter = False
event . exit = True
2014-08-15 16:45:35 +00:00
#print "hover exit items:", prevItems
2012-03-02 02:55:32 +00:00
for item in prevItems :
event . currentItem = item
try :
2021-01-15 05:12:36 +00:00
# NOTE: isQObjectAlive(item) was added for PySide6 where
# verlet_chain_demo.py triggers a RuntimeError.
if isQObjectAlive ( item ) and item . scene ( ) is self :
2019-06-20 05:08:54 +00:00
item . hoverEvent ( event )
2012-03-02 02:55:32 +00:00
except :
debug . printExc ( " Error sending hover exit event: " )
finally :
del self . hoverItems [ item ]
2014-08-15 16:45:35 +00:00
# Update last hover event unless:
# - mouse is dragging (move+buttons); in this case we want the dragged
# item to continue receiving events until the drag is over
2021-06-06 01:17:43 +00:00
# - event is not a mouse event (QEvent.Type.Leave sometimes appears here)
if ( ev . type ( ) == ev . Type . GraphicsSceneMousePress or
( ev . type ( ) == ev . Type . GraphicsSceneMouseMove and not ev . buttons ( ) ) ) :
2012-03-02 02:55:32 +00:00
self . lastHoverEvent = event ## save this so we can ask about accepted events later.
def sendDragEvent ( self , ev , init = False , final = False ) :
## Send a MouseDragEvent to the current dragItem or to
## items near the beginning of the drag
event = MouseDragEvent ( ev , self . clickEvents [ 0 ] , self . lastDrag , start = init , finish = final )
#print "dragEvent: init=", init, 'final=', final, 'self.dragItem=', self.dragItem
if init and self . dragItem is None :
if self . lastHoverEvent is not None :
acceptedItem = self . lastHoverEvent . dragItems ( ) . get ( event . button ( ) , None )
else :
acceptedItem = None
2019-06-20 05:08:54 +00:00
if acceptedItem is not None and acceptedItem . scene ( ) is self :
2012-03-02 02:55:32 +00:00
#print "Drag -> pre-selected item:", acceptedItem
self . dragItem = acceptedItem
event . currentItem = self . dragItem
try :
self . dragItem . mouseDragEvent ( event )
except :
debug . printExc ( " Error sending drag event: " )
else :
#print "drag -> new item"
for item in self . itemsNearEvent ( event ) :
#print "check item:", item
2012-04-15 14:19:30 +00:00
if not item . isVisible ( ) or not item . isEnabled ( ) :
continue
2012-03-02 02:55:32 +00:00
if hasattr ( item , ' mouseDragEvent ' ) :
event . currentItem = item
try :
item . mouseDragEvent ( event )
except :
debug . printExc ( " Error sending drag event: " )
if event . isAccepted ( ) :
#print " --> accepted"
self . dragItem = item
2021-06-11 02:25:05 +00:00
if item . flags ( ) & item . GraphicsItemFlag . ItemIsFocusable :
2021-06-06 01:17:43 +00:00
item . setFocus ( QtCore . Qt . FocusReason . MouseFocusReason )
2012-03-02 02:55:32 +00:00
break
elif self . dragItem is not None :
event . currentItem = self . dragItem
try :
self . dragItem . mouseDragEvent ( event )
except :
debug . printExc ( " Error sending hover exit event: " )
self . lastDrag = event
return event . isAccepted ( )
def sendClickEvent ( self , ev ) :
## if we are in mid-drag, click events may only go to the dragged item.
if self . dragItem is not None and hasattr ( self . dragItem , ' mouseClickEvent ' ) :
ev . currentItem = self . dragItem
self . dragItem . mouseClickEvent ( ev )
## otherwise, search near the cursor
else :
if self . lastHoverEvent is not None :
acceptedItem = self . lastHoverEvent . clickItems ( ) . get ( ev . button ( ) , None )
else :
acceptedItem = None
if acceptedItem is not None :
ev . currentItem = acceptedItem
try :
acceptedItem . mouseClickEvent ( ev )
except :
debug . printExc ( " Error sending click event: " )
else :
for item in self . itemsNearEvent ( ev ) :
2012-04-15 14:19:30 +00:00
if not item . isVisible ( ) or not item . isEnabled ( ) :
continue
2012-03-02 02:55:32 +00:00
if hasattr ( item , ' mouseClickEvent ' ) :
ev . currentItem = item
try :
item . mouseClickEvent ( ev )
except :
debug . printExc ( " Error sending click event: " )
if ev . isAccepted ( ) :
2021-06-11 02:25:05 +00:00
if item . flags ( ) & item . GraphicsItemFlag . ItemIsFocusable :
2021-06-06 01:17:43 +00:00
item . setFocus ( QtCore . Qt . FocusReason . MouseFocusReason )
2012-03-02 02:55:32 +00:00
break
2012-12-22 22:04:07 +00:00
self . sigMouseClicked . emit ( ev )
2012-03-02 02:55:32 +00:00
return ev . isAccepted ( )
2020-11-06 22:06:53 +00:00
def addItem ( self , item ) :
# extend QGraphicsScene.addItem to emit a sigItemAdded signal
ret = QtGui . QGraphicsScene . addItem ( self , item )
self . sigItemAdded . emit ( item )
return ret
def removeItem ( self , item ) :
# extend QGraphicsScene.removeItem to emit a sigItemRemoved signal
ret = QtGui . QGraphicsScene . removeItem ( self , item )
self . sigItemRemoved . emit ( item )
return ret
2012-03-02 02:55:32 +00:00
def items ( self , * args ) :
items = QtGui . QGraphicsScene . items ( self , * args )
2018-12-20 17:40:36 +00:00
return self . translateGraphicsItems ( items )
2012-03-02 02:55:32 +00:00
def selectedItems ( self , * args ) :
items = QtGui . QGraphicsScene . selectedItems ( self , * args )
2018-12-20 17:40:36 +00:00
return self . translateGraphicsItems ( items )
2012-03-02 02:55:32 +00:00
def itemAt ( self , * args ) :
item = QtGui . QGraphicsScene . itemAt ( self , * args )
return self . translateGraphicsItem ( item )
2021-06-06 01:17:43 +00:00
def itemsNearEvent ( self , event , selMode = QtCore . Qt . ItemSelectionMode . IntersectsItemShape , sortOrder = QtCore . Qt . SortOrder . DescendingOrder , hoverable = False ) :
2012-03-02 02:55:32 +00:00
"""
Return an iterator that iterates first through the items that directly intersect point ( in Z order )
followed by any other items that are within the scene ' s click radius.
"""
#tr = self.getViewWidget(event.widget()).transform()
view = self . views ( ) [ 0 ]
tr = view . viewportTransform ( )
r = self . _clickRadius
rect = view . mapToScene ( QtCore . QRect ( 0 , 0 , 2 * r , 2 * r ) ) . boundingRect ( )
seen = set ( )
if hasattr ( event , ' buttonDownScenePos ' ) :
point = event . buttonDownScenePos ( )
else :
point = event . scenePos ( )
w = rect . width ( )
h = rect . height ( )
rgn = QtCore . QRectF ( point . x ( ) - w , point . y ( ) - h , 2 * w , 2 * h )
#self.searchRect.setRect(rgn)
items = self . items ( point , selMode , sortOrder , tr )
## remove items whose shape does not contain point (scene.items() apparently sucks at this)
items2 = [ ]
for item in items :
2013-01-10 03:21:32 +00:00
if hoverable and not hasattr ( item , ' hoverEvent ' ) :
continue
2019-06-20 05:08:54 +00:00
if item . scene ( ) is not self :
continue
2014-02-09 15:38:29 +00:00
shape = item . shape ( ) # Note: default shape() returns boundingRect()
2012-03-02 02:55:32 +00:00
if shape is None :
continue
2014-02-09 15:38:29 +00:00
if shape . contains ( item . mapFromScene ( point ) ) :
2012-03-02 02:55:32 +00:00
items2 . append ( item )
## Sort by descending Z-order (don't trust scene.itms() to do this either)
## use 'absolute' z value, which is the sum of all item/parent ZValues
def absZValue ( item ) :
if item is None :
return 0
return item . zValue ( ) + absZValue ( item . parentItem ( ) )
2019-11-12 16:45:42 +00:00
items2 . sort ( key = absZValue , reverse = True )
2012-03-02 02:55:32 +00:00
return items2
#for item in items:
##seen.add(item)
#shape = item.mapToScene(item.shape())
#if not shape.contains(point):
#continue
#yield item
#for item in self.items(rgn, selMode, sortOrder, tr):
##if item not in seen:
#yield item
2012-03-11 15:59:45 +00:00
def getViewWidget ( self ) :
return self . views ( ) [ 0 ]
#def getViewWidget(self, widget):
### same pyqt bug -- mouseEvent.widget() doesn't give us the original python object.
### [[doesn't seem to work correctly]]
#if HAVE_SIP and isinstance(self, sip.wrapper):
#addr = sip.unwrapinstance(sip.cast(widget, QtGui.QWidget))
##print "convert", widget, addr
#for v in self.views():
#addr2 = sip.unwrapinstance(sip.cast(v, QtGui.QWidget))
##print " check:", v, addr2
#if addr2 == addr:
#return v
#else:
#return widget
2013-11-20 20:13:35 +00:00
2012-03-02 02:55:32 +00:00
def addParentContextMenus ( self , item , menu , event ) :
"""
Can be called by any item in the scene to expand its context menu to include parent context menus .
Parents may implement getContextMenus to add new menus / actions to the existing menu .
getContextMenus must accept 1 argument ( the event that generated the original menu ) and
return a single QMenu or a list of QMenus .
The final menu will look like :
2012-04-18 04:02:15 +00:00
| Original Item 1
| Original Item 2
| . . .
| Original Item N
| - - - - - - - - - - - - - - - - - -
| Parent Item 1
| Parent Item 2
| . . .
| Grandparent Item 1
| . . .
2012-03-02 02:55:32 +00:00
2012-04-18 04:02:15 +00:00
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
* * Arguments : * *
item The item that initially created the context menu
( This is probably the item making the call to this function )
menu The context menu being shown by the item
event The original event that triggered the menu to appear .
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
2012-03-02 02:55:32 +00:00
"""
2013-11-20 20:13:35 +00:00
2012-03-02 02:55:32 +00:00
menusToAdd = [ ]
2012-03-11 15:59:45 +00:00
while item is not self :
2012-03-02 02:55:32 +00:00
item = item . parentItem ( )
2012-03-11 15:59:45 +00:00
if item is None :
item = self
2013-11-22 04:29:03 +00:00
if not hasattr ( item , " getContextMenus " ) :
continue
2013-11-20 20:13:35 +00:00
subMenus = item . getContextMenus ( event ) or [ ]
if isinstance ( subMenus , list ) : ## so that some items (like FlowchartViewBox) can return multiple menus
menusToAdd . extend ( subMenus )
else :
menusToAdd . append ( subMenus )
if menusToAdd :
2012-03-02 02:55:32 +00:00
menu . addSeparator ( )
2013-11-20 20:13:35 +00:00
2012-03-02 02:55:32 +00:00
for m in menusToAdd :
2012-03-11 15:59:45 +00:00
if isinstance ( m , QtGui . QMenu ) :
2020-11-11 17:02:46 +00:00
menu . addAction ( m . menuAction ( ) )
2012-03-11 15:59:45 +00:00
elif isinstance ( m , QtGui . QAction ) :
menu . addAction ( m )
else :
raise Exception ( " Cannot add object %s (type= %s ) to QMenu. " % ( str ( m ) , str ( type ( m ) ) ) )
2012-03-02 02:55:32 +00:00
return menu
2012-03-11 15:59:45 +00:00
def getContextMenus ( self , event ) :
self . contextMenuItem = event . acceptedItem
return self . contextMenu
def showExportDialog ( self ) :
if self . exportDialog is None :
2013-12-23 15:22:53 +00:00
from . import exportDialog
2012-03-11 15:59:45 +00:00
self . exportDialog = exportDialog . ExportDialog ( self )
self . exportDialog . show ( self . contextMenuItem )
2012-03-02 02:55:32 +00:00
@staticmethod
def translateGraphicsItem ( item ) :
2018-12-20 17:40:36 +00:00
# This function is intended as a workaround for a problem with older
# versions of PyQt (< 4.9?), where methods returning 'QGraphicsItem *'
# lose the type of the QGraphicsObject subclasses and instead return
# generic QGraphicsItem wrappers.
2012-03-02 02:55:32 +00:00
if HAVE_SIP and isinstance ( item , sip . wrapper ) :
2018-12-20 17:40:36 +00:00
obj = item . toGraphicsObject ( )
if obj is not None :
item = obj
2012-03-02 02:55:32 +00:00
return item
@staticmethod
def translateGraphicsItems ( items ) :
2012-05-11 22:05:41 +00:00
return list ( map ( GraphicsScene . translateGraphicsItem , items ) )