diff --git a/soitool/inline_editable_soi_view.py b/soitool/inline_editable_soi_view.py index 5465d93ca3f0ad05387c8aa7d9eafd63f3ad6fc8..dc5ba49e0a8d997eb49272d8d6b2165fa6f080d7 100644 --- a/soitool/inline_editable_soi_view.py +++ b/soitool/inline_editable_soi_view.py @@ -10,22 +10,53 @@ from PySide2.QtWidgets import ( QGraphicsRectItem, QGraphicsProxyWidget, ) -from PySide2.QtGui import QFont, QPixmap, QBrush, QPalette, QPainter, QTransform +from PySide2.QtGui import ( + QFont, + QPixmap, + QBrush, + QPalette, + QPainter, +) from PySide2.QtPrintSupport import QPrinter from soitool.soi import ModuleLargerThanBinError from soitool.dialog_wrappers import exec_warning_dialog from soitool.serialize_export_import_soi import generate_soi_filename -class ProxyLabelWithConditionalPainting(QGraphicsProxyWidget): + +class ProxyLabelWithCustomQPrintText(QGraphicsProxyWidget): + """QGraphicsItem that prints a custom text when printed onto QPrint. + + Useful to have a piece of text be painted differently in a QGraphicsScene + depending on whether it's drawn to QPrint or not. + + Note that this class doesn't have to use a QLabel. It was done to KISS. + + ## How it works + + When the QGrahpicsScene wants to render it's items it first uses the + "bounding rects" of it's items to figure out which of them needs to be + redrawn. It then redraws items using their `paint` functions. By overriding + `paint` we can control how our item is drawn. One of the parameters to the + `paint` function is the QPainter that is being used, which we can use to + print custom text onto QPrinter + + Parameters + ---------- + default_text : str + Text to be drawn for all QPainters except QPrint + printing_text : str + Text to be drawn if the QPainters QPrint + """ + def __init__(self, default_text, printing_text): - super(ProxyLabelWithConditionalPainting, self).__init__() + super(ProxyLabelWithCustomQPrintText, self).__init__() self.default_text = default_text self.printing_text = printing_text # self.boundingRect is updated at the end of this function, so this # default value is in practice never used. - self.bounding_rect = QRectF(0,0,0,0) + self.bounding_rect = QRectF(0, 0, 0, 0) self.label = QLabel(self.default_text) self.setWidget(self.label) @@ -46,7 +77,7 @@ class ProxyLabelWithConditionalPainting(QGraphicsProxyWidget): Because of this we're returning a rectangle (starting at 0,0) that encapsulates the self.widget(), seen from this items local coordinate system. This class purposefully lets self.widget() have different - content depending on the QPaintDevice, so we give a bounding rect that + content depending on the QPainter, so we give a bounding rect that encapsulates the largest content. Returns @@ -55,8 +86,12 @@ class ProxyLabelWithConditionalPainting(QGraphicsProxyWidget): Bounding rect that enpasulates both alternative contents of self.widget() """ - bounding_rect_default_text = self.label.fontMetrics().boundingRect(self.default_text) - bounding_rect_printing_text = self.label.fontMetrics().boundingRect(self.printing_text) + bounding_rect_default_text = self.label.fontMetrics().boundingRect( + self.default_text + ) + bounding_rect_printing_text = self.label.fontMetrics().boundingRect( + self.printing_text + ) largest_width = max( bounding_rect_default_text.width(), bounding_rect_printing_text.width(), @@ -65,28 +100,47 @@ class ProxyLabelWithConditionalPainting(QGraphicsProxyWidget): bounding_rect_default_text.height(), bounding_rect_printing_text.height(), ) - return QRectF(0,0,largest_width, largest_height) + return QRectF(0, 0, largest_width, largest_height) def paint(self, painter, option, widget): - """ + """Overridden to paint text depending on `painter` parameter. + Source: https://doc.qt.io/qt-5/qgraphicsitem.html#paint + + Parameter + --------- + painter : QPainter + QPainter that is painting the item. Used to determine which text + to draw. + option : QStyleOptionGraphicsItem + Passed on to superclass implementation. See source. + widget : QWidget + Passed on to superclass implementation. See source. """ if isinstance(painter.device(), QPrinter): self.label.setText(self.printing_text) else: self.label.setText(self.default_text) - # TODO TODO TODO https://stackoverflow.com/a/49910888/3545896 - - # See: https://stackoverflow.com/a/47037607/3545896 + # From Qt docs: "Prepares the item for a geometry change. Call this + # function before changing the bounding rect of an item to keep + # QGraphicsScene's index up to date." + # https://doc.qt.io/qt-5/qgraphicsitem.html#prepareGeometryChange self.prepareGeometryChange() + + # QLabel doesn't adjust it size automatically, so do it here + # https://stackoverflow.com/a/47037607/3545896 self.label.adjustSize() - super(ProxyLabelWithConditionalPainting, self).paint(painter, option, widget) + + # Let super handle the actual painting + super(ProxyLabelWithCustomQPrintText, self).paint( + painter, option, widget + ) def boundingRect(self): - """Give QRectF that bounds this item. Overriden. + """Give QRectF that bounds this item. Overridden. - Overriden to provide custom bounding rect. Custom bounding rect is + Overridden to provide custom bounding rect. Custom bounding rect is needed because this item has two different contents depending on where it is drawn, which Qt does not respect (or understand) out of the box. Overrides this: https://doc.qt.io/qt-5/qgraphicsitem.html#boundingRect @@ -109,7 +163,13 @@ class ProxyLabelWithConditionalPainting(QGraphicsProxyWidget): class InlineEditableSOIView(QScrollArea): - """Widget som kan byttes ut med view, edit etc.""" + """Widget that allows for "inline" editing of an SOI. Also prints to PDF. + + Paramters + --------- + soi : soitool.SOI + SOI to edit. + """ def is_widget_in_scene(self, widget): """Indicate wether given widget already has a proxy in the scene.""" @@ -156,7 +216,13 @@ class InlineEditableSOIView(QScrollArea): self.number_of_pages = 1 self.proxies = set() - # NOTE: These variables are only included to support PDF output of the widget. When rendering self.scene onto a QPrinter this widget will use these variables to indicate that "copy number self.copy_current is being printed, out of a total of self.copy_total copies". By looping self.copy_total times, updating self.copy_current each time and rendering onto a QPrinter it is possible to print multiple copies of the SOI, each marked with a copy number. + # NOTE: These variables are only included to support PDF output of the + # widget. When rendering self.scene onto a QPrinter this widget will + # use these variables to indicate that "copy number self.copy_current + # is being printed, out of a total of self.copy_total copies". By + # looping self.copy_total times, updating self.copy_current each time + # and rendering onto a QPrinter it is possible to print multiple + # copies of the SOI, each marked with a copy number. self.copy_current = 1 self.copy_total = 3 @@ -229,7 +295,7 @@ class InlineEditableSOIView(QScrollArea): ) for i in range(self.copy_total): - + # Update copy number and redraw pages so that it is reflected # in the scene self.copy_current = i + 1 @@ -357,9 +423,13 @@ class InlineEditableSOIView(QScrollArea): # NOTE: See __init__ for explanation on self.copy_current and # self.copy_total - proxy = ProxyLabelWithConditionalPainting("1 av N", f"{self.copy_current} av {self.copy_total}") + proxy = ProxyLabelWithCustomQPrintText( + "1 av N", f"{self.copy_current} av {self.copy_total}" + ) proxy.label.setStyleSheet("background-color: rgba(0,0,0,0%)") proxy.label.setFont(QFont("Times New Roman", 40)) + + # need to call this when label of proxy changes. See function docstring proxy.update_bounding_rect() # Source: https://stackoverflow.com/a/8638114/3545896 @@ -369,7 +439,7 @@ class InlineEditableSOIView(QScrollArea): ) # NOTE that this position is only correct for the default text. During - # PDF printing the ProxyLabelWithConditionalPainting class will paint + # PDF printing the ProxyLabelWithCustomQPrintText class will paint # itself with a different text, and thus not be perfectly centered. proxy.setPos(x + 18, copy_number_y_pos + label_width / 2) proxy.setRotation(-90)