Undo functionality for QPainter drawEllipse() function - python-3.x

I'm new to PyQt and started playing around with some online code. The drawEllipse() function found in QPainter class draws an ellipse based on defined parameters. My question is once we call the function and it draws it on our scene, how do I undo this operation? There seems to be no such function that can do this in the documentation.
Example drawing code:
def draw(self, x, y):
painter = QPainter()
painter.begin(self.image)
painter.setPen(QPen(Qt.red, 5, Qt.SolidLine))
painter.drawEllipse(QPoint(y,x),10,10)
painter.end()
self.scene.addPixmap(QPixmap.fromImage(self.image))
So if a keystroke of Ctrl+Z is pressed this drawn circle should disappear, is this possible?

I do not understand why you use QImage because if you want to graph circles you should use QGraphicsEllipseItem.
To implement the undo method we must store the items in a list, then when you call the undo method you get the last element and remove it from the QGraphicsScene using removeItem().
In the following example, a circle is added each time you click.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
lay = QtWidgets.QVBoxLayout(self)
self.gv = QtWidgets.QGraphicsView()
lay.addWidget(self.gv)
self.scene = QtWidgets.QGraphicsScene(0, 0, 400, 400)
self.gv.setScene(self.scene)
self.gv.installEventFilter(self)
self.items = []
shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+Z"), self)
shortcut.activated.connect(self.undo)
def eventFilter(self, obj, event):
if obj == self.gv and event.type() == QtCore.QEvent.MouseButtonPress:
p = self.gv.mapToScene(event.pos())
self.draw(p)
return QtWidgets.QWidget.eventFilter(self, obj, event)
def draw(self, p):
it = QtWidgets.QGraphicsEllipseItem(0, 0, 10, 10)
it.setPen(QtGui.QPen(QtCore.Qt.red, 5, QtCore.Qt.SolidLine))
self.scene.addItem(it)
it.setPos(p)
self.items.append(it)
def undo(self):
if self.items:
it = self.items.pop()
self.scene.removeItem(it)
del it
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

Related

How not to get a gtkdrawingarea cleared in the draw event?

My drawingarea is cleared everytime the draw event is called.
How to avoid a drawingarea to be cleared ?
Thanks
#!/usr/bin/env python3
import gi
gi.require_version('Gtk','3.0')
from gi.repository import Gtk, Gdk
import cairo
import math
class MouseButtons:
LEFT_BUTTON = 1
RIGHT_BUTTON = 3
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.darea = Gtk.DrawingArea()
self.darea.connect("draw", self.on_draw)
self.darea.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.add(self.darea)
self.set_title("Fill & stroke")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
self.darea.connect("button-press-event", self.on_button_press)
self.coords = []
self.show_all()
def on_draw(self, wid, cr):
cr.set_source_rgb(0.6, 0.6, 0.6)
cr.arc(self.coords[0], self.coords[1], 40, 0, 2*math.pi)
cr.fill()
def on_button_press(self, w, e):
if e.type == Gdk.EventType.BUTTON_PRESS \
and e.button == MouseButtons.LEFT_BUTTON:
self.coords = [e.x, e.y]
self.darea.queue_draw()
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
In this example, each time I click on the drawingarea, a circle is drawn. I would like to draw the new circle but without to redrawing the previous one.
Is it possible ?
I would recommend adjusting your mental model of the drawing area; don't think of it as being "cleared" every time the draw handler is called. Rather, think of it like this: the draw handler is called every time the drawing area needs to be redrawn from scratch (among other reasons: because some other window moved in front of it, or because your program asked for a draw update). The drawing area's contents, once drawn, are not persisted anywhere.
If you need persistent window contents, then you should use a backing store and draw that onto the screen in the draw handler, or you could use a canvas library if you want to treat existing drawn objects as independently existing.
I found he answer :
#!/usr/bin/env python3
import gi
gi.require_version('Gtk','3.0')
from gi.repository import Gtk, Gdk
import cairo
import math
class MouseButtons:
LEFT_BUTTON = 1
RIGHT_BUTTON = 3
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.darea = Gtk.DrawingArea()
self.darea.connect("draw", self.on_draw)
self.darea.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.add(self.darea)
self.set_title("Fill & stroke")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
self.darea.connect("button-press-event", self.on_button_press)
self.show_all()
a = self.darea.get_allocation()
print (a.x, a.y, a.width, a.height)
self.img = cairo.ImageSurface(cairo.Format.RGB24, a.width, a.height)
def on_draw(self, wid, cr):
cr.set_source_surface(self.img, 0, 0)
cr.paint()
def on_button_press(self, w, e):
if e.type == Gdk.EventType.BUTTON_PRESS \
and e.button == MouseButtons.LEFT_BUTTON:
cr = cairo.Context(self.img)
cr.set_source_rgb(0.6, 0.6, 0.6)
cr.arc(e.x, e.y, 40, 0, 2*math.pi)
cr.fill()
self.darea.queue_draw()
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()

