[app] Support for footnotes
This commit is contained in:
parent
35757ce03d
commit
e3b0e1c453
17 changed files with 527 additions and 112 deletions
|
@ -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"/>
|
||||
|
|
|
@ -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
|
||||
|
||||
Component {
|
||||
id: linkMenuComponent
|
||||
BooksLinkMenu {
|
||||
onOrientationChanged: {
|
||||
if (footnoteView) {
|
||||
footnoteView.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: linkMenuComponent
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
198
app/qml/BooksFootnoteView.qml
Normal file
198
app/qml/BooksFootnoteView.qml
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {} }
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)) {
|
||||
connect(aOwner, SIGNAL(destroyed(QObject*)),
|
||||
SLOT(releaseImages(QObject*)));
|
||||
} else {
|
||||
QStringList ids = iOwnerMap.value(aOwner);
|
||||
ids.append(aId);
|
||||
iOwnerMap.insert(aOwner, ids);
|
||||
}
|
||||
QStringList ids = iOwnerMap.value(aOwner);
|
||||
if (ids.isEmpty()) {
|
||||
connect(aOwner, SIGNAL(destroyed(QObject*)),
|
||||
SLOT(releaseImages(QObject*)));
|
||||
}
|
||||
iImageMap.insert(aId, aData);
|
||||
ids.append(aId);
|
||||
iOwnerMap.insert(aOwner, ids);
|
||||
iImageMap.insert(aId, aImage);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,21 +100,16 @@ 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;
|
||||
} else {
|
||||
*aSize = aRequestedSize;
|
||||
return image->scaled(aRequestedSize, Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
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,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
} else {
|
||||
HWARN("No such image:" << aId);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
iKind = IMAGE;
|
||||
iImageId = imageElement.id();
|
||||
iImageData = imageElement.image();
|
||||
HDEBUG("image element" << iImageId.c_str() <<
|
||||
iImageData->width() << iImageData->height());
|
||||
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();
|
||||
HDEBUG("image element" << iImageId.c_str() <<
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
@ -87,40 +85,9 @@ ZLColor BooksTextView::backgroundColor() const
|
|||
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");
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue