1
# This file is part of the qpopplerview package.
3
# Copyright (c) 2010, 2011 by Wilbert Berendsen
5
# This program is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU General Public License
7
# as published by the Free Software Foundation; either version 2
8
# of the License, or (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# See http://www.gnu.org/licenses/ for more information.
22
Manages and positions a group of Page instances.
27
from PyQt4.QtCore import QObject, QPoint, QRect, QSize, Qt, pyqtSignal
39
class AbstractLayout(QObject):
40
"""Manages page.Page instances with a list-like api.
42
You can iterate over the layout itself, which yields all Page instances.
43
You can also iterate over pages(), which only yields the Page instances
48
redraw = pyqtSignal(QRect)
49
changed = pyqtSignal()
50
scaleChanged = pyqtSignal(float)
53
super(AbstractLayout, self).__init__()
59
self._scaleChanged = False
63
"""(Internal) Makes the page have ourselves as layout."""
65
page.layout().remove(page)
66
page._layout = weakref.ref(self)
69
def disown(self, page):
70
"""(Internal) Removes ourselves as owner of the page."""
71
page._layout = lambda: None
73
def append(self, page):
75
self._pages.append(page)
77
def insert(self, position, page):
79
self._pages.insert(position, page)
81
def extend(self, pages):
85
def remove(self, page):
86
self._pages.remove(page)
89
def pop(self, index=None):
90
page = self._pages.pop(index)
98
return len(self._pages)
101
return len(self._pages)
103
def __nonzero__(self):
106
def __contains__(self, page):
107
return page in self._pages
109
def __getitem__(self, item):
110
return self._pages[item]
112
def __delitem__(self, item):
113
if isinstance(item, slice):
114
for page in self._pages[item]:
117
self.disown(self._pages[item])
118
del self._pages[item]
120
def __setitem__(self, item, new):
121
if isinstance(item, slice):
122
old = self._pages[item]
123
self._pages[item] = new
124
for page in self._pages[item]:
129
self.disown(self._pages[item])
130
self._pages[item] = new
133
def index(self, page):
134
"""Returns the index at which the given Page can be found in our Layout."""
135
return self._pages.index(page)
137
def setSize(self, size):
138
"""Sets our size. Mainly done after layouting."""
142
"""Returns our size as QSize()."""
146
"""Returns our width."""
147
return self._size.width()
150
"""Returns our height."""
151
return self._size.height()
153
def setDPI(self, xdpi, ydpi=None):
154
"""Sets our DPI in X and Y direction. If Y isn't given, uses the X value."""
155
self._dpi = xdpi, ydpi or xdpi
160
"""Returns our DPI as a tuple(XDPI, YDPI)."""
164
"""Returns the scale (1.0 == 100%)."""
167
def setScale(self, scale):
168
"""Sets the scale (1.0 == 100%) of all our Pages."""
169
if scale != self._scale:
173
self._scaleChanged = True
175
def setPageWidth(self, width, sameScale=True):
176
"""Sets the width of all pages.
178
If sameScale is True (default), the largest page will be scaled to the given
179
width (minus margin). All pages will then be scaled to that scale.
180
If sameScale is False all pages will be scaled individually to the same width.
183
if sameScale and any(self.pages()):
184
self.setScale(self.widest().scaleForWidth(width))
189
def setPageHeight(self, height, sameScale=True):
190
"""Sets the height of all pages.
192
If sameScale is True (default), the largest page will be scaled to the given
193
height (minus margin). All pages will then be scaled to that scale.
194
If sameScale is False all pages will be scaled individually to the same height.
197
if sameScale and any(self.pages()):
198
self.setScale(self.heighest().scaleForWidth(height))
201
page.setHeight(height)
203
def setMargin(self, margin):
204
"""Sets the margin around the pages in pixels."""
205
self._margin = margin
208
"""Returns the margin around the pages in pixels."""
211
def setSpacing(self, spacing):
212
"""Sets the space between the pages in pixels."""
213
self._spacing = spacing
216
"""Returns the space between the pages in pixels."""
219
def fit(self, size, mode):
220
"""Fits the layout in the given ViewMode."""
221
if mode and any(self.pages()):
224
scales.append(self.widest().scaleForWidth(size.width() - self.margin() * 2))
226
scales.append(self.heighest().scaleForHeight(size.height() - self.margin() * 2))
227
self.setScale(min(scales))
230
"""Performs the layouting (positions the Pages and adjusts our size)."""
232
if self._scaleChanged:
233
self.scaleChanged.emit(self._scale)
234
self._scaleChanged = False
238
"""This is called by update().
240
You must implement this method to position the Pages and adjust our size.
241
See Layout for a possible implementation.
246
def updatePage(self, page):
247
"""Called by the Page when an image has been generated."""
248
self.redraw.emit(page.rect())
250
def page(self, document, pageNumber):
251
"""Returns the page (visible or not) from a Poppler.Document with page number.
253
Returns None if that page is not available.
256
# Specific layouts may use faster algorithms to find the page.
258
page = self[pageNumber]
262
if page.document() == document:
265
if page.document() == document and page.pageNumber() == pageNumber:
269
"""Yields our pages that are visible()."""
274
def pageAt(self, point):
275
"""Returns the page that contains the given QPoint."""
276
# Specific layouts may use faster algorithms to find the page.
277
for page in self.pages():
278
if page.rect().contains(point):
281
def pagesAt(self, rect):
282
"""Yields the pages touched by the given QRect."""
283
# Specific layouts may use faster algorithms to find the pages.
284
for page in self.pages():
285
if page.rect().intersects(rect):
288
def linkAt(self, point):
289
"""Returns (page, link) if pos points to a Poppler.Link in a Page, else (None, None)."""
290
page = self.pageAt(point)
292
links = page.linksAt(point)
294
return page, links[0]
298
"""Returns the widest visible page (in its natural page size)."""
299
pages = list(self.pages())
301
return max(pages, key = lambda p: p.pageSize().width())
304
"""Returns the heighest visible page (in its natural page size)."""
305
pages = list(self.pages())
307
return max(pages, key = lambda p: p.pageSize().height())
310
"""Returns the width of the widest visible page."""
312
return page.width() if page else 0
315
"""Returns the height of the heighest visible page."""
316
page = self.heighest()
317
return page.height() if page else 0
319
def load(self, document):
320
"""Convenience mehod to load all the pages of the given Poppler.Document using page.Page()."""
322
for num in range(document.numPages()):
323
p = page.Page(document, num)
324
p.setScale(self._scale)
328
class Layout(AbstractLayout):
329
"""A basic layout that shows pages from right to left or top to bottom."""
331
super(Layout, self).__init__()
332
self._orientation = Qt.Vertical
334
def setOrientation(self, orientation):
335
"""Sets our orientation to either Qt.Vertical or Qt.Horizontal."""
336
self._orientation = orientation
338
def orientation(self):
339
"""Returns our orientation (either Qt.Vertical or Qt.Horizontal)."""
340
return self._orientation
343
"""Orders our pages."""
344
if self._orientation == Qt.Vertical:
345
width = self.maxWidth() + self._margin * 2
347
for page in self.pages():
348
page.setPos(QPoint((width - page.width()) / 2, top))
349
top += page.height() + self._spacing
350
top += self._margin - self._spacing
351
self.setSize(QSize(width, top))
353
height = self.maxHeight() + self._margin * 2
355
for page in self.pages():
356
page.setPos(QPoint(left, (height - page.height()) / 2))
357
left += page.width() + self._spacing
358
left += self._margin - self._spacing
359
self.setSize(QSize(left, height))