~ubuntu-branches/ubuntu/vivid/frescobaldi/vivid

« back to all changes in this revision

Viewing changes to frescobaldi_app/qpopplerview/layout.py

  • Committer: Package Import Robot
  • Author(s): Ryan Kavanagh
  • Date: 2012-01-03 16:20:11 UTC
  • mfrom: (1.4.1)
  • Revision ID: package-import@ubuntu.com-20120103162011-tsjkwl4sntwmprea
Tags: 2.0.0-1
* New upstream release 
* Drop the following uneeded patches:
  + 01_checkmodules_no_python-kde4_build-dep.diff
  + 02_no_pyc.diff
  + 04_no_binary_lilypond_upgrades.diff
* Needs new dependency python-poppler-qt4
* Update debian/watch for new download path
* Update copyright file with new holders and years

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# This file is part of the qpopplerview package.
 
2
#
 
3
# Copyright (c) 2010, 2011 by Wilbert Berendsen
 
4
#
 
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.
 
9
#
 
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.
 
14
#
 
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.
 
19
 
 
20
 
 
21
"""
 
22
Manages and positions a group of Page instances.
 
23
"""
 
24
 
 
25
import weakref
 
26
 
 
27
from PyQt4.QtCore import QObject, QPoint, QRect, QSize, Qt, pyqtSignal
 
28
 
 
29
from . import page
 
30
from . import (
 
31
    # viewModes:
 
32
    FixedScale,
 
33
    FitWidth,
 
34
    FitHeight,
 
35
    FitBoth,
 
36
)
 
37
 
 
38
 
 
39
class AbstractLayout(QObject):
 
40
    """Manages page.Page instances with a list-like api.
 
41
    
 
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
 
44
    that are visible().
 
45
    
 
46
    """
 
47
    
 
48
    redraw = pyqtSignal(QRect)
 
49
    changed = pyqtSignal()
 
50
    scaleChanged = pyqtSignal(float)
 
51
    
 
52
    def __init__(self):
 
53
        super(AbstractLayout, self).__init__()
 
54
        self._pages = []
 
55
        self._size = QSize()
 
56
        self._margin = 4
 
57
        self._spacing = 8
 
58
        self._scale = 1.0
 
59
        self._scaleChanged = False
 
60
        self._dpi = (72, 72)
 
61
        
 
62
    def own(self, page):
 
63
        """(Internal) Makes the page have ourselves as layout."""
 
64
        if page.layout():
 
65
            page.layout().remove(page)
 
66
        page._layout = weakref.ref(self)
 
67
        page.computeSize()
 
68
    
 
69
    def disown(self, page):
 
70
        """(Internal) Removes ourselves as owner of the page."""
 
71
        page._layout = lambda: None
 
72
        
 
73
    def append(self, page):
 
74
        self.own(page)
 
75
        self._pages.append(page)
 
76
        
 
77
    def insert(self, position, page):
 
78
        self.own(page)
 
79
        self._pages.insert(position, page)
 
80
    
 
81
    def extend(self, pages):
 
82
        for page in pages:
 
83
            self.append(page)
 
84
            
 
85
    def remove(self, page):
 
86
        self._pages.remove(page)
 
87
        self.disown(page)
 
88
    
 
89
    def pop(self, index=None):
 
90
        page = self._pages.pop(index)
 
91
        self.disown(page)
 
92
        return page
 
93
    
 
94
    def clear(self):
 
95
        del self[:]
 
96
    
 
97
    def count(self):
 
98
        return len(self._pages)
 
99
        
 
100
    def __len__(self):
 
101
        return len(self._pages)
 
102
    
 
103
    def __nonzero__(self):
 
104
        return True
 
105
    
 
106
    def __contains__(self, page):
 
107
        return page in self._pages
 
108
    
 
109
    def __getitem__(self, item):
 
110
        return self._pages[item]
 
111
        
 
112
    def __delitem__(self, item):
 
113
        if isinstance(item, slice):
 
114
            for page in self._pages[item]:
 
115
                self.disown(page)
 
116
        else:
 
117
            self.disown(self._pages[item])
 
118
        del self._pages[item]
 
119
    
 
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]:
 
125
                self.own(page)
 
126
            for page in old:
 
127
                self.disown(page)
 
128
        else:
 
129
            self.disown(self._pages[item])
 
130
            self._pages[item] = new
 
131
            self.own(new)
 
132
    
 
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)
 
136
        
 
137
    def setSize(self, size):
 
138
        """Sets our size. Mainly done after layouting."""
 
139
        self._size = size
 
140
        
 
141
    def size(self):
 
142
        """Returns our size as QSize()."""
 
143
        return self._size
 
144
    
 
145
    def width(self):
 
146
        """Returns our width."""
 
147
        return self._size.width()
 
148
    
 
149
    def height(self):
 
150
        """Returns our height."""
 
151
        return self._size.height()
 
152
    
 
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
 
156
        for page in self:
 
157
            page.computeSize()
 
158
    
 
159
    def dpi(self):
 
160
        """Returns our DPI as a tuple(XDPI, YDPI)."""
 
161
        return self._dpi
 
162
        
 
163
    def scale(self):
 
164
        """Returns the scale (1.0 == 100%)."""
 
165
        return self._scale
 
166
    
 
167
    def setScale(self, scale):
 
168
        """Sets the scale (1.0 == 100%) of all our Pages."""
 
169
        if scale != self._scale:
 
170
            self._scale = scale
 
171
            for page in self:
 
172
                page.setScale(scale)
 
173
            self._scaleChanged = True
 
174
    
 
175
    def setPageWidth(self, width, sameScale=True):
 
176
        """Sets the width of all pages.
 
177
        
 
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.
 
181
        
 
182
        """
 
