i have a page containing two tabs.i want to add a fadeIn effect when i change the tabs.Is that possible?
import sys
from PyQt4.QtCore import QTimeLine
from PyQt4.QtGui import *
class FaderWidget(QWidget):
def __init__(self, old_widget, new_widget):
QWidget.__init__(self, new_widget)
self.old_pixmap = QPixmap(new_widget.size())
old_widget.render(self.old_pixmap)
self.pixmap_opacity = 1.0
self.timeline = QTimeLine()
self.timeline.valueChanged.connect(self.animate)
self.timeline.finished.connect(self.close)
self.timeline.setDuration(333)
self.timeline.start()
self.resize(new_widget.size())
self.show()
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
painter.setOpacity(self.pixmap_opacity)
painter.drawPixmap(0, 0, self.old_pixmap)
painter.end()
def animate(self, value):
self.pixmap_opacity = 1.0 - value
self.repaint()
class StackedWidget(QStackedWidget):
def __init__(self, parent = None):
QStackedWidget.__init__(self, parent)
def setCurrentIndex(self, index):
self.fader_widget = FaderWidget(self.currentWidget(), self.widget(index))
QStackedWidget.setCurrentIndex(self, index)
def setPage1(self):
self.setCurrentIndex(0)
def setPage2(self):
self.setCurrentIndex(1)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = QWidget()
stack = StackedWidget()
stack.addWidget(QCalendarWidget())
editor = QTextEdit()
editor.setPlainText("Hello world! "*100)
stack.addWidget(editor)
page1Button = QPushButton("Page 1")
page2Button = QPushButton("Page 2")
page1Button.clicked.connect(stack.setPage1)
page2Button.clicked.connect(stack.setPage2)
layout = QGridLayout(window)
layout.addWidget(stack, 0, 0, 1, 2)
layout.addWidget(page1Button, 1, 0)
layout.addWidget(page2Button, 1, 1)
window.show()
sys.exit(app.exec_())
this is code that shows some fade effect but i m getting nothing from it and how it works and how to implement on tabs. it will be really appreciable if someone could help me implement it on tabs as well.
thanks in advance.
With the same logic as the code you show, each widget will be placed inside a QStackedWidget, where one of them will be the widget that will be displayed and the other will be the FaderWidget.
class FaderWidget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.pixmap_opacity = None
self.timeline = QTimeLine(333, self)
self.timeline.valueChanged.connect(self.animate)
self.timeline.finished.connect(self.close)
def start(self, old_widget, new_widget):
self.pixmap_opacity = 1.0
self.old_pixmap = QPixmap(new_widget.size())
old_widget.render(self.old_pixmap)
self.timeline.start()
self.resize(new_widget.size())
self.show()
def paintEvent(self, event):
if self.pixmap_opacity:
QWidget.paintEvent(self, event)
painter = QPainter(self)
painter.setOpacity(self.pixmap_opacity)
painter.drawPixmap(0, 0, self.old_pixmap)
def animate(self, value):
self.pixmap_opacity = 1.0 - value
self.update()
class FaderTabWidget(QTabWidget):
def __init__(self, parent=None):
QTabWidget.__init__(self, parent)
self.currentChanged.connect(self.onCurrentIndex)
self.last = -1
self.current = self.currentIndex()
def onCurrentIndex(self, index):
self.last = self.current
self.current = self.currentIndex()
if self.widget(self.last):
self.widget(self.last).setCurrentIndex(1)
old_widget = self.widget(self.last).widget(0)
current_widget = self.widget(self.current).widget(0)
fade = self.widget(self.current).widget(1)
fade.start(old_widget, current_widget)
def addTab(self, widget, text):
stack = QStackedWidget(self)
stack.addWidget(widget)
fade = FaderWidget(self)
fade.timeline.finished.connect(lambda: stack.setCurrentIndex(0))
stack.addWidget(fade)
stack.setCurrentIndex(0 if self.currentIndex() == -1 else 1)
QTabWidget.addTab(self, stack, text)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = QWidget()
tabWidget = FaderTabWidget()
tabWidget.addTab(QCalendarWidget(), "Tab1")
editor = QTextEdit()
editor.setPlainText("Hello world! " * 100)
tabWidget.addTab(editor, "Tab2")
layout = QVBoxLayout(window)
layout.addWidget(tabWidget)
window.show()
sys.exit(app.exec_())
Related
I'm trying to draw a line interactively but I can't figure out how to do it. Anyone can help me?
I'm using PyQt5. I create a QGraphicsScene, I override mouse press event but when I try to draw the draw the it draw nothing.
Finally, I have another question. How can I snap this line to the grid?
import math
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class neGraphicScene(QGraphicsScene):
_colorBackGround: str
_colorLightLines: str
_colorDarkLines: str
_penLight: QPen
_penDark: QPen
gridSize: int = 20
gridSquares: int = 5
isLineDrawing = False
def __init__(self, parent=None):
QGraphicsScene.__init__(self, QRectF(-1000, -1000, 2000, 2000), parent)
self.setSceneColor()
self.begin, self.destination = QPoint(), QPoint()
def setSceneColor(self, _lightLines="#2f2f2f", _darkLines="#292929", _background="#393939"):
self._colorBackGround = _background
self._colorLightLines = _lightLines
self._colorDarkLines = _darkLines
self._penLight = QPen(QColor(self._colorLightLines))
self._penLight.setWidth(1)
self._penDark = QPen(QColor(self._colorDarkLines))
self._penDark.setWidth(2)
self.setBackgroundBrush(QColor(self._colorBackGround))
def setGraphicScene(self, width, height):
self.setSceneRect(-width // 2, -height // 2, width, height)
def mouseDoubleClickEvent(self, event):
QGraphicsScene.mouseMoveEvent(self, event)
def mousePressEvent(self, event):
self.isLineDrawing = True
if event.buttons() & Qt.LeftButton:
print('Point 1')
self.begin = event.pos()
self.destination = self.begin
self.update()
def mouseMoveEvent(self, event):
if event.buttons() & Qt.LeftButton:
print('Point 2')
self.destination = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.isLineDrawing = False
print('Point 3')
def drawBackground(self, painter, rect):
super(neGraphicScene, self).drawBackground(painter, rect)
# self.drawBackgroundGrid(painter, rect)
if self.isLineDrawing:
if not self.begin.isNull() and not self.destination.isNull():
line = QLineF(self.begin, self.destination)
painter.setPen(Qt.red)
painter.drawLine(line)
This is the QGraphicsView and the QWidgetStuff to made the minimal example working properly:
class neGraphicView(QGraphicsView):
def __init__(self, graphicScene, parent=None):
super().__init__(parent)
# self._dragPos = QPoint(0, 0)
self.sharedGraphicScene = graphicScene
self.setScene(self.sharedGraphicScene)
self._mousePressed = False
self._isPanning = False
self.initUI()
def initUI(self):
self.setRenderHints(
QPainter.Antialiasing | QPainter.HighQualityAntialiasing |
QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setDragMode(QGraphicsView.RubberBandDrag)
class NodeEditorWindow(QWidget):
mainLayout: QVBoxLayout
mainGraphicsScene: neGraphicScene
view: neGraphicView
def __init__(self, parent=None):
super(NodeEditorWindow, self).__init__(parent)
self.initUI()
def initUI(self):
self.setGeometry(200, 200, 800, 600)
self.mainLayout = QVBoxLayout()
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.mainLayout)
self.mainGraphicsScene = neGraphicScene()
self.view = neGraphicView(self.mainGraphicsScene, self)
self.mainLayout.addWidget(self.view)
self.setWindowTitle("Node Editor")
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle("Fusion")
win = NodeEditorWindow()
win.show()
sys.exit(app.exec())
This is the grid function:
def drawBackgroundGrid(self, _painter, _rect):
# here we create our grid
_left = int(math.floor(_rect.left()))
_right = int(math.ceil(_rect.right()))
_top = int(math.floor(_rect.top()))
_bottom = int(math.ceil(_rect.bottom()))
first_left = _left - (_left % self.gridSize)
first_top = _top - (_top % self.gridSize)
try:
# compute all lines to be drawn
lightLines, darkLines = [], []
for x in range(first_left, _right, self.gridSize):
if x % (self.gridSize * self.gridSquares) != 0:
lightLines.append(QLineF(x, _top, x, _bottom))
else:
darkLines.append(QLineF(x, _top, x, _bottom))
for y in range(first_top, _bottom, self.gridSize):
if y % (self.gridSize * self.gridSquares) != 0:
lightLines.append(QLineF(_left, y, _right, y))
else:
darkLines.append(QLineF(_left, y, _right, y))
# draw the lines
_painter.setPen(self._penLight)
_painter.drawLines(*lightLines)
_painter.setPen(self._penDark)
_painter.drawLines(*darkLines)
except Exception as e:
# print(e)
pass
I'm trying to draw a circle instead of dots
Here is the class where it paints red dots on pressing event at any place in the widget
I want to draw a circle (hollow circle) as an outline not a solid circle that hide part of my picture
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Canvas(QWidget):
def __init__(self, photo, *args, **kwargs):
super().__init__(*
args, **kwargs)
self.image = QImage(photo)
self.setFixedSize(self.image.width(), self.image.height())
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
qp = QPainter(self.image)
qp.setRenderHint(QPainter.Antialiasing)
qp.setPen(QPen(Qt.red, 5))
qp.setBrush(Qt.red)
qp.drawPoint(event.pos())
self.update()
def paintEvent(self, event):
qp = QPainter(self)
rect = event.rect()
qp.drawImage(rect, self.image, rect)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
w = QWidget()
self.setCentralWidget(w)
grid = QGridLayout(w)
grid.addWidget(Canvas('photo.jpeg'))
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = MainWindow()
gui.show()
sys.exit(app.exec_())
I believe this is what you're trying to do.
In which case you need to take advantage of which paint device to pass to QPainter. During mousePressEvent and mouseMoveEvent use QPainter(self) so anything painted will only last until the next update. Then in mouseReleaseEvent when satisfied with the size of the circle, you can paint on the QImage with QPainter(self.image) to permanently draw the circle.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Canvas(QWidget):
def __init__(self, photo, *args, **kwargs):
super().__init__(*args, **kwargs)
self.image = QImage(photo)
self.setFixedSize(self.image.width(), self.image.height())
self.pressed = self.moving = False
self.revisions = []
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.pressed = True
self.center = event.pos()
self.update()
def mouseMoveEvent(self, event):
if event.buttons() & Qt.LeftButton:
self.moving = True
r = (event.pos().x() - self.center.x()) ** 2 + (event.pos().y() - self.center.y()) ** 2
self.radius = r ** 0.5
self.update()
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
self.revisions.append(self.image.copy())
qp = QPainter(self.image)
self.draw_circle(qp) if self.moving else self.draw_point(qp)
self.pressed = self.moving = False
self.update()
def paintEvent(self, event):
qp = QPainter(self)
rect = event.rect()
qp.drawImage(rect, self.image, rect)
if self.moving:
self.draw_circle(qp)
elif self.pressed:
self.draw_point(qp)
def draw_point(self, qp):
qp.setPen(QPen(Qt.black, 5))
qp.drawPoint(self.center)
def draw_circle(self, qp):
qp.setRenderHint(QPainter.Antialiasing)
qp.setPen(QPen(Qt.black, 3, Qt.DashLine))
qp.drawEllipse(self.center, self.radius, self.radius)
def undo(self):
if self.revisions:
self.image = self.revisions.pop()
self.update()
def reset(self):
if self.revisions:
self.image = self.revisions[0]
self.revisions.clear()
self.update()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
w = QWidget()
self.setCentralWidget(w)
canvas = Canvas('photo.png')
grid = QGridLayout(w)
grid.addWidget(canvas)
QShortcut(QKeySequence('Ctrl+Z'), self, canvas.undo)
QShortcut(QKeySequence('Ctrl+R'), self, canvas.reset)
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = MainWindow()
gui.show()
sys.exit(app.exec_())
hello I have QTheard worker that start with click at QAction.
....
self.start_update = QAction('&Start', self)
self.start_update.triggered.connect(self._start_thread)
self.stop_update.setVisible(True)
self.stop_update.triggered.connect(self._stop_thread)
self.stop_update.setVisible(False)
...
...
def _start_thread(self):
self.start_update.setVisible(False)
self.stop_update.setVisible(True)
self.myworker.start()
...
...
def _stop_thread(self):
self.myworker.stop() # That close app with error
self.stop_update.setVisible(False)
self.start_update.setVisible(True)
...
Please help how to close terminate Worker correctly. Atm worker have __del__ and run (logic) method. Maybe need to add some method for correct worker closing?
Your application might look like this:
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class AThread(QThread):
threadSignalAThread = pyqtSignal(int)
def __init__(self):
super().__init__()
def run(self):
count = 0
while count < 1000:
QThread.msleep(200)
count += 1
self.threadSignalAThread.emit(count)
class MsgBoxAThread(QDialog):
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
self.label = QLabel("")
layout.addWidget(self.label)
close_btn = QPushButton("Close window")
layout.addWidget(close_btn)
close_btn.clicked.connect(self.close)
self.setGeometry(900, 300, 400, 80)
self.setWindowTitle('MsgBox AThread(QThread)')
class Example(QMainWindow):
def __init__(self, parent=None, *args):
super().__init__(parent, *args)
self.setWindowTitle("Pyqt5 stop QThread worker on QAction")
self.setGeometry(550, 300, 300, 300)
centralWidget = QWidget(self)
self.setCentralWidget(centralWidget)
layout = QVBoxLayout(centralWidget)
self.lbl = QLabel("Start")
layout.addWidget(self.lbl)
bar = self.menuBar()
barThread = bar.addMenu('Thread')
quit = bar.addMenu('Quit')
quit.aboutToShow.connect(app.quit)
self.start_update = QAction('&Start', self)
self.start_update.setShortcut('Ctrl+S')
self.start_update.triggered.connect(self._start_thread)
self.stop_update = QAction('Sto&p', self)
self.stop_update.setShortcut('Ctrl+P')
self.stop_update.setVisible(False)
self.stop_update.triggered.connect(self._stop_thread)
barThread.addAction(self.start_update)
barThread.addAction(self.stop_update)
self.msg = MsgBoxAThread()
self.myworker = None
self.counter = 0
self.timer = QTimer()
self.timer.setInterval(1000)
self.timer.timeout.connect(self.recurring_timer)
self.timer.start()
self.show()
def recurring_timer(self):
self.counter += 1
self.lbl.setText(" Do something in the GUI: <b> %d </b>" % self.counter)
def _start_thread(self):
self.start_update.setVisible(False)
self.stop_update.setVisible(True)
self.myworker = AThread()
self.myworker.threadSignalAThread.connect(self.on_threadSignalAThread)
self.myworker.finished.connect(self.finishedAThread)
self.myworker.start()
def finishedAThread(self):
self.myworker = None
self.start_update.setVisible(True)
self.stop_update.setVisible(False)
def on_threadSignalAThread(self, value):
self.msg.label.setText(str(value))
if not self.msg.isVisible():
self.msg.show()
def _stop_thread(self):
self.stop_update.setVisible(False)
self.start_update.setVisible(True)
self.myworker.terminate()
self.myworker = None
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Question',
"Are you sure you want to close the application?",
QMessageBox.Yes,
QMessageBox.No)
if reply == QMessageBox.Yes:
if self.myworker:
self.myworker.quit()
del self.myworker
self.msg.close()
super(Example, self).closeEvent(event)
else:
event.ignore()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
#ex.show()
sys.exit(app.exec_())
I want to change an image with my mouse. So, everytime I click somewhere, the image should change. I can show an image only one time. So I need to separate the initialization of everything that is needed to show an image from the part of code that is responsable for building an image.
Here is what I have got by far
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import pyqtSlot
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.gx=1
self.gy=1
self.tlb=QLabel()
self.lbl=QLabel()
self.image = QImage(512, 512, QImage.Format_RGB32)
self.hbox = QHBoxLayout()
self.pixmap = QPixmap()
self.initUI()
def mousePressEvent(self, QMouseEvent):
px = QMouseEvent.pos().x()
py = QMouseEvent.pos().y()
size = self.frameSize()
self.gx = px-size.width()/2
self.gy = py-size.height()/2
self.fillImage()
def initUI(self):
self.hbox = QHBoxLayout(self)
self.pixmap = QPixmap()
size = self.frameSize()
self.fillImage()
self.lbl = QLabel(self)
self.lbl.setPixmap(self.pixmap)
self.hbox.addWidget(self.lbl)
self.setLayout(self.hbox)
self.move(300, 200)
self.setWindowTitle('Red Rock')
self.tlb = QLabel(str(self.gx)+" : "+str(self.gy), self)
self.tlb.move(12,3)
self.show()
def fillImage(self):
for x in range(0, 512):
t = -1+(x/512)*2
color = (1 - (3 - 2*abs(t))*t**2)
for y in range(0, 512):
t1 = -1+(y/512)*2
color1 = (1 - (3 - 2*abs(t1))*t1**2)
result = (255/2)+(color * color1 * (t*self.gx+t1*self.gy) )*(255/2)
self.image.setPixel(x, y, qRgb(result, result, result))
self.pixmap = self.pixmap.fromImage(self.image)
self.tlb = QLabel(str(self.gx)+" : "+str(self.gy), self)
print(self.gx)
self.update()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The print(self.gx) shows me that self.gx is changed, but the image isn't changed at all.
What do I do wrong?
You will have to tell the GUI that it needs to refresh the image.
In QT it seems you will need to call the update() or repaint() methods of the widget.
I've added self.lbl.setPixmap(self.pixmap) into fillImage before self.repaint() and self.update() and now it works, then i changed a little the code and now it looks like this
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import pyqtSlot
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.gx=1
self.gy=1
self.lbl=QLabel()
self.tlb = None
self.image = QImage(512, 512, QImage.Format_RGB32)
self.hbox = QHBoxLayout()
self.pixmap = QPixmap()
self.length = 1
self.initUI()
def mousePressEvent(self, QMouseEvent):
px = QMouseEvent.pos().x()
py = QMouseEvent.pos().y()
size = self.frameSize()
self.gx = px-size.width()/2
self.gy = py-size.height()/2
h = (self.gx**2+self.gy**2)**0.5
self.gx/=h
self.gy/=h
self.gx*=self.length
self.gy*=self.length
self.fillImage()
def wheelEvent(self,event):
self.length+=(event.delta()*0.001)
print(self.length)
def initUI(self):
self.hbox = QHBoxLayout(self)
self.pixmap = QPixmap()
self.move(300, 200)
self.setWindowTitle('Red Rock')
self.addedWidget = None
self.fillImage()
self.setLayout(self.hbox)
self.show()
def fillImage(self):
for x in range(0, 512):
t = -1+(x/512)*2
color = (1 - (3 - 2*abs(t))*t**2)
for y in range(0, 512):
t1 = -1+(y/512)*2
color1 = (1 - (3 - 2*abs(t1))*t1**2)
result = (255/2)+(color * color1 * (t*self.gx+t1*self.gy) )*(255/2)
self.image.setPixel(x, y, qRgb(result, result, result))
self.pixmap = self.pixmap.fromImage(self.image)
if self.lbl == None:
self.lbl = QLabel(self)
else:
self.lbl.setPixmap(self.pixmap)
if self.addedWidget == None:
self.hbox.addWidget(self.lbl)
self.addedWidget = True
if self.tlb==None:
self.tlb = QLabel(str(self.gx)+" : "+str(self.gy), self)
self.tlb.move(12,3)
else:
self.tlb.setText(str(self.gx)+" : "+str(self.gy))
self.repaint()
self.update()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
short version:
- take the code, run it, (all you need is two png icons)
- resize the window to be a lot larger
- dragndrop one of the icon far away (at least 300+ pixels away)
- then resize window back to original size
- then try to scroll to see the icon you dragndropped away.
- you will not be able. because scrollarea is too small.
- why?
long version:
i'm having trouble figuring how to update my scrollarea to reflect added or modified contents in my application.
i'm displaying icons, i can dragndrop them.
if i make the window bigger, dragndrop one icon to the bottom,
and then size back my window,
the scrollarea does not allow me to scroll to the bottom to see my icon.
basicaly, once the app started, scrollarea dimension never change.
how can i make the scrollarea, upon dragndrop, to update to new size ?
it could be bigger like shown in the screenshot below,
or smaller if all my icons are grouped in upper left corner for example..
if the content fit in the window, i will not show the slider.
here's a screenshot showing the problem,
it's the same window, i just resize it, and dragndrop one icon at the bottom:
(scrollarea is not updated, so i cannot scroll down to my icon i've put at the bottom)
here's the code so far:
#!/usr/bin/python3
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class DragWidget(QFrame):
def __init__(self, parent=None):
super(DragWidget, self).__init__(parent)
self.setMinimumSize(200, 200)
self.setAcceptDrops(True)
test_icon1 = QLabel(self)
test_icon1.setPixmap(QPixmap('./images/closeicon.png'))
test_icon1.move(20, 20)
test_icon1.show()
test_icon1.setAttribute(Qt.WA_DeleteOnClose)
test_icon2 = QLabel(self)
test_icon2.setPixmap(QPixmap('./images/openicon.png'))
test_icon2.move(60, 20)
test_icon2.show()
test_icon2.setAttribute(Qt.WA_DeleteOnClose)
def dragEnterEvent(self, event):
if event.mimeData().hasFormat('application/x-dnditemdata'):
if event.source() == self:
event.setDropAction(Qt.MoveAction)
event.accept()
else:
event.acceptProposedAction()
else:
event.ignore()
dragMoveEvent = dragEnterEvent
def dropEvent(self, event):
if event.mimeData().hasFormat('application/x-dnditemdata'):
itemData = event.mimeData().data('application/x-dnditemdata')
dataStream = QDataStream(itemData, QIODevice.ReadOnly)
pixmap = QPixmap()
offset = QPoint()
dataStream >> pixmap >> offset
newIcon = QLabel(self)
newIcon.setPixmap(pixmap)
newIcon.move(event.pos() - offset)
newIcon.show()
newIcon.setAttribute(Qt.WA_DeleteOnClose)
if event.source() == self:
event.setDropAction(Qt.MoveAction)
event.accept()
else:
event.acceptProposedAction()
else:
event.ignore()
def mousePressEvent(self, event):
child = self.childAt(event.pos())
if not child:
return
pixmap = QPixmap(child.pixmap())
itemData = QByteArray()
dataStream = QDataStream(itemData, QIODevice.WriteOnly)
dataStream << pixmap << QPoint(event.pos() - child.pos())
mimeData = QMimeData()
mimeData.setData('application/x-dnditemdata', itemData)
drag = QDrag(self)
drag.setMimeData(mimeData)
drag.setPixmap(pixmap)
drag.setHotSpot(event.pos() - child.pos())
tempPixmap = QPixmap(pixmap)
painter = QPainter()
painter.begin(tempPixmap)
painter.fillRect(pixmap.rect(), QColor(127, 127, 127, 127))
painter.end()
child.setPixmap(tempPixmap)
if drag.exec_(Qt.CopyAction | Qt.MoveAction) == Qt.MoveAction:
child.close()
else:
child.show()
child.setPixmap(pixmap)
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__()
widget = QWidget()
palette = QPalette()
palette.setBrush(QPalette.Background, QBrush(QPixmap("images/pattern.png")))
widget.setPalette(palette)
layout = QVBoxLayout(self)
layout.addWidget(DragWidget())
widget.setLayout(layout)
scroll = QScrollArea()
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(True)
scroll.setWidget(widget)
vlayout = QVBoxLayout(self)
vlayout.setContentsMargins(0, 0, 0, 0)
vlayout.setSpacing(0)
vlayout.addWidget(scroll)
self.setLayout(vlayout)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window('./')
sys.exit(app.exec_())
it turned out, i needed to modify dropEvent method,
to take the X and Y of the dropped icon and use those values for setMinimumSize().
like this:
#!/usr/bin/python3
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class DragWidget(QFrame):
def __init__(self, parent=None):
super(DragWidget, self).__init__(parent)
self.setMinimumSize(200, 200)
self.setAcceptDrops(True)
self.test_icon1 = QLabel(self)
self.test_icon1.setPixmap(QPixmap('./images/closeicon.png'))
self.test_icon1.move(20, 20)
self.test_icon1.show()
self.test_icon1.setAttribute(Qt.WA_DeleteOnClose)
self.test_icon2 = QLabel(self)
self.test_icon2.setPixmap(QPixmap('./images/openicon.png'))
self.test_icon2.move(60, 20)
self.test_icon2.show()
self.test_icon2.setAttribute(Qt.WA_DeleteOnClose)
def dragEnterEvent(self, event):
if event.mimeData().hasFormat('application/x-dnditemdata'):
if event.source() == self:
event.setDropAction(Qt.MoveAction)
event.accept()
else:
event.acceptProposedAction()
else:
event.ignore()
dragMoveEvent = dragEnterEvent
def dropEvent(self, event):
if event.mimeData().hasFormat('application/x-dnditemdata'):
itemData = event.mimeData().data('application/x-dnditemdata')
dataStream = QDataStream(itemData, QIODevice.ReadOnly)
pixmap = QPixmap()
offset = QPoint()
dataStream >> pixmap >> offset
newIcon = QLabel(self)
newIcon.setPixmap(pixmap)
newIcon.move(event.pos() - offset)
newIcon.show()
newIcon.setAttribute(Qt.WA_DeleteOnClose)
if newIcon.y()+32 > self.minimumHeight():
self.setMinimumHeight(newIcon.y()+32)
if newIcon.x()+32 > self.minimumWidth():
self.setMinimumWidth(newIcon.x()+32)
if event.source() == self:
event.setDropAction(Qt.MoveAction)
event.accept()
else:
event.acceptProposedAction()
else:
event.ignore()
def mousePressEvent(self, event):
child = self.childAt(event.pos())
if not child:
return
pixmap = QPixmap(child.pixmap())
itemData = QByteArray()
dataStream = QDataStream(itemData, QIODevice.WriteOnly)
dataStream << pixmap << QPoint(event.pos() - child.pos())
mimeData = QMimeData()
mimeData.setData('application/x-dnditemdata', itemData)
drag = QDrag(self)
drag.setMimeData(mimeData)
drag.setPixmap(pixmap)
drag.setHotSpot(event.pos() - child.pos())
tempPixmap = QPixmap(pixmap)
painter = QPainter()
painter.begin(tempPixmap)
painter.fillRect(pixmap.rect(), QColor(127, 127, 127, 127))
painter.end()
child.setPixmap(tempPixmap)
if drag.exec_(Qt.CopyAction | Qt.MoveAction) == Qt.MoveAction:
child.close()
else:
child.show()
child.setPixmap(pixmap)
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__()
self.pattern = "images/pattern.png"
self.widget = QWidget()
self.palette = QPalette()
self.palette.setBrush(QPalette.Background, QBrush(QPixmap(self.pattern)))
self.widget.setPalette(self.palette)
layout = QVBoxLayout(self)
layout.addWidget(DragWidget())
self.widget.setLayout(layout)
scroll = QScrollArea()
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
scroll.setWidgetResizable(True)
scroll.setWidget(self.widget)
vlayout = QVBoxLayout(self)
vlayout.setContentsMargins(0, 0, 0, 0)
vlayout.setSpacing(0)
vlayout.addWidget(scroll)
self.setLayout(vlayout)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window('./')
window2 = Window('./')
sys.exit(app.exec_())
note the dropEvent() method of DragWidget() class.
if newIcon.y()+32 > self.minimumHeight():
self.setMinimumHeight(newIcon.y()+32)
if newIcon.x()+32 > self.minimumWidth():
self.setMinimumWidth(newIcon.x()+32)
so if the icon new position is greater than
the minimumSize (minimumWidth and minimumHeight),
then add the offset to self.minimumSize
thanks to Avaris from #pyqt channel for the help :)