unsupported operand type(s) for +=: 'QPoint' and 'QPointF' in pyqt6 - pyqt6

I have an application built on pyqt6 and wanted to add a feature ( displaying a panorama image),
so I found this code on the internet to display panorama image using pyqt5.
can someone help me convert it to pyqt6?
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Panoramic(QtWidgets.QWidget):
def __init__(self, imagePath):
QtWidgets.QWidget.__init__(self)
self.setCursor(QtCore.Qt.CrossCursor)
# keep a reference of the original image
self.source = QtGui.QPixmap(imagePath)
self.pano = QtGui.QPixmap(self.source.width() * 3, self.source.height())
self.center = self.pano.rect().center()
# use a QPointF for precision
self.delta = QtCore.QPointF()
self.deltaTimer = QtCore.QTimer(interval=25, timeout=self.moveCenter)
self.sourceRect = QtCore.QRect()
# create a pixmap with three copies of the source;
# this could be avoided by smart repainting and translation of the source
# but since paintEvent automatically clips the painting, it should be
# faster then computing the new rectangle each paint cycle, at the cost
# of a few megabytes of memory.
self.setMaximumSize(self.source.size())
qp = QtGui.QPainter(self.pano)
qp.drawPixmap(0, 0, self.source)
qp.drawPixmap(self.source.width(), 0, self.source)
qp.drawPixmap(self.source.width() * 2, 0, self.source)
qp.end()
def moveCenter(self):
if not self.delta:
return
self.center += self.delta
# limit the vertical position
if self.center.y() < self.sourceRect.height() * .5:
self.center.setY(self.sourceRect.height() * .5)
elif self.center.y() > self.source.height() - self.height() * .5:
self.center.setY(self.source.height() - self.height() * .5)
# reset the horizontal position if beyond the center of the virtual image
if self.center.x() < self.source.width() * .5:
self.center.setX(self.source.width() * 1.5)
elif self.center.x() > self.source.width() * 2.5:
self.center.setX(self.source.width() * 1.5)
self.sourceRect.moveCenter(self.center.toPoint())
self.update()
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.mousePos = event.pos()
def mouseMoveEvent(self, event):
if event.buttons() != QtCore.Qt.LeftButton:
return
delta = event.pos() - self.mousePos
# use a fraction to get small movements, and ensure we're not too fast
self.delta.setX(max(-25, min(25, delta.x() * .125)))
self.delta.setY(max(-25, min(25, delta.y() * .125)))
if not self.deltaTimer.isActive():
self.deltaTimer.start()
def mouseReleaseEvent(self, event):
self.deltaTimer.stop()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.drawPixmap(self.rect(), self.pano, self.sourceRect)
# resize and reposition the coordinates whenever the window is resized
def resizeEvent(self, event):
self.sourceRect.setSize(self.size())
self.sourceRect.moveCenter(self.center)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Panoramic('pano5.jpg')
w.show()
sys.exit(app.exec_())
Here is what I tried to do.
changed QtCore.Qt.CrossCursor to QtCore.Qt.CursorShape.CrossCursor
changed QtCore.Qt.LeftButton to QtCore.Qt.MouseButton.LeftButton
changed sys.exit(app.exec_()) to sys.exit(app.exec())
now I reached the point of getting this error
'line 32, in moveCenter
self.center += self.delta
TypeError: unsupported operand type(s) for +=: 'QPoint' and 'QPointF''
and could not find a way to work around it in pyqt6

Here are the changes that solved my issue:
Changed self.center = QPointF(self.pano.rect().center())) to self.center += self.delta.toPoint()).
and changed self.sourceRect.moveCenter(self.center.toPoint()) to self.sourceRect.moveCenter(self.center).
Now the code is successfully converted from pyqt5 to pyqt6.

Related

Is there a way to prevent scrolling of an iPython notebook, when calling 'mouse_scroll' event over a plot figure inside a cell?

