[app] Support for internal hyperlinks

This commit is contained in:
Slava Monich 2016-10-16 20:07:03 +03:00
parent cd3194e1a2
commit b2d01710e8
8 changed files with 303 additions and 21 deletions

View file

@ -160,6 +160,7 @@ SilicaFlickable {
titleVisible: _currentState.title titleVisible: _currentState.title
pageNumberVisible: _currentState.page pageNumberVisible: _currentState.page
title: bookModel.title title: bookModel.title
onJumpToPage: bookView.jumpTo(page)
onPageClicked: { onPageClicked: {
root.pageClicked(index) root.pageClicked(index)
globalSettings.pageDetails = (globalSettings.pageDetails+ 1) % _visibilityStates.length globalSettings.pageDetails = (globalSettings.pageDetails+ 1) % _visibilityStates.length

View file

@ -52,6 +52,7 @@ Item {
signal pageClicked() signal pageClicked()
signal imagePressed(var url, var rect) signal imagePressed(var url, var rect)
signal browserLinkPressed(var url) signal browserLinkPressed(var url)
signal jumpToPage(var page)
PageWidget { PageWidget {
id: widget id: widget
@ -60,6 +61,8 @@ Item {
model: bookModel model: bookModel
onBrowserLinkPressed: view.browserLinkPressed(url) onBrowserLinkPressed: view.browserLinkPressed(url)
onImagePressed: view.imagePressed(url, rect) onImagePressed: view.imagePressed(url, rect)
onActiveTouch: pressImage.animate(x, y)
onJumpToPage: view.jumpToPage(page)
} }
BooksTitleLabel { BooksTitleLabel {
@ -86,6 +89,54 @@ Item {
Behavior on opacity {} Behavior on opacity {}
} }
Image {
id: pressImage
source: globalSettings.invertColors ? "images/press-invert.svg" : "images/press.svg"
visible: opacity > 0
opacity: 0
readonly property int maxsize: Math.max(view.width, view.height)
ParallelAnimation {
id: pressAnimation
NumberAnimation {
target: pressImage
easing.type: Easing.InOutQuad
properties: "width,height"
from: pressImage.sourceSize.width
to: pressImage.maxsize
}
NumberAnimation {
id: pressAnimationX
target: pressImage
easing.type: Easing.InOutQuad
properties: "x"
}
NumberAnimation {
id: pressAnimationY
target: pressImage
easing.type: Easing.InOutQuad
properties: "y"
}
NumberAnimation {
target: pressImage
easing.type: Easing.InOutQuad
properties: "opacity"
from: 0.5
to: 0
}
}
function animate(x0, y0) {
pressAnimation.stop();
opacity = 0
width = pressImage.sourceSize.width
height = pressImage.sourceSize.height
pressAnimationX.from = x = x0 - Math.round(width/2)
pressAnimationY.from = y = y0 - Math.round(height/2)
pressAnimationX.to = x0 - maxsize/2
pressAnimationY.to = y0 - maxsize/2
pressAnimation.start()
}
}
Label { Label {
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
@ -104,6 +155,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: view.pageClicked() onClicked: view.pageClicked()
onPressed: widget.handlePress(mouseX, mouseY)
onPressAndHold: widget.handleLongPress(mouseX, mouseY) onPressAndHold: widget.handleLongPress(mouseX, mouseY)
} }
} }

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100"
height="100"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="press-invert.svg">
<defs
id="defs4">
<linearGradient
id="linearGradient3771">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3773" />
<stop
id="stop3781"
offset="0.5"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop3779"
offset="0.69732136"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop3775" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3771"
id="radialGradient3777"
cx="51.42857"
cy="53.57143"
fx="51.42857"
fy="53.57143"
r="30"
gradientTransform="matrix(1,0,0,1.0119048,0,-0.63775609)"
gradientUnits="userSpaceOnUse" />
<filter
inkscape:collect="always"
id="filter3803">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.5089022"
id="feGaussianBlur3805" />
</filter>
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-952.36218)">
<path
sodipodi:type="arc"
style="fill:url(#radialGradient3777);fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:6;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;opacity:0.50000000000000000;filter:url(#filter3803)"
id="path2985"
sodipodi:cx="51.42857"
sodipodi:cy="53.57143"
sodipodi:rx="30"
sodipodi:ry="30.357143"
d="m 81.42857,53.57143 a 30,30.357143 0 1 1 -60,0 30,30.357143 0 1 1 60,0 z"
transform="matrix(1.6666667,0,0,1.6470588,-35.714283,914.12689)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

88
app/qml/images/press.svg Normal file
View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100"
height="100"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="press">
<defs
id="defs4">
<linearGradient
id="linearGradient3771">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3773" />
<stop
id="stop3781"
offset="0.5"
style="stop-color:#000000;stop-opacity:1;" />
<stop
id="stop3779"
offset="0.7"
style="stop-color:#000000;stop-opacity:1;" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3775" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3771"
id="radialGradient3777"
cx="51.42857"
cy="53.57143"
fx="51.42857"
fy="53.57143"
r="30"
gradientTransform="matrix(1,0,0,1.0119048,0,-0.63775609)"
gradientUnits="userSpaceOnUse" />
<filter
inkscape:collect="always"
id="filter3803">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.5089022"
id="feGaussianBlur3805" />
</filter>
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-952.36218)">
<path
sodipodi:type="arc"
style="fill:url(#radialGradient3777);fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:6;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;opacity:0.50000000000000000;filter:url(#filter3803)"
id="path2985"
sodipodi:cx="51.42857"
sodipodi:cy="53.57143"
sodipodi:rx="30"
sodipodi:ry="30.357143"
d="m 81.42857,53.57143 a 30,30.357143 0 1 1 -60,0 30,30.357143 0 1 1 60,0 z"
transform="matrix(1.6666667,0,0,1.6470588,-35.714283,914.12689)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -350,6 +350,17 @@ BooksPos BooksBookModel::pageMark(int aPage) const
return BooksPos(); return BooksPos();
} }
int BooksBookModel::linkToPage(const std::string& aLink) const
{
if (iData && !iData->iBookModel.isNull()) {
BookModel::Label label = iData->iBookModel->label(aLink);
if (label.ParagraphNumber >= 0) {
return iData->pickPage(BooksPos(label.ParagraphNumber, 0, 0));
}
}
return -1;
}
shared_ptr<BookModel> BooksBookModel::bookModel() const shared_ptr<BookModel> BooksBookModel::bookModel() const
{ {
return iData ? iData->iBookModel : NULL; return iData ? iData->iBookModel : NULL;

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:
@ -14,7 +14,7 @@
* notice, this list of conditions and the following disclaimer in * notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the * the documentation and/or other materials provided with the
* distribution. * distribution.
* * Neither the name of Nemo Mobile nor the names of its contributors * * Neither the name of Jolla Ltd nor the names of its contributors
* may be used to endorse or promote products derived from this * may be used to endorse or promote products derived from this
* software without specific prior written permission. * software without specific prior written permission.
* *
@ -125,6 +125,7 @@ public:
shared_ptr<ZLTextModel> bookTextModel() const; shared_ptr<ZLTextModel> bookTextModel() const;
shared_ptr<ZLTextModel> contentsModel() const; shared_ptr<ZLTextModel> contentsModel() const;
shared_ptr<ZLTextStyle> textStyle() const { return iTextStyle; } shared_ptr<ZLTextStyle> textStyle() const { return iTextStyle; }
int linkToPage(const std::string& aLink) const;
int fontSizeAdjust() const; int fontSizeAdjust() const;
// QAbstractListModel // QAbstractListModel

View file

@ -157,12 +157,12 @@ void BooksPageWidget::RenderTask::performTask()
} }
// ========================================================================== // ==========================================================================
// BooksPageWidget::LongPressTask // BooksPageWidget::PressTask
// ========================================================================== // ==========================================================================
class BooksPageWidget::LongPressTask : public BooksTask { class BooksPageWidget::PressTask : public BooksTask {
public: public:
LongPressTask(shared_ptr<BooksPageWidget::Data> aData, int aX, int aY) : PressTask(shared_ptr<BooksPageWidget::Data> aData, int aX, int aY) :
iData(aData), iX(aX), iY(aY), iKind(REGULAR) {} iData(aData), iX(aX), iY(aY), iKind(REGULAR) {}
void performTask(); void performTask();
@ -179,7 +179,7 @@ public:
shared_ptr<ZLImageData> iImageData; shared_ptr<ZLImageData> iImageData;
}; };
void BooksPageWidget::LongPressTask::performTask() void BooksPageWidget::PressTask::performTask()
{ {
if (!isCanceled()) { if (!isCanceled()) {
const BooksTextView& view = *iData->iView; const BooksTextView& view = *iData->iView;
@ -222,7 +222,7 @@ void BooksPageWidget::LongPressTask::performTask()
iKind = link.kind(); iKind = link.kind();
iLink = link.label(); iLink = link.label();
iLinkType = link.hyperlinkType(); iLinkType = link.hyperlinkType();
HDEBUG("link" << iLink.c_str()); HDEBUG("link" << kind << iLink.c_str());
} }
return; return;
} }
@ -261,6 +261,7 @@ BooksPageWidget::BooksPageWidget(QQuickItem* aParent) :
iSettings(NULL), iSettings(NULL),
iResetTask(NULL), iResetTask(NULL),
iRenderTask(NULL), iRenderTask(NULL),
iPressTask(NULL),
iLongPressTask(NULL), iLongPressTask(NULL),
iEmpty(false), iEmpty(false),
iPage(-1) iPage(-1)
@ -279,6 +280,7 @@ BooksPageWidget::~BooksPageWidget()
HDEBUG("page" << iPage); HDEBUG("page" << iPage);
if (iResetTask) iResetTask->release(this); if (iResetTask) iResetTask->release(this);
if (iRenderTask) iRenderTask->release(this); if (iRenderTask) iRenderTask->release(this);
if (iPressTask) iPressTask->release(this);
if (iLongPressTask) iLongPressTask->release(this); if (iLongPressTask) iLongPressTask->release(this);
} }
@ -559,23 +561,49 @@ void BooksPageWidget::onRenderTaskDone()
update(); update();
} }
void BooksPageWidget::onPressTaskDone()
{
HASSERT(sender() == iPressTask);
HDEBUG(iPressTask->iKind);
PressTask* task = iPressTask;
iPressTask = NULL;
if (task->iKind != REGULAR) {
Q_EMIT activeTouch(task->iX, task->iY);
}
task->release(this);
}
void BooksPageWidget::onLongPressTaskDone() void BooksPageWidget::onLongPressTaskDone()
{ {
HASSERT(sender() == iLongPressTask); HASSERT(sender() == iLongPressTask);
HDEBUG(iLongPressTask->iKind); HDEBUG(iLongPressTask->iKind);
if (iLongPressTask->iKind == EXTERNAL_HYPERLINK) { PressTask* task = iLongPressTask;
iLongPressTask = NULL;
if (task->iKind == EXTERNAL_HYPERLINK) {
static const std::string HTTP("http://"); static const std::string HTTP("http://");
static const std::string HTTPS("https://"); static const std::string HTTPS("https://");
if (ZLStringUtil::stringStartsWith(iLongPressTask->iLink, HTTP) || if (ZLStringUtil::stringStartsWith(task->iLink, HTTP) ||
ZLStringUtil::stringStartsWith(iLongPressTask->iLink, HTTPS)) { ZLStringUtil::stringStartsWith(task->iLink, HTTPS)) {
QString url(QString::fromStdString(iLongPressTask->iLink)); QString url(QString::fromStdString(task->iLink));
Q_EMIT browserLinkPressed(url); Q_EMIT browserLinkPressed(url);
} }
} else if (iLongPressTask->iKind == IMAGE) { } else if (task->iKind == INTERNAL_HYPERLINK) {
if (iModel) {
int page = iModel->linkToPage(task->iLink);
if (page >= 0) {
HDEBUG("link to page" << page);
Q_EMIT jumpToPage(page);
}
}
} 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(iLongPressTask->iImageId); QString id = QString::fromStdString(task->iImageId);
QString path; QString path;
if (iModel) { if (iModel) {
BooksBook* book = iModel->book(); BooksBook* book = iModel->book();
@ -592,13 +620,11 @@ void BooksPageWidget::onLongPressTaskDone()
} }
static const QString IMAGE_URL("image://%1/%2"); static const QString IMAGE_URL("image://%1/%2");
QString url = IMAGE_URL.arg(BooksImageProvider::PROVIDER_ID, id); QString url = IMAGE_URL.arg(BooksImageProvider::PROVIDER_ID, id);
BooksImageProvider::instance()->addImage(iModel, id, BooksImageProvider::instance()->addImage(iModel, id, task->iImageData);
iLongPressTask->iImageData); Q_EMIT imagePressed(url, task->iRect);
Q_EMIT imagePressed(url, iLongPressTask->iRect);
} }
iLongPressTask->release(this); task->release(this);
iLongPressTask = NULL;
} }
void BooksPageWidget::updateSize() void BooksPageWidget::updateSize()
@ -640,7 +666,17 @@ void BooksPageWidget::handleLongPress(int aX, int aY)
HDEBUG(aX << aY); HDEBUG(aX << aY);
if (!iResetTask && !iRenderTask && !iData.isNull()) { if (!iResetTask && !iRenderTask && !iData.isNull()) {
if (iLongPressTask) iLongPressTask->release(this); if (iLongPressTask) iLongPressTask->release(this);
iLongPressTask = new LongPressTask(iData, aX, aY); iLongPressTask = new PressTask(iData, aX, aY);
iTaskQueue->submit(iLongPressTask, this, SLOT(onLongPressTaskDone())); iTaskQueue->submit(iLongPressTask, this, SLOT(onLongPressTaskDone()));
} }
} }
void BooksPageWidget::handlePress(int aX, int aY)
{
HDEBUG(aX << aY);
if (!iResetTask && !iRenderTask && !iData.isNull()) {
if (iPressTask) iPressTask->release(this);
iPressTask = new PressTask(iData, aX, aY);
iTaskQueue->submit(iPressTask, this, SLOT(onPressTaskDone()));
}
}

