I would like to know the "best practice" to change the behavior of some buttons to do the following:
I want with a click to appear a menu. Or when you drag this same button you could drop it in another and this will "draw" a line connecting them.
Here is an Example: The idea is to connect those "jack" buttons to any other "input" buttons.
I was using the Qt designer and I realize that the buttons properties only the "acceptDrops" property is listed, but I can't make it work. Signals/Slots doesn't list something about dragging or dropping.
So I think that the only way to do it is creating a "Custom Widget" or "reimplementing" a button by code. Maybe the same thing with Signals/Slots
What is the best approach to do so if I don't want to modify the pyuic generated file?
UPDATE: The approach that I tried is by using Qt designer and the "Promoted widgets" option. That allows me to create separate class files and reimplement some elements. I already tested by promoting a PushButton to a "DragButton" and created a class for it:
from PyQt4 import QtGui, QtCore
class DragButton(QtGui.QPushButton):
def __init__(self, parent):
super(DragButton, self).__init__(parent)
self.allowDrag = True
def setAllowDrag(self, allowDrag):
if type(allowDrag) == bool:
self.allowDrag = allowDrag
else:
raise TypeError("You have to set a boolean type")
def mouseMoveEvent(self, e):
if e.buttons() != QtCore.Qt.RightButton:
return
if self.allowDrag == True:
# write the relative cursor position to mime data
mimeData = QtCore.QMimeData()
# simple string with 'x,y'
mimeData.setText('%d,%d' % (e.x(), e.y()))
print mimeData.text()
# let's make it fancy. we'll show a "ghost" of the button as we drag
# grab the button to a pixmap
pixmap = QtGui.QPixmap.grabWidget(self)
# below makes the pixmap half transparent
painter = QtGui.QPainter(pixmap)
painter.setCompositionMode(painter.CompositionMode_DestinationIn)
painter.fillRect(pixmap.rect(), QtGui.QColor(0, 0, 0, 127))
painter.end()
# make a QDrag
drag = QtGui.QDrag(self)
# put our MimeData
drag.setMimeData(mimeData)
# set its Pixmap
drag.setPixmap(pixmap)
# shift the Pixmap so that it coincides with the cursor position
drag.setHotSpot(e.pos())
# start the drag operation
# exec_ will return the accepted action from dropEvent
if drag.exec_(QtCore.Qt.LinkAction | QtCore.Qt.MoveAction) == QtCore.Qt.LinkAction:
print 'linked'
else:
print 'moved'
def mousePressEvent(self, e):
QtGui.QPushButton.mousePressEvent(self, e)
if e.button() == QtCore.Qt.LeftButton:
print 'press'
#AQUI DEBO IMPLEMENTAR EL MENU CONTEXTUAL
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
# get the relative position from the mime data
mime = e.mimeData().text()
x, y = map(int, mime.split(','))
# move
# so move the dragged button (i.e. event.source())
print e.pos()
#e.source().move(e.pos()-QtCore.QPoint(x, y))
# set the drop action as LinkAction
e.setDropAction(QtCore.Qt.LinkAction)
# tell the QDrag we accepted it
e.accept()
I got some hints and take snippets from this post: PyQt4 - Drag and Drop
At this point I'm able to drag this button, and drop it into another of the same type that have the "acceptDrops" property set true in Qt designer. However, I still want to restrict dragging of some buttons (perhaps by setting at the main file with UpdateUi method) because some will be just for accepting drops
UPDATE 2: Now I'm trying to write a class which paint lines or "wires" connecting those buttons.
I'm trying to draw a line between two widgets (two pushbuttons) into a graphicsView with their positions as reference. But when I try, the line is drawn in a wrong place. I also tried using functions like mapToGlobal or mapToParent with different results, but still wrong. In the same class I have another method that draws lines with the mouse, and works ok. I was taking it like a reference or example, but it seems that the events position has a different coordinate system. Well, i don't know why is happening this.
The buttons and the graphicsview are inside a Widget, which is also inside a Window.
Here it is the class, the method that we are talking about is from PyQt4 import QtGui, QtCore
class WiringGraphicsView(QtGui.QGraphicsView):
def __init__(self, parent):
QtGui.QGraphicsView.__init__(self, parent)
self.setScene(QtGui.QGraphicsScene(self))
self.setSceneRect(QtCore.QRectF(self.viewport().rect()))
def mousePressEvent(self, event):
self._start = event.pos()
def mouseReleaseEvent(self, event):
start = QtCore.QPointF(self.mapToScene(self._start))
end = QtCore.QPointF(self.mapToScene(event.pos()))
brush = QtGui.QBrush(QtGui.QColor(255, 0, 0) )
pen = QtGui.QPen(brush, 2)
line = QtGui.QGraphicsLineItem(QtCore.QLineF(start, end))
line.setPen(pen)
self.scene().addItem( line )
def paintWire(self, start_widget, end_widget):
start_position = QtCore.QPointF(self.mapToScene(start_widget.pos()))
end_position = QtCore.QPointF(self.mapToScene(end_widget.pos()))
brush = QtGui.QBrush(QtGui.QColor(255, 0, 0) )
pen = QtGui.QPen(brush, 2)
line = QtGui.QGraphicsLineItem(QtCore.QLineF(start_position, end_position))
line.setPen(pen)
self.scene().addItem( line )
If is there any better way to implement this, please tell me.