Is there a way to prevent the entire notebook from scrolling, when scrolling over the figure to cycle through the images as shown in this example (and reproduced below): https://matplotlib.org/stable/gallery/event_handling/image_slices_viewer.html
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
class IndexTracker:
def __init__(self, ax, X):
self.ax = ax
ax.set_title('use scroll wheel to navigate images')
self.X = X
rows, cols, self.slices = X.shape
self.ind = self.slices//2
self.im = ax.imshow(self.X[:, :, self.ind])
self.update()
def on_scroll(self, event):
print("%s %s" % (event.button, event.step))
if event.button == 'up':
self.ind = (self.ind + 1) % self.slices
else:
self.ind = (self.ind - 1) % self.slices
self.update()
def update(self):
self.im.set_data(self.X[:, :, self.ind])
self.ax.set_ylabel('slice %s' % self.ind)
self.im.axes.figure.canvas.draw_idle()
def plot(X):
mpl.rc('image', cmap='gray')
fig, ax = plt.subplots(1, 1)
plot = IndexTracker(ax, X)
fig.canvas.mpl_connect('scroll_event', plot.scroll)
plt.show()
On VS Code, using the,
%matplotlib widget
backend, I call the following function in a new code cell:
X = np.random.rand(20, 20, 40)
plot(X)
I am able to successfully generate an interactive plot in an iPython notebook, whereby scrolling over the figure scrolls through the "image slices". The scroll event only works if I hover the cursor over the plot/figure (as it should), however the entire notebook scrolls as well, thereby moving the cursor out of the figure frame.
Is there a way to prevent the notebook from scrolling when the cursor is hovering over a figure in the cell output?

QListWiget - Flow "BottomToTop"?

