Source code for qpageview.widgetoverlay

# -*- coding: utf-8 -*-
#
# This file is part of the qpageview package.
#
# Copyright (c) 2019 - 2019 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# See http://www.gnu.org/licenses/ for more information.

"""
View mixin class to display QWidgets on top of a Page.
"""

import collections

from PyQt5.QtCore import QPoint, QRect, Qt

from . import constants


OverlayData = collections.namedtuple("OverlayData", "page point rect alignment")


[docs]class WidgetOverlayViewMixin: """Mixin class to add widgets to be displayed on top of pages. Widgets are added using addWidget(), and become children of the viewport. This class adds the following instance attribute: deleteUnusedOverlayWidgets = True If True, unused widgets are deleted using QObject.deleteLater(). Otherwise, only the parent is set to None. A widget becomes unused if the Page it was added to disappears from the page layout. """ deleteUnusedOverlayWidgets = True def __init__(self, parent=None): self._widgets = {} super().__init__(parent)
[docs] def addWidget(self, widget, page, where=None, alignment=None): """Add widget to be displayed on top of page. The widget becomes a child of the viewport. The `where` argument can be a QPoint or a QRect. If a rect is given, the widget is resized to occupy that rectangle. The rect should be in page coordinates. When the zoom factor is changed, the widget will be resized. If a point is given, the widget is not resized and aligned on the point using the specified alignment (top-left if None). If where is None, the widget occupies the whole page. You can also use this method to change the page or rect for a widget that already has been added. """ if not alignment: alignment = Qt.AlignTop | Qt.AlignLeft # translate rect to original coordinates rect = None point = None if where is not None: if isinstance(where, QPoint): point = page.mapFromPage().point(where) else: rect = page.mapFromPage().rect(where) else: rect = page.pageRect() widget.setParent(self.viewport()) self._widgets[widget] = OverlayData(page, point, rect, alignment) self._updateWidget(widget) widget.setVisible(page in set(self.visiblePages()))
[docs] def removeWidget(self, widget): """Remove the widget. The widget is not deleted, but its parent is set to None. """ try: del self._widgets[widget] except KeyError: pass else: widget.setParent(None)
[docs] def widgets(self, page=None): """Yield all widgets (for the Page if given).""" if page: for widget, d in self._widgets.items(): if d.page is page: yield widget else: for widget in self._widgets: yield widget
[docs] def removeWidgets(self, page=None): """Remove all widgets (for the Page if given). The widget are not deleted, but their parent is set to None. """ if page: for widget in list(self.widgets(page)): widget.setParent(None) del self._widgets[widget] else: for widget in self._widgets: widget.setParent(None) self._widgets.clear()
def _updateWidget(self, widget): """Internal. Updates size and position of the specified widget.""" d = self._widgets[widget] pos = self.layoutPosition() + d.page.pos() if d.point: point = pos + d.page.mapToPage().point(d.point) geom = util.alignrect(widget.geometry(), point, d.alignment) else: # d.rect: rect = d.page.mapToPage().rect(d.rect) geom = rect.translated(pos) widget.setGeometry(geom) def _updateWidgets(self): """Internal. Updates size and position of the widgets.""" pages = set(self.visiblePages()) remove = [] for widget, d in self._widgets.items(): if d.page in self.pageLayout(): self._updateWidget(widget) widget.setVisible(d.page in pages) else: remove.append(widget) # remove widgets that are not used anymore for w in remove: w.setParent(None) del self._widgets[w] if self.deleteUnusedOverlayWidgets: for w in remove: w.deleteLater()
[docs] def updatePageLayout(self, lazy=False): """Reimplemented to update the size and position of the widgets.""" super().updatePageLayout(lazy) self._updateWidgets()
[docs] def scrollContentsBy(self, dx, dy): """Reimplemented to scroll the page widgets along with the layout.""" super().scrollContentsBy(dx, dy) d = QPoint(dx, dy) for widget in self._widgets.keys(): widget.move(widget.pos() + d)
[docs] def resizeEvent(self, ev): """Reimplemented to keep page widgets in the right position.""" super().resizeEvent(ev) # in fixed scale mode, call _updateWidgets(). In other view modes, # updatePageLayout() is called which calls _updateWidgets() anyway. if self.viewMode() == constants.FixedScale: self._updateWidgets()