PyQt5 - How to draw a dot on mouse click position?

I am trying to draw a dot on my main window, but the dot is not shown.
I've tried bounding mousePressEvent to paintEvent, but it didn't work as well. Here's current version of my code(which is not working too). Also I tried place a point with drawPoint method and it didn't work too.
import sys
from PyQt5 import QtWidgets, QtGui, QtCore, uic
class GUI(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi('gui.ui', self)
self.setFixedSize(self.size())
self.show()
def mousePressEvent(self, e):
print(e.pos())
qp = QtGui.QPainter()
qp.begin(self)
qp.setPen(QtCore.Qt.red)
qp.drawEllipse(e.pos().x(), e.pos().y(), 10, 10)
qp.end()
self.update()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = GUI()
sys.exit(app.exec_())
I know that mousePressEvent is working since I get coords of the click.
I am okay to change methods of dot-placing or type of dots to place, but it should have customizable color and size.
You should only draw within the paintEvent method, and this paint does not save memory so if you want to graph several points you must store them in some container, for example using QPolygon.
paintEvent() is called every time you call update() or repaint(), for example it is called every time it is resized, the window is moved, etc.
import sys
from PyQt5 import QtWidgets, QtGui, QtCore, uic
class GUI(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi('gui.ui', self)
self.setFixedSize(self.size())
self.show()
self.points = QtGui.QPolygon()
def mousePressEvent(self, e):
self.points << e.pos()
self.update()
def paintEvent(self, ev):
qp = QtGui.QPainter(self)
qp.setRenderHint(QtGui.QPainter.Antialiasing)
pen = QtGui.QPen(QtCore.Qt.red, 5)
brush = QtGui.QBrush(QtCore.Qt.red)
qp.setPen(pen)
qp.setBrush(brush)
for i in range(self.points.count()):
qp.drawEllipse(self.points.point(i), 5, 5)
# or
# qp.drawPoints(self.points)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = GUI()
sys.exit(app.exec_())

PYQT how to draw line between two buttons

from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.view = View(self)
self.button = QtGui.QPushButton('Clear View', self)
self.button.clicked.connect(self.handleClearView)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(self.button)
def handleClearView(self):
self.view.scene().clear()
class DragButton(QtGui.QPushButton):
def mousePressEvent(self, event):
self.__mousePressPos = None
self.__mouseMovePos = None
if event.button() == QtCore.Qt.LeftButton:
self.__mousePressPos = event.globalPos()
self.__mouseMovePos = event.globalPos()
#super(DragButton, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
# adjust offset from clicked point to origin of widget
currPos = self.mapToGlobal(self.pos())
globalPos = event.globalPos()
diff = globalPos - self.__mouseMovePos
newPos = self.mapFromGlobal(currPos + diff)
self.move(newPos)
self.__mouseMovePos = globalPos
#super(DragButton, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if self.__mousePressPos is not None:
moved = event.globalPos() - self.__mousePressPos
if moved.manhattanLength() > 3:
event.ignore()
return
#super(DragButton, self).mouseReleaseEvent(event)
class View(QtGui.QGraphicsView):
def __init__(self, parent):
QtGui.QGraphicsView.__init__(self, parent)
self.setScene(QtGui.QGraphicsScene(self))
self.setSceneRect(QtCore.QRectF(self.viewport().rect()))
btn1=DragButton('Test1', self)
btn2=DragButton('Test2', self)
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()))
self.scene().addItem(
QtGui.QGraphicsLineItem(QtCore.QLineF(start, end)))
for point in (start, end):
text = self.scene().addSimpleText(
'(%d, %d)' % (point.x(), point.y()))
text.setBrush(QtCore.Qt.red)
text.setPos(point)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
Here is my code. There are two movable buttons on the QGraphicsView and I can draw line on the QGraphicsView with mouse dragging. But what I want to do is to draw line between two buttons. For detail, If I right click the btn1(Test1) and then right click the btn2(Test2) , the line would be created between two buttons. I'm struggling this problem for a month. Plz Help!
I am assuming that the line you need to draw between the buttons must also be movable. If not it is just simple you can just use :
lines = QtGui.QPainter()
lines.setPen(self)
lines.drawLine(x1,y1,x2,y2)
So, if the line needs to be movable along with the buttons then first you create a mini widget consisting of Two Buttons and the Line, so you can move the whole widget. This might help!, in that case.

