QGraphicsView behaving differently when mouse is held over a QGraphicsProxyWidget - python

I have a pyside2 GUI that uses QGraphicsView.
I use setDragMode(QGraphicsView.ScrollHandDrag) to make the view dragable, but i override the cursor with viewport().setCursor(Qt.ArrowCursor) on mouseReleaseEvent to avoid constantly having the open-hand in stead of the normal arrow cursor.
This is explained here: Changing the cursor in a QGraphicsView (in c++)
In the GUI there is also a QGraphicsProxyWidget with a QLabel. When the mouse is placed over the ProxyWidget, the viewport().setCursor(Qt.ArrowCursor) does not work (the moseReleaseEvent is called, so i know that setCursor is called), and when the mouse leaves the ProxyWidget, the open-hand cursor shows in stead of the arrow-cursor.
When the mouse is placed all other places in the QGraphicsView everything is working as expected.
Does anyone know why setCursor is behaving differently when the mouse is placed over a proxyWidget?
In QGraphicsView:
def mouseReleaseEvent(self, event):
QGraphicsView.mouseReleaseEvent(self, event)
self.viewport().setCursor(Qt.ArrowCursor)
def infoBoxShow(self, edge, mouse_pos):
if self.info_box is None:
self.info_box = VardeInfoBox_v2.InfoBox()
self.info_box.corresponding_edge = edge
self.info_box.setPos(mouse_pos)
self.info_box.setInfoText(edge)
self.main_scene.addItem(self.info_box)
InfoBox (As you can see i have tried to set some flags without success):
class InfoBox(QGraphicsItem):
Type = QGraphicsItem.UserType + 1
def __init__(self):
QGraphicsItem.__init__(self)
self.setFlag(QGraphicsItem.hover)
self.setZValue(4)
proxy = QGraphicsProxyWidget(self)
widget = QLabel("TEST!")
widget.setAttribute(Qt.WA_TransparentForMouseEvents)
widget.setWindowModality(Qt.NonModal)
proxy.setWidget(widget)
self.corresponding_edge = None

Related

QToolButton with popup QSlider

I'm trying to make volume button, on click it should mute/unmute and and on hover it should popup QSlider, so user can set whatever level he wants. Now I'm trying to achieve this by showing slider window in enterEvent and hiding it in leaveEvent:
class VolumeButton(QToolButton):
def __init__(self, parent=None):
super().__init__(parent)
self.setIcon(volumeicon)
self.slider = QSlider()
self.slider.setWindowFlags(Qt.FramelessWindowHint)
self.slider.setWindowModality(Qt.NonModal)
def enterEvent(self, event):
self.slider.move(self.mapToGlobal(self.rect().topLeft()))
self.slider.show()
def leaveEvent(self, event):
self.slider.hide()
The problem is that mapToGlobal seems to be connected in some way with enterEvent and it creates recursion, but without mapToGlobal I can't place slider at the right position.
I'm not sure that QToolButton and FramelessWindow are the right widgets to achieve wished result, so let me know if there a better ways to do that.
The problem is not from mapToGlobal, but from the fact that the leaveEvent is fired as soon as the slider is shown: since the slider is in the same coordinates of the mouse, Qt considers that the mouse has "left" the button (and "entered" the slider).
You cannot use the simple leaveEvent for this, as you need to check the cursor position against both the button and the slider.
A possible solution is to create a QRegion that contains the geometry of both widgets and check if the cursor is inside it. In order to process the mouse events of the slider, an event filter must be installed on it:
class VolumeButton(QToolButton):
def __init__(self, parent=None):
super().__init__(parent)
self.setIcon(volumeicon)
self.slider = QSlider()
self.slider.setWindowFlags(Qt.FramelessWindowHint)
self.slider.setWindowModality(Qt.NonModal)
self.slider.installEventFilter(self)
def isInside(self):
buttonRect = self.rect().translated(self.mapToGlobal(QPoint()))
if not self.slider.isVisible():
return QCursor.pos() in buttonRect
region = QRegion(buttonRect)
region |= QRegion(self.slider.geometry())
return region.contains(QCursor.pos())
def enterEvent(self, event):
if not self.slider.isVisible():
self.slider.move(self.mapToGlobal(QPoint()))
self.slider.show()
def leaveEvent(self, event):
if not self.isInside():
self.slider.hide()
def eventFilter(self, source, event):
if source == self.slider and event.type() == event.Leave:
if not self.isInside():
self.slider.hide()
return super().eventFilter(source, event)
Note that self.rect() is always at coordinates (0, 0), so you can just use self.mapToGlobal(QPoint()) to get the widget's global position. On the other hand, you might want to show the slider "outside" the button, so you could use self.mapToGlobal(self.rect().bottomLeft()).
Be aware that trying to place the slider "outside" the button geometry might result in a problem if the user moves the mouse in the gap between the button and the slider; in that case, you will need to create a valid region that covers both widgets and a reasonable space between them.

