More Symbian fixes.
[dorian] / bookview.cpp
1 #include <QDebug>
2 #include <QWebFrame>
3 #include <QMouseEvent>
4 #include <QFile>
5 #include <QDir>
6 #include <QTimer>
7
8 #ifdef Q_WS_MAEMO_5
9 #   include <QAbstractKineticScroller>
10 #endif
11
12 #include "book.h"
13 #include "bookview.h"
14 #include "library.h"
15 #include "settings.h"
16 #include "trace.h"
17 #include "progress.h"
18
19 #ifdef Q_WS_MAC
20 #   define ICON_PREFIX ":/icons/mac/"
21 #else
22 #   define ICON_PREFIX ":/icons/"
23 #endif
24
25 BookView::BookView(QWidget *parent):
26     QWebView(parent), contentIndex(-1), mBook(0),
27     restorePositionAfterLoad(false), positionAfterLoad(0), loaded(false),
28     contentsHeight(0), scrollerMonitor(-1)
29 {
30     Trace t("BookView::BookView");
31     settings()->setAttribute(QWebSettings::AutoLoadImages, true);
32     settings()->setAttribute(QWebSettings::JavascriptEnabled, true);
33     settings()->setAttribute(QWebSettings::JavaEnabled, false);
34     settings()->setAttribute(QWebSettings::PluginsEnabled, false);
35     settings()->setAttribute(QWebSettings::PrivateBrowsingEnabled, true);
36     settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, false);
37     settings()->setAttribute(QWebSettings::JavascriptCanAccessClipboard, false);
38     settings()->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, 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
48 #if defined(Q_WS_MAEMO_5)
49     // Suppress unwanted text selections on Maemo
50     installEventFilter(this);
51 #endif
52     QWebFrame *frame = page()->mainFrame();
53 #if defined(Q_WS_MAEMO_5)
54     frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
55 #endif
56     frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
57
58     bookmarkImage = QImage(":/icons/bookmark.png");
59
60     connect(this, SIGNAL(loadFinished(bool)), this, SLOT(onLoadFinished(bool)));
61     connect(frame, SIGNAL(javaScriptWindowObjectCleared()),
62             this, SLOT(addJavaScriptObjects()));
63     connect(frame, SIGNAL(contentsSizeChanged(const QSize &)),
64             this, SLOT(onContentsSizeChanged(const QSize &)));
65     connect(Settings::instance(), SIGNAL(valueChanged(const QString &)),
66             this, SLOT(onSettingsChanged(const QString &)));
67     Settings *s = Settings::instance();
68     s->setValue("zoom", s->value("zoom", 160));
69     s->setValue("font", s->value("font",
70 #if defined(Q_WS_MAEMO_5) || defined(Q_WS_X11)
71                                  "Serif"
72 #elif defined(Q_WS_MAC)
73                                  "Hoefler Text"
74 #else
75                                  "Times New Roman"
76 #endif
77                                  ));
78     s->setValue("scheme", s->value("scheme", "default"));
79     setBook(0);
80
81 #ifdef Q_WS_MAEMO_5
82     scroller = property("kineticScroller").value<QAbstractKineticScroller *>();
83 #endif
84 }
85
86 BookView::~BookView()
87 {
88     Trace t("BookView::~BookView");
89 }
90
91 void BookView::loadContent(int index)
92 {
93     Trace t("BookView::loadContent");
94     if (!mBook) {
95         return;
96     }
97     if ((index < 0) || (index >= mBook->parts.size())) {
98         return;
99     }
100
101     QString contentFile(mBook->content[mBook->parts[index]].href);
102     if (mBook->parts[index] == "error") {
103         setHtml(contentFile);
104     }
105     else {
106         loaded = false;
107         emit partLoadStart(index);
108         load(QUrl(mBook->rootPath() + "/" + contentFile));
109     }
110     contentIndex = index;
111 }
112
113 void BookView::setBook(Book *book)
114 {
115     Trace t("BookView::setBook");
116
117     // Save position in current book
118     setLastBookmark();
119
120     // Open new book, restore last position
121     if (book != mBook) {
122         mBook = book;
123         if (book) {
124             contentIndex = -1;
125             if (book->open()) {
126                 restoreLastBookmark();
127             } else {
128                 mBook = 0;
129                 contentIndex = 0;
130                 setHtml(tr("Failed to open book"));
131             }
132         }
133         else {
134             contentIndex = 0;
135             setHtml(tr("No book"));
136         }
137     }
138 }
139
140 Book *BookView::book()
141 {
142     return mBook;
143 }
144
145 void BookView::goPrevious()
146 {
147     Trace t("BookView::goPrevious");
148     if (mBook && (contentIndex > 0)) {
149         mBook->setLastBookmark(contentIndex - 1, 0);
150         loadContent(contentIndex - 1);
151     }
152 }
153
154 void BookView::goNext()
155 {
156     Trace t("BookView::goNext");
157     if (mBook && (contentIndex < (mBook->parts.size() - 1))) {
158         mBook->setLastBookmark(contentIndex + 1, 0);
159         loadContent(contentIndex + 1);
160     }
161 }
162
163 void BookView::setLastBookmark()
164 {
165     Trace t("BookView::setLastBookmark");
166     if (mBook) {
167         int height = contentsHeight;
168         int pos = page()->mainFrame()->scrollPosition().y();
169         qDebug() << QString("At %1 (%2%, height %3)").
170                 arg(pos).arg((qreal)pos / (qreal)height * 100).arg(height);
171         mBook->setLastBookmark(contentIndex, (qreal)pos / (qreal)height);
172     }
173 }
174
175 void BookView::restoreLastBookmark()
176 {
177     Trace t("BookView::restoreLastBookmark");
178     if (mBook) {
179         goToBookmark(mBook->lastBookmark());
180     }
181 }
182
183 void BookView::goToBookmark(const Book::Bookmark &bookmark)
184 {
185     Trace t("BookView::goToBookmark");
186     if (mBook) {
187         if (bookmark.part != contentIndex) {
188             qDebug () << "Loading new part" << bookmark.part;
189             mBook->setLastBookmark(bookmark.part, bookmark.pos);
190             restorePositionAfterLoad = true;
191             positionAfterLoad = bookmark.pos;
192             loadContent(bookmark.part);
193         } else {
194             goToPosition(bookmark.pos);
195         }
196     }
197 }
198
199 void BookView::onLoadFinished(bool ok)
200 {
201     Trace t("BookView::onLoadFinished");
202     if (!ok) {
203         qDebug() << "Not OK";
204         return;
205     }
206     loaded = true;
207     onSettingsChanged("scheme");
208     emit partLoadEnd(contentIndex);
209     showProgress();
210 }
211
212 void BookView::onSettingsChanged(const QString &key)
213 {
214     Trace t("BookView::onSettingsChanged " + key);
215     if (key == "zoom") {
216         setZoomFactor(Settings::instance()->value(key).toFloat() / 100.);
217     }
218     else if (key == "font") {
219         QString face = Settings::instance()->value("font").toString();
220         settings()->setFontFamily(QWebSettings::StandardFont, face);
221     }
222     else if (key == "scheme") {
223         QWebFrame *frame = page()->mainFrame();
224         QString scheme = Settings::instance()->value("scheme").toString();
225         if ((scheme != "day") && (scheme != "night") && (scheme != "sand") &&
226             (scheme != "default")) {
227             scheme = "default";
228         }
229         QFile script(":/styles/" + scheme + ".js");
230         script.open(QFile::ReadOnly);
231         QString scriptText = script.readAll();
232         script.close();
233         QVariant ret = frame->evaluateJavaScript(scriptText);
234     }
235 }
236
237 void BookView::paintEvent(QPaintEvent *e)
238 {
239     QWebView::paintEvent(e);
240     if (!mBook || !loaded) {
241         return;
242     }
243
244     // Paint bookmarks
245     QPoint scrollPos = page()->mainFrame()->scrollPosition();
246     QPixmap bookmarkPixmap = QPixmap::fromImage(bookmarkImage);
247     QPainter painter(this);
248     foreach (Book::Bookmark b, mBook->bookmarks()) {
249         if (b.part != contentIndex) {
250             continue;
251         }
252         int height = contentsHeight;
253         int bookmarkPos = (int)((qreal)height * (qreal)b.pos);
254         painter.drawPixmap(2, bookmarkPos - scrollPos.y(), bookmarkPixmap);
255     }
256 }
257
258 void BookView::mousePressEvent(QMouseEvent *e)
259 {
260     QWebView::mousePressEvent(e);
261 #ifdef Q_WS_MAEMO_5
262     // Start monitoring kinetic scroll
263     if (scroller) {
264         scrollerMonitor = startTimer(250);
265     }
266 #else
267     // Handle mouse presses on the scroll bar
268     QWebFrame *frame = page()->mainFrame();
269     if (frame->scrollBarGeometry(Qt::Vertical).contains(e->pos())) {
270         e->accept();
271         return;
272     }
273 #endif // Q_WS_MAEMO_5
274     e->ignore();
275 }
276
277 void BookView::wheelEvent(QWheelEvent *e)
278 {
279     QWebView::wheelEvent(e);
280     showProgress();
281 }
282
283 void BookView::addBookmark()
284 {
285     Trace t("BookView::addBookmark");
286     if (!mBook) {
287         return;
288     }
289     int y = page()->mainFrame()->scrollPosition().y();
290     int height = page()->mainFrame()->contentsSize().height();
291     qDebug() << ((qreal)y / (qreal)height);
292     mBook->addBookmark(contentIndex, (qreal)y / (qreal)height);
293     update();
294 }
295
296 QString BookView::tmpPath()
297 {
298     return QDir::tempPath() + "/dorian";
299 }
300
301 bool BookView::eventFilter(QObject *o, QEvent *e)
302 {
303     if (e->type() != QEvent::Paint && e->type() != QEvent::MouseMove) {
304         if (e->type() == QEvent::Resize) {
305             qDebug() << "BookView::eventFilter QEvent::Resize to"
306                     << page()->mainFrame()->contentsSize().height();
307         } else {
308             qDebug() << "BookView::eventFilter" << Trace::event(e->type());
309         }
310     }
311
312     // Work around Qt bug that sometimes selects web view contents during swipe
313     switch (e->type()) {
314     case QEvent::MouseButtonPress:
315         emit suppressedMouseButtonPress();
316         mousePressed = true;
317         break;
318     case QEvent::MouseButtonRelease:
319         showProgress();
320         mousePressed = false;
321         break;
322     case QEvent::MouseMove:
323         if (mousePressed) {
324             return true;
325         }
326         break;
327     case QEvent::MouseButtonDblClick:
328         return true;
329     default:
330         break;
331     }
332
333     return QObject::eventFilter(o, e);
334 }
335
336 void BookView::addJavaScriptObjects()
337 {
338     page()->mainFrame()->addToJavaScriptWindowObject("bv", this);
339 }
340
341 void BookView::onContentsSizeChanged(const QSize &size)
342 {
343     contentsHeight = size.height();
344     if (restorePositionAfterLoad) {
345         qDebug() << "BookView::onContentSizeChanged: Time to restore";
346         restorePositionAfterLoad = false;
347         goToPosition(positionAfterLoad);
348     }
349 }
350
351 void BookView::leaveEvent(QEvent *e)
352 {
353     Trace t("BookView::leaveEvent");
354     // Save current position, to be restored later
355     setLastBookmark();
356     QWebView::leaveEvent(e);
357 }
358
359 void BookView::enterEvent(QEvent *e)
360 {
361     Trace t("BookView::enterEvent");
362     // Restore position saved at Leave event. This seems to be required,
363     // after temporarily switching from portrait to landscape and back
364     restoreLastBookmark();
365     QWebView::enterEvent(e);
366 }
367
368 void BookView::goToPosition(qreal position)
369 {
370     int scrollPos = (int)((qreal)contentsHeight * position);
371     page()->mainFrame()->setScrollPosition(QPoint(0, scrollPos));
372     // FIXME: update();
373     qDebug() << "BookView::goToPosition: To" << scrollPos << "("
374             << (position * 100) << "%, height" << contentsHeight << ")";
375 }
376
377 void BookView::showProgress()
378 {
379     if (mBook) {
380         qreal pos = (qreal)(page()->mainFrame()->scrollPosition().y()) /
381                     (qreal)contentsHeight;
382         emit progress(mBook->getProgress(contentIndex, pos));
383     }
384 }
385
386 void BookView::timerEvent(QTimerEvent *e)
387 {
388     if (e->timerId() == scrollerMonitor) {
389 #ifdef Q_WS_MAEMO_5
390         if (scroller &&
391             ((scroller->state() == QAbstractKineticScroller::AutoScrolling) ||
392              (scroller->state() == QAbstractKineticScroller::Pushing))) {
393             showProgress();
394         } else {
395             killTimer(scrollerMonitor);
396         }
397 #endif // Q_WS_MAEMO_5
398     }
399 }
400
401 void BookView::keyPressEvent(QKeyEvent* event)
402 {
403     switch (event->key()) {
404     case Qt::Key_F7:
405         goNextPage();
406         event->accept();
407         break;
408     case Qt::Key_F8:
409         goPreviousPage();
410         event->accept();
411         break;
412     default:
413         ;
414     }
415     QWebView::keyPressEvent(event);
416 }
417
418 void BookView::goPreviousPage()
419 {
420     QWebFrame *frame = page()->mainFrame();
421     int pos = frame->scrollPosition().y();
422     frame->scroll(0, -height());
423     if (pos == frame->scrollPosition().y()) {
424         if (contentIndex > 0) {
425             Book::Bookmark bookmark(contentIndex - 1, 1.0);
426             mBook->setLastBookmark(contentIndex - 1, 1.0);
427             goToBookmark(bookmark);
428         }
429     } else {
430         showProgress();
431     }
432 }
433
434 void BookView::goNextPage()
435 {
436     Trace t("BookView::goNextPage");
437     QWebFrame *frame = page()->mainFrame();
438     int pos = frame->scrollPosition().y();
439     frame->scroll(0, height());
440     if (pos == frame->scrollPosition().y()) {
441         goNext();
442     } else {
443         setLastBookmark();
444         showProgress();
445     }
446 }