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