How to get cursor location within window in PyQt5/PySide2?

Questions and Briefing
Hello. I am trying to fix a problem with my PyQt5 window which has a custom Title Bar. If you maximize the window and you hover over the title bar with the cursor and drag the window to move it, the window will resize and you will be able to move it as normal.
However, when the window restores previous state before it is maximized using the cursor click and drag method, the cursor does not grip onto the location on the title bar to move the window, but, it stays in the same place where the click and drag method was started so the cursor is on the other side of the screen mysteriously moving the window.
How would I make it so the cursor would grip onto the title bar and when the window is restored using the click and drag method it would be locked on to that same location, the same as you would have for normal applications.
Sub-Questions
How would I get the location of the cursor inside the window? I have managed to get the cursor location using QCursor.pos() but that returns the global position on the actual screen but I think I need it on the window so I can try to make it so the cursor grips onto the title bar.
I have tried to...
Use QCursor.pos() before the click and drag restoration method is done and then I did QCursor.setPos() to set the previously captured position, it does work but then it causes the window to move with it and the cursor is also out of position from the intended grip location inside the window.
Code
class MainWindow(QMainWindow): # Main Window
def __init__(self): # Main initialisation
super(MainWindow, self).__init__() # Call superclass
# UI Setup
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# Move Window
def move_window(event):
# Restore Window (before movement)
if UIFunctions.return_status(self) == 1: # If maximized
UIFunctions.maximize_restore(self)
# QCursor.setPos(self.previous_cursor_pos)
if event.buttons() == Qt.LeftButton: # If Left Click (move window)
self.move(self.pos() + event.globalPos() - self.dragPos)
self.dragPos = event.globalPos()
event.accept()
# Set Title Bar
self.ui.title_bar.mouseMoveEvent = move_window
# Set UI Definitions
UIFunctions.ui_definitions(self)
self.show() # Show main window
def mousePressEvent(self, event): # Mouse Press Event
self.dragPos = event.globalPos()
(Main Class with Move Window method)
Other Info
Python Version: Python 3.8.6
Module Used: PySide2
Happy to provide more information.
musicmante's solution in the comments fixed my issue. I was trying to use something like self.pos.x() and after I switched it to self.mapFromGlobal(QCursor.pos()).x()

How to place an image at a certain position in QtGraphicsView/QtGraphicsScene (PyQt4)?

I'm creating an application using Qt Designer and PyQt4. I want to know how I can add an image to the QtGraphicsView widget to my desired location.
For example, when I click on the QtGraphicsView widget, I want to add an image to that exact location. I've searched online but I couldn't find anything that was of any help.
I've created a scene subclass to manage the items that will be displayed in the QtGraphicsView widget. I'm able to get the coordinates of the place where I click but I don't know how to place the item in that certain position. Below is my code:
class graphicsScene(QtGui.QGraphicsScene):
def __init__(self, parent=None):
super(graphicsScene, self).__init__(parent)
def mousePressEvent(self, event):
position = QtCore.QPointF(event.scenePos())
pixmap = QtGui.QPixmap("host.png")
pixmap_scaled = pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio)
self.itemAt(pixmap_scaled,position.x(),position.y())
self.addPixmap(pixmap_scaled)
print "pressed here: " + str(position.x()) + ", " + str(position.y())
self.update()
def mouseReleaseEvent(self, event):
position = QtCore.QPointF(event.scenePos())
print "released here: " + str(position.x()) + ", " + str(position.y())
self.update()
class form(QtGui.QMainWindow):
def __init__(self):
super(mininetGUI, self).__init__()
self.ui = uic.loadUi('form.ui')
self.scene = graphicsScene()
self.ui.view.setScene(self.scene)
Use addItem(your_pixmap_object) to add the QPixmap to the scene. Then you can use setPos(...) on the returned QGraphicsItem (this is returned when you use addItem(...) and the insertion of the item into the scene was succcessful).The point that you pass to your setPos(...) would be the one that is part of the event (as you have done already by calling event.scenePos()).
pixmap = QPixmap(...)
sceneItem = self.addItem(pixmap)
sceneItem.setPos(event.scenePos())
If you want to use QGraphicsPixmapItem the procedure is the same as the one above but just use self.addPixmap(...) as you have done in your code.
There is one thing that you might want to handle in addition to just placing the item - the case where you press the mouse button, move the cursor somewhere else in the scene while still pressing the button and then release it. This will insert the item at the starting position of your movement event (while pressing the button and moving) however this might not be what you want to do. You have to think about whether it will be better to handle the insertion inside the mouseReleaseEvent(...) instead. It really depends on how you want things to work for this particular scenario.

