Speed up paging through the book. Clean up code for saving/restoring
[dorian] / bookview.cpp
1 #include <QDir>
2 #include <QtGui>
3 #include <QWebFrame>
4
5 #if defined(Q_OS_SYMBIAN)
6 #   include "mediakeysobserver.h"
7 #   include "flickcharm.h"
8 #endif
9
10 #include "book.h"
11 #include "bookview.h"
12 #include "library.h"
13 #include "settings.h"
14 #include "trace.h"
15 #include "progress.h"
16 #include "progressdialog.h"
17 #include "platform.h"
18
19 BookView::BookView(QWidget *parent): QWebView(parent), contentIndex(-1),
20     mBook(0), restorePositionAfterLoad(false), positionAfterLoad(0),
21     restoreFragmentAfterLoad(false), loaded(false), grabbingVolumeKeys(false)
22 {
23     TRACE;
24
25     // Set up web view defaults
26     settings()->setAttribute(QWebSettings::AutoLoadImages, true);
27     settings()->setAttribute(QWebSettings::JavascriptEnabled, true);
28     settings()->setAttribute(QWebSettings::JavaEnabled, false);
29     settings()->setAttribute(QWebSettings::PluginsEnabled, false);
30     settings()->setAttribute(QWebSettings::PrivateBrowsingEnabled, true);
31     settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, false);
32     settings()->setAttribute(QWebSettings::JavascriptCanAccessClipboard, false);
33     settings()->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled,
34                              false);
35     settings()->setAttribute(QWebSettings::OfflineWebApplicationCacheEnabled,
36                              false);
37     settings()->setAttribute(QWebSettings::LocalStorageEnabled, false);
38     settings()->setAttribute(QWebSettings::ZoomTextOnly, true);
39     settings()->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls,
40                              false);
41     settings()->setDefaultTextEncoding("utf-8");
42     page()->setContentEditable(false);
43     QWebFrame *frame = page()->mainFrame();
44 #if defined(Q_WS_MAEMO_5) || defined(Q_OS_SYMBIAN)
45     frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
46 #endif
47     frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
48     connect(this, SIGNAL(loadFinished(bool)), this, SLOT(onLoadFinished(bool)));
49     connect(frame, SIGNAL(javaScriptWindowObjectCleared()),
50             this, SLOT(addJavaScriptObjects()));
51
52     // Suppress unwanted text selections on Maemo and Symbian
53 #if defined(Q_WS_MAEMO_5) || defined(Q_OS_SYMBIAN)
54     installEventFilter(this);
55 #endif
56
57     // Pre-load bookmark icon
58     bookmarkImage = QImage(":/icons/bookmark.png");
59
60     // Handle settings changes, force handling initial settings
61     connect(Settings::instance(), SIGNAL(valueChanged(const QString &)),
62             this, SLOT(onSettingsChanged(const QString &)));
63     setBook(0);
64
65     // Enable kinetic scrolling
66 #if defined(Q_WS_MAEMO_5)
67     scrollerMonitor = 0;
68     scroller = property("kineticScroller").value<QAbstractKineticScroller *>();
69 #elif defined(Q_OS_SYMBIAN)
70     scrollerMonitor = 0;
71     charm = new FlickCharm(this);
72     charm->activateOn(this);
73 #endif
74
75     // Observe media keys on Symbian
76 #ifdef Q_OS_SYMBIAN
77     MediaKeysObserver *observer = MediaKeysObserver::instance();
78     connect(observer, SIGNAL(mediaKeyPressed(MediaKeysObserver::MediaKeys)),
79             this, SLOT(onMediaKeysPressed(MediaKeysObserver::MediaKeys)));
80 #endif
81 }
82
83 void BookView::loadContent(int index)
84 {
85     TRACE;
86
87     if (!mBook) {
88         return;
89     }
90     if ((index < 0) || (index >= mBook->parts.size())) {
91         return;
92     }
93
94     QString contentFile(mBook->content[mBook->parts[index]].href);
95     if (mBook->parts[index] == "error") {
96         setHtml(contentFile);
97     } else {
98         loaded = false;
99         emit partLoadStart(index);
100         QUrl u = QUrl::fromLocalFile(QDir(mBook->rootPath()).
101                                      absoluteFilePath(contentFile));
102         qDebug() << "Loading" << u;
103         load(u);
104     }
105     contentIndex = index;
106 }
107
108 void BookView::setBook(Book *book)
109 {
110     TRACE;
111
112     // Save position in current book
113     setLastBookmark();
114
115     // Open new book, restore last position
116     if (book != mBook) {
117         mBook = book;
118         if (book) {
119             contentIndex = -1;
120             if (book->open()) {
121                 restoreLastBookmark();
122             } else {
123                 mBook = 0;
124                 contentIndex = 0;
125                 setHtml(tr("Failed to open book"));
126             }
127         }
128         else {
129             contentIndex = 0;
130             setHtml(tr("No book"));
131         }
132     }
133 }
134
135 Book *BookView::book()
136 {
137     return mBook;
138 }
139
140 void BookView::goPrevious()
141 {
142     TRACE;
143     if (mBook && (contentIndex > 0)) {
144         mBook->setLastBookmark(contentIndex - 1, 0);
145         loadContent(contentIndex - 1);
146     }
147 }
148
149 void BookView::goNext()
150 {
151     TRACE;
152     if (mBook && (contentIndex < (mBook->parts.size() - 1))) {
153         mBook->setLastBookmark(contentIndex + 1, 0);
154         loadContent(contentIndex + 1);
155     }
156 }
157
158 void BookView::setLastBookmark()
159 {
160     TRACE;
161     if (mBook) {
162         QWebFrame *frame = page()->mainFrame();
163         int height = frame->contentsSize().height();
164         int pos = frame->scrollPosition().y();
165         qDebug() << QString("At %1 (%2%, height %3)").
166                 arg(pos).arg((qreal)pos / (qreal)height * 100).arg(height);
167         mBook->setLastBookmark(contentIndex, (qreal)pos / (qreal)height);
168     }
169 }
170
171 void BookView::restoreLastBookmark()
172 {
173     TRACE;
174     if (mBook) {
175         goToBookmark(mBook->lastBookmark());
176     }
177 }
178
179 void BookView::goToBookmark(const Book::Bookmark &bookmark)
180 {
181     TRACE;
182     if (mBook) {
183         if (bookmark.part != contentIndex) {
184             qDebug () << "Loading new part" << bookmark.part;
185             mBook->setLastBookmark(bookmark.part, bookmark.pos);
186             restorePositionAfterLoad = true;
187             positionAfterLoad = bookmark.pos;
188             loadContent(bookmark.part);
189         } else {
190             goToPosition(bookmark.pos);
191         }
192     }
193 }
194
195 void BookView::goToPart(int part, const QString &fragment)
196 {
197     TRACE;
198     if (mBook) {
199         if (fragment.isEmpty()) {
200             goToBookmark(Book::Bookmark(part, 0));
201         } else {
202             if (part != contentIndex) {
203                 qDebug() << "Loading new part" << part;
204                 restoreFragmentAfterLoad = true;
205                 fragmentAfterLoad = fragment;
206                 loadContent(part);
207             } else {
208                 goToFragment(fragment);
209                 showProgress();
210             }
211         }
212     }
213 }
214
215 void BookView::goToFragment(const QString &fragment)
216 {
217     TRACE;
218     if (!fragment.isEmpty()) {
219         QVariant ret = page()->mainFrame()->evaluateJavaScript(
220                 QString("window.location='") + fragment + "'");
221         qDebug() << ret;
222         // FIXME: setLastBookmark();
223     }
224 }
225
226 void BookView::onLoadFinished(bool ok)
227 {
228     TRACE;
229     if (!ok) {
230         qDebug() << "Not OK";
231         return;
232     }
233     loaded = true;
234     onSettingsChanged("scheme");
235     onSettingsChanged("zoom");
236     onSettingsChanged("font");
237
238     QTimer::singleShot(210, this, SLOT(restoreAfterLoad()));
239 }
240
241 void BookView::restoreAfterLoad()
242 {
243     TRACE;
244     if (restoreFragmentAfterLoad) {
245         qDebug() << "Restorint to fragment" << fragmentAfterLoad;
246         goToFragment(fragmentAfterLoad);
247         restoreFragmentAfterLoad = false;
248     } else if (restorePositionAfterLoad) {
249         qDebug() << "Restoring to position" << positionAfterLoad;
250         goToPosition(positionAfterLoad);
251         restorePositionAfterLoad = false;
252     }
253
254     emit partLoadEnd(contentIndex);
255     showProgress();
256 }
257
258 void BookView::onSettingsChanged(const QString &key)
259 {
260     Settings *s = Settings::instance();
261     Platform *p = Platform::instance();
262
263     if (key == "zoom") {
264         int value = s->value(key, p->defaultZoom()).toInt();
265         qDebug() << "BookView::onSettingsChanged: zoom" << value;
266         setZoomFactor(value / 100.);
267     }
268     else if (key == "font") {
269         QString face = s->value(key, p->defaultFont()).toString();
270         qDebug() << "BookView::onSettingsChanged: font" << face;
271         settings()->setFontFamily(QWebSettings::StandardFont, face);
272     }
273     else if (key == "scheme") {
274         QWebFrame *frame = page()->mainFrame();
275         QString scheme = Settings::instance()->value("scheme").toString();
276         if ((scheme != "day") && (scheme != "night") && (scheme != "sand") &&
277             (scheme != "default")) {
278             scheme = "default";
279         }
280         qDebug() << "BookView::onSettingsChanged: scheme" << scheme;
281         QFile script(":/styles/" + scheme + ".js");
282         script.open(QFile::ReadOnly);
283         QString scriptText = script.readAll();
284         script.close();
285         (void)frame->evaluateJavaScript(scriptText);
286     }
287     else if (key == "usevolumekeys") {
288         bool grab = s->value(key, false).toBool();
289         qDebug() << "BookView::onSettingsChanged: usevolumekeys" << grab;
290         grabVolumeKeys(grab);
291     }
292 }
293
294 void BookView::paintEvent(QPaintEvent *e)
295 {
296     QWebView::paintEvent(e);
297     if (!mBook || !loaded) {
298         return;
299     }
300
301     // Paint bookmarks
302     QWebFrame *frame = page()->mainFrame();
303     int contentsHeight = frame->contentsSize().height();
304     QPoint scrollPos = frame->scrollPosition();
305     QPixmap bookmarkPixmap = QPixmap::fromImage(bookmarkImage);
306     QPainter painter(this);
307     foreach (Book::Bookmark b, mBook->bookmarks()) {
308         if (b.part != contentIndex) {
309             continue;
310         }
311         int height = contentsHeight;
312         int bookmarkPos = (int)((qreal)height * (qreal)b.pos);
313         painter.drawPixmap(2, bookmarkPos - scrollPos.y(), bookmarkPixmap);
314     }
315 }
316
317 void BookView::mousePressEvent(QMouseEvent *e)
318 {
319     QWebView::mousePressEvent(e);
320 #if defined(Q_WS_MAEMO_5)
321     // Start monitoring kinetic scroll
322     if (scrollerMonitor) {
323         killTimer(scrollerMonitor);
324         scrollerMonitor = 0;
325     }
326     if (scroller) {
327         scrollerMonitor = startTimer(500);
328     }
329 #else
330     // Handle mouse presses on the scroll bar
331     QWebFrame *frame = page()->mainFrame();
332     if (frame->scrollBarGeometry(Qt::Vertical).contains(e->pos())) {
333         e->accept();
334         return;
335     }
336 #endif // Q_WS_MAEMO_5
337     e->ignore();
338 }
339
340 void BookView::wheelEvent(QWheelEvent *e)
341 {
342     QWebView::wheelEvent(e);
343     showProgress();
344 }
345
346 void BookView::addBookmark(const QString &note)
347 {
348     TRACE;
349     if (!mBook) {
350         return;
351     }
352     int y = page()->mainFrame()->scrollPosition().y();
353     int height = page()->mainFrame()->contentsSize().height();
354     qDebug() << ((qreal)y / (qreal)height);
355     mBook->addBookmark(contentIndex, (qreal)y / (qreal)height, note);
356     update();
357 }
358
359 QString BookView::tmpPath()
360 {
361     return QDir::tempPath() + "/dorian";
362 }
363
364 bool BookView::eventFilter(QObject *o, QEvent *e)
365 {
366 #if 0
367     if (e->type() != QEvent::Paint && e->type() != QEvent::MouseMove) {
368         if (e->type() == QEvent::Resize) {
369             qDebug() << "BookView::eventFilter QEvent::Resize to"
370                     << page()->mainFrame()->contentsSize().height();
371         } else if (e->type() == QEvent::Timer) {
372             qDebug() << "BookView::eventFilter" << "QEvent::Timer"
373                     << ((QTimerEvent *)e)->timerId();
374         } else {
375             qDebug() << "BookView::eventFilter" << Trace::event(e->type());
376         }
377     }
378 #endif
379
380     // Work around Qt bug that sometimes selects web view contents during swipe
381     switch (e->type()) {
382     case QEvent::MouseButtonPress:
383         emit suppressedMouseButtonPress();
384         mousePressed = true;
385         break;
386     case QEvent::MouseButtonRelease:
387         showProgress();
388         mousePressed = false;
389         break;
390     case QEvent::MouseMove:
391         if (mousePressed) {
392             return true;
393         }
394         break;
395     case QEvent::MouseButtonDblClick:
396         return true;
397     default:
398         break;
399     }
400
401     return QObject::eventFilter(o, e);
402 }
403
404 void BookView::addJavaScriptObjects()
405 {
406     page()->mainFrame()->addToJavaScriptWindowObject("bv", this);
407 }
408
409 void BookView::goToPosition(qreal position)
410 {
411     int contentsHeight = page()->mainFrame()->contentsSize().height();
412     int scrollPos = (int)((qreal)contentsHeight * position);
413     page()->mainFrame()->setScrollPosition(QPoint(0, scrollPos));
414     // FIXME: update();
415     qDebug() << "BookView::goToPosition: To" << scrollPos << "("
416             << (position * 100) << "%, height" << contentsHeight << ")";
417 }
418
419 void BookView::showProgress()
420 {
421     if (mBook) {
422         int contentsHeight = page()->mainFrame()->contentsSize().height();
423         qreal pos = (qreal)(page()->mainFrame()->scrollPosition().y()) /
424                     (qreal)contentsHeight;
425         emit progress(mBook->getProgress(contentIndex, pos));
426     }
427 }
428
429 void BookView::timerEvent(QTimerEvent *e)
430 {
431 #if defined(Q_WS_MAEMO_5)
432     if (e->timerId() == scrollerMonitor) {
433         if (scroller &&
434             ((scroller->state() == QAbstractKineticScroller::AutoScrolling) ||
435              (scroller->state() == QAbstractKineticScroller::Pushing))) {
436             showProgress();
437         } else {
438             killTimer(scrollerMonitor);
439             scrollerMonitor = -1;
440         }
441     }
442 #endif
443     QWebView::timerEvent(e);
444 }
445
446 void BookView::goPreviousPage()
447 {
448     QWebFrame *frame = page()->mainFrame();
449     int pos = frame->scrollPosition().y();
450     frame->scroll(0, -height());
451     if (pos == frame->scrollPosition().y()) {
452         if (contentIndex > 0) {
453             Book::Bookmark bookmark(contentIndex - 1, 1.0);
454             mBook->setLastBookmark(contentIndex - 1, 1.0);
455             goToBookmark(bookmark);
456         }
457     } else {
458         showProgress();
459     }
460 }
461
462 void BookView::goNextPage()
463 {
464     TRACE;
465     QWebFrame *frame = page()->mainFrame();
466     int pos = frame->scrollPosition().y();
467     frame->scroll(0, height());
468     if (pos == frame->scrollPosition().y()) {
469         goNext();
470     } else {
471         // setLastBookmark();
472         showProgress();
473     }
474 }
475
476 void BookView::grabVolumeKeys(bool grab)
477 {
478     TRACE;
479     grabbingVolumeKeys = grab;
480 }
481
482 #ifdef Q_OS_SYMBIAN
483
484 void BookView::onMediaKeysPressed(MediaKeysObserver::MediaKeys key)
485 {
486     TRACE;
487     qDebug() << "Key" << (int)key;
488     if (grabbingVolumeKeys) {
489         if (key == MediaKeysObserver::EVolIncKey) {
490             qDebug() << "Volume up";
491             goPreviousPage();
492         } else if (key == MediaKeysObserver::EVolDecKey){
493             qDebug() << "Volume down";
494             goNextPage();
495         }
496     }
497 }
498
499 #endif // Q_OS_SYMBIAN