[app] Support for footnotes

This commit is contained in:
Slava Monich 2016-11-02 16:33:26 +03:00
parent 35757ce03d
commit e3b0e1c453
17 changed files with 527 additions and 112 deletions

View file

@ -27,7 +27,7 @@
<style id="15" partial="true" name="Internal Hyperlink" allowHyphenations="false" hyperlink="internal"/> <style id="15" partial="true" name="Internal Hyperlink" allowHyphenations="false" hyperlink="internal"/>
<style id="37" partial="true" name="External Hyperlink" allowHyphenations="false" hyperlink="external"/> <style id="37" partial="true" name="External Hyperlink" allowHyphenations="false" hyperlink="external"/>
<style id="38" partial="true" name="Link to Another Book" allowHyphenations="false" hyperlink="book"/> <style id="38" partial="true" name="Link to Another Book" allowHyphenations="false" hyperlink="book"/>
<style id="16" partial="true" name="Footnote" fontSizeDelta="-6" vShift="10" allowHyphenations="false" hyperlink="internal"/> <style id="16" partial="true" name="Footnote" fontSizeDelta="-4" vShift="10" allowHyphenations="false" hyperlink="internal"/>
<style id="17" partial="true" name="Emphasis" italic="true"/> <style id="17" partial="true" name="Emphasis" italic="true"/>
<style id="18" partial="true" name="Strong" bold="true"/> <style id="18" partial="true" name="Strong" bold="true"/>
<style id="19" partial="true" name="Subscript" fontSizeDelta="-4" vShift="-4" allowHyphenations="false"/> <style id="19" partial="true" name="Subscript" fontSizeDelta="-4" vShift="-4" allowHyphenations="false"/>

View file

@ -42,6 +42,7 @@ SilicaFlickable {
signal closeBook() signal closeBook()
signal pageClicked(var page) signal pageClicked(var page)
property int orientation: Orientation.Portrait
property int _currentPage: bookListWatcher.currentIndex property int _currentPage: bookListWatcher.currentIndex
property bool _loading: minLoadingDelay.running || bookModel.loading property bool _loading: minLoadingDelay.running || bookModel.loading
property var _currentState: _visibilityStates[Settings.pageDetails % _visibilityStates.length] property var _currentState: _visibilityStates[Settings.pageDetails % _visibilityStates.length]
@ -63,22 +64,33 @@ SilicaFlickable {
qsTrId("harbour-books-book-view-applying_smaller_fonts") qsTrId("harbour-books-book-view-applying_smaller_fonts")
] ]
interactive: (!linkMenu || !linkMenu.visible) && (!imageView || !imageView.visible) interactive: (!linkMenu || !linkMenu.visible) &&
(!imageView || !imageView.visible) &&
(!footnoteView || !footnoteView.visible)
property var linkMenu property var linkMenu
property var imageView property var imageView
property var footnoteView
Component { onOrientationChanged: {
id: linkMenuComponent if (footnoteView) {
BooksLinkMenu { footnoteView.cancel()
} }
} }
Component {
id: linkMenuComponent
BooksLinkMenu { }
}
Component { Component {
id: imageViewComponent id: imageViewComponent
BooksImageView { BooksImageView { }
imageBackground: Settings.pageBackgroundColor }
}
Component {
id: footnoteViewComponent
BooksFootnoteView { }
} }
PullDownMenu { PullDownMenu {
@ -177,8 +189,15 @@ SilicaFlickable {
if (!linkMenu) { if (!linkMenu) {
linkMenu = linkMenuComponent.createObject(root) linkMenu = linkMenuComponent.createObject(root)
} }
linkMenu.url = url linkMenu.show(url)
linkMenu.show() }
}
onFootnotePressed: {
if (_currentPage == index) {
if (!footnoteView) {
footnoteView = footnoteViewComponent.createObject(root)
}
footnoteView.show(touchX,touchY,text,url)
} }
} }
} }

View file