View file

@ -90,6 +90,7 @@ public:
BooksMargins margins() const { return iMargins; } BooksMargins margins() const { return iMargins; }
Q_INVOKABLE void handleLongPress(int aX, int aY); Q_INVOKABLE void handleLongPress(int aX, int aY);
Q_INVOKABLE void handlePress(int aX, int aY);
Q_SIGNALS: Q_SIGNALS:
void loadingChanged(); void loadingChanged();
@ -102,6 +103,8 @@ Q_SIGNALS:
void bottomMarginChanged(); void bottomMarginChanged();
void browserLinkPressed(QString url); void browserLinkPressed(QString url);
void imagePressed(QString url, QRect rect); void imagePressed(QString url, QRect rect);
void activeTouch(int x, int y);
void jumpToPage(int page);
private Q_SLOTS: private Q_SLOTS:
void onWidthChanged(); void onWidthChanged();
@ -115,6 +118,7 @@ private Q_SLOTS:
void onInvertColorsChanged(); void onInvertColorsChanged();
void onResetTaskDone(); void onResetTaskDone();
void onRenderTaskDone(); void onRenderTaskDone();
void onPressTaskDone();
void onLongPressTaskDone(); void onLongPressTaskDone();
private: private:
@ -128,7 +132,7 @@ private:
private: private:
class ResetTask; class ResetTask;
class RenderTask; class RenderTask;
class LongPressTask; class PressTask;
shared_ptr<BooksTaskQueue> iTaskQueue; shared_ptr<BooksTaskQueue> iTaskQueue;
shared_ptr<ZLTextStyle> iTextStyle; shared_ptr<ZLTextStyle> iTextStyle;
@ -141,7 +145,8 @@ private:
shared_ptr<QImage> iImage; shared_ptr<QImage> iImage;
ResetTask* iResetTask; ResetTask* iResetTask;
RenderTask* iRenderTask; RenderTask* iRenderTask;
LongPressTask* iLongPressTask; PressTask* iPressTask;
PressTask* iLongPressTask;
bool iEmpty; bool iEmpty;
int iPage; int iPage;
}; };