I want to know if there is any workaround to the missing flow() property "BottomToTop"?
I'm currently working on a little pet project. Simply a QListWidget, containing a custom made QWidget item, added by the user. No problem with that part. I just want the item to be listed "BottomToTop". Any suggestions?
Do just need to insert the new elements at the bottom instead of the top of your list?
I assume what you call bottom in the beginning for your list?
If so, you can use QListWidget::insertItem
yourListWidget->(0, yourNewWidgetItem);
This is my closest solution to what I've been asking (Credit to -ymoreau for the Spacer idea).
But I'm not completely happy with it. When enough items are added and the scrollbar appears, an annoying (to my eye) white-space appears at the bottom. Any solution to that? And ofc. is there a more elegant solution to this? Seems abit overkill to me...
from PyQt5.QtWidgets import (QApplication, QWidget, QListWidget,
QListWidgetItem, QVBoxLayout, QHBoxLayout,
QPushButton, QSpacerItem, QLabel, QSizePolicy)
from PyQt5.QtCore import QSize
from PyQt5 import QtCore
import sys
class SpacerWidget(QWidget):
def __init__(self, parent=None):
super(SpacerWidget, self).__init__(parent)
self.spacer = QSpacerItem(self.width(), self.height(),
QSizePolicy.Expanding,
QSizePolicy.Expanding)
self.setStyleSheet("border: none;")
self.layout = QHBoxLayout()
self.layout.setSpacing(0)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.addItem(self.spacer)
self.setLayout(self.layout)
class SpacerWidgetItem(QListWidgetItem):
def __init__(self, parent=None):
super(SpacerWidgetItem, self).__init__(parent)
def setSize(self, width, new_height):
if new_height < 10:
self.setSizeHint(QSize(0, 0))
else:
self.setSizeHint(QSize(width, new_height))
class ItemWidget(QWidget):
def __init__(self, parent=None):
super(ItemWidget, self).__init__(parent)
self.left_label = QLabel("Some info!")
self.left_label.setMaximumHeight(35)
self.left_label.setAlignment(QtCore.Qt.AlignLeft)
self.right_label = QLabel("Some other info!")
self.right_label.setMaximumHeight(35)
self.right_label.setAlignment(QtCore.Qt.AlignRight)
self.layout = QHBoxLayout()
self.layout.setSpacing(0)
self.layout.addWidget(self.left_label)
self.layout.addWidget(self.right_label)
self.setLayout(self.layout)
class Widget(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(300, 320)
self.itemsinlist = 0
self.layout = QVBoxLayout()
self.add_btn = QPushButton("Add Item")
self.add_btn.setMinimumSize(QSize(300, 50))
self.add_btn.clicked.connect(self.addListItem)
self.list_w = QListWidget()
self.list_w.setMinimumSize(300, 270)
self.list_w.resize(300, 270)
self.list_w.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.list_w.setResizeMode(1)
self.spacer = SpacerWidgetItem(self.list_w)
self.spacer.setSizeHint(QSize(self.list_w.minimumWidth(),
self.list_w.minimumHeight()))
self.spacer_widget = SpacerWidget()
self.list_w.addItem(self.spacer)
self.list_w.setItemWidget(self.spacer, self.spacer_widget)
self.layout.addWidget(self.list_w)
self.layout.addWidget(self.add_btn)
self.layout.setContentsMargins(1, 1, 1, 1)
self.layout.setSpacing(0)
self.setLayout(self.layout)
self.show()
def addListItem(self):
new_label = QListWidgetItem(self.list_w)
new_label.setSizeHint(QSize(self.list_w.minimumWidth(), 35))
new_label_widget = ItemWidget()
self.list_w.addItem(new_label)
self.list_w.setItemWidget(new_label, new_label_widget)
self.list_w.scrollToBottom()
self.itemsinlist += 1
self.spacer.setSize(self.width(),
self.list_w.height() -
(self.itemsinlist * 35)-2)
def resizeEvent(self, event):
self.spacer.setSizeHint(QSize(self.width(),
self.list_w.height() -
(self.itemsinlist * 35)-2))
self.list_w.scrollToBottom()
def main():
app = QApplication(sys.argv)
wid = Widget()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

PyQt – Load data from .txt file via Drag and Drop

I have a folder TreeView on the left layout and I want to drag a .txt file from there and drop it to the other layout. Hopefully, I want to load the data of that dropped file on a variable.
For the code’s needs, I used till now (to my “real” code) the np.loadtxt() to load the data, so I’d like to use it here too.
In case it matters, the .txt file contains 4 columns (coordinates).
I post my code. The program closes when I drop the file.
Thanks in advance!
import sys, time, os
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import numpy as np
import pylab as pl
import random
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.folderLayout = QWidget();
self.pathRoot = QDir.rootPath()
self.dirmodel = QFileSystemModel(self)
self.dirmodel.setRootPath(QDir.currentPath())
self.indexRoot = self.dirmodel.index(self.dirmodel.rootPath())
self.folder_view = QTreeView();
self.folder_view.setDragEnabled(True)
self.folder_view.setModel(self.dirmodel)
self.folder_view.setRootIndex(self.indexRoot)
self.selectionModel = self.folder_view.selectionModel()
self.left_layout = QVBoxLayout()
self.left_layout.addWidget(self.folder_view)
self.folderLayout.setLayout(self.left_layout)
splitter_filebrowser = QSplitter(Qt.Horizontal)
splitter_filebrowser.addWidget(self.folderLayout)
splitter_filebrowser.addWidget(Figure_Canvas(self))
splitter_filebrowser.setStretchFactor(1, 1)
hbox = QHBoxLayout(self)
hbox.addWidget(splitter_filebrowser)
self.centralWidget().setLayout(hbox)
self.setWindowTitle('Simple drag & drop')
self.setGeometry(750, 100, 600, 500)
class Figure_Canvas(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.setAcceptDrops(True)
blabla = QLineEdit()
self.right_layout = QVBoxLayout()
self.right_layout.addWidget(blabla)
self.buttonLayout = QWidget()
self.buttonLayout.setLayout(self.right_layout)
def dragEnterEvent(self, e):
if e.mimeData().hasFormat('text/uri-list'):
e.accept()
else:
e.ignore()
def dropEvent(self, e):
print("something")
data = np.loadtxt(e.mimeData())
print(data)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
ex.show()
app.exec_()

PySide/PyQt truncate text in QLabel based on minimumSize

I am wondering how to best truncate text in a QLabel based on it's maximum width/height.
The incoming text could be any length, but in order to keep a tidy layout I'd like to truncate long strings to fill a maximum amount of space (widget's maximum width/height).
E.g.:
'A very long string where there should only be a short one, but I can't control input to the widget as it's a user given value'
would become:
'A very long string where there should only be a short one, but ...'
based on the required space the current font needs.
How can I achieve this best?
Here is a simple example of what I'm after, though this is based on word count, not available space:
import sys
from PySide.QtGui import *
from PySide.QtCore import *
def truncateText(text):
maxWords = 10
words = text.split(' ')
return ' '.join(words[:maxWords]) + ' ...'
app = QApplication(sys.argv)
mainWindow = QWidget()
layout = QHBoxLayout()
mainWindow.setLayout(layout)
text = 'this is a very long string, '*10
label = QLabel(truncateText(text))
label.setWordWrap(True)
label.setFixedWidth(200)
layout.addWidget(label)
mainWindow.show()
sys.exit(app.exec_())
Even easier - use the QFontMetrics.elidedText method and overload the paintEvent, here's an example:
from PyQt4.QtCore import Qt
from PyQt4.QtGui import QApplication,\
QLabel,\
QFontMetrics,\
QPainter
class MyLabel(QLabel):
def paintEvent( self, event ):
painter = QPainter(self)
metrics = QFontMetrics(self.font())
elided = metrics.elidedText(self.text(), Qt.ElideRight, self.width())
painter.drawText(self.rect(), self.alignment(), elided)
if ( __name__ == '__main__' ):
app = None
if ( not QApplication.instance() ):
app = QApplication([])
label = MyLabel()
label.setText('This is a really, long and poorly formatted runon sentence used to illustrate a point')
label.setWindowFlags(Qt.Dialog)
label.show()
if ( app ):
app.exec_()
I found that #Eric Hulser's answer, while great, didn't work when the label was put into another widget.
I came up with this by hacking together Eric's response with the Qt Elided Label Example. It should behave just like a regular label, yet elide horizontally when the text width exceeds the widget width. It has an extra argument for different elide modes. I also wrote some tests for fun :)
If you want to use PyQt5...
Change "PySide2" to "PyQt5"
Change "Signal" to "pyqtSignal"
Enjoy!
Eliding Label
# eliding_label.py
from PySide2 import QtCore, QtWidgets, QtGui
class ElidingLabel(QtWidgets.QLabel):
"""Label with text elision.
QLabel which will elide text too long to fit the widget. Based on:
https://doc-snapshots.qt.io/qtforpython-5.15/overviews/qtwidgets-widgets-elidedlabel-example.html
Parameters
----------
text : str
Label text.
mode : QtCore.Qt.TextElideMode
Specify where ellipsis should appear when displaying texts that
don’t fit.
Default is QtCore.Qt.ElideMiddle.
Possible modes:
QtCore.Qt.ElideLeft
QtCore.Qt.ElideMiddle
QtCore.Qt.ElideRight
parent : QWidget
Parent widget. Default is None.
f : Qt.WindowFlags()
https://doc-snapshots.qt.io/qtforpython-5.15/PySide2/QtCore/Qt.html#PySide2.QtCore.PySide2.QtCore.Qt.WindowType
"""
elision_changed = QtCore.Signal(bool)
def __init__(self, text='', mode=QtCore.Qt.ElideMiddle, **kwargs):
super().__init__(**kwargs)
self._mode = mode
self.is_elided = False
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
self.setText(text)
def setText(self, text):
self._contents = text
# This line set for testing. Its value is the return value of
# QFontMetrics.elidedText, set in paintEvent. The variable
# must be initialized for testing. The value should always be
# the same as contents when not elided.
self._elided_line = text
self.update()
def text(self):
return self._contents
def paintEvent(self, event):
super().paintEvent(event)
did_elide = False
painter = QtGui.QPainter(self)
font_metrics = painter.fontMetrics()
text_width = font_metrics.horizontalAdvance(self.text())
# layout phase
text_layout = QtGui.QTextLayout(self._contents, painter.font())
text_layout.beginLayout()
while True:
line = text_layout.createLine()
if not line.isValid():
break
line.setLineWidth(self.width())
if text_width >= self.width():
self._elided_line = font_metrics.elidedText(self._contents, self._mode, self.width())
painter.drawText(QtCore.QPoint(0, font_metrics.ascent()), self._elided_line)
did_elide = line.isValid()
break
else:
line.draw(painter, QtCore.QPoint(0, 0))
text_layout.endLayout()
if did_elide != self.is_elided:
self.is_elided = did_elide
self.elision_changed.emit(did_elide)
if __name__ == '__main__':
app = QtWidgets.QApplication([])
long_text = "this is some long text, wouldn't you say?"
elabel = ElidingLabel(long_text)
elabel.show()
app.exec_()
Test Eliding Label
# test_eliding_label.py.py
#
# Run tests with
#
# python3 -m unittest test_eliding_label.py --failfast --quiet
import unittest
import unittest.mock
from PySide2 import QtCore, QtWidgets, QtGui, QtTest
import eliding_label
if not QtWidgets.QApplication.instance():
APP = QtWidgets.QApplication([]) # pragma: no cover
class TestElidingLabelArguments(unittest.TestCase):
def test_optional_text_argument(self):
elabel = eliding_label.ElidingLabel()
self.assertEqual(elabel.text(), "")
def test_text_argument_sets_label_text(self):
elabel = eliding_label.ElidingLabel(text="Test text")
self.assertEqual(elabel.text(), "Test text")
def test_optional_elision_mode_argument(self):
elabel = eliding_label.ElidingLabel()
self.assertEqual(elabel._mode, QtCore.Qt.ElideMiddle)
class TestElidingLabel(unittest.TestCase):
def setUp(self):
self.elabel = eliding_label.ElidingLabel()
def test_elabel_is_a_label(self):
self.assertIsInstance(self.elabel, QtWidgets.QLabel)
def test_has_elision_predicate(self):
self.assertEqual(self.elabel.is_elided, False)
def test_elision_predicate_changes_when_text_width_exceeds_widget_width(self):
# NOTE: This is a bit of a stretch, inducing a paint event
# when the event loop isn't running. Throws a bunch of C++
# sourced text which can't be (easily) caught.
self.elabel.setFixedWidth(25)
self.assertEqual(self.elabel.width(), 25)
long_text = "This is line is definely longer than 25 pixels."
painter = QtGui.QPainter()
font_metrics = painter.fontMetrics()
long_text_width = font_metrics.horizontalAdvance(long_text)
self.assertGreater(long_text_width, 25)
self.elabel.setText(long_text)
x = self.elabel.x()
y = self.elabel.y()
w = self.elabel.width()
h = self.elabel.height()
paint_event = QtGui.QPaintEvent(QtGui.QRegion(x, y, w, h))
self.elabel.paintEvent(paint_event)
self.assertEqual(self.elabel.is_elided, True)
def test_text_is_elided_when_text_width_exceeds_widget_width(self):
# NOTE: This is a bit of a stretch, inducing a paint event
# when the event loop isn't running. Throws a bunch of C++
# sourced text which can't be (easily) caught.
self.elabel.setFixedWidth(25)
self.assertEqual(self.elabel.width(), 25)
long_text = "This is line is definely longer than 25 pixels."
painter = QtGui.QPainter()
font_metrics = painter.fontMetrics()
long_text_width = font_metrics.horizontalAdvance(long_text)
self.assertGreater(long_text_width, 25)
self.elabel.setText(long_text)
x = self.elabel.x()
y = self.elabel.y()
w = self.elabel.width()
h = self.elabel.height()
paint_event = QtGui.QPaintEvent(QtGui.QRegion(x, y, w, h))
self.elabel.paintEvent(paint_event)
# PySide2.QtGui.QFontMetrics.elidedText states, "If the string
# text is wider than width , returns an elided version of the
# string (i.e., a string with '…' in it). Otherwise, returns
# the original string."
self.assertEqual(self.elabel._elided_line, '…')
def test_text_is_not_elided_when_text_width_is_less_than_widget_width(self):
# NOTE: This is a bit of a stretch, inducing a paint event
# when the event loop isn't running. Throws a bunch of C++
# sourced text which can't be (easily) caught.
self.elabel.setFixedWidth(500)
self.assertEqual(self.elabel.width(), 500)
short_text = "Less than 500"
painter = QtGui.QPainter()
font_metrics = painter.fontMetrics()
short_text_width = font_metrics.horizontalAdvance(short_text)
self.assertLess(short_text_width, 500)
self.elabel.setText(short_text)
x = self.elabel.x()
y = self.elabel.y()
w = self.elabel.width()
h = self.elabel.height()
paint_event = QtGui.QPaintEvent(QtGui.QRegion(x, y, w, h))
self.elabel.paintEvent(paint_event)
# PySide2.QtGui.QFontMetrics.elidedText states, "If the string
# text is wider than width , returns an elided version of the
# string (i.e., a string with '…' in it). Otherwise, returns
# the original string."
self.assertEqual(self.elabel._elided_line, short_text)
def test_stores_full_text_even_when_elided(self):
# NOTE: This is a bit of a stretch, inducing a paint event
# when the event loop isn't running. Throws a bunch of C++
# sourced text which can't be (easily) caught.
self.elabel.setFixedWidth(25)
self.assertEqual(self.elabel.width(), 25)
long_text = "This is line is definely longer than 25 pixels."
painter = QtGui.QPainter()
font_metrics = painter.fontMetrics()
long_text_width = font_metrics.horizontalAdvance(long_text)
self.assertGreater(long_text_width, 25)
self.elabel.setText(long_text)
x = self.elabel.x()
y = self.elabel.y()
w = self.elabel.width()
h = self.elabel.height()
paint_event = QtGui.QPaintEvent(QtGui.QRegion(x, y, w, h))
self.elabel.paintEvent(paint_event)
# PySide2.QtGui.QFontMetrics.elidedText states, "If the string
# text is wider than width , returns an elided version of the
# string (i.e., a string with '…' in it). Otherwise, returns
# the original string."
self.assertEqual(self.elabel._elided_line, '…')
self.assertEqual(self.elabel.text(), long_text)
def test_has_elision_changed_signal(self):
self.assertIsInstance(self.elabel.elision_changed, QtCore.Signal)
def test_elision_changed_signal_emits_on_change_to_is_elided_predicate(self):
mock = unittest.mock.Mock()
self.elabel.elision_changed.connect(mock.method)
# NOTE: This is a bit of a stretch, inducing a paint event
# when the event loop isn't running. Throws a bunch of C++
# sourced text which can't be (easily) caught.
# Induce elision
self.elabel.setFixedWidth(150)
self.assertEqual(self.elabel.width(), 150)
long_text = "This line is definitely going to be more than 150 pixels"
painter = QtGui.QPainter()
font_metrics = painter.fontMetrics()
long_text_width = font_metrics.horizontalAdvance(long_text)
self.assertGreater(long_text_width, 150)
self.elabel.setText(long_text)
self.assertEqual(self.elabel.is_elided, False) # no elide until painting
x = self.elabel.x()
y = self.elabel.y()
w = self.elabel.width()
h = self.elabel.height()
paint_event = QtGui.QPaintEvent(QtGui.QRegion(x, y, w, h))
self.elabel.paintEvent(paint_event)
self.assertEqual(self.elabel.is_elided, True)
mock.method.assert_called_once()
# Remove elision
short_text = "Less than 150"
painter = QtGui.QPainter()
font_metrics = painter.fontMetrics()
short_text_width = font_metrics.horizontalAdvance(short_text)
self.assertLess(short_text_width, 150)
self.elabel.setText(short_text)
self.assertEqual(self.elabel.is_elided, True) # still elided until painting
x = self.elabel.x()
y = self.elabel.y()
w = self.elabel.width()
h = self.elabel.height()
paint_event = QtGui.QPaintEvent(QtGui.QRegion(x, y, w, h))
self.elabel.paintEvent(paint_event)
self.assertEqual(self.elabel.is_elided, False)
self.assertEqual(mock.method.call_count, 2)
You can achieves this through determining the width with QFontMetrics, see this answer.
You would probably want to use or create some algorithm which finds the place to cut quickly, unless doing it in a simple for loop would be sufficient.
simpler solution if you want show QLabel in center in provided area
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
label.minimumSizeHint = lambda self=label: QSize(0, QLabel.minimumSizeHint(self).height() )

Painting using PYQT

I am trying to implement a program like paint in PYQT. I am trying to use the code of Scribble example in PYQT package which can be found in: C:\Python26\Lib\site-packages\PyQt4\examples\widgets. Now regarding this I have 2 questions:
In the Scribble program, when painting, the paint doesn't get updated visually in real-time as you are holding the mouse button and scribbling. I found out that this problem comes from the function drawLineTo from class ScribbleArea, the line:
self.update(QtCore.QRect(self.lastPoint, endPoint).normalized().adjusted(-rad, -rad, +rad, +rad))
Now if I simply replace this line with
self.update()
the problem is solved, but the cursor is not in the exact location the paintin happens.
Do you know what parameters I can add in the self.update() which solves both problems?
I want to open an image as Scribble does and paint on it and then save only the paint with a blank lets say white background (without the original image in the back ground). Can you tell me how to do this?
I would appreciate your answer to either of the questions.
Thanks!
Just learning pyqt, but I modified the code to take care of the cursor mismatch problem (and modified the openImage method to also keep the image size in sync with the window):
#!/usr/bin/env python
#############################################################################
##
## Copyright (C) 2010 Riverbank Computing Limited.
## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
## All rights reserved.
##
## This file is part of the examples of PyQt.
##
## $QT_BEGIN_LICENSE:BSD$
## You may use this file under the terms of the BSD license as follows:
##
## "Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are
## met:
## * Redistributions of source code must retain the above copyright
## notice, this list of conditions and the following disclaimer.
## * Redistributions in binary form must reproduce the above copyright
## notice, this list of conditions and the following disclaimer in
## the documentation and/or other materials provided with the
## distribution.
## * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
## the names of its contributors may be used to endorse or promote
## products derived from this software without specific prior written
## permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
## $QT_END_LICENSE$
##
#############################################################################
# These are only needed for Python v2 but are harmless for Python v3.
import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)
from PyQt4 import QtCore, QtGui
class ScribbleArea(QtGui.QWidget):
"""
this scales the image but it's not good, too many refreshes really mess it up!!!
"""
def __init__(self, parent=None):
super(ScribbleArea, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_StaticContents)
self.modified = False
self.scribbling = False
self.myPenWidth = 1
self.myPenColor = QtCore.Qt.blue
imageSize = QtCore.QSize(500, 500)
# self.image = QtGui.QImage()
self.image = QtGui.QImage(imageSize, QtGui.QImage.Format_RGB32)
self.lastPoint = QtCore.QPoint()
def openImage(self, fileName):
loadedImage = QtGui.QImage()
if not loadedImage.load(fileName):
return False
w = loadedImage.width()
h = loadedImage.height()
self.mainWindow.resize(w, h)
# newSize = loadedImage.size().expandedTo(self.size())
# self.resizeImage(loadedImage, newSize)
self.image = loadedImage
self.modified = False
self.update()
return True
def saveImage(self, fileName, fileFormat):
visibleImage = self.image
self.resizeImage(visibleImage, self.size())
if visibleImage.save(fileName, fileFormat):
self.modified = False
return True
else:
return False
def setPenColor(self, newColor):
self.myPenColor = newColor
def setPenWidth(self, newWidth):
self.myPenWidth = newWidth
def clearImage(self):
self.image.fill(QtGui.qRgb(255, 255, 255))
self.modified = True
self.update()
def mousePressEvent(self, event):
# print "self.image.width() = %d" % self.image.width()
# print "self.image.height() = %d" % self.image.height()
# print "self.image.size() = %s" % self.image.size()
# print "self.size() = %s" % self.size()
# print "event.pos() = %s" % event.pos()
if event.button() == QtCore.Qt.LeftButton:
self.lastPoint = event.pos()
self.scribbling = True
def mouseMoveEvent(self, event):
if (event.buttons() & QtCore.Qt.LeftButton) and self.scribbling:
self.drawLineTo(event.pos())
def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.LeftButton and self.scribbling:
self.drawLineTo(event.pos())
self.scribbling = False
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.drawImage(event.rect(), self.image)
def resizeEvent(self, event):
# print "resize event"
# print "event = %s" % event
# print "event.oldSize() = %s" % event.oldSize()
# print "event.size() = %s" % event.size()
self.resizeImage(self.image, event.size())
# if self.width() > self.image.width() or self.height() > self.image.height():
# newWidth = max(self.width() + 128, self.image.width())
# newHeight = max(self.height() + 128, self.image.height())
# print "newWidth = %d, newHeight = %d" % (newWidth, newHeight)
# self.resizeImage(self.image, QtCore.QSize(newWidth, newHeight))
# self.update()
super(ScribbleArea, self).resizeEvent(event)
def drawLineTo(self, endPoint):
painter = QtGui.QPainter(self.image)
painter.setPen(QtGui.QPen(self.myPenColor, self.myPenWidth,
QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawLine(self.lastPoint, endPoint)
self.modified = True
# rad = self.myPenWidth / 2 + 2
# self.update(QtCore.QRect(self.lastPoint, endPoint).normalized().adjusted(-rad, -rad, +rad, +rad))
self.update()
self.lastPoint = QtCore.QPoint(endPoint)
def resizeImage(self, image, newSize):
if image.size() == newSize:
return
# print "image.size() = %s" % repr(image.size())
# print "newSize = %s" % newSize
# this resizes the canvas without resampling the image
newImage = QtGui.QImage(newSize, QtGui.QImage.Format_RGB32)
newImage.fill(QtGui.qRgb(255, 255, 255))
painter = QtGui.QPainter(newImage)
painter.drawImage(QtCore.QPoint(0, 0), image)
## this resampled the image but it gets messed up with so many events...
## painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True)
## painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing, True)
#
# newImage = QtGui.QImage(newSize, QtGui.QImage.Format_RGB32)
# newImage.fill(QtGui.qRgb(255, 255, 255))
# painter = QtGui.QPainter(newImage)
# srcRect = QtCore.QRect(QtCore.QPoint(0,0), image.size())
# dstRect = QtCore.QRect(QtCore.QPoint(0,0), newSize)
## print "srcRect = %s" % srcRect
## print "dstRect = %s" % dstRect
# painter.drawImage(dstRect, image, srcRect)
self.image = newImage
def print_(self):
printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
printDialog = QtGui.QPrintDialog(printer, self)
if printDialog.exec_() == QtGui.QDialog.Accepted:
painter = QtGui.QPainter(printer)
rect = painter.viewport()
size = self.image.size()
size.scale(rect.size(), QtCore.Qt.KeepAspectRatio)
painter.setViewport(rect.x(), rect.y(), size.width(), size.height())
painter.setWindow(self.image.rect())
painter.drawImage(0, 0, self.image)
painter.end()
def isModified(self):
return self.modified
def penColor(self):
return self.myPenColor
def penWidth(self):
return self.myPenWidth
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.saveAsActs = []
self.scribbleArea = ScribbleArea(self)
self.scribbleArea.clearImage()
self.scribbleArea.mainWindow = self # maybe not using this?
self.setCentralWidget(self.scribbleArea)
self.createActions()
self.createMenus()
self.setWindowTitle("Scribble")
self.resize(500, 500)
def closeEvent(self, event):
if self.maybeSave():
event.accept()
else:
event.ignore()
def open(self):
if self.maybeSave():
fileName = QtGui.QFileDialog.getOpenFileName(self, "Open File",
QtCore.QDir.currentPath())
if fileName:
self.scribbleArea.openImage(fileName)
def save(self):
action = self.sender()
fileFormat = action.data()
self.saveFile(fileFormat)
def penColor(self):
newColor = QtGui.QColorDialog.getColor(self.scribbleArea.penColor())
if newColor.isValid():
self.scribbleArea.setPenColor(newColor)
def penWidth(self):
newWidth, ok = QtGui.QInputDialog.getInteger(self, "Scribble",
"Select pen width:", self.scribbleArea.penWidth(), 1, 50, 1)
if ok:
self.scribbleArea.setPenWidth(newWidth)
def about(self):
QtGui.QMessageBox.about(self, "About Scribble",
"<p>The <b>Scribble</b> example shows how to use "
"QMainWindow as the base widget for an application, and how "
"to reimplement some of QWidget's event handlers to receive "
"the events generated for the application's widgets:</p>"
"<p> We reimplement the mouse event handlers to facilitate "
"drawing, the paint event handler to update the application "
"and the resize event handler to optimize the application's "
"appearance. In addition we reimplement the close event "
"handler to intercept the close events before terminating "
"the application.</p>"
"<p> The example also demonstrates how to use QPainter to "
"draw an image in real time, as well as to repaint "
"widgets.</p>")
def createActions(self):
self.openAct = QtGui.QAction("&Open...", self, shortcut="Ctrl+O",
triggered=self.open)
for format in QtGui.QImageWriter.supportedImageFormats():
format = str(format)
text = format.upper() + "..."
action = QtGui.QAction(text, self, triggered=self.save)
action.setData(format)
self.saveAsActs.append(action)
self.printAct = QtGui.QAction("&Print...", self,
triggered=self.scribbleArea.print_)
self.exitAct = QtGui.QAction("E&xit", self, shortcut="Ctrl+Q",
triggered=self.close)
self.penColorAct = QtGui.QAction("&Pen Color...", self,
triggered=self.penColor)
self.penWidthAct = QtGui.QAction("Pen &Width...", self,
triggered=self.penWidth)
self.clearScreenAct = QtGui.QAction("&Clear Screen", self,
shortcut="Ctrl+L", triggered=self.scribbleArea.clearImage)
self.aboutAct = QtGui.QAction("&About", self, triggered=self.about)
self.aboutQtAct = QtGui.QAction("About &Qt", self,
triggered=QtGui.qApp.aboutQt)
def createMenus(self):
self.saveAsMenu = QtGui.QMenu("&Save As", self)
for action in self.saveAsActs:
self.saveAsMenu.addAction(action)
fileMenu = QtGui.QMenu("&File", self)
fileMenu.addAction(self.openAct)
fileMenu.addMenu(self.saveAsMenu)
fileMenu.addAction(self.printAct)
fileMenu.addSeparator()
fileMenu.addAction(self.exitAct)
optionMenu = QtGui.QMenu("&Options", self)
optionMenu.addAction(self.penColorAct)
optionMenu.addAction(self.penWidthAct)
optionMenu.addSeparator()
optionMenu.addAction(self.clearScreenAct)
helpMenu = QtGui.QMenu("&Help", self)
helpMenu.addAction(self.aboutAct)
helpMenu.addAction(self.aboutQtAct)
self.menuBar().addMenu(fileMenu)
self.menuBar().addMenu(optionMenu)
self.menuBar().addMenu(helpMenu)
def maybeSave(self):
if self.scribbleArea.isModified():
ret = QtGui.QMessageBox.warning(self, "Scribble",
"The image has been modified.\n"
"Do you want to save your changes?",
QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard |
QtGui.QMessageBox.Cancel)
if ret == QtGui.QMessageBox.Save:
return self.saveFile('png')
elif ret == QtGui.QMessageBox.Cancel:
return False
return True
def saveFile(self, fileFormat):
initialPath = QtCore.QDir.currentPath() + '/untitled.' + fileFormat
fileName = QtGui.QFileDialog.getSaveFileName(self, "Save As",
initialPath,
"%s Files (*.%s);;All Files (*)" % (fileFormat.upper(), fileFormat))
if fileName:
return self.scribbleArea.saveImage(fileName, fileFormat)
return False
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

Resources