@ -0,0 +1,198 @@
/*
Copyright (C) 2016 Jolla Ltd.
Contact: Slava Monich <slava.monich@jolla.com>
You may use this file under the terms of BSD license as follows:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Jolla Ltd nor the names of its contributors may
be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*/
import QtQuick 2.0
import Sailfish.Silica 1.0
Rectangle {
id: root
visible: opacity > 0
opacity: 0.0
anchors.fill: parent
color: Theme.rgba(Theme.highlightDimmerColor, 0.9)
readonly property real footnoteX: Math.round((root.width-footnoteItem.width)/2)
readonly property real footnoteY: Theme.paddingMedium
Behavior on opacity { FadeAnimation {} }
function show(startX,startY,text,url) {
flickable.scrollToTop()
image.source = url
if (state !== "show") {
footnoteItem.scale = 0
footnoteItem.x = startX
footnoteItem.y = startY
footnoteLabel.text = text
state = "show"
}
}
function cancel() {
state = "cancel"
}
function hide() {
state = "hide"
}
Connections {
target: Settings
onInvertColorsChanged: cancel()
}
MouseArea {
anchors.fill: parent
onPressed: root.hide()
}
Item {
id: footnoteItem
x: footnoteX
y: footnoteY
width: footnote.width
height: Math.round(root.height + footnote.height)/2 - Theme.paddingMedium
transformOrigin: Item.TopLeft
Label {
id: footnoteLabel
width: parent.width
height: Math.round(root.height - footnote.height/2) - 2*Theme.paddingMedium
color: Theme.highlightColor
verticalAlignment: Text.AlignBottom
maximumLineCount: 4
visible: opacity > 0
Behavior on opacity { FadeAnimation {} }
anchors {
top: parent.top
bottom: footnote.top
bottomMargin: Theme.paddingMedium
}
}
Rectangle {
id: footnote
radius: Theme.paddingMedium/2
border.color: Settings.invertedPageBackgroundColor
color: Settings.pageBackgroundColor
width: image.width + 2*Theme.paddingMedium
height: Math.min(root.height/2, image.height + 2*Theme.paddingMedium)
anchors {
bottom: parent.bottom
}
SilicaFlickable {
id: flickable
anchors {
fill: parent
margins: Theme.paddingMedium
}
clip: true
contentWidth: image.width
contentHeight: image.height
Image {
id: image
}
VerticalScrollDecorator {}
}
}
}
states: [
State {
name: "invisible"
PropertyChanges {
target: root
opacity: 0
}
PropertyChanges {
target: footnoteItem
scale: 0
}
},
State {
name: "hide"
extend: "invisible"
},
State {
name: "cancel"
extend: "invisible"
PropertyChanges {
target: footnoteItem
transformOrigin: Item.Center
}
},
State {
name: "show"
PropertyChanges {
target: root
opacity: 1
}
PropertyChanges {
target: footnoteItem
x: footnoteX
y: footnoteY
scale: 1
transformOrigin: Item.TopLeft
}
}
]
transitions: [
Transition {
from: "*"
to: "show"
NumberAnimation {
properties: "opacity,x,y,scale"
duration: 200
easing.type: Easing.InOutQuad
}
},
Transition {
from: "show"
to: "hide"
NumberAnimation {
properties: "opacity,x,y,scale"
duration: 200
easing.type: Easing.InOutQuad
}
},
Transition {
from: "show"
to: "cancel"
NumberAnimation {
properties: "opacity,scale"
duration: 200
easing.type: Easing.InOutQuad
}
}
]
}

View file

@ -40,7 +40,6 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
color: Theme.rgba(Theme.highlightDimmerColor, 0.9) color: Theme.rgba(Theme.highlightDimmerColor, 0.9)
property alias imageBackground: background.color
readonly property real maxImageWidth: width - 2*Theme.horizontalPageMargin readonly property real maxImageWidth: width - 2*Theme.horizontalPageMargin
readonly property real maxImageHeight: height - 2*Theme.paddingLarge readonly property real maxImageHeight: height - 2*Theme.paddingLarge
readonly property real finalImageWidth: Math.ceil(image.landscape ? maxImageWidth : (maxImageHeight * image.sourceSize.width / image.sourceSize.height)) readonly property real finalImageWidth: Math.ceil(image.landscape ? maxImageWidth : (maxImageHeight * image.sourceSize.width / image.sourceSize.height))
@ -53,6 +52,7 @@ Rectangle {
Rectangle { Rectangle {
id: background id: background
anchors.fill: image anchors.fill: image
color: Settings.pageBackgroundColor
} }
Image { Image {

View file

@ -45,7 +45,6 @@ Rectangle {
} }
readonly property bool landscape: width > height readonly property bool landscape: width > height
property alias url: linkLabel.text
Behavior on opacity { FadeAnimation {} } Behavior on opacity { FadeAnimation {} }
@ -131,7 +130,8 @@ Rectangle {
} }
} }
function show() { function show(url) {
linkLabel.text = url
opacity = 1.0 opacity = 1.0
} }

View file

