According to the drag and drop documentation, I was able to implement the drag functionality on my QToolButton, but that is overriding standard button click behaviour and I am unable to check if the button was pressed or there was an intent to start a drag by dragging mouse.
Here is my QToolBar..
class toolbarButton(QToolButton):
def __init__(self, parent = None, item = None):
super(toolbarButton, self).__init__(parent)
self.setIcon(...)
self.setIconSize(QSize(40, 40))
self.dragStartPosition = 0
...
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.dragStartPosition = event.pos()
def mouseMoveEvent(self, event):
if not (event.buttons() and Qt.LeftButton):
return
if (event.pos() - self.dragStartPosition).manhattanLength() < QApplication.startDragDistance():
return
drag = QDrag(self)
mimeData = QMimeData()
...
drag.setMimeData(mimeData)
drag.exec(Qt.CopyAction)
def sizeHint(self):
return self.minimumSizeHint()
def minimumSizeHint(self):
return QSize(30, 30)
My initial thought, was to add the emit when the distance is less than startdragdistance but that would be incorrect as it would fire everytime I moved my mouse. Is there a way of achieving this in PyQt5? That I get standard QToolButton behaviour on button press and my custom behaviour on button drag?
When you override a method you are removing the default behavior, so that does not happen then you must call the parent method through super():
def mousePressEvent(self, event):
super().mousePressEvent(event)
if event.button() == Qt.LeftButton:
self.dragStartPosition = event.pos()
Related
I have a row of buttons, each of which can accept drops. However, when I leave a button with my cursor with another button being dragged, the 'dragLeaveEvent' is not being called.
class Button(QtGui.QPushButton):
def __init__(self):
super(Button, self).__init__()
self.setAcceptDrops(True)
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
drag = QtGui.QDrag(self)
mime = QtCore.QMimeData()
mime.setText("f")
drag.setMimeData(mime)
drag.exec_()
def dragEnterEvent(self, event):
print "enter"
def dragLeaveEvent(self, event):
print "leave"
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.mainLayout = QtGui.QVBoxLayout()
self.setLayout(self.mainLayout)
for i in range(10):
btn = Button()
self.mainLayout.addWidget(btn)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
As the documentation about dragEnterEvent() reports (it's from Qt5, but the same was valid for Qt4 also):
If the event is ignored, the widget won't receive any drag move events.
Note: any drag move events.
This also means drop events.
By default, all drag events are ignored for most widgets if the drag enter event is not accepted, so if you want to receive all events (including the leave event) that first event must be accepted.
class Button(QtGui.QPushButton):
# ...
def dragEnterEvent(self, event):
event.accept()
print "enter"
lets say that i know/have the tabIndex from the tab below the cursor in the QTabBar, how could i get the widget inside of X tab in my tabBar? i dont Want the CurrentWidget(), because thats to easy, i want to get the widget of every tab that i want, in order to insert the right one into another QTabWidget when i drop it.
class ColtTab(qg.QTabWidget):
def __init__(self):
super(ColtTab,self).__init__()
self.setAcceptDrops(True)
self.tabBar = self.tabBar()
self.tabBar.setMouseTracking(True)
#self.tabBar.installEventFilter(self)
self.setDocumentMode(True)
self.indexTab = None
# Events
def mouseMoveEvent(self, e):
if e.buttons() != qc.Qt.MiddleButton:
return
globalPos = self.mapToGlobal(e.pos())
print(globalPos)
tabBar = self.tabBar
print(tabBar)
posInTab = tabBar.mapFromGlobal(globalPos)
print(posInTab)
self.indexTab = tabBar.tabAt(e.pos())
print(self.indexTab)
tabRect = tabBar.tabRect(self.indexTab)
print(tabRect)
print(tabRect.size())
pixmap = qg.QPixmap(tabRect.size())
tabBar.render(pixmap,qc.QPoint(),qg.QRegion(tabRect))
mimeData = qc.QMimeData()
drag = qg.QDrag(tabBar)
drag.setMimeData(mimeData)
drag.setPixmap(pixmap)
cursor = qg.QCursor(qc.Qt.OpenHandCursor)
drag.setHotSpot(e.pos() - posInTab)
drag.setDragCursor(cursor.pixmap(),qc.Qt.MoveAction)
dropAction = drag.exec_(qc.Qt.MoveAction)
def mousePressEvent(self, e):
#super().mousePressEvent(e)
if e.button() == qc.Qt.RightButton:
print('press')
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
e.setDropAction(qc.Qt.MoveAction)
e.accept()
self.insertTab(self.indexTab,self.currentWidget(), self.tabText(self.indexTab))
# HERE IN THE DROP EVENT I NEED TO INSERT THE CORRECT TAB INTO ANOTHER TABWIDGET, BUT I CAN ONLY INSERT THE CURRENT
My actual application is much more complicated than this, but the example below sums up the majority of my problem. I have multiple QLabels that I've subclassed to make them clickable. The labels display 16x16 images which requires a process of loading the images via Pillow, converting them to ImageQt objects, and then setting the pixmap of the label. In the example, I have 3 clickable QLabels that run the print_something function each time I click on them. My goal is to be able to hold the mouse down, and for each label I hover over, the function gets called. Any pointers would be great.
from PyQt5 import QtCore, QtWidgets, QtGui
from PIL import Image
from PIL.ImageQt import ImageQt
import sys
class ClickableLabel(QtWidgets.QLabel):
def __init__(self):
super().__init__()
clicked = QtCore.pyqtSignal()
def mousePressEvent(self, ev):
if app.mouseButtons() & QtCore.Qt.LeftButton:
self.clicked.emit()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central_widget = QtWidgets.QWidget()
self.setFixedSize(300, 300)
image = Image.open("16x16image.png")
image_imageqt = ImageQt(image)
hbox = QtWidgets.QHBoxLayout()
hbox.setSpacing(0)
hbox.addStretch()
label01 = ClickableLabel()
label01.setPixmap(QtGui.QPixmap.fromImage(image_imageqt))
label01.clicked.connect(self.print_something)
hbox.addWidget(label01)
label02 = ClickableLabel()
label02.setPixmap(QtGui.QPixmap.fromImage(image_imageqt))
label02.clicked.connect(self.print_something)
hbox.addWidget(label02)
label03 = ClickableLabel()
label03.setPixmap(QtGui.QPixmap.fromImage(image_imageqt))
label03.clicked.connect(self.print_something)
hbox.addWidget(label03)
hbox.addStretch()
central_widget.setLayout(hbox)
self.setCentralWidget(central_widget)
def print_something(self):
print("Printing something..")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
The cause of the problem is stated in the docs for QMouseEvent:
Qt automatically grabs the mouse when a mouse button is pressed inside
a widget; the widget will continue to receive mouse events until the
last mouse button is released.
It does not look like there is a simple way around this, so something hackish will be required. One idea is to initiate a fake drag and then use dragEnterEvent instead of enterEvent. Something like this should probably work:
class ClickableLabel(QtWidgets.QLabel):
clicked = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
self.setAcceptDrops(True)
self.dragstart = None
def mousePressEvent(self, event):
if event.buttons() & QtCore.Qt.LeftButton:
self.dragstart = event.pos()
self.clicked.emit()
def mouseReleaseEvent(self, event):
self.dragstart = None
def mouseMoveEvent(self, event):
if (self.dragstart is not None and
event.buttons() & QtCore.Qt.LeftButton and
(event.pos() - self.dragstart).manhattanLength() >
QtWidgets.qApp.startDragDistance()):
self.dragstart = None
drag = QtGui.QDrag(self)
drag.setMimeData(QtCore.QMimeData())
drag.exec_(QtCore.Qt.LinkAction)
def dragEnterEvent(self, event):
event.acceptProposedAction()
if event.source() is not self:
self.clicked.emit()
I want to detect middle mouse clicks on a QTabWidget. I was expecting there to be a mouse event related signal on QWidget, but all I am seeing are methods.
Do I need to subclass the QTabWidget and then override said methods in order to do what I want, or am I missing something?
You can either install an event filter on the QTabBar (returned by QTabWidget.tabBar()) to receive and handle press and release events, or subclass QTabBar to redefine mousePressEvent and mouseReleaseEvent and replace the QTabBar of the QTabWidget with QTabWidget.setTabBar().
Example using the event filter:
class MainWindow(QMainWindow):
def __init__(self):
super(QMainWindow,self).__init__()
self.tabWidget = QTabWidget(self)
self.setCentralWidget(self.tabWidget)
self.tabWidget.tabBar().installEventFilter(self)
self.tabWidget.tabBar().previousMiddleIndex = -1
def eventFilter(self, object, event):
if object == self.tabWidget.tabBar() and \
event.type() in [QEvent.MouseButtonPress,
QEvent.MouseButtonRelease] and \
event.button() == Qt.MidButton:
tabIndex = object.tabAt(event.pos())
if event.type() == QEvent.MouseButtonPress:
object.previousMiddleIndex = tabIndex
else:
if tabIndex != -1 and tabIndex == object.previousMiddleIndex:
self.onTabMiddleClick(tabIndex)
object.previousMiddleIndex = -1
return True
return False
# function called with the index of the clicked Tab
def onTabMiddleClick(self, index):
pass
Example using a QTabBar subclass:
class TabBar(QTabBar):
middleClicked = pyqtSignal(int)
def __init__(self):
super(QTabBar, self).__init__()
self.previousMiddleIndex = -1
def mousePressEvent(self, mouseEvent):
if mouseEvent.button() == Qt.MidButton:
self.previousIndex = self.tabAt(mouseEvent.pos())
QTabBar.mousePressEvent(self, mouseEvent)
def mouseReleaseEvent(self, mouseEvent):
if mouseEvent.button() == Qt.MidButton and \
self.previousIndex == self.tabAt(mouseEvent.pos()):
self.middleClicked.emit(self.previousIndex)
self.previousIndex = -1
QTabBar.mouseReleaseEvent(self, mouseEvent)
class MainWindow(QMainWindow):
def __init__(self):
super(QMainWindow,self).__init__()
self.tabWidget = QTabWidget(self)
self.setCentralWidget(self.tabWidget)
self.tabBar = TabBar()
self.tabWidget.setTabBar(self.tabBar)
self.tabBar.middleClicked.connect(self.onTabMiddleClick)
# function called with the index of the clicked Tab
def onTabMiddleClick(self, index):
pass
(In case you wonder why there is so much code for such a simple task, a click is defined as a press event followed by a release event at roughly the same spot, so the index of the pressed tab has to be the same as the released tab).
So I have a frameless QDialog that I want to be able to move around simply by clicking and dragging it. Given the code below, dragging the dialog always snaps the very top-left (0,0) of the dialog to the mouse. How might I circumvent this, or rather, what might the math be for it?
Standard QDialog with the following basic subclass:
class Main(QtGui.QDialog):
def __init__(self, args):
QtGui.QDialog.__init__(self)
def mouseMoveEvent(self, event):
super(Main, self).mouseMoveEvent(event)
if self.leftClick == True: self.moveWindow(event.globalPos())
def mousePressEvent(self, event):
super(Main, self).mousePressEvent(event)
if event.button() == QtCore.Qt.LeftButton:
self.leftClick = True
def mouseReleaseEvent(self, event):
super(Main, self).mouseReleaseEvent(event)
self.leftClick = False
Instead of event.pos(), try calling event.globalPos(). From the QMouseEvent reference, "If you move the widget as a result of the mouse event, use the global position returned by globalPos() to avoid a shaking motion."
Proposed solution moves Window, but mouse cursor jumps to 0,0 of Window. I wanted mouse cursor to stay on x,y of the Window all the time.
Here is upgraded version of the code [in QT5]:
X=0
X2=8 #!!!!
Y=0
Y2=30 #!!!!
class Main(QtWidgets.QMainWindow):
leftClick = False #! IMPORTANT
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
def mouseMoveEvent(self, event):
super(Main, self).mouseMoveEvent(event)
if self.leftClick == True:
self.move(event.globalPos().x()-X-X2,event.globalPos().y()-Y-Y2)
def mousePressEvent(self, event):
super(Main, self).mousePressEvent(event)
if event.button() == QtCore.Qt.LeftButton:
self.leftClick = True
global X,Y
X=event.pos().x()
Y=event.pos().y()
def mouseReleaseEvent(self, event):
super(Main, self).mouseReleaseEvent(event)
self.leftClick = False
For frameless window (made with window.setMask()) I need some constants like X2 and Y2, because "masked" frameless window is a bit smaller than a real framed window. Don't know how to calculate this difference yet.
UP. After long time I've found one critical bug. If you press Left Mouse Button on any pushbutton (just after the application start) and drag the mouse cursor away from that pushbutton, your application will crash, because we refer to nonexistent variable LeftClick. That's why in class Main we need to create LeftClick.