pyqt5: why the mimeData().text() returns nothing?

learning PyQt5 recently, I've tried to drag a QPushButton learning this tutorial Drag & drop a button widget, and made some improvements to place the button more accurate, so I add
mime = e.mimeData().text()
x, y = mime.split(',')
according to #Avaris for this question, but I found e.mimeData().text() returned nothing which supposed to be the coordinate of local position of the cursor with respect to the button, i tried to print(mime), and got a blank line with nothing, then i print(mime.split(',')) and got ['']。
here's the code:
import sys
from PyQt5.QtWidgets import QPushButton, QWidget, QApplication, QLabel
from PyQt5.QtCore import Qt, QMimeData
from PyQt5.QtGui import QDrag
from PyQt5 import QtCore
class Button(QPushButton):
def __init__(self, title, parent):
super().__init__(title, parent)
def mouseMoveEvent(self, e):
if e.buttons() != Qt.RightButton:
return
mimeData = QMimeData()
drag = QDrag(self)
drag.setMimeData(mimeData)
dropAction = drag.exec_(Qt.MoveAction)
def mousePressEvent(self, e):
QPushButton.mousePressEvent(self, e)
if e.button() == Qt.LeftButton:
print('press')
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setAcceptDrops(True)
self.button = Button('Button', self)
self.button.move(100, 65)
self.setWindowTitle('Click or Move')
self.setGeometry(300, 300, 280, 150)
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
position = e.pos()
mime = e.mimeData().text()
x, y = mime.split(',')
#print(mime.split(','))
self.button.move(position - QtCore.QPoint(int(x), int(y)))
e.setDropAction(Qt.MoveAction)
e.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
ex.show()
app.exec_()
In the answer of #Avaris, you will notice they set the mimedata with the button position in the mouseMoveEvent:
mimeData = QtCore.QMimeData()
# simple string with 'x,y'
mimeData.setText('%d,%d' % (e.x(), e.y()))
The mimedata does not contain anything by default. You have to set everything yourself! Have a look at the documentation for QMimeData to see what else you can do (other than setting arbitrary text)
Drag and Drop in Camera View
def dragEnterEvent(self, event): # Drag lines
mimeData = QtCore.QMimeData()
if mimeData.hasText:
event.accept()
else:
event.ignore()
def dropEvent(self, event): # Drop lines
mimeData = QtCore.QMimeData()
format = 'application/x-qabstractitemmodeldatalist'
data=event.mimeData().data(format) # Drag Drop get data's name
name_str = codecs.decode(data,'utf-8') # Convert byte to string
mimeData.setText(name_str)
# print(name_str[26:].replace('\x00','').strip("")) # remove white space
if mimeData.hasText:
print(name_str)
# write what you will do

Pyqt: How-to set item always in the center of a QGraphicsView when scrolling is possible?

I have a QGraphicsView. To that I added a QGraphicsScene and to that a added an QPixmap(*.jpeg) and QGraphicsEllipseItem(a circle). The QPixmap is much more bigger than the QGraphicsView, so scrolling is enabled. The problem is that both QPixmap and QGraphicsEllipseItem are moving. But I want a fixed position for the QGraphicsEllipseItem in QGraphicsView. It should always be in the center of the QGraphicsView.
How can I do that? Hope someone can help.
Add a signal handler for the scroll signal of the scroll bars (use QAbstractSlider::sliderMoved()).
Then you can query the view for it's left/top offset and size and position the circle accordingly. See the explanation for QAbstractScrollArea to get you started.
If you subclass QGraphicsView, you can override the scrollContentsBy method to set the position of the ellipse whenever the scroll area changes. Here's some really minimal code:
import sys
from PyQt4 import QtGui
class MyView(QtGui.QGraphicsView):
def __init__(self, scene, parent = None):
super(MyView, self).__init__(parent)
self.scene = scene
self.setScene(scene)
self.ellipse = QtGui.QGraphicsEllipseItem(0, 0, 30, 30, scene = self.scene)
self.scene.addItem(self.ellipse)
def scrollContentsBy(self, x, y):
super(MyView, self).scrollContentsBy(x, y)
self.ellipse.setPos(self.mapToScene(28, 28))
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
super(MainWindow, self).__init__(parent)
pixmap = QtGui.QPixmap()
pixmap.load('imagefile.jpg')
scene = QtGui.QGraphicsScene(self)
scene.setSceneRect(0, 0, pixmap.width(), pixmap.height())
item = QtGui.QGraphicsPixmapItem(pixmap)
scene.addItem(item)
self.view = MyView(scene, self)
self.view.setMinimumSize(100, 100)
def main():
app = QtGui.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
app.exec_()
if __name__ == '__main__':
main()

Resources