@ -61,6 +61,7 @@ Page {
anchors.fill: parent anchors.fill: parent
opacity: book ? 1 : 0 opacity: book ? 1 : 0
visible: opacity > 0 visible: opacity > 0
orientation: root.orientation
book: Settings.currentBook ? Settings.currentBook : null book: Settings.currentBook ? Settings.currentBook : null
onCloseBook: Settings.currentBook = null onCloseBook: Settings.currentBook = null
Behavior on opacity { FadeAnimation {} } Behavior on opacity { FadeAnimation {} }

View file

@ -51,6 +51,7 @@ Item {
signal pageClicked() signal pageClicked()
signal imagePressed(var url, var rect) signal imagePressed(var url, var rect)
signal footnotePressed(var touchX, var touchY, var text, var url)
signal browserLinkPressed(var url) signal browserLinkPressed(var url)
signal jumpToPage(var page) signal jumpToPage(var page)
@ -59,9 +60,10 @@ Item {
anchors.fill: parent anchors.fill: parent
model: bookModel model: bookModel
onBrowserLinkPressed: view.browserLinkPressed(url) onBrowserLinkPressed: view.browserLinkPressed(url)
onImagePressed: view.imagePressed(url, rect) onImagePressed: view.imagePressed(imageId, rect)
onActiveTouch: pressImage.animate(x, y) onActiveTouch: pressImage.animate(touchX, touchY)
onJumpToPage: view.jumpToPage(page) onJumpToPage: view.jumpToPage(page)
onShowFootnote: view.footnotePressed(touchX,touchY,text,imageId)
} }
BooksTitleLabel { BooksTitleLabel {

View file

@ -348,6 +348,15 @@ shared_ptr<ZLTextModel> BooksBookModel::bookTextModel() const
return model; return model;
} }
shared_ptr<ZLTextModel> BooksBookModel::footnoteModel(const std::string& aId) const
{
shared_ptr<ZLTextModel> model;
if (iData && !iData->iBookModel.isNull()) {
model = iData->iBookModel->footnoteModel(aId);
}
return model;
}
shared_ptr<ZLTextModel> BooksBookModel::contentsModel() const shared_ptr<ZLTextModel> BooksBookModel::contentsModel() const
{ {
shared_ptr<ZLTextModel> model; shared_ptr<ZLTextModel> model;

View file

@ -120,6 +120,7 @@ public:
shared_ptr<BookModel> bookModel() const; shared_ptr<BookModel> bookModel() const;
shared_ptr<ZLTextModel> bookTextModel() const; shared_ptr<ZLTextModel> bookTextModel() const;
shared_ptr<ZLTextModel> contentsModel() const; shared_ptr<ZLTextModel> contentsModel() const;
shared_ptr<ZLTextModel> footnoteModel(const std::string& aId) const;
shared_ptr<ZLTextStyle> textStyle() const { return iTextStyle; } shared_ptr<ZLTextStyle> textStyle() const { return iTextStyle; }
int linkToPage(const std::string& aLink) const; int linkToPage(const std::string& aLink) const;
int fontSizeAdjust() const; int fontSizeAdjust() const;

View file

@ -32,7 +32,6 @@
*/ */
#include "BooksImageProvider.h" #include "BooksImageProvider.h"
#include "image/ZLQtImageManager.h"
#include "HarbourDebug.h" #include "HarbourDebug.h"
const QString BooksImageProvider::PROVIDER_ID("bookImage"); const QString BooksImageProvider::PROVIDER_ID("bookImage");
@ -66,21 +65,18 @@ void
BooksImageProvider::addImage( BooksImageProvider::addImage(
QObject* aOwner, QObject* aOwner,
QString aId, QString aId,
shared_ptr<ZLImageData> aData) QImage aImage)
{ {
if (aOwner && !aId.isEmpty() && !aData.isNull()) { if (aOwner && !aId.isEmpty() && !aImage.isNull()) {
QMutexLocker locker(&iMutex); QMutexLocker locker(&iMutex);
if (!iImageMap.contains(aId)) { QStringList ids = iOwnerMap.value(aOwner);
if (!iOwnerMap.contains(aOwner)) { if (ids.isEmpty()) {
connect(aOwner, SIGNAL(destroyed(QObject*)), connect(aOwner, SIGNAL(destroyed(QObject*)),
SLOT(releaseImages(QObject*))); SLOT(releaseImages(QObject*)));
} else {
QStringList ids = iOwnerMap.value(aOwner);
ids.append(aId);
iOwnerMap.insert(aOwner, ids);
}
} }
iImageMap.insert(aId, aData); ids.append(aId);
iOwnerMap.insert(aOwner, ids);
iImageMap.insert(aId, aImage);
} }
} }
@ -104,21 +100,16 @@ BooksImageProvider::requestImage(
const QSize& aRequestedSize) const QSize& aRequestedSize)
{ {
QMutexLocker locker(&iMutex); QMutexLocker locker(&iMutex);
shared_ptr<ZLImageData> ptr = iImageMap.value(aId); QImage image = iImageMap.value(aId);
if (!ptr.isNull()) { if (!image.isNull()) {
HDEBUG(aId); HDEBUG(aId << image.size());
const ZLQtImageData& data = (const ZLQtImageData&)*ptr; if (aRequestedSize.isEmpty() || image.size() == aRequestedSize) {
const QImage* image = data.image(); *aSize = image.size();
HASSERT(image); return image;
if (image) { } else {
if (aRequestedSize.isEmpty() || image->size() == aRequestedSize) { *aSize = aRequestedSize;
*aSize = image->size(); return image.scaled(aRequestedSize, Qt::IgnoreAspectRatio,
return *image; Qt::SmoothTransformation);
} else {
*aSize = aRequestedSize;
return image->scaled(aRequestedSize, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
} }
} else { } else {
HWARN("No such image:" << aId); HWARN("No such image:" << aId);

View file

@ -34,8 +34,6 @@
#ifndef BOOKS_IMAGE_PROVIDER_H #ifndef BOOKS_IMAGE_PROVIDER_H
#define BOOKS_IMAGE_PROVIDER_H #define BOOKS_IMAGE_PROVIDER_H
#include "ZLImageManager.h"
#include <QImage> #include <QImage>
#include <QMutex> #include <QMutex>
#include <QQuickImageProvider> #include <QQuickImageProvider>
@ -51,7 +49,7 @@ public:
BooksImageProvider(QObject* aParent = NULL); BooksImageProvider(QObject* aParent = NULL);
virtual ~BooksImageProvider(); virtual ~BooksImageProvider();
void addImage(QObject* aOwner, QString aId, shared_ptr<ZLImageData> aData); void addImage(QObject* aOwner, QString aId, QImage aImage);
virtual QImage requestImage(const QString& aId, QSize* aSize, virtual QImage requestImage(const QString& aId, QSize* aSize,
const QSize& aRequestedSize); const QSize& aRequestedSize);
@ -60,7 +58,7 @@ public Q_SLOTS:
private: private:
QMutex iMutex; QMutex iMutex;
QHash<QString, shared_ptr<ZLImageData> > iImageMap; QHash<QString, QImage> iImageMap;
QHash<QObject*, QStringList> iOwnerMap; QHash<QObject*, QStringList> iOwnerMap;
static BooksImageProvider* gInstance; static BooksImageProvider* gInstance;
}; };

View file

@ -37,12 +37,15 @@
#include "BooksDefs.h" #include "BooksDefs.h"
#include "bookmodel/FBTextKind.h" #include "bookmodel/FBTextKind.h"
#include "image/ZLQtImageManager.h"
#include "ZLStringUtil.h" #include "ZLStringUtil.h"
#include "HarbourDebug.h" #include "HarbourDebug.h"
#include <QPainter> #include <QPainter>
static const QString IMAGE_URL("image://%1/%2");
// ========================================================================== // ==========================================================================
// BooksPageWidget::Data // BooksPageWidget::Data
// ========================================================================== // ==========================================================================
@ -141,21 +144,110 @@ public:
shared_ptr<BooksPageWidget::Data> iData; shared_ptr<BooksPageWidget::Data> iData;
int iWidth; int iWidth;
int iHeight; int iHeight;
shared_ptr<QImage> iImage; QImage iImage;
}; };
void BooksPageWidget::RenderTask::performTask() void BooksPageWidget::RenderTask::performTask()
{ {
if (!isCanceled() && !iData.isNull() && !iData->iView.isNull() && if (!isCanceled() && !iData.isNull() && !iData->iView.isNull() &&
iWidth > 0 && iHeight > 0) { iWidth > 0 && iHeight > 0) {
iImage = new QImage(iWidth, iHeight, QImage::Format_RGB32); iImage = QImage(iWidth, iHeight, QImage::Format_RGB32);
if (!isCanceled()) { if (!isCanceled()) {
QPainter painter(&*iImage); QPainter painter(&iImage);
iData->paint(&painter); iData->paint(&painter);
} }
} }
} }
// ==========================================================================
// BooksPageWidget::FootnoteTask
// ==========================================================================
class BooksPageWidget::FootnoteTask : public BooksTask, ZLTextArea::Properties {
public:
FootnoteTask(int aX, int aY, int aMaxWidth, int aMaxHeight,
QString aPath, QString aLinkText, QString aRef,
shared_ptr<ZLTextModel> aTextModel, shared_ptr<ZLTextStyle> aTextStyle,
bool aInvertColors) :
iTextModel(aTextModel), iTextStyle(aTextStyle),
iInvertColors(aInvertColors), iX(aX), iY(aY),
iMaxWidth(aMaxWidth), iMaxHeight(aMaxHeight),
iRef(aRef), iLinkText(aLinkText), iPath(aPath) {}
~FootnoteTask();
void performTask();
// ZLTextArea::Properties
shared_ptr<ZLTextStyle> baseStyle() const;
ZLColor color(const std::string& aStyle) const;
bool isSelectionEnabled() const;
public:
shared_ptr<ZLTextModel> iTextModel;
shared_ptr<ZLTextStyle> iTextStyle;
bool iInvertColors;
int iX;
int iY;
int iMaxWidth;
int iMaxHeight;
QString iRef;
QString iLinkText;
QString iPath;
QImage iImage;
};
BooksPageWidget::FootnoteTask::~FootnoteTask()
{
}
shared_ptr<ZLTextStyle> BooksPageWidget::FootnoteTask::baseStyle() const
{
return iTextStyle;
}
ZLColor BooksPageWidget::FootnoteTask::color(const std::string& aStyle) const
{
return BooksPaintContext::realColor(aStyle, iInvertColors);
}
bool BooksPageWidget::FootnoteTask::isSelectionEnabled() const
{
return false;
}
void BooksPageWidget::FootnoteTask::performTask()
{
if (!isCanceled()) {
// Determine the size of the footnote canvas
ZLTextParagraphCursorCache cache;
BooksPaintContext sizeContext(iMaxWidth, iMaxHeight);
ZLTextAreaController sizeController(sizeContext, *this, &cache);
ZLSize size;
sizeController.setModel(iTextModel);
sizeController.preparePaintInfo();
sizeController.area().paint(&size);
if (!size.isEmpty() && !isCanceled()) {
// Now actually paint it
size.myWidth = (size.myWidth + 3) & -4;
HDEBUG("footnote size:" << size.myWidth << "x" << size.myHeight);
cache.clear();
BooksPaintContext paintContext(size.myWidth, size.myHeight);
paintContext.setInvertColors(iInvertColors);
ZLTextAreaController paintController(paintContext, *this, &cache);
iImage = QImage(size.myWidth, size.myHeight, QImage::Format_RGB32);
QPainter painter(&iImage);
paintContext.beginPaint(&painter);
paintContext.clear(iInvertColors ?
BooksTextView::INVERTED_BACKGROUND :
BooksTextView::DEFAULT_BACKGROUND);
paintController.setModel(iTextModel);
paintController.preparePaintInfo();
paintController.area().paint();
paintContext.endPaint();
}
}
}
// ========================================================================== // ==========================================================================
// BooksPageWidget::PressTask // BooksPageWidget::PressTask
// ========================================================================== // ==========================================================================
@ -166,6 +258,7 @@ public:
iData(aData), iX(aX), iY(aY), iKind(REGULAR) {} iData(aData), iX(aX), iY(aY), iKind(REGULAR) {}
void performTask(); void performTask();
QString getLinkText(ZLTextWordCursor& aCursor);
public: public:
shared_ptr<BooksPageWidget::Data> iData; shared_ptr<BooksPageWidget::Data> iData;
@ -176,9 +269,27 @@ public:
std::string iLink; std::string iLink;
std::string iLinkType; std::string iLinkType;
std::string iImageId; std::string iImageId;
shared_ptr<ZLImageData> iImageData; QString iLinkText;
QImage iImage;
}; };
QString BooksPageWidget::PressTask::getLinkText(ZLTextWordCursor& aCursor)
{
QString text;
while (!aCursor.isEndOfParagraph() && !isCanceled() &&
aCursor.element().kind() != ZLTextElement::WORD_ELEMENT) {
aCursor.nextWord();
}
while (!aCursor.isEndOfParagraph() && !isCanceled() &&
aCursor.element().kind() == ZLTextElement::WORD_ELEMENT) {
const ZLTextWord& word = (ZLTextWord&)aCursor.element();
if (!text.isEmpty()) text.append(' ');
text.append(QString::fromUtf8(word.Data, word.Size));
aCursor.nextWord();
}
return text;
}
void BooksPageWidget::PressTask::performTask() void BooksPageWidget::PressTask::performTask()
{ {
if (!isCanceled()) { if (!isCanceled()) {
@ -219,10 +330,12 @@ void BooksPageWidget::PressTask::performTask()
if (entry.isStart() && !stopped[entry.kind()]) { if (entry.isStart() && !stopped[entry.kind()]) {
const ZLTextHyperlinkControlEntry& link = const ZLTextHyperlinkControlEntry& link =
(ZLTextHyperlinkControlEntry&) entry; (ZLTextHyperlinkControlEntry&) entry;
iKind = link.kind(); iKind = kind;
iLink = link.label(); iLink = link.label();
iLinkType = link.hyperlinkType(); iLinkType = link.hyperlinkType();
HDEBUG("link" << kind << iLink.c_str()); iLinkText = getLinkText(cursor);
HDEBUG("link" << kind << iLinkText <<
iLink.c_str());
} }
return; return;
} }
@ -237,11 +350,17 @@ void BooksPageWidget::PressTask::performTask()
if (element.kind() == ZLTextElement::IMAGE_ELEMENT) { if (element.kind() == ZLTextElement::IMAGE_ELEMENT) {
const ZLTextImageElement& imageElement = const ZLTextImageElement& imageElement =
(const ZLTextImageElement&)element; (const ZLTextImageElement&)element;
iKind = IMAGE; shared_ptr<ZLImageData> data = imageElement.image();
iImageId = imageElement.id(); if (!data.isNull()) {
iImageData = imageElement.image(); const QImage* image = ((ZLQtImageData&)(*data)).image();
HDEBUG("image element" << iImageId.c_str() << if (image && !image->isNull()) {
iImageData->width() << iImageData->height()); iKind = IMAGE;
iImage = *image;
iImageId = imageElement.id();
HDEBUG("image element" << iImageId.c_str() <<
iImage.width() << iImage.height());
}
}
} }
} }
} }
@ -263,6 +382,7 @@ BooksPageWidget::BooksPageWidget(QQuickItem* aParent) :
iRenderTask(NULL), iRenderTask(NULL),
iPressTask(NULL), iPressTask(NULL),
iLongPressTask(NULL), iLongPressTask(NULL),
iFootnoteTask(NULL),
iEmpty(false), iEmpty(false),
iPage(-1) iPage(-1)
{ {
@ -287,6 +407,7 @@ BooksPageWidget::~BooksPageWidget()
if (iRenderTask) iRenderTask->release(this); if (iRenderTask) iRenderTask->release(this);
if (iPressTask) iPressTask->release(this); if (iPressTask) iPressTask->release(this);
if (iLongPressTask) iLongPressTask->release(this); if (iLongPressTask) iLongPressTask->release(this);
if (iFootnoteTask) iFootnoteTask->release(this);
} }
void BooksPageWidget::setModel(BooksBookModel* aModel) void BooksPageWidget::setModel(BooksBookModel* aModel)
@ -443,7 +564,7 @@ void BooksPageWidget::paint(QPainter* aPainter)
{ {
if (!iImage.isNull()) { if (!iImage.isNull()) {
HDEBUG("page" << iPage); HDEBUG("page" << iPage);
aPainter->drawImage(0, 0, *iImage); aPainter->drawImage(0, 0, iImage);
iEmpty = false; iEmpty = false;
} else if (iPage >= 0 && iPageMark.valid() && !iData.isNull()) { } else if (iPage >= 0 && iPageMark.valid() && !iData.isNull()) {
if (!iRenderTask) { if (!iRenderTask) {
@ -471,6 +592,18 @@ void BooksPageWidget::resetView()
iResetTask->release(this); iResetTask->release(this);
iResetTask = NULL; iResetTask = NULL;
} }
if (iPressTask) {
iPressTask->release(this);
iPressTask = NULL;
}
if (iLongPressTask) {
iLongPressTask->release(this);
iLongPressTask = NULL;
}
if (iFootnoteTask) {
iFootnoteTask->release(this);
iFootnoteTask = NULL;
}
iData.reset(); iData.reset();
if (iPage >= 0 && iPageMark.valid() && if (iPage >= 0 && iPageMark.valid() &&
width() > 0 && height() > 0 && iModel) { width() > 0 && height() > 0 && iModel) {
@ -547,6 +680,31 @@ void BooksPageWidget::onPressTaskDone()
task->release(this); task->release(this);
} }
void BooksPageWidget::onFootnoteTaskDone()
{
HASSERT(sender() == iFootnoteTask);
FootnoteTask* task = iFootnoteTask;
iFootnoteTask = NULL;
if (!task->iImage.isNull()) {
// Footnotes with normal and inverted background need to
// have different ids so that the cached image with the wrong
// background doesn't show up after we invert the colors
static const QString NORMAL("n");
static const QString INVERTED("i");
static const QString FOOTNOTE_ID("footnote/%1#%2?p=%3&t=%4&s=%5x%6");
QString id = FOOTNOTE_ID.arg(task->iPath, task->iRef).
arg(iPage).arg(task->iInvertColors ? INVERTED : NORMAL).
arg(task->iImage.width()).arg(task->iImage.height());
QString url = IMAGE_URL.arg(BooksImageProvider::PROVIDER_ID, id);
HDEBUG(url);
BooksImageProvider::instance()->addImage(iModel, id, task->iImage);
Q_EMIT showFootnote(task->iX, task->iY, task->iLinkText, url);
}
task->release(this);
}
void BooksPageWidget::onLongPressTaskDone() void BooksPageWidget::onLongPressTaskDone()
{ {
HASSERT(sender() == iLongPressTask); HASSERT(sender() == iLongPressTask);
@ -571,28 +729,46 @@ void BooksPageWidget::onLongPressTaskDone()
Q_EMIT jumpToPage(page); Q_EMIT jumpToPage(page);
} }
} }
} else if (task->iKind == FOOTNOTE) {
if (iModel && task->iLink.length() > 0) {
std::string ref = task->iLink.substr(1);
shared_ptr<ZLTextModel> note = iModel->footnoteModel(ref);
BooksBook* book = iModel->book();
if (!note.isNull() && book) {
// Render the footnote
HDEBUG("footnote" << ref.c_str());
if (iFootnoteTask) iFootnoteTask->release(this);
iFootnoteTask = new FootnoteTask(task->iX, task->iY,
width()*3/4, height()*10, book->path(), task->iLinkText,
QString::fromStdString(ref), note, iTextStyle,
iSettings->invertColors());
iTaskQueue->submit(iFootnoteTask, this,
SLOT(onFootnoteTaskDone()));
}
}
} else if (task->iKind == IMAGE) { } else if (task->iKind == IMAGE) {
// Make sure that the book path is mixed into the image id to handle // Make sure that the book path is mixed into the image id to handle
// the case of different books having images with identical ids // the case of different books having images with identical ids
QString id = QString::fromStdString(task->iImageId); QString imageId = QString::fromStdString(task->iImageId);
QString path; QString path;
if (iModel) { if (iModel) {
BooksBook* book = iModel->book(); BooksBook* book = iModel->book();
if (book) { if (book) {
path = book->path(); path = book->path();
if (!path.isEmpty()) { if (!path.isEmpty()) {
if (!id.contains(path)) { if (!imageId.contains(path)) {
QString old = id; QString old = imageId;
id = path + ":" + old; imageId = path + ":" + old;
HDEBUG(old << "-> " << id); HDEBUG(old << "-> " << imageId);
} }
} }
} }
} }
static const QString IMAGE_URL("image://%1/%2"); static const QString IMAGE_ID("image/%1");
QString url = IMAGE_URL.arg(BooksImageProvider::PROVIDER_ID, id); QString id = IMAGE_ID.arg(imageId);
BooksImageProvider::instance()->addImage(iModel, id, task->iImageData); BooksImageProvider::instance()->addImage(iModel, id, task->iImage);
Q_EMIT imagePressed(url, task->iRect); Q_EMIT imagePressed(IMAGE_URL.arg(BooksImageProvider::PROVIDER_ID, id),
task->iRect);
} }
task->release(this); task->release(this);
@ -601,7 +777,7 @@ void BooksPageWidget::onLongPressTaskDone()
void BooksPageWidget::updateSize() void BooksPageWidget::updateSize()
{ {
HDEBUG("page" << iPage << QSize(width(), height())); HDEBUG("page" << iPage << QSize(width(), height()));
iImage.reset(); iImage = QImage();
resetView(); resetView();
} }

