I am designing an app to draw electrical circuit models and I need to insert elements on scene by clicking on a button and after on the scene.
The way it should work is the following: I press the button to insert a node, then it shows a message saying "Press where you want to insert the element", you press, and the element appear on screen.
I think the problem is that I have to stop the code to get the position and then continue or something like that.
Below I show a part of the code (the original contains more classes and a second tab for calculations, that it is not needed for this trouble and it is not connected in any way):
from PyQt5 import sip
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import QRectF, Qt, QSize, QPointF, QPoint, QLineF, showbase
from PIL import Image
import calculation
class Main(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Nodal Equations")
self.setWindowIcon(QIcon("images/flash.ico"))
self.setGeometry(150,50,1650,950)
self.setFixedSize(self.size())
self.UI()
self.show()
def UI(self):
self.toolBar()
self.tabwidgets()
self.widgets()
self.layouts()
def toolBar(self):
self.tb = QToolBar("Toolbar")
self.addToolBar(Qt.RightToolBarArea, self.tb)
self.tb.setIconSize(QSize(50,50))
# Toolbar buttons
self.addNode = QAction(QIcon("images/node.png"),"Node",self)
self.tb.addAction(self.addNode)
self.addNode.triggered.connect(self.funcAddNode)
self.tb.addSeparator()
def tabwidgets(self):
self.tabs = QTabWidget()
self.tabs.blockSignals(True) # To update tabs. After defining every layout we have to set it to False
self.setCentralWidget(self.tabs) # If this is not used, we cannot see the widgets (Needed if we use QMainWindow)
self.modelViewTab = QWidget()
self.tabs.addTab(self.modelViewTab, "Model View")
def widgets(self):
# Model View widgets
self.view = ModelView()
self.instructionsLabel = QLabel("Welcome to Node Equations Solver. To start select an order on the toolbar menu", self)
def layouts(self):
# Model View Tab layouts
self.mainModelLayout = QVBoxLayout()
self.sceneLayout = QHBoxLayout()
self.instructionsLayout = QHBoxLayout()
self.mainModelLayout.setAlignment(Qt.AlignCenter)
##### Adding widgets
self.sceneLayout.addWidget(self.view)
self.instructionsLayout.addWidget(self.instructionsLabel)
##### Adding layouts
self.mainModelLayout.addLayout(self.sceneLayout)
self.mainModelLayout.addLayout(self.instructionsLayout)
self.modelViewTab.setLayout(self.mainModelLayout)
def funcAddNode(self):
self.node = Node(0,500,500)
self.view.scene.addItem(self.node)
class Node(QGraphicsEllipseItem):
def __init__(self,number, x, y):
super(Node, self).__init__(0, 0, 10, 10)
self.number = number
self.setPos(x, y)
self.setBrush(Qt.yellow)
self.setAcceptHoverEvents(True)
# Mouse hover events
def hoverEnterEvent(self, event):
app.instance().setOverrideCursor(Qt.OpenHandCursor)
def hoverLeaveEvent(self, event):
app.instance().restoreOverrideCursor()
# Mouse click events
def mousePressEvent(self, event):
app.instance().setOverrideCursor(Qt.ClosedHandCursor)
def mouseReleaseEvent(self, event):
app.instance().restoreOverrideCursor()
def mouseMoveEvent(self, event):
orig_cursor_position = event.lastScenePos()
updated_cursor_position = event.scenePos()
orig_position = self.scenePos()
updated_cursor_x = updated_cursor_position.x() - orig_cursor_position.x() + orig_position.x()
updated_cursor_y = updated_cursor_position.y() - orig_cursor_position.y() + orig_position.y()
if updated_cursor_x < 0:
self.setPos(QPointF(0, updated_cursor_y))
elif updated_cursor_y < 0:
self.setPos(QPointF(updated_cursor_x, 0))
elif updated_cursor_x + self.boundingRect().right() > 1550:
self.setPos(QPointF(1550 - self.boundingRect().width(), updated_cursor_y))
elif updated_cursor_y + self.boundingRect().bottom() > 840:
self.setPos(QPointF(updated_cursor_x, 840 - self.boundingRect().height()))
else:
self.setPos(QPointF(updated_cursor_x, updated_cursor_y))
class ModelView(QGraphicsView):
def __init__(self):
super().__init__()
self.setRenderHints(QPainter.Antialiasing)
self.scene = QGraphicsScene()
self.setScene(self.scene)
self.setSceneRect(0, 0, 1550, 840)
##### This is a way I tried to pick the cursor position and store it, but it didn't work
# def mousePressEvent(self, event):
# x = event.scenePos().x()
# y = event.scenePos().y()
# return (x,y)
def main():
global app
app = QApplication(sys.argv)
window = Main()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
You need to change the return (x,y) to something useful, i.e. emitting a signal or actually adding an element.
mousePressEvent is a method that does not return anything (void in C++).
Related
I have a widget containing two buttons that can be (drag and drop) swapped using the mouse middle button. I am trying to restrain the mouse cursor from leaving the QWidget area when dragging and dropping a Qpushbutton... I am using dragMoveEvent() which offsets the cursor every time it crosses the border of the widget. It works when you move the mouse slowly but fast movements will make the cursor leave the area. What is the best way to make this happen? Thanks.
PS: Go to the Drag and Drop area for reference
import os
import random
import sys
import time
from PySide2 import QtOpenGL
from PySide2 import QtWidgets
from PySide2.QtCore import QEvent, QMimeData, QPoint, QRect
from PySide2.QtGui import QCursor, QDrag, QWindow
# import nuke
# import nukescripts
from collapse import Collapse
try:
from PySide import QtGui, QtCore
except ImportError:
from PySide2 import QtCore
from PySide2 import QtWidgets as QtGui
from PySide2 import QtGui as QtG
class CreateNodeBoard(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
self.nukePathSeparator = "/"
#self.toolPath = self.getFullPathWithExt()
self.currentDir = os.path.dirname(os.path.realpath(__file__))
################################################################################
# GUI
################################################################################
self.setMinimumWidth(350)
self.mainLayout = QtGui.QVBoxLayout()
self.mainLayout.setSpacing(0)
self.mainLayout.setAlignment(QtCore.Qt.AlignTop)
self.setLayout(self.mainLayout)
self.target = None
self.setAcceptDrops(True)
self.nodeBoardWidget = QtGui.QWidget()
self.nodeBoardWidget.setAcceptDrops(True)
nodeBoardVLayout = QtWidgets.QVBoxLayout()
self.nodeBoardWidget.setLayout(nodeBoardVLayout)
self.userButtonLayout = QtGui.QGridLayout()
nodeBoardVLayout.addLayout(self.userButtonLayout)
button1 = QtWidgets.QPushButton("a")
button2 = QtWidgets.QPushButton("b")
self.userButtonLayout.addWidget(button1)
self.userButtonLayout.addWidget(button2)
self.userButtonLayout.setAlignment(QtCore.Qt.AlignLeft)
self.mainLayout.addWidget(self.nodeBoardWidget)
def get_index(self, pos):
for i in range(self.userButtonLayout.count()):
buttonGlob = self.userButtonLayout.itemAt(i).widget().mapToGlobal(QPoint(0,0))
if QtCore.QRect(buttonGlob.x(), buttonGlob.y(), 80, 23).contains(pos) and i != self.target:
return i
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton:
self.target = self.get_index(QCursor.pos())
else:
self.target = None
def mouseMoveEvent(self, event):
if event.buttons() & QtCore.Qt.MiddleButton and self.target is not None:
print("moving")
drag = QDrag(self.userButtonLayout.itemAt(self.target).widget())
pix = self.userButtonLayout.itemAt(self.target).widget().grab()
mimedata = QMimeData()
mimedata.setImageData(pix)
drag.setMimeData(mimedata)
drag.setPixmap(pix)
drag.setHotSpot(QPoint(40,10))
drag.exec_()
def dragMoveEvent(self, event):
cursorPos = QCursor.pos()
widgetPos = self.nodeBoardWidget.mapToGlobal(QPoint(0,0))
if cursorPos.x() < widgetPos.x() or cursorPos.y() < widgetPos.y():
QCursor.setPos(QCursor.pos().x() + 1 , QCursor.pos().y() + 1 )
event.accept()
def dragEnterEvent(self, event):
print("drag enter event")
if event.mimeData().hasImage():
event.accept()
else:
event.ignore()
def dropEvent(self, event):
print("drop")
buttonGlob = self.userButtonLayout.itemAt(self.target).widget().mapToGlobal(self.pos())
if not QtCore.QRect(buttonGlob.x(), buttonGlob.y(), 80, 23).contains(QCursor.pos()):
source = self.get_index(QCursor.pos())
if source is None:
return
i, j = max(self.target, source), min(self.target, source)
p1, p2 = self.userButtonLayout.getItemPosition(i), self.userButtonLayout.getItemPosition(j)
self.userButtonLayout.addItem(self.userButtonLayout.takeAt(i), *p2)
self.userButtonLayout.addItem(self.userButtonLayout.takeAt(j), *p1)
self.target = None
app = QtWidgets.QApplication(sys.argv)
# Create a Qt widget, which will be our window.
window = CreateNodeBoard()
window.show() # IMPORTANT!!!!! Windows are hidden by default.
# Start the event loop.
app.exec_()
EDIT
So after further investigation and testing the code on both LINUX/WINDOWS I have come to the conclusion that both behaviours are caused by the programme exceeding the maximum recursion limit. Any time the mouse cursor during the drag event leaves the assigned widget something causes the events to call each other and this causes my app to crash. Having this as a standalone app does not cause any problem and I do not know why? Also, I have no ideas how this programme goes into recursion.
The previous solution of mine where I tried to create a "safe zone" for the mouse did not solve the issue as far as there are certain mouse movements causing the same bug.
Here is a better version of a working code. As I have already mentioned it works as a standalone GUI but causes the programme to crash within another software environment.
from __future__ import print_function
import sys
try:
from PySide import QtWidgets, QtCore
except ImportError:
from PySide2 import QtCore
from PySide2 import QtWidgets
from PySide2 import QtGui
from PySide2 import QtOpenGL
class CreateNodeBoard(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
################################################################################
# GUI
################################################################################
self.setMinimumWidth(350)
self.mainLayout = QtWidgets.QVBoxLayout()
self.mainLayout.setSpacing(0)
self.mainLayout.setAlignment(QtCore.Qt.AlignTop)
self.setLayout(self.mainLayout)
self.target = None
self.targetWidget = None
self.setAcceptDrops(True)
################################################################################
# GUI - NODE BOARD
################################################################################
# Create a Layout to hold all widgets
self.nodeBoardWidget = QtWidgets.QWidget()
self.nodeBoardWidget.setAcceptDrops(True)
nodeBoardVLayout = QtWidgets.QVBoxLayout()
self.nodeBoardWidget.setLayout(nodeBoardVLayout)
# create a grid layout inside nodeBoaardVLayout and load buttons from JSON
self.userButtonLayout = QtWidgets.QGridLayout()
nodeBoardVLayout.addLayout(self.userButtonLayout)
button1 = QtWidgets.QPushButton('button1')
self.userButtonLayout.addWidget(button1)
button2 = QtWidgets.QPushButton('button2')
self.userButtonLayout.addWidget(button2)
button3 = QtWidgets.QPushButton('test button')
button3.clicked.connect(self._test)
self.userButtonLayout.addWidget(button3)
self.userButtonLayout.setAlignment(QtCore.Qt.AlignLeft)
self.mainLayout.addWidget(self.nodeBoardWidget)
nodeBoardVLayout.addStretch(1)
############################################################################
# test
############################################################################
def _test(self):
print(self.topLevelWidget())
def dragLeaveEvent(self, event):
print("dragLeaveEvent :", event)
# XXX: does not work on macOS
# self.drag.cancel()
# parent = self.parent().mapToGlobal(self.drag.hotSpot())
# QtGui.QCursor.setPos(parent.x() + 50, parent.y() + 50)
# XXX: could still causes a crash
# q = QMessageBox()
# q.setText('no can do')
# q.exec_()
def leaveEvent(self, event):
pass
def enterEvent(self, event):
pass
################################################################################
# DRAG AND DROP
################################################################################
def get_index(self, pos):
for i in range(self.userButtonLayout.count()):
buttonGlob = self.userButtonLayout.itemAt(
i).widget().mapToGlobal(QtCore.QPoint(0, 0))
if QtCore.QRect(buttonGlob.x(), buttonGlob.y(), 80, 23).contains(pos) and i != self.target:
return i
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton:
self.target = self.get_index(QtGui.QCursor.pos())
else:
self.target = None
def mouseMoveEvent(self, event):
if event.buttons() and QtCore.Qt.MiddleButton and self.target is not None:
print("mouseClickEvent :", event)
self.drag = QtGui.QDrag(
self.userButtonLayout.itemAt(self.target).widget())
pix = self.userButtonLayout.itemAt(self.target).widget().grab()
mimedata = QtCore.QMimeData()
mimedata.setImageData(pix)
self.drag.setMimeData(mimedata)
self.drag.setPixmap(pix)
self.drag.setHotSpot(QtCore.QPoint(40, 10))
self.drag.exec_()
def dragMoveEvent(self, event):
# print("dragMoveEvent :", event)
cursorPos = QtGui.QCursor.pos()
widgetPos = self.nodeBoardWidget.mapToGlobal(QtCore.QPoint(0, 0))
if cursorPos.x() <= widgetPos.x() or cursorPos.y() <= widgetPos.y():
QtGui.QCursor.setPos(QtGui.QCursor.pos().x() +
10, QtGui.QCursor.pos().y() + 10)
def dragEnterEvent(self, event):
print("dragEnterEvent :", event)
# XXX: if ignored, will not crash but will not propagate events
event.accept()
def dropEvent(self, event):
# print("dropEvent :", event)
buttonGlob = self.userButtonLayout.itemAt(
self.target).widget().mapToGlobal(self.pos())
if not QtCore.QRect(buttonGlob.x(), buttonGlob.y(), 80, 23).contains(QtGui.QCursor.pos()):
source = self.get_index(QtGui.QCursor.pos())
if source is None:
return
i, j = max(self.target, source), min(self.target, source)
p1, p2 = self.userButtonLayout.getItemPosition(
i), self.userButtonLayout.getItemPosition(j)
self.userButtonLayout.addItem(self.userButtonLayout.takeAt(i), *p2)
self.userButtonLayout.addItem(self.userButtonLayout.takeAt(j), *p1)
self.target = None
class TestWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.test_widget = QtWidgets.QWidget()
self.set_test()
_layout = QtWidgets.QHBoxLayout()
_layout.addWidget(CreateNodeBoard())
_layout.addWidget(self.test_widget)
self.setLayout(_layout)
def set_test(self):
"""Adjacent test widget"""
self.test_widget.setAutoFillBackground(True)
self.test_widget.setPalette(QtGui.QColor(255, 0, 0))
_test_layout = QtWidgets.QVBoxLayout()
_test_layout.addWidget(QtWidgets.QLabel('TEST WIDGET'))
self.test_widget.setLayout(_test_layout)
try:
import nukescripts
except ImportError as error:
APP = QtWidgets.QApplication(sys.argv)
WINDOW = TestWidget()
WINDOW.show()
APP.exec_()
else:
nukescripts.panels.registerWidgetAsPanel(
'TestWidget', 'DragDrop',
'DragDrop.MainWindow')
Premise
This answer is very limited to the specific question (prevent the user to move the mouse outside the boundaries of the given widget). Unfortunately, it's not a complete solution due to many conceptual problems in the given code:
both drag and drop events should always be managed by the widget that will actually handle them (in this case, nodeBoardWidget), not their parent;
getting the layout index of an item should always consider the item geometry (using a fixed size is discouraged, since widget sizes depend on lots of aspects) and the fact that an item could not be a widget (nested layouts are still layout items, so layout.itemAt().widget() could return None);
"swapping" items based on item indexes doesn't always keep the item index, as the resulting indexes could be unreliable (especially for grid layouts);
Partial solution
The important aspect to keep in mind is that trying to move the mouse by a small and fixed amount to "fix" its position is wrong, because mouse events are not continuous: if the mouse is moved very fast from x=0 to x=100 you don't get all values between 0 and 100, but only a small fraction of the intermediate positions.
For the same reason, trying to "fix" the position just by a fixed amount of pixels is wrong, since the offset can be variable depending on the mouse speed.
The above results in dragMoveEvent not being called if the mouse moves too fast outside the parent boundaries. While in your specific case it "works", it's only because you implemented the function in the parent (which, as said, is not the suggested approach, and this is a clear example of that reason). If the mouse position has to be "contained", the dragLeaveEvent must be implemented instead.
class CreateNodeBoard(QtWidgets.QWidget):
def __init__(self, parent = None):
# ...
self.targetWidget = None
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton:
widget = QtWidgets.QApplication.widgetAt(event.globalPos())
if (widget != self.nodeBoardWidget and
self.nodeBoardWidget.isAncestorOf(widget)):
self.targetWidget = widget
def mouseMoveEvent(self, event):
if self.targetWidget:
drag = QDrag(self.targetWidget)
pix = self.targetWidget.grab()
mimedata = QMimeData()
mimedata.setImageData(pix)
drag.setMimeData(mimedata)
drag.setPixmap(pix)
drag.setHotSpot(QPoint(40,10))
drag.exec_()
def dragEnterEvent(self, event):
if self.nodeBoardWidget.isAncestorOf(event.source()):
event.accept()
def dragLeaveEvent(self, event):
geo = self.nodeBoardWidget.rect().translated(
self.nodeBoardWidget.mapToGlobal(QtCore.QPoint()))
pos = QtGui.QCursor.pos()
if pos not in geo:
if pos.x() < geo.x():
pos.setX(geo.x())
elif pos.x() > geo.right():
pos.setX(geo.right())
if pos.y() < geo.y():
pos.setY(geo.y())
elif pos.y() > geo.bottom():
pos.setY(geo.bottom())
QtGui.QCursor.setPos(pos)
I strongly suggest you to study the above example and considerations, as your code has lots of conceptual issues that my answer could not fix if not by creating a completely new example done from scratch. Also, since it's pretty clear that you're getting your code from various sources found on the web, I also suggest you to do that with awareness. Imitation is a good way of learning, but not without understanding what is being done. Do your research on all functions and classes used, and study all the related documentation, starting from layout managers, drag and drop and not forgetting about official code styling practices.
FOUND A FIX:
the dragEnterEvent caused the whole thing to go into recursion which causes the app to crash. (Linux terminal kept showing maximum recursion limit exceeded every time I moved the dragEvent outside the widgets area)
So to fix this I have created a condition inside the dragEnterEvent that if the mouse cursor moves outside the widgets it should ignore the event.
################################################################################
# DRAG AND DROP
################################################################################
def get_index(self, pos):
for i in range(self.userButtonLayout.count()):
buttonGlob = self.userButtonLayout.itemAt(i).widget().mapToGlobal(QtCore.QPoint(0,0))
if QtCore.QRect(buttonGlob.x(), buttonGlob.y(), 80, 23).contains(pos) and i != self.target:
return i
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton:
self.target = self.get_index(QtGui.QCursor.pos())
if event.buttons() & QtCore.Qt.MiddleButton and self.target is not None:
drag = QtGui.QDrag(self.userButtonLayout.itemAt(self.target).widget())
pix = self.userButtonLayout.itemAt(self.target).widget().grab()
mimedata = QtCore.QMimeData()
mimedata.setImageData(pix)
drag.setMimeData(mimedata)
drag.setPixmap(pix)
drag.setHotSpot(QtCore.QPoint(40,10))
drag.exec_()
else:
self.target = None
def dragLeaveEvent(self, event):
if self.cursorInWidget():
drag = QtGui.QDrag(self.userButtonLayout.itemAt(self.target).widget())
drag.cancel()
def cursorInWidget(self):
cursorPos = QtGui.QCursor.pos()
widgetWidth = self.nodeBoardWidget.geometry().width()
widgetHeight = self.nodeBoardWidget.geometry().height()
widgetPos = self.nodeBoardWidget.mapToGlobal(QtCore.QPoint(0,0))
if cursorPos.x() <= widgetPos.x() or cursorPos.y() <= widgetPos.y() or cursorPos.x() >= (widgetPos.x() + widgetWidth) or cursorPos.y() >= (widgetPos.y() + widgetHeight):
return False
else:
return True
def dragEnterEvent(self, event):
if self.cursorInWidget():
event.accept()
else:
event.ignore()
def dropEvent(self, event):
buttonGlob = self.userButtonLayout.itemAt(self.target).widget().mapToGlobal(self.pos())
if not QtCore.QRect(buttonGlob.x(), buttonGlob.y(), 80, 23).contains(QtGui.QCursor.pos()):
source = self.get_index(QtGui.QCursor.pos())
if source is None:
return
i, j = max(self.target, source), min(self.target, source)
p1, p2 = self.userButtonLayout.getItemPosition(i), self.userButtonLayout.getItemPosition(j)
self.userButtonLayout.addItem(self.userButtonLayout.takeAt(i), *p2)
self.userButtonLayout.addItem(self.userButtonLayout.takeAt(j), *p1)
self.target = None
I'm new to Qt, and specially to PyQt5. I'm trying to develop a GUI using QGraphicsView, QGraphicsScene and QGraphicsPixmapItem. My objective is to add items to the scene when the user clicks on the scene (achieved using mousePressedEvent() in a QGraphicsScene subclass) and, using mouseMoveEvent(), I was able to move the element.
Then, I discovered that, with my implementation, the items could be moved like "pushing" them from outside the bounding rect. So, in order to fix it, after some searching, I decided to implement a subclass of QGraphicsPixmapItem to implement its own event functions.
Nevertheless, I found out that my item does not recognize mousePressed nor mouseMove events, but the ones from QGraphicsScene. My questions are:
What is the most efficient way to move elements without having the first problem I encountered?
Is it possible to combine both scene and item event handlers? I have not understood event propagation completely.
To make it more clear, I leave my code down below for the moving problem:
#!/usr/bin/env python3
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class GraphicsScene(QGraphicsScene):
def __init__(self):
super(GraphicsScene, self).__init__()
self.image = 'car.png' # Image of your own
self.inserted = False
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton and not self.inserted:
img = QPixmap(self.image).scaled(50, 50, Qt.KeepAspectRatio)
pixmap = QGraphicsPixmapItem(img)
offset = pixmap.boundingRect().topLeft() - pixmap.boundingRect().center()
pixmap.setOffset(offset.x(), offset.y())
pixmap.setShapeMode(QGraphicsPixmapItem.BoundingRectShape)
pixmap.setFlag(QGraphicsItem.ItemIsSelectable, True)
pixmap.setPos(event.scenePos())
super().mousePressEvent(event)
self.addItem(pixmap)
self.inserted = True
else:
pass
def mouseMoveEvent(self, event):
super().mouseMoveEvent(event)
item = self.itemAt(event.scenePos(), QTransform())
if item is None:
return
orig_cursor_position = event.lastScenePos()
updated_cursor_position = event.scenePos()
orig_position = item.scenePos()
updated_cursor_x = updated_cursor_position.x() - orig_cursor_position.x() + orig_position.x()
updated_cursor_y = updated_cursor_position.y() - orig_cursor_position.y() + orig_position.y()
item.setPos(QPointF(updated_cursor_x, updated_cursor_y))
class MainWindow(QMainWindow):
def __init__(self):
super(QMainWindow, self).__init__()
self.resize(600, 600)
self.canvas = QGraphicsView()
self.scene = GraphicsScene()
self.setCentralWidget(self.canvas)
self.canvas.setScene(self.scene)
def showEvent(self, event):
self.canvas.setSceneRect(QRectF(self.canvas.viewport().rect()))
def resizeEvent(self, event):
self.canvas.setSceneRect(QRectF(self.canvas.viewport().rect()))
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
I think that the OP is unnecessarily complicated since the QGraphicsItems (like QGraphicsPixmapItem) already implement this functionality and it only remains to activate the QGraphicsItem::ItemIsMovable flag:
class GraphicsScene(QGraphicsScene):
def __init__(self):
super(GraphicsScene, self).__init__()
self.image = "car.png" # Image of your own
self.inserted = False
def mousePressEvent(self, event):
super().mousePressEvent(event)
if event.button() == Qt.LeftButton and not self.inserted:
img = QPixmap(self.image).scaled(50, 50, Qt.KeepAspectRatio)
pixmap = QGraphicsPixmapItem(img)
pixmap.setOffset(-pixmap.boundingRect().center())
pixmap.setShapeMode(QGraphicsPixmapItem.BoundingRectShape)
pixmap.setFlag(QGraphicsItem.ItemIsSelectable, True)
pixmap.setFlag(QGraphicsItem.ItemIsMovable, True)
pixmap.setPos(event.scenePos())
self.addItem(pixmap)
self.inserted = True
Override mouseMoveEvent is unnecessary.
I have made a splitter. I have been trying to bring a right-click menu when the splitter handle is right-clicked.
Here is the code I have created. Currently, it doesn't recognise the right clicks on the handle of the splitter. (note: this code currently updates count if right-clicked in the first frame of the splitter i,e Top left frame)
from PyQt5.QtWidgets import *
from PyQt5 import QtGui
from PyQt5.QtCore import Qt
# This class is to create the outer window
class OuterLayout(QMainWindow):
def __init__(self):
super().__init__()
self.window()
def window(self):
self.setMinimumSize(1000, 900)
self.showMaximized()
self.setWindowIcon(QtGui.QIcon('Images/Logo_small.png'))
self.setWindowTitle('Splitter')
self.menu_bar()
inner_layout = SplitterLayout()
layout = inner_layout.add_layout()
self.setCentralWidget(layout)
def menu_bar(self):
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu('File')
self.file(file_menu)
edit_menu = menu_bar.addMenu('Edit')
self.edit(edit_menu)
def file(self, file):
new = QAction('New', self)
file.addAction(new)
def edit(self, edit):
pass
# This class creates the splitter window
class SplitterLayout(QWidget):
def __init__(self):
super(QWidget, self).__init__()
self.count = 0
self.splitter_handle_width = 3 # This is to set the width of the handle
# This is a method to add a new splitter window
def add_layout(self):
left = QFrame()
left.setFrameShape(QFrame.StyledPanel)
bottom = QFrame()
bottom.setFrameShape(QFrame.StyledPanel)
splitter1 = QSplitter(Qt.Horizontal)
splitter1.setHandleWidth(self.splitter_handle_width)
lineedit = QLineEdit()
lineedit.setStyleSheet('background-color:green')
splitter1.addWidget(left)
splitter1.addWidget(lineedit)
splitter1.setSizes([200, 200])
print(splitter1.handle(3))
splitter1.mousePressEvent = self.splitter_clicked
splitter2 = QSplitter(Qt.Vertical)
splitter2.setHandleWidth(self.splitter_handle_width)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)
return splitter2
def splitter_clicked(self, event):
self.count += 1
print('splitter_double clicked' + str(self.count))
# def mousePressEvent(self, event):
# if event.button == Qt.RightButton:
# print('Right mouse clicked')
#
# elif event.button == Qt.LeftButton:
# print('Left mouse clicked')
def main():
splitter = QApplication([])
outer_layout = OuterLayout()
outer_layout.show()
splitter.exec_()
if __name__ == '__main__':
main()
The trick here is to create a custom QSplitterHandle class and override QSplitterHandle.mousePressEvent and a custom QSplitter class where you override createHandle so that it returns the custom QSplitterHandle instead of the standard one, i.e.
class MySplitter(QSplitter):
def createHandle(self):
return MySplitterHandle(self.orientation(), self)
class MySplitterHandle(QSplitterHandle):
def mousePressEvent(self, event):
if event.button() == Qt.RightButton:
print('Right mouse clicked')
elif event.button() == Qt.LeftButton:
print('Left mouse clicked')
super().mousePressEvent(event)
Finally, to use the custom QSplitter you need to replace all occurrences of QSplitter with MySplitter in SplitterLayout.
I am working on a drag and drop GUI in pyqt5 and am trying to get the target widget of the drag and drop operation but when i try the target() function of the QDrag object i returns <main.MainWindow object at 0x0000025FDAC09EE0> and I dont know how to use that. I want to access the index of the widget in a QGridLayout so that I can make the two widgets swap places.
Here is my code:
import sys
from PyQt5.QtCore import Qt, QMimeData
from PyQt5.QtGui import QDrag
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QPushButton, QWidget, QApplication, QGridLayout, QScrollArea, QMainWindow, QSlider
class Stroj:
def __init__(self, rok, naziv, trajanje):
self.rok = rok
self.naziv = naziv
self.trajanje = trajanje
class Button(QPushButton):
drag = 0
def __init__(self, title, parent):
super().__init__(title, parent)
def mouseMoveEvent(self, e):
if e.buttons() != Qt.LeftButton:
return
mimeData = QMimeData()
mimeData.setText(self.text())
self.drag = QDrag(self)
self.drag.setMimeData(mimeData)
self.drag.setPixmap(self.grab())
self.drag.setHotSpot(self.rect().center())
dropAction = self.drag.exec_(Qt.MoveAction)
class MainWindow(QMainWindow):
layout = QGridLayout()
btns = []
snd = ""
i = 0
j = 0
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setAcceptDrops(True)
self.scroll = QScrollArea()
self.widget = QWidget()
self.drag = QDrag(self)
SL = []
for x in range(30):
self.btns.append(x)
for x in range(30):
self.btns[x] = Button(str(x), self)
self.btns[x].setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.layout.addWidget(self.btns[x], self.i, self.j)
if(self.j > 5):
self.j = 0
self.i += 1
else:
self.j += 1
self.widget.setLayout(self.layout)
self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.scroll.setWidgetResizable(True)
self.scroll.setWidget(self.widget)
self.setCentralWidget(self.scroll)
self.setWindowTitle('Raspored')
self.setGeometry(350, 75, 950, 750)
def dragEnterEvent(self, e):
self.snd = e.mimeData().text()
e.accept()
def dragMoveEvent(self, e):
e.accept()
def dropEvent(self, e):
sender = self.snd
position = e.pos()
position.setX(int(position.x() - self.btns[int(sender)].width() / 2))
position.setY(int(position.y() - self.btns[int(sender)].height() / 2))
self.btns[int(sender)].move(position)
print(self.layout.indexOf(e.source()))
print(e.source().drag.target())
e.setDropAction(Qt.MoveAction)
e.accept()
def main():
app = QApplication(sys.argv)
main = MainWindow()
main.show()
app.exec_()
if __name__ == '__main__':
main()
The target of a drop event is always the widget that receives the drop action, so it's pretty obvious that if you intercept the event from the main window instance, you'll get the main window as target.
If you need to find the widget at a specific position, you need to use QApplication.widgetAt(pos).
In the following example, modified from the given code, I'm accepting the dragEnter/dragMove events only when the source is a Button instance, and the target is not the same. Then I switch those buttons using their position in the layout.
class MainWindow(QMainWindow):
# ...
def dragEnterEvent(self, e):
e.accept()
def dragMoveEvent(self, e):
source = e.source()
target = QApplication.widgetAt(self.mapToGlobal(e.pos()))
if (isinstance(e.source(), Button) and isinstance(target, Button) and target != source):
e.accept()
else:
e.ignore()
def dropEvent(self, e):
source = e.source()
target = QApplication.widgetAt(self.mapToGlobal(e.pos()))
if (not isinstance(source, Button) or not isinstance(target, Button)
or target == source):
return
layout = self.widget.layout()
sourceIndex = layout.indexOf(source)
sourcePos = layout.getItemPosition(sourceIndex)
targetIndex = layout.indexOf(target)
targetPos = layout.getItemPosition(targetIndex)
layout.addWidget(source, *targetPos)
layout.addWidget(target, *sourcePos)
e.accept()
Consider that this is a very simple implementation: you should also ensure that the widgets are actually children of the same window and are in the same layout.
So the idea is that I should be able to fire up the script, select a portion of the screen, then hit enter (or otherwise trigger it) to save the selection.
I've got a good bit of the code from other posts and things, but now I'm stuck. I can select any portion of the screen and resize as needed, but I can't seem to get it to recognize the "enter" key. Right now the "keyPressEvent" function is just supposed to print a message so I know it worked, but I got nothing. Any ideas?
import sys
from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import Qt, QPoint, QRect, QSize
from PyQt5.QtGui import QScreen
from PyQt5.QtWidgets import QApplication, QLabel, QRubberBand
class MyLabel(QtWidgets.QLabel):
def __init__(self, parent=None):
QtWidgets.QLabel.__init__(self, parent)
self.selection = QtWidgets.QRubberBand(QtWidgets.QRubberBand.Rectangle, self)
def keyPressEvent(self, qKeyEvent):
print(qKeyEvent.key())
if qKeyEvent.key() == QtCore.Qt.Key_Return:
print('Enter pressed')
else:
super().keyPressEvent(qKeyEvent)
def mousePressEvent(self, event):
'''
Mouse is pressed. If selection is visible either set dragging mode (if close to border) or hide selection.
If selection is not visible make it visible and start at this point.
'''
if event.button() == QtCore.Qt.LeftButton:
position = QtCore.QPoint(event.pos())
if self.selection.isVisible():
# visible selection
if (self.upper_left - position).manhattanLength() < 20:
# close to upper left corner, drag it
self.mode = "drag_upper_left"
elif (self.lower_right - position).manhattanLength() < 20:
# close to lower right corner, drag it
self.mode = "drag_lower_right"
else:
# clicked somewhere else, hide selection
self.selection.hide()
else:
# no visible selection, start new selection
self.upper_left = position
self.lower_right = position
self.mode = "drag_lower_right"
self.selection.show()
def mouseMoveEvent(self, event):
'''
Mouse moved. If selection is visible, drag it according to drag mode.
'''
if self.selection.isVisible():
# visible selection
if self.mode is "drag_lower_right":
self.lower_right = QtCore.QPoint(event.pos())
elif self.mode is "drag_upper_left":
self.upper_left = QtCore.QPoint(event.pos())
# update geometry
self.selection.setGeometry(QtCore.QRect(self.upper_left, self.lower_right).normalized())
class mainUI(QtWidgets.QWidget):
def __init__(self):
super(mainUI, self).__init__()
self.initUI()
def initUI(self):
layout = QtWidgets.QVBoxLayout(self)
label = MyLabel(self)
pixmap = QScreen.grabWindow(app.primaryScreen(), app.desktop().winId())
label.setPixmap(pixmap)
layout.addWidget(label)
self.setLayout(layout)
geometry = app.desktop().availableGeometry()
self.setFixedSize(geometry.width(), geometry.height())
# self.setWindowFlags( self.windowFlags() | Qt.FramelessWindowHint)
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = mainUI()
sys.exit(app.exec_())
EDIT
Alright this code works, although to be honest I'm not 100% sure why.
import sys
from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import Qt, QPoint, QRect, QSize
from PyQt5.QtGui import QScreen
from PyQt5.QtWidgets import QApplication, QLabel, QRubberBand, QAction
class KpeWindow(QtWidgets.QLabel):
def __init__(self, parent=None):
QtWidgets.QLabel.__init__(self,parent)
main = QtWidgets.QVBoxLayout(self)
self.selection = QtWidgets.QRubberBand(QtWidgets.QRubberBand.Rectangle, self)
# label = QLabel(self)
# label.setText('Test the keyPressEvent')
# main.addWidget(label)
# self.adjustSize()
# self.setLayout(main)
def keyPressEvent(self, event):
print(event.key())
if event.key() == QtCore.Qt.Key_Return:
print('yay')
#QtWidgets.QMessageBox.warning(self, 'MDI', 'keyPressEvent')
self.parent().keyPressEvent(event)
def mousePressEvent(self, event):
'''
Mouse is pressed. If selection is visible either set dragging mode (if close to border) or hide selection.
If selection is not visible make it visible and start at this point.
'''
print(event)
if event.button() == QtCore.Qt.LeftButton:
position = QtCore.QPoint(event.pos())
if self.selection.isVisible():
# visible selection
if (self.upper_left - position).manhattanLength() < 20:
# close to upper left corner, drag it
self.mode = "drag_upper_left"
elif (self.lower_right - position).manhattanLength() < 20:
# close to lower right corner, drag it
self.mode = "drag_lower_right"
else:
# clicked somewhere else, hide selection
self.selection.hide()
else:
# no visible selection, start new selection
self.upper_left = position
self.lower_right = position
self.mode = "drag_lower_right"
self.selection.show()
def mouseMoveEvent(self, event):
'''
Mouse moved. If selection is visible, drag it according to drag mode.
'''
if self.selection.isVisible():
# visible selection
if self.mode is "drag_lower_right":
self.lower_right = QtCore.QPoint(event.pos())
elif self.mode is "drag_upper_left":
self.upper_left = QtCore.QPoint(event.pos())
# update geometry
self.selection.setGeometry(QtCore.QRect(self.upper_left, self.lower_right).normalized())
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
QtWidgets.QMainWindow.__init__(self)
#self.setWindowTitle('KeyPressEvent Test')
# main = QtWidgets.QVBoxLayout(self)
# child = KpeWindow(self)
# child.setFocusPolicy(Qt.StrongFocus)
# self.setFocusProxy(child)
# main.addWidget(child)
# child.setFocus(True)
layout = QtWidgets.QVBoxLayout(self)
label = KpeWindow(self)
pixmap = QScreen.grabWindow(app.primaryScreen(), app.desktop().winId())
label.setPixmap(pixmap)
layout.addWidget(label)
#new
label.setFocusPolicy(Qt.StrongFocus)
self.setFocusProxy(label)
label.setFocus(True)
self.setLayout(layout)
geometry = app.desktop().availableGeometry()
self.setFixedSize(geometry.width(), geometry.height())
# self.setWindowFlags( self.windowFlags() | Qt.FramelessWindowHint)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
You need to set the focus policy on the label to get keyboard events:
class MyLabel(QtWidgets.QLabel):
def __init__(self, parent=None):
...
self.setFocusPolicy(Qt.TabFocus)