pyqtgraph Lock dock layout

Is there a way to lock docks in pyqtgraph so that the user cannot move them around?
I'm using a small touchscreen to display a pyqtgraph application with multiple docks. It is very easy for the user to accidentally move a dock. When that happens the screen becomes unusable because of the size. I would like to prevent the user from moving the docks.
However, the user must still be able to choose between docks (i.e. treat them like a tab widget).
Just to be clear, I want to prevent a dock from being detached and I want to prevent the dock from being drug to a new location.
Thanks,
Chris
I managed to disable the ability to detach and drag docks by overriding the Dock class' methods.
Dragging a dock moves it to another location. So I overrode all of the 'drag' event handlers with methods that do nothing (i.e. a no-op).
Double-clicking on a dock's label will cause the dock to detach. So, I overrode the dock's label's double-click event handler with a no-op.
Replace Dock with MyDock in your code. UPDATE: I added code to override the drag methods for the DockArea too because I was still able to move DockAreas around.
Here is the code:
##
# This class is used to eliminate a standard Dock class' ability to detach and
# move (i.e. dragging this Dock will have no effect)
#
class MyDock(Dock):
def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False, autoOrientation=True):
# Initialize the baseclass
#
Dock.__init__(self, name, area, size, widget, hideTitle, autoOrientation)
# Override the label's double click event. Normally double clicking
# the dock's label will cause it to detach into it's own window.
#
self.label.mouseDoubleClickEvent=self.noopEvent
def dragEventEnter(self, ev):
pass
def dragMoveEvent(self, ev):
pass
def dragLeaveEvent(self, ev):
pass
def dragDropEvent(self, ev):
pass
def noopEvent(self,ev):
pass
class MyDockArea(DockArea):
def dragEventEnter(self, ev):
pass
def dragMoveEvent(self, ev):
pass
def dragLeaveEvent(self, ev):
pass
def dragDropEvent(self, ev):
pas

Scrolling QGraphicsView programmatically

I've want to implement a scroll/pan-feature on a QGraphicsView in my (Py)Qt application. It's supposed to work like this: The user presses the middle mouse button, and the view scrolls as the user moves the mouse (this is quite a common feature).
I tried using the scroll() method inherited from QWidget. However, this somehow moves the view instead - scrollbars and all. See picture.
So, given that this is not the way I'm supposed to do this, how should I? Or is it the correct way, but I do something else wrong? The code I use:
def __init__(self):
...
self.ui.imageArea.mousePressEvent=self.evImagePress
self.ui.imageArea.mouseMoveEvent=self.evMouseMove
self.scrollOnMove=False
self.scrollOrigin=[]
...
def evImagePress(self, event):
if event.button() == Qt.LeftButton:
self.evImageLeftClick(event)
if event.button() == Qt.MidButton:
self.scrollOnMove=not self.scrollOnMove
if self.scrollOnMove:
self.scrollOrigin=[event.x(), event.y()]
...
def evMouseMove(self, event):
if self.scrollOnMove:
self.ui.imageArea.scroll(event.x()-self.scrollOrigin[0],
event.y()-self.scrollOrigin[1])
It works as I expect, except for the whole move-the-widget business.
Fails to scroll http://img55.imageshack.us/img55/3222/scrollfail.jpg
My addition to translate() method.
It works great unless you scale the scene. If you do this, you'll notice, that the image is not in sync with your mouse movements. That's when mapToScene() comes to help. You should map your points from mouse events to scene coordinates. Then the mapped difference goes to translate(), voila viola- your scene follows your mouse with a great precision.
For example:
QPointF tmp2 = mapToScene(event->pos());
QPointF tmp = tmp2.mapToScene(previous_point);
translate(tmp.x(),tmp.y());
I haven't done this myself but this is from the QGraphicsView documentation
... When the scene is larger
than the scroll bars' values, you can
choose to use translate() to navigate
the scene instead.
By using scroll you are moving the widget, translate should achieve what you are looking for, moving the contents of the QGraphicsScene underneath the view
Answer given by denis is correct to get translate to work. The comment by PF4Public is also valid: this can screw up scaling. My workaround is different than P4FPublc's -- instead of mapToScene I preserve the anchor and restore it after a translation:
previousAnchor = view.transformationAnchor()
#have to set this for self.translate() to work.
view.setTransformationAnchor(QGraphicsView.NoAnchor)
view.translate(x_diff,y_diff)
#have to reset the anchor or scaling (zoom) stops working:
view.setTransformationAnchor(previousAnchor)
You can set the QGraphicsScene's area that will be displayed by the QGraphicsView with the method QGraphicsView::setSceneRect(). So when you press the button and move the mouse, you can change the center of the displayed part of the scene and achieve your goal.

Categories

Resources