View file

@ -97,9 +97,10 @@ Q_SIGNALS:
void topMarginChanged(); void topMarginChanged();
void bottomMarginChanged(); void bottomMarginChanged();
void browserLinkPressed(QString url); void browserLinkPressed(QString url);
void imagePressed(QString url, QRect rect); void imagePressed(QString imageId, QRect rect);
void activeTouch(int x, int y); void activeTouch(int touchX, int touchY);
void jumpToPage(int page); void jumpToPage(int page);
void showFootnote(int touchX, int touchY, QString text, QString imageId);
private Q_SLOTS: private Q_SLOTS:
void onWidthChanged(); void onWidthChanged();
@ -115,6 +116,7 @@ private Q_SLOTS:
void onRenderTaskDone(); void onRenderTaskDone();
void onPressTaskDone(); void onPressTaskDone();
void onLongPressTaskDone(); void onLongPressTaskDone();
void onFootnoteTaskDone();
private: private:
void paint(QPainter *painter); void paint(QPainter *painter);
@ -128,6 +130,7 @@ private:
class ResetTask; class ResetTask;
class RenderTask; class RenderTask;
class PressTask; class PressTask;
class FootnoteTask;
QSharedPointer<BooksSettings> iSettings; QSharedPointer<BooksSettings> iSettings;
shared_ptr<BooksTaskQueue> iTaskQueue; shared_ptr<BooksTaskQueue> iTaskQueue;
@ -137,11 +140,12 @@ private:
BooksBookModel* iModel; BooksBookModel* iModel;
BooksMargins iMargins; BooksMargins iMargins;
shared_ptr<Data> iData; shared_ptr<Data> iData;
shared_ptr<QImage> iImage; QImage iImage;
ResetTask* iResetTask; ResetTask* iResetTask;
RenderTask* iRenderTask; RenderTask* iRenderTask;
PressTask* iPressTask; PressTask* iPressTask;
PressTask* iLongPressTask; PressTask* iLongPressTask;
FootnoteTask* iFootnoteTask;
bool iEmpty; bool iEmpty;
int iPage; int iPage;
}; };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015 Jolla Ltd. * Copyright (C) 2015-2016 Jolla Ltd.
* Contact: Slava Monich <slava.monich@jolla.com> * Contact: Slava Monich <slava.monich@jolla.com>
* *
* You may use this file under the terms of the BSD license as follows: * You may use this file under the terms of the BSD license as follows:
@ -36,6 +36,8 @@
#include "HarbourDebug.h" #include "HarbourDebug.h"
#include "ZLImage.h" #include "ZLImage.h"
#include "ZLTextStyle.h"
#include "ZLStringUtil.h"
#include "image/ZLQtImageManager.h" #include "image/ZLQtImageManager.h"
#include <QPainter> #include <QPainter>
@ -255,9 +257,45 @@ int BooksPaintContext::height() const
return iHeight; return iHeight;
} }
ZLColor BooksPaintContext::realColor(quint8 aRed, quint8 aGreen, quint8 aBlue) const ZLColor BooksPaintContext::realColor(quint8 aRed, quint8 aGreen, quint8 aBlue, bool aInvert)
{ {
return iInvertColors ? return aInvert ?
ZLColor(255-aRed, 255-aGreen, 255-aBlue) : ZLColor(255-aRed, 255-aGreen, 255-aBlue) :
ZLColor(aRed, aGreen, aBlue); ZLColor(aRed, aGreen, aBlue);
} }
ZLColor BooksPaintContext::realColor(const std::string& aStyle, bool aInvert)
{
static const std::string INTERNAL_HYPERLINK("internal");
static const std::string EXTERNAL_HYPERLINK("external");
static const std::string BOOK_HYPERLINK("book");
if (ZLStringUtil::startsWith(aStyle, '#')) {
if (aStyle.length() == 7) {
int i, value = 0;
for (i=1; i<7; i++) {
int nibble = ZLStringUtil::fromHex(aStyle[i]);
if (nibble >= 0) {
value <<= 4;
value |= nibble;
} else {
break;
}
}
if (i == 7) {
return realColor(ZLColor(value), aInvert);
}
}
} else if (aStyle == INTERNAL_HYPERLINK) {
return realColor(33, 96, 180, aInvert);
} else if (aStyle == EXTERNAL_HYPERLINK) {
return realColor(33, 96, 180, aInvert);
} else if (aStyle == BOOK_HYPERLINK) {
return realColor(23, 68, 128, aInvert);
} else if (aStyle == ZLTextStyle::SELECTION_BACKGROUND) {
return realColor(82, 131, 194, aInvert);
} else if (aStyle == ZLTextStyle::HIGHLIGHTED_TEXT) {
return realColor(60, 139, 255, aInvert);
}
return realColor(0, 0, 0, aInvert);
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015 Jolla Ltd. * Copyright (C) 2015-2016 Jolla Ltd.
* Contact: Slava Monich <slava.monich@jolla.com> * Contact: Slava Monich <slava.monich@jolla.com>
* *
* You may use this file under the terms of the BSD license as follows: * You may use this file under the terms of the BSD license as follows:
@ -88,6 +88,10 @@ public:
void drawFilledCircle(int x, int y, int r); void drawFilledCircle(int x, int y, int r);
void setInvertColors(bool aInvertColors); void setInvertColors(bool aInvertColors);
static ZLColor realColor(const std::string& aStyle, bool aInvertColors);
static ZLColor realColor(quint8 aRed, quint8 aGreen, quint8 aBlue, bool aInvert);
static ZLColor realColor(const ZLColor aColor, bool aInvert);
ZLColor realColor(const std::string& aStyle) const;
ZLColor realColor(quint8 aRed, quint8 aGreen, quint8 aBlue) const; ZLColor realColor(quint8 aRed, quint8 aGreen, quint8 aBlue) const;
ZLColor realColor(const ZLColor aColor) const; ZLColor realColor(const ZLColor aColor) const;
@ -116,6 +120,12 @@ inline QColor qtColor(const ZLColor& aColor)
{ return QColor(aColor.Red, aColor.Green, aColor.Blue); } { return QColor(aColor.Red, aColor.Green, aColor.Blue); }
inline ZLColor BooksPaintContext::realColor(const ZLColor aColor) const inline ZLColor BooksPaintContext::realColor(const ZLColor aColor) const
{ return realColor(aColor.Red, aColor.Green, aColor.Blue); } { return realColor(aColor.Red, aColor.Green, aColor.Blue); }
inline ZLColor BooksPaintContext::realColor(quint8 aRed, quint8 aGreen, quint8 aBlue) const
{ return realColor(aRed, aGreen, aBlue, iInvertColors); }
inline ZLColor BooksPaintContext::realColor(const ZLColor aColor, bool aInvert)
{ return realColor(aColor.Red, aColor.Green, aColor.Blue, aInvert); }
inline ZLColor BooksPaintContext::realColor(const std::string& aStyle) const
{ return realColor(aStyle, iInvertColors); }
inline void BooksPaintContext::setInvertColors(bool aInvertColors) inline void BooksPaintContext::setInvertColors(bool aInvertColors)
{ iInvertColors = aInvertColors; } { iInvertColors = aInvertColors; }

View file

@ -55,6 +55,7 @@ class BooksSettings : public QObject
Q_PROPERTY(QString removableRoot READ removableRoot NOTIFY removableRootChanged) Q_PROPERTY(QString removableRoot READ removableRoot NOTIFY removableRootChanged)
Q_PROPERTY(QColor primaryPageToolColor READ primaryPageToolColor CONSTANT) Q_PROPERTY(QColor primaryPageToolColor READ primaryPageToolColor CONSTANT)
Q_PROPERTY(QColor highlightPageToolColor READ highlightPageToolColor NOTIFY invertColorsChanged) Q_PROPERTY(QColor highlightPageToolColor READ highlightPageToolColor NOTIFY invertColorsChanged)
Q_PROPERTY(QColor invertedPageBackgroundColor READ highlightPageToolColor NOTIFY invertColorsChanged)
Q_PROPERTY(QColor pageBackgroundColor READ pageBackgroundColor NOTIFY pageBackgroundColorChanged) Q_PROPERTY(QColor pageBackgroundColor READ pageBackgroundColor NOTIFY pageBackgroundColorChanged)
Q_PROPERTY(int orientation READ orientation NOTIFY orientationChanged) Q_PROPERTY(int orientation READ orientation NOTIFY orientationChanged)
class TextStyle; class TextStyle;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015 Jolla Ltd. * Copyright (C) 2015-2016 Jolla Ltd.
* Contact: Slava Monich <slava.monich@jolla.com> * Contact: Slava Monich <slava.monich@jolla.com>
* *
* You may use this file under the terms of the BSD license as follows: * You may use this file under the terms of the BSD license as follows:
@ -34,8 +34,6 @@
#include "BooksTextView.h" #include "BooksTextView.h"
#include "BooksTextStyle.h" #include "BooksTextStyle.h"
#include "ZLStringUtil.h"
#define SUPER ZLTextView #define SUPER ZLTextView
const ZLColor BooksTextView::DEFAULT_BACKGROUND(255, 255, 255); const ZLColor BooksTextView::DEFAULT_BACKGROUND(255, 255, 255);
@ -87,40 +85,9 @@ ZLColor BooksTextView::backgroundColor() const
return iPaintContext.realColor(DEFAULT_BACKGROUND); return iPaintContext.realColor(DEFAULT_BACKGROUND);
} }
ZLColor BooksTextView::color(const std::string &aStyle) const ZLColor BooksTextView::color(const std::string& aStyle) const
{ {
static const std::string INTERNAL_HYPERLINK("internal"); return iPaintContext.realColor(aStyle);
static const std::string EXTERNAL_HYPERLINK("external");
static const std::string BOOK_HYPERLINK("book");
if (ZLStringUtil::startsWith(aStyle, '#')) {
if (aStyle.length() == 7) {
int i, value = 0;
for (i=1; i<7; i++) {
int nibble = ZLStringUtil::fromHex(aStyle[i]);
if (nibble >= 0) {
value <<= 4;
value |= nibble;
} else {
break;
}
}
if (i == 7) {
return iPaintContext.realColor(ZLColor(value));
}
}
} else if (aStyle == INTERNAL_HYPERLINK) {
return iPaintContext.realColor(33, 96, 180);
} else if (aStyle == EXTERNAL_HYPERLINK) {
return iPaintContext.realColor(33, 96, 180);
} else if (aStyle == BOOK_HYPERLINK) {
return iPaintContext.realColor(23, 68, 128);
} else if (aStyle == ZLTextStyle::SELECTION_BACKGROUND) {
return iPaintContext.realColor(82, 131, 194);
} else if (aStyle == ZLTextStyle::HIGHLIGHTED_TEXT) {
return iPaintContext.realColor(60, 139, 255);
}
return iPaintContext.realColor(0, 0, 0);
} }
shared_ptr<ZLTextStyle> BooksTextView::baseStyle() const shared_ptr<ZLTextStyle> BooksTextView::baseStyle() const