1 /* This file is part of the KDE project
2  * Copyright (C) 2006, 2009 Thomas Zander <zander@kde.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #include "KWViewModeNormal.h"
21 #include "KWPageManager.h"
22 #include "KWPage.h"
23 #include "KWView.h"
24 #include <KoViewConverter.h>
25 
26 #include <WordsDebug.h>
27 
28 #define GAP 20
29 
KWViewModeNormal()30 KWViewModeNormal::KWViewModeNormal()
31     : m_pageSpreadMode(false)
32 {
33 }
34 
mapExposedRects(const QRectF & viewRect,KoViewConverter * viewConverter) const35 QVector<KWViewMode::ViewMap> KWViewModeNormal::mapExposedRects(const QRectF &viewRect, KoViewConverter *viewConverter) const
36 {
37     QVector<ViewMap> answer;
38     if (!viewConverter) return answer;
39 
40 #if 1
41     if (m_pageTops.isEmpty())
42         return answer;
43     KWPage page  = m_pageManager->begin();
44     const int pageOffset = page.pageNumber();
45 
46     // Perform a binary search for page-index using our m_pageTops cache.
47     int begin = 0;
48     int end = m_pageTops.count() - 1;
49     int index = 0;
50     const qreal value = viewConverter->viewToDocument(viewRect.topLeft()).y();
51     if (m_pageTops.value(end) <= value) { // check extremes. Only end is needed since begin is zero.
52         begin = end;
53         index = end;
54     }
55     while (end - begin > 1) {
56         index = begin + (end - begin) / 2;
57         qreal diff = m_pageTops.value(index) - value;
58         if (diff < 0)
59             begin = index;
60         else if (diff > 0)
61             end = index;
62         else
63             break;
64     }
65     // index is now the number of the first possible page that can
66     // contain the viewRect.  The next step is to find the
67     // corresponding Page.  Since we have no way to get to the Nth
68     // page directly we have to enumerate them from the beginning.
69     //
70     // We use 1 since we might hit a pagespread in the binary search,
71     // so start one page early.
72     while (index > 1) {
73         page = page.next();
74         --index;
75     }
76 
77     // From here we loop through some more pages after the found one
78     // and see if they intersect with the page in question.  When we
79     // have two pages in row that don't intersect we break the loop.
80     qreal offsetX = 0.0;
81     int emptyPages = 0;
82     for(; page.isValid(); page = page.next()) {
83         Q_ASSERT_X(page.pageNumber()-pageOffset < m_pageTops.count(), __FUNCTION__,
84                    QString("Pagemanager has more pages than viewmode (%1>%2 with pageOffset=%3 and pageNumber=%4 and pageCount=%5). Make sure you add pages via the document!")
85                    .arg(page.pageNumber()-pageOffset).arg(m_pageTops.count())
86                    .arg(pageOffset).arg(page.pageNumber()).arg(m_pageManager->pageCount()).toLocal8Bit());
87 
88 
89         // Some invariants
90         const QRectF pageRect = page.rect();
91         const qreal offsetY = m_pageTops[page.pageNumber() - pageOffset] - pageRect.top();
92 
93         bool pageIntersects = false;
94 
95         // 1. First handle the page itself.
96         const QRectF zoomedPage = viewConverter->documentToView(pageRect);
97         ViewMap vm;
98         vm.page = page;
99         //kDebug(32003) <<"page" << page.pageNumber();
100         vm.distance = viewConverter->documentToView(QPointF(offsetX, offsetY));
101 
102         const QRectF targetPage(zoomedPage.x() + vm.distance.x(), zoomedPage.y() + vm.distance.y(),
103                                 zoomedPage.width(), zoomedPage.height());
104         QRectF intersection = targetPage.intersected(viewRect);
105         if (! intersection.isEmpty()) {
106             intersection.moveTopLeft(intersection.topLeft() - vm.distance);
107             vm.clipRect = intersection.toRect();
108             answer.append(vm);
109             pageIntersects = true;
110         }
111 
112         // 2. Then handle the annotation area if annotations are active.
113         //
114         //    The reason we don't do them together with the page
115         //    itself is because the pages have a gap between them, but
116         //    the annotation area should be unbroken.
117         //
118         // NOTE: 'annotation' below means the annotation area.
119         //
120         // FIXME: We should only do this if the annotation area is
121         //        actually shown. How can we inside the KWViewMode
122         //        know if annotations are active?
123         if (1 /* annotations are shown */) {
124             const QRectF annotationRect = pageRect.adjusted(page.width(), 0,
125                                                             KWCanvasBase::AnnotationAreaWidth, GAP);
126             const QRectF zoomedAnnotation = viewConverter->documentToView(annotationRect);
127             ViewMap vm2;
128             vm2.page = page;
129             vm2.distance = viewConverter->documentToView(QPointF(offsetX, offsetY));
130 
131             const QRectF targetAnnotation(zoomedAnnotation.x() + vm2.distance.x(),
132                                           zoomedAnnotation.y() + vm2.distance.y(),
133                                           zoomedAnnotation.width(), zoomedAnnotation.height());
134             intersection = targetAnnotation.intersected(viewRect);
135             if (! intersection.isEmpty()) {
136                 intersection.moveTopLeft(intersection.topLeft() - vm2.distance);
137                 vm2.clipRect = intersection.toRect();
138                 answer.append(vm2);
139                 pageIntersects = true;
140             }
141         }
142 
143         // Record whether this page had an intersection with the view rect.
144         if (pageIntersects) {
145             emptyPages = 0;
146         } else {
147             ++emptyPages;
148         }
149         if (emptyPages > 2) // Since we show at max 2 pages side by side this is an easy rule
150             break;
151 
152         if (m_pageSpreadMode) {
153             if (page.pageSide() == KWPage::Left)
154                 offsetX = page.width() + GAP;
155             else
156                 offsetX = 0.0;
157         }
158     }
159 #else
160     KWPage page  = m_pageManager->begin();
161     Q_ASSERT(page.isValid());
162     qreal offsetX = 0.0;
163     const int pageOffset = page.pageNumber();
164     for(; page.isValid(); page = page.next()) {
165         const QRectF pageRect = page.rect();
166         const QRectF zoomedPage = viewConverter->documentToView(pageRect);
167         ViewMap vm;
168         vm.page = page;
169 
170         const qreal offsetY = m_pageTops[page.pageNumber() - pageOffset] - pageRect.top();
171         vm.distance = viewConverter->documentToView(QPointF(offsetX, offsetY));
172 #if 0
173         const QRectF targetPage(zoomedPage.x() + vm.distance.x(), zoomedPage.y() + vm.distance.y(), zoomedPage.width() , zoomedPage.height());
174         QRectF intersection = targetPage.intersected(viewRect);
175         if (! intersection.isEmpty()) {
176             intersection.moveTopLeft(intersection.topLeft() - vm.distance);
177             vm.clipRect = intersection.toRect();
178             answer.append(vm);
179         }
180 #else
181         const QRectF targetPage(zoomedPage.x() + vm.distance.x(), zoomedPage.y() + vm.distance.y(), zoomedPage.width() , zoomedPage.height());
182         vm.clipRect = targetPage.toRect();
183         answer.append(vm);
184 #endif
185     }
186 #endif
187     return answer;
188 }
189 
updatePageCache()190 void KWViewModeNormal::updatePageCache()
191 {
192     if (!m_pageManager) {
193         warnWords << "Error detected while running KWViewModeNormal::updatePageCache: PageManager not set";
194         return;
195     }
196 
197     m_pageSpreadMode = false;
198     foreach (const KWPage &page, m_pageManager->pages()) {
199         Q_UNUSED(page);
200     }
201     m_pageTops.clear();
202     qreal width = 0.0, bottom = 0.0;
203     if (m_pageSpreadMode) { // two pages next to each other per row
204         qreal top = 0.0, last = 0.0, halfWidth = 0.0;
205         foreach (const KWPage &page, m_pageManager->pages()) {
206             switch (page.pageSide()) {
207             case KWPage::Left:
208                 m_pageTops.append(top);
209                 last = page.height();
210                 halfWidth = page.width() + GAP;
211                 width = qMax(width, halfWidth);
212                 bottom = top + last;
213                 break;
214             case KWPage::Right:
215                 m_pageTops.append(top);
216                 top += qMax(page.height(), last);
217                 last = 0.0;
218                 width = qMax(width, halfWidth + page.width());
219                 halfWidth = 0.0;
220                 bottom = top;
221                 top += GAP;
222                 break;
223             default:
224                 Q_ASSERT(false);
225                 break;
226             }
227         }
228     } else { // each page on a row
229         qreal top = 0.0;
230         foreach (const KWPage &page, m_pageManager->pages()) {
231             m_pageTops.append(top);
232             top += page.height() + GAP;
233             width = qMax(width, page.width());
234         }
235         bottom = top;
236     }
237     if (bottom > GAP)
238         bottom -= GAP; // remove one too many added
239     m_contents = QSizeF(width, bottom);
240 }
241 
documentToView(const QPointF & point,KoViewConverter * viewConverter) const242 QPointF KWViewModeNormal::documentToView(const QPointF & point, KoViewConverter *viewConverter) const
243 {
244     Q_ASSERT(viewConverter);
245 
246     KWPage page = m_pageManager->page(point);
247     if (! page.isValid())
248         page = m_pageManager->last();
249     if (! page.isValid())
250         return QPointF();
251     int pageIndex = page.pageNumber() - m_pageManager->begin().pageNumber();
252     qreal x = 0;
253     if (m_pageSpreadMode && page.pageSide() == KWPage::Right) {
254         KWPage prevPage = m_pageManager->page(page.pageNumber() - 1);
255         if (prevPage.isValid())
256             x = prevPage.width();
257     }
258 
259     QPointF offsetInPage(point.x(),  + point.y() - page.offsetInDocument());
260     Q_ASSERT(pageIndex >= 0);
261     Q_ASSERT(pageIndex < m_pageTops.count());
262     QPointF translated(x, m_pageTops[pageIndex]);
263     return viewConverter->documentToView(translated + offsetInPage);
264 }
265 
viewToDocument(const QPointF & point,KoViewConverter * viewConverter) const266 QPointF KWViewModeNormal::viewToDocument(const QPointF & point, KoViewConverter *viewConverter) const
267 {
268     Q_ASSERT(viewConverter);
269 
270     QPointF clippedPoint(qMax(qreal(0.0), point.x()), qMax(qreal(0.0), point.y()));
271     QPointF translated = viewConverter->viewToDocument(clippedPoint);
272     int pageNumber = 0;
273     foreach (qreal top, m_pageTops) {
274         if (translated.y() < top)
275             break;
276         pageNumber++;
277     }
278     translated = viewConverter->viewToDocument(point);
279     KWPage page = m_pageManager->page(pageNumber - 1 + m_pageManager->begin().pageNumber());
280     qreal xOffset = translated.x();
281 
282     if (page.isValid() && m_pageSpreadMode && page.pageSide() == KWPage::Right && page != m_pageManager->begin()) {
283         // there is a page displayed left of this one.
284         KWPage prevPage = page.previous();
285         if (xOffset <= prevPage.width()) // was left page instead of right :)
286             page = prevPage;
287         else
288             xOffset -= prevPage.width();
289     }
290 
291     if (! page.isValid()) // below doc or right of last page
292         return QPointF(m_contents.width(), m_contents.height());
293 
294     qreal yOffset = translated.y();
295     if (pageNumber >= 0)
296         yOffset -= m_pageTops[pageNumber -1];
297 
298     return QPointF(xOffset, page.offsetInDocument() + yOffset);
299 }
300