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