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