1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
# -*- 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")
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)
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()))
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)
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
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()
def updatePageLayout(self, lazy=False):
"""Reimplemented to update the size and position of the widgets."""
super().updatePageLayout(lazy)
self._updateWidgets()
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)
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()
|