[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="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="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="18" partial="true" name="Strong" bold="true"/>
<style id="19" partial="true" name="Subscript" fontSizeDelta="-4" vShift="-4" allowHyphenations="false"/>

View file

@ -42,6 +42,7 @@ SilicaFlickable {
signal closeBook()
signal pageClicked(var page)
property int orientation: Orientation.Portrait
property int _currentPage: bookListWatcher.currentIndex
property bool _loading: minLoadingDelay.running || bookModel.loading
property var _currentState: _visibilityStates[Settings.pageDetails % _visibilityStates.length]
@ -63,22 +64,33 @@ SilicaFlickable {
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 imageView
property var footnoteView
onOrientationChanged: {
if (footnoteView) {
footnoteView.cancel()
}
}
Component {
id: linkMenuComponent
BooksLinkMenu {
}
BooksLinkMenu { }
}
Component {
id: imageViewComponent
BooksImageView {
imageBackground: Settings.pageBackgroundColor
BooksImageView { }
}
Component {
id: footnoteViewComponent
BooksFootnoteView { }
}
PullDownMenu {
@ -177,8 +189,15 @@ SilicaFlickable {
if (!linkMenu) {
linkMenu = linkMenuComponent.createObject(root)
}
linkMenu.url = url
linkMenu.show()
linkMenu.show(url)
}
}
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
color: Theme.rgba(Theme.highlightDimmerColor, 0.9)
property alias imageBackground: background.color
readonly property real maxImageWidth: width - 2*Theme.horizontalPageMargin
readonly property real maxImageHeight: height - 2*Theme.paddingLarge
readonly property real finalImageWidth: Math.ceil(image.landscape ? maxImageWidth : (maxImageHeight * image.sourceSize.width / image.sourceSize.height))
@ -53,6 +52,7 @@ Rectangle {
Rectangle {
id: background
anchors.fill: image
color: Settings.pageBackgroundColor
}
Image {

View file

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

View file

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

View file

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

View file

@ -348,6 +348,15 @@ shared_ptr<ZLTextModel> BooksBookModel::bookTextModel() const
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> model;

View file

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

View file

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

View file

@ -34,8 +34,6 @@
#ifndef BOOKS_IMAGE_PROVIDER_H
#define BOOKS_IMAGE_PROVIDER_H
#include "ZLImageManager.h"
#include <QImage>
#include <QMutex>
#include <QQuickImageProvider>
@ -51,7 +49,7 @@ public:
BooksImageProvider(QObject* aParent = NULL);
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,
const QSize& aRequestedSize);
@ -60,7 +58,7 @@ public Q_SLOTS:
private:
QMutex iMutex;
QHash<QString, shared_ptr<ZLImageData> > iImageMap;
QHash<QString, QImage> iImageMap;
QHash<QObject*, QStringList> iOwnerMap;
static BooksImageProvider* gInstance;
};

View file

@ -37,12 +37,15 @@
#include "BooksDefs.h"
#include "bookmodel/FBTextKind.h"
#include "image/ZLQtImageManager.h"
#include "ZLStringUtil.h"
#include "HarbourDebug.h"
#include <QPainter>
static const QString IMAGE_URL("image://%1/%2");
// ==========================================================================
// BooksPageWidget::Data
// ==========================================================================
@ -141,21 +144,110 @@ public:
shared_ptr<BooksPageWidget::Data> iData;
int iWidth;
int iHeight;
shared_ptr<QImage> iImage;
QImage iImage;
};
void BooksPageWidget::RenderTask::performTask()
{
if (!isCanceled() && !iData.isNull() && !iData->iView.isNull() &&
iWidth > 0 && iHeight > 0) {
iImage = new QImage(iWidth, iHeight, QImage::Format_RGB32);
iImage = QImage(iWidth, iHeight, QImage::Format_RGB32);
if (!isCanceled()) {
QPainter painter(&*iImage);
QPainter painter(&iImage);
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
// ==========================================================================
@ -166,6 +258,7 @@ public:
iData(aData), iX(aX), iY(aY), iKind(REGULAR) {}
void performTask();
QString getLinkText(ZLTextWordCursor& aCursor);
public:
shared_ptr<BooksPageWidget::Data> iData;
@ -176,9 +269,27 @@ public:
std::string iLink;
std::string iLinkType;
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()
{
if (!isCanceled()) {
@ -219,10 +330,12 @@ void BooksPageWidget::PressTask::performTask()
if (entry.isStart() && !stopped[entry.kind()]) {
const ZLTextHyperlinkControlEntry& link =
(ZLTextHyperlinkControlEntry&) entry;
iKind = link.kind();
iKind = kind;
iLink = link.label();
iLinkType = link.hyperlinkType();
HDEBUG("link" << kind << iLink.c_str());
iLinkText = getLinkText(cursor);
HDEBUG("link" << kind << iLinkText <<
iLink.c_str());
}
return;
}
@ -237,11 +350,17 @@ void BooksPageWidget::PressTask::performTask()
if (element.kind() == ZLTextElement::IMAGE_ELEMENT) {
const ZLTextImageElement& imageElement =
(const ZLTextImageElement&)element;
shared_ptr<ZLImageData> data = imageElement.image();
if (!data.isNull()) {
const QImage* image = ((ZLQtImageData&)(*data)).image();
if (image && !image->isNull()) {
iKind = IMAGE;
iImage = *image;
iImageId = imageElement.id();
iImageData = imageElement.image();
HDEBUG("image element" << iImageId.c_str() <<
iImageData->width() << iImageData->height());
iImage.width() << iImage.height());
}
}
}
}
}
@ -263,6 +382,7 @@ BooksPageWidget::BooksPageWidget(QQuickItem* aParent) :
iRenderTask(NULL),
iPressTask(NULL),
iLongPressTask(NULL),
iFootnoteTask(NULL),
iEmpty(false),
iPage(-1)
{
@ -287,6 +407,7 @@ BooksPageWidget::~BooksPageWidget()
if (iRenderTask) iRenderTask->release(this);
if (iPressTask) iPressTask->release(this);
if (iLongPressTask) iLongPressTask->release(this);
if (iFootnoteTask) iFootnoteTask->release(this);
}
void BooksPageWidget::setModel(BooksBookModel* aModel)
@ -443,7 +564,7 @@ void BooksPageWidget::paint(QPainter* aPainter)
{
if (!iImage.isNull()) {
HDEBUG("page" << iPage);
aPainter->drawImage(0, 0, *iImage);
aPainter->drawImage(0, 0, iImage);
iEmpty = false;
} else if (iPage >= 0 && iPageMark.valid() && !iData.isNull()) {
if (!iRenderTask) {
@ -471,6 +592,18 @@ void BooksPageWidget::resetView()
iResetTask->release(this);
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();
if (iPage >= 0 && iPageMark.valid() &&
width() > 0 && height() > 0 && iModel) {
@ -547,6 +680,31 @@ void BooksPageWidget::onPressTaskDone()
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()
{
HASSERT(sender() == iLongPressTask);
@ -571,28 +729,46 @@ void BooksPageWidget::onLongPressTaskDone()
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) {
// Make sure that the book path is mixed into the image id to handle
// the case of different books having images with identical ids
QString id = QString::fromStdString(task->iImageId);
QString imageId = QString::fromStdString(task->iImageId);
QString path;
if (iModel) {
BooksBook* book = iModel->book();
if (book) {
path = book->path();
if (!path.isEmpty()) {
if (!id.contains(path)) {
QString old = id;
id = path + ":" + old;
HDEBUG(old << "-> " << id);
if (!imageId.contains(path)) {
QString old = imageId;
imageId = path + ":" + old;
HDEBUG(old << "-> " << imageId);
}
}
}
}
static const QString IMAGE_URL("image://%1/%2");
QString url = IMAGE_URL.arg(BooksImageProvider::PROVIDER_ID, id);
BooksImageProvider::instance()->addImage(iModel, id, task->iImageData);
Q_EMIT imagePressed(url, task->iRect);
static const QString IMAGE_ID("image/%1");
QString id = IMAGE_ID.arg(imageId);
BooksImageProvider::instance()->addImage(iModel, id, task->iImage);
Q_EMIT imagePressed(IMAGE_URL.arg(BooksImageProvider::PROVIDER_ID, id),
task->iRect);
}
task->release(this);
@ -601,7 +777,7 @@ void BooksPageWidget::onLongPressTaskDone()
void BooksPageWidget::updateSize()
{
HDEBUG("page" << iPage << QSize(width(), height()));
iImage.reset();
iImage = QImage();
resetView();
}

View file

@ -97,9 +97,10 @@ Q_SIGNALS:
void topMarginChanged();
void bottomMarginChanged();
void browserLinkPressed(QString url);
void imagePressed(QString url, QRect rect);
void activeTouch(int x, int y);
void imagePressed(QString imageId, QRect rect);
void activeTouch(int touchX, int touchY);
void jumpToPage(int page);
void showFootnote(int touchX, int touchY, QString text, QString imageId);
private Q_SLOTS:
void onWidthChanged();
@ -115,6 +116,7 @@ private Q_SLOTS:
void onRenderTaskDone();
void onPressTaskDone();
void onLongPressTaskDone();
void onFootnoteTaskDone();
private:
void paint(QPainter *painter);
@ -128,6 +130,7 @@ private:
class ResetTask;
class RenderTask;
class PressTask;
class FootnoteTask;
QSharedPointer<BooksSettings> iSettings;
shared_ptr<BooksTaskQueue> iTaskQueue;
@ -137,11 +140,12 @@ private:
BooksBookModel* iModel;
BooksMargins iMargins;
shared_ptr<Data> iData;
shared_ptr<QImage> iImage;
QImage iImage;
ResetTask* iResetTask;
RenderTask* iRenderTask;
PressTask* iPressTask;
PressTask* iLongPressTask;
FootnoteTask* iFootnoteTask;
bool iEmpty;
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>
*
* You may use this file under the terms of the BSD license as follows:
@ -36,6 +36,8 @@
#include "HarbourDebug.h"
#include "ZLImage.h"
#include "ZLTextStyle.h"
#include "ZLStringUtil.h"
#include "image/ZLQtImageManager.h"
#include <QPainter>
@ -255,9 +257,45 @@ int BooksPaintContext::height() const
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(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>
*
* 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 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(const ZLColor aColor) const;
@ -116,6 +120,12 @@ inline QColor qtColor(const ZLColor& aColor)
{ return QColor(aColor.Red, aColor.Green, aColor.Blue); }
inline ZLColor BooksPaintContext::realColor(const ZLColor aColor) const
{ 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)
{ iInvertColors = aInvertColors; }

View file

@ -55,6 +55,7 @@ class BooksSettings : public QObject
Q_PROPERTY(QString removableRoot READ removableRoot NOTIFY removableRootChanged)
Q_PROPERTY(QColor primaryPageToolColor READ primaryPageToolColor CONSTANT)
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(int orientation READ orientation NOTIFY orientationChanged)
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>
*
* You may use this file under the terms of the BSD license as follows:
@ -34,8 +34,6 @@
#include "BooksTextView.h"
#include "BooksTextStyle.h"
#include "ZLStringUtil.h"
#define SUPER ZLTextView
const ZLColor BooksTextView::DEFAULT_BACKGROUND(255, 255, 255);
@ -89,38 +87,7 @@ ZLColor BooksTextView::backgroundColor() const
ZLColor BooksTextView::color(const std::string& aStyle) const
{
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 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);
return iPaintContext.realColor(aStyle);
}
shared_ptr<ZLTextStyle> BooksTextView::baseStyle() const