183
        if sameScale and any(self.pages()):
 
184
            self.setScale(self.widest().scaleForWidth(width))
 
185
        else:
 
186
            for page in self:
 
187
                page.setWidth(width)
 
188
    
 
189
    def setPageHeight(self, height, sameScale=True):
 
190
        """Sets the height of all pages.
 
191
        
 
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.
 
195
        
 
196
        """
 
197
        if sameScale and any(self.pages()):
 
198
            self.setScale(self.heighest().scaleForWidth(height))
 
199
        else:
 
200
            for page in self:
 
201
                page.setHeight(height)
 
202
            
 
203
    def setMargin(self, margin):
 
204
        """Sets the margin around the pages in pixels."""
 
205
        self._margin = margin
 
206
        
 
207
    def margin(self):
 
208
        """Returns the margin around the pages in pixels."""
 
209
        return self._margin
 
210
        
 
211
    def setSpacing(self, spacing):
 
212
        """Sets the space between the pages in pixels."""
 
213
        self._spacing = spacing
 
214
        
 
215
    def spacing(self):
 
216
        """Returns the space between the pages in pixels."""
 
217
        return self._spacing
 
218
        
 
219
    def fit(self, size, mode):
 
220
        """Fits the layout in the given ViewMode."""
 
221
        if mode and any(self.pages()):
 
222
            scales = []
 
223
            if mode & FitWidth:
 
224
                scales.append(self.widest().scaleForWidth(size.width() - self.margin() * 2))
 
225
            if mode & FitHeight:
 
226
                scales.append(self.heighest().scaleForHeight(size.height() - self.margin() * 2))
 
227
            self.setScale(min(scales))
 
228
        
 
229
    def update(self):
 
230
        """Performs the layouting (positions the Pages and adjusts our size)."""
 
231
        self.reLayout()
 
232
        if self._scaleChanged:
 
233
            self.scaleChanged.emit(self._scale)
 
234
            self._scaleChanged = False
 
235
        self.changed.emit()
 
236
        
 
237
    def reLayout(self):
 
238
        """This is called by update().
 
239
        
 
240
        You must implement this method to position the Pages and adjust our size.
 
241
        See Layout for a possible implementation.
 
242
        
 
243
        """
 
244
        pass
 
245
    
 
246
    def updatePage(self, page):
 
247
        """Called by the Page when an image has been generated."""
 
248
        self.redraw.emit(page.rect())
 
249
        
 
250
    def page(self, document, pageNumber):
 
251
        """Returns the page (visible or not) from a Poppler.Document with page number.
 
252
        
 
253
        Returns None if that page is not available.
 
254
        
 
255
        """
 
256
        # Specific layouts may use faster algorithms to find the page.
 
257
        try:
 
258
            page = self[pageNumber]
 
259
        except IndexError:
 
260
            pass
 
261
        else:
 
262
            if page.document() == document:
 
263
                return page
 
264
        for page in self:
 
265
            if page.document() == document and page.pageNumber() == pageNumber:
 
266
                return page
 
267
    
 
268
    def pages(self):
 
269
        """Yields our pages that are visible()."""
 
270
        for page in self:
 
271
            if page.visible():
 
272
                yield page
 
273
        
 
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):
 
279
                return page
 
280
    
 
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):
 
286
                yield page
 
287
        
 
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)
 
291
        if page:
 
292
            links = page.linksAt(point)
 
293
            if links:
 
294
                return page, links[0]
 
295
        return None, None
 
296
        
 
297
    def widest(self):
 
298
        """Returns the widest visible page (in its natural page size)."""
 
299
        pages = list(self.pages())
 
300
        if pages:
 
301
            return max(pages, key = lambda p: p.pageSize().width())
 
302
            
 
303
    def heighest(self):
 
304
        """Returns the heighest visible page (in its natural page size)."""
 
305
        pages = list(self.pages())
 
306
        if pages:
 
307
            return max(pages, key = lambda p: p.pageSize().height())
 
308
    
 
309
    def maxWidth(self):
 
310
        """Returns the width of the widest visible page."""
 
311
        page = self.widest()
 
312
        return page.width() if page else 0
 
313
        
 
314
    def maxHeight(self):
 
315
        """Returns the height of the heighest visible page."""
 
316
        page = self.heighest()
 
317
        return page.height() if page else 0
 
318
        
 
319
    def load(self, document):
 
320
        """Convenience mehod to load all the pages of the given Poppler.Document using page.Page()."""
 
321
        self.clear()
 
322
        for num in range(document.numPages()):
 
323
            p = page.Page(document, num)
 
324
            p.setScale(self._scale)
 
325
            self.append(p)
 
326
 
 
327
 
 
328
class Layout(AbstractLayout):
 
329
    """A basic layout that shows pages from right to left or top to bottom."""
 
330
    def __init__(self):
 
331
        super(Layout, self).__init__()
 
332
        self._orientation = Qt.Vertical
 
333
        
 
334
    def setOrientation(self, orientation):
 
335
        """Sets our orientation to either Qt.Vertical or Qt.Horizontal."""
 
336
        self._orientation = orientation
 
337
        
 
338
    def orientation(self):
 
339
        """Returns our orientation (either Qt.Vertical or Qt.Horizontal)."""
 
340
        return self._orientation
 
341
    
 
342
    def reLayout(self):
 
343
        """Orders our pages."""
 
344
        if self._orientation == Qt.Vertical:
 
345
            width = self.maxWidth() + self._margin * 2
 
346
            top = self._margin
 
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))
 
352
        else:
 
353
            height = self.maxHeight() + self._margin * 2
 
354
            left = self._margin
 
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))
 
360
            
 
361