[app] Open external links on long tap

This commit is contained in:
Slava Monich 2016-10-08 14:56:14 +03:00
parent 1c7c897909
commit c15d6b840d
11 changed files with 390 additions and 3 deletions

View file

@ -7,14 +7,15 @@
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions modification, are permitted provided that the following conditions
are met: are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the Jolla Ltd nor the * Neither the name of Jolla Ltd nor the names of its contributors
names of its contributors may be used to endorse or promote products may be used to endorse or promote products derived from this software
derived from this software without specific prior written permission. without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
@ -62,6 +63,16 @@ SilicaFlickable {
qsTrId("harbour-books-book-view-applying_smaller_fonts") qsTrId("harbour-books-book-view-applying_smaller_fonts")
] ]
interactive: !linkMenu || !linkMenu.visible
property var linkMenu
Component {
id: linkMenuComponent
BooksLinkMenu {
}
}
PullDownMenu { PullDownMenu {
MenuItem { MenuItem {
//% "Back to library" //% "Back to library"
@ -98,6 +109,7 @@ SilicaFlickable {
settings: globalSettings settings: globalSettings
onJumpToPage: bookView.jumpTo(index) onJumpToPage: bookView.jumpTo(index)
onCurrentPageChanged: { onCurrentPageChanged: {
if (linkMenu) linkMenu.hide()
if (currentPage >= 0 && bookView._jumpingTo < 0) { if (currentPage >= 0 && bookView._jumpingTo < 0) {
pager.currentPage = currentPage pager.currentPage = currentPage
} }
@ -125,6 +137,7 @@ SilicaFlickable {
spacing: Theme.paddingMedium spacing: Theme.paddingMedium
opacity: _loading ? 0 : 1 opacity: _loading ? 0 : 1
visible: opacity > 0 visible: opacity > 0
interactive: root.interactive
delegate: BooksPageView { delegate: BooksPageView {
width: bookView.width width: bookView.width
height: bookView.height height: bookView.height
@ -143,6 +156,15 @@ SilicaFlickable {
root.pageClicked(index) root.pageClicked(index)
globalSettings.pageDetails = (globalSettings.pageDetails+ 1) % _visibilityStates.length globalSettings.pageDetails = (globalSettings.pageDetails+ 1) % _visibilityStates.length
} }
onBrowserLinkPressed: {
if (_currentPage == index) {
if (!linkMenu) {
linkMenu = linkMenuComponent.createObject(root)
}
linkMenu.url = url
linkMenu.show()
}
}
} }
property int _jumpingTo: -1 property int _jumpingTo: -1

142
app/qml/BooksLinkMenu.qml Normal file
View file

@ -0,0 +1,142 @@
/*
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.0
opacity: 0.0
width: parent.width
height: parent.height
gradient: Gradient {
GradientStop { position: 0.0; color: Theme.highlightDimmerColor }
GradientStop { position: 1.0; color: Theme.rgba(Theme.highlightDimmerColor, 0.9) }
}
readonly property bool landscape: width > height
property alias url: linkLabel.text
Behavior on opacity { FadeAnimation {} }
Column {
width: parent.width - 2*Theme.horizontalPageMargin
spacing: Theme.paddingMedium
anchors {
top: parent.top
topMargin: Theme.paddingLarge*2
left: parent.left
leftMargin: Theme.horizontalPageMargin
}
Label {
id: title
anchors.horizontalCenter: parent.horizontalCenter
//: External link menu title
//% "Link"
text: qsTrId("harbour-books-book-browser_link-title")
width: root.width
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 2
color: Theme.highlightColor
font.pixelSize: Theme.fontSizeExtraLarge
horizontalAlignment: Text.AlignHCenter
opacity: 0.6
}
Label {
id: linkLabel
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.highlightColor
width: root.width
wrapMode: Text.Wrap
elide: Text.ElideRight
maximumLineCount: landscape ? 1 : 4
font.pixelSize: Theme.fontSizeMedium
horizontalAlignment: Text.AlignHCenter
opacity: 0.6
}
}
MouseArea {
anchors.fill: parent
onPressed: {
root.hide()
}
}
HighlightBar {
id: menuHighlight
}
Column {
id: menu
anchors.bottom: parent.bottom
anchors.bottomMargin: landscape ? Theme.paddingLarge : Theme.itemSizeSmall
width: parent.width
property var highlightBar: menuHighlight
BooksMenuItem {
//: Open link in browser
//% "Open in browser"
text: qsTrId("harbour-books-book-browser_link-open_in_browser")
onClicked: {
console.log("opening", root.url)
root.hide()
Qt.openUrlExternally(root.url)
}
}
BooksMenuItem {
//: Copy link to clipboard
//% "Copy to clipboard"
text: qsTrId("harbour-books-book-browser_link-copy_to_clipboard")
onClicked: {
root.hide()
Clipboard.text = root.url
}
}
}
function show() {
opacity = 1.0
}
function hide() {
opacity = 0.0
menuHighlight.clearHighlight()
}
}

50
app/qml/BooksMenuItem.qml Normal file
View file

@ -0,0 +1,50 @@
/*
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
// The parent is expected to provide HighlightBar component via highlightBar
// property
MenuItem {
id: root
down: mouseArea.pressed
property var _highlightBar: parent.highlightBar
MouseArea {
id: mouseArea
property bool highlighted
anchors.fill: parent
onPressed: _highlightBar.highlight(mouseArea, root.parent)
onReleased: if (_highlightBar.highlightedItem === mouseArea) _highlightBar.clearHighlight()
onClicked: root.clicked()
}
}

View file

@ -49,12 +49,14 @@ Item {
property bool pageNumberVisible property bool pageNumberVisible
signal pageClicked() signal pageClicked()
signal browserLinkPressed(var url)
PageWidget { PageWidget {
id: widget id: widget
anchors.fill: parent anchors.fill: parent
settings: globalSettings settings: globalSettings
model: bookModel model: bookModel
onBrowserLinkPressed: view.browserLinkPressed(url)
} }
BooksTitleLabel { BooksTitleLabel {
@ -99,5 +101,6 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: view.pageClicked() onClicked: view.pageClicked()
onPressAndHold: widget.handleLongPress(mouseX, mouseY)
} }
} }

View file

@ -35,6 +35,9 @@
#include "BooksTextStyle.h" #include "BooksTextStyle.h"
#include "BooksDefs.h" #include "BooksDefs.h"
#include "bookmodel/FBTextKind.h"
#include "ZLStringUtil.h"
#include "HarbourDebug.h" #include "HarbourDebug.h"
#include <QPainter> #include <QPainter>
@ -152,6 +155,57 @@ void BooksPageWidget::RenderTask::performTask()
} }
} }
// ==========================================================================
// BooksPageWidget::LongPressTask
// ==========================================================================
class BooksPageWidget::LongPressTask : public BooksTask {
public:
LongPressTask(shared_ptr<BooksPageWidget::Data> aData, int aX, int aY) :
iData(aData), iX(aX), iY(aY) {}
void performTask();
public:
shared_ptr<BooksPageWidget::Data> iData;
int iX;
int iY;
ZLTextKind iKind;
std::string iLinkText;
std::string iLinkType;
};
void BooksPageWidget::LongPressTask::performTask()
{
if (!isCanceled()) {
const ZLTextArea& area = iData->iView->textArea();
const ZLTextElementRectangle* rect = area.elementByCoordinates(iX, iY);
if (rect && !isCanceled() &&
((rect->Kind == ZLTextElement::WORD_ELEMENT) ||
(rect->Kind == ZLTextElement::IMAGE_ELEMENT))) {
ZLTextWordCursor cursor = area.startCursor();
cursor.moveToParagraph(rect->ParagraphIndex);
cursor.moveToParagraphStart();
for (int i=0; i<rect->ElementIndex && !isCanceled(); i++) {
const ZLTextElement& element = cursor.element();
if (element.kind() == ZLTextElement::CONTROL_ELEMENT) {
const ZLTextControlEntry& controlEntry =
((const ZLTextControlElement&)element).entry();
if (controlEntry.isHyperlink()) {
const ZLTextHyperlinkControlEntry& hyperLinkEntry =
((const ZLTextHyperlinkControlEntry&)controlEntry);
iKind = hyperLinkEntry.kind();
iLinkText = hyperLinkEntry.label();
iLinkType = hyperLinkEntry.hyperlinkType();
return;
}
}
cursor.nextWord();
}
}
}
}
// ========================================================================== // ==========================================================================
// BooksPageWidget // BooksPageWidget
// ========================================================================== // ==========================================================================
@ -165,6 +219,7 @@ BooksPageWidget::BooksPageWidget(QQuickItem* aParent) :
iSettings(NULL), iSettings(NULL),
iResetTask(NULL), iResetTask(NULL),
iRenderTask(NULL), iRenderTask(NULL),
iLongPressTask(NULL),
iEmpty(false), iEmpty(false),
iPage(-1) iPage(-1)
{ {
@ -182,6 +237,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 (iLongPressTask) iLongPressTask->release(this);
} }
void BooksPageWidget::setModel(BooksBookModel* aModel) void BooksPageWidget::setModel(BooksBookModel* aModel)
@ -461,6 +517,26 @@ void BooksPageWidget::onRenderTaskDone()
update(); update();
} }
void BooksPageWidget::onLongPressTaskDone()
{
static const std::string HTTP("http://");
static const std::string HTTPS("https://");
HASSERT(sender() == iLongPressTask);
HDEBUG(iLongPressTask->iKind <<
iLongPressTask->iLinkType.c_str() <<
iLongPressTask->iLinkText.c_str());
if (iLongPressTask->iKind == EXTERNAL_HYPERLINK &&
(ZLStringUtil::stringStartsWith(iLongPressTask->iLinkText, HTTP) ||
ZLStringUtil::stringStartsWith(iLongPressTask->iLinkText, HTTPS))) {
Q_EMIT browserLinkPressed(QString::fromStdString(iLongPressTask->iLinkText));
}
iLongPressTask->release(this);
iLongPressTask = NULL;
}
void BooksPageWidget::updateSize() void BooksPageWidget::updateSize()
{ {
HDEBUG("page" << iPage << QSize(width(), height())); HDEBUG("page" << iPage << QSize(width(), height()));
@ -494,3 +570,13 @@ void BooksPageWidget::onResizeTimeout()
// setHeight() method // setHeight() method
updateSize(); updateSize();
} }
void BooksPageWidget::handleLongPress(int aX, int aY)
{
HDEBUG(aX << aY);
if (!iResetTask && !iRenderTask && !iData.isNull()) {
if (iLongPressTask) iLongPressTask->release(this);
iLongPressTask = new LongPressTask(iData, aX, aY);
iTaskQueue->submit(iLongPressTask, this, SLOT(onLongPressTaskDone()));
}
}

View file

@ -89,6 +89,8 @@ public:
BooksMargins margins() const { return iMargins; } BooksMargins margins() const { return iMargins; }
Q_INVOKABLE void handleLongPress(int aX, int aY);
Q_SIGNALS: Q_SIGNALS:
void loadingChanged(); void loadingChanged();
void pageChanged(); void pageChanged();
@ -98,6 +100,7 @@ Q_SIGNALS:
void rightMarginChanged(); void rightMarginChanged();
void topMarginChanged(); void topMarginChanged();
void bottomMarginChanged(); void bottomMarginChanged();
void browserLinkPressed(QString url);
private Q_SLOTS: private Q_SLOTS:
void onWidthChanged(); void onWidthChanged();
@ -111,6 +114,7 @@ private Q_SLOTS:
void onInvertColorsChanged(); void onInvertColorsChanged();
void onResetTaskDone(); void onResetTaskDone();
void onRenderTaskDone(); void onRenderTaskDone();
void onLongPressTaskDone();
private: private:
void paint(QPainter *painter); void paint(QPainter *painter);
@ -123,6 +127,7 @@ private:
private: private:
class ResetTask; class ResetTask;
class RenderTask; class RenderTask;
class LongPressTask;
shared_ptr<BooksTaskQueue> iTaskQueue; shared_ptr<BooksTaskQueue> iTaskQueue;
shared_ptr<ZLTextStyle> iTextStyle; shared_ptr<ZLTextStyle> iTextStyle;
@ -135,6 +140,7 @@ private:
shared_ptr<QImage> iImage; shared_ptr<QImage> iImage;
ResetTask* iResetTask; ResetTask* iResetTask;
RenderTask* iRenderTask; RenderTask* iRenderTask;
LongPressTask* iLongPressTask;
bool iEmpty; bool iEmpty;
int iPage; int iPage;
}; };

View file

@ -134,5 +134,21 @@
<extracomment>Combo box value for landscape orientation</extracomment> <extracomment>Combo box value for landscape orientation</extracomment>
<translation>Querformat</translation> <translation>Querformat</translation>
</message> </message>
<message id="harbour-books-book-browser_link-title">
<source>Link</source>
<oldsource>External link</oldsource>
<extracomment>External link menu title</extracomment>
<translation type="unfinished">Link</translation>
</message>
<message id="harbour-books-book-browser_link-open_in_browser">
<source>Open in browser</source>
<extracomment>Open link in browser</extracomment>
<translation type="unfinished">Im Browser öffnen</translation>
</message>
<message id="harbour-books-book-browser_link-copy_to_clipboard">
<source>Copy to clipboard</source>
<extracomment>Copy link to clipboard</extracomment>
<translation>In Zwischenablage kopieren</translation>
</message>
</context> </context>
</TS> </TS>

View file

@ -134,5 +134,21 @@
<extracomment>Combo box value for landscape orientation</extracomment> <extracomment>Combo box value for landscape orientation</extracomment>
<translation>Vaaka</translation> <translation>Vaaka</translation>
</message> </message>
<message id="harbour-books-book-browser_link-title">
<source>Link</source>
<oldsource>External link</oldsource>
<extracomment>External link menu title</extracomment>
<translation type="unfinished">Linkki</translation>
</message>
<message id="harbour-books-book-browser_link-open_in_browser">
<source>Open in browser</source>
<extracomment>Open link in browser</extracomment>
<translation type="unfinished">Avaa selaimessa</translation>
</message>
<message id="harbour-books-book-browser_link-copy_to_clipboard">
<source>Copy to clipboard</source>
<extracomment>Copy link to clipboard</extracomment>
<translation>Kopioi leikepöydälle</translation>
</message>
</context> </context>
</TS> </TS>

View file

@ -136,5 +136,20 @@
<extracomment>Combo box value for landscape orientation</extracomment> <extracomment>Combo box value for landscape orientation</extracomment>
<translation>Альбомная</translation> <translation>Альбомная</translation>
</message> </message>
<message id="harbour-books-book-browser_link-title">
<source>Link</source>
<extracomment>External link menu title</extracomment>
<translation>Ссылка</translation>
</message>
<message id="harbour-books-book-browser_link-open_in_browser">
<source>Open in browser</source>
<extracomment>Open link in browser</extracomment>
<translation>Открыть в браузере</translation>
</message>
<message id="harbour-books-book-browser_link-copy_to_clipboard">
<source>Copy to clipboard</source>
<extracomment>Copy link to clipboard</extracomment>
<translation>Скопировать</translation>
</message>
</context> </context>
</TS> </TS>

View file

@ -134,5 +134,21 @@
<extracomment>Combo box value for landscape orientation</extracomment> <extracomment>Combo box value for landscape orientation</extracomment>
<translation>Liggande</translation> <translation>Liggande</translation>
</message> </message>
<message id="harbour-books-book-browser_link-title">
<source>Link</source>
<oldsource>External link</oldsource>
<extracomment>External link menu title</extracomment>
<translation type="unfinished">Länk</translation>
</message>
<message id="harbour-books-book-browser_link-open_in_browser">
<source>Open in browser</source>
<extracomment>Open link in browser</extracomment>
<translation type="unfinished">Öppna i webbläsaren</translation>
</message>
<message id="harbour-books-book-browser_link-copy_to_clipboard">
<source>Copy to clipboard</source>
<extracomment>Copy link to clipboard</extracomment>
<translation>Kopiera till urklipp</translation>
</message>
</context> </context>
</TS> </TS>

View file

@ -134,5 +134,20 @@
<extracomment>Combo box value for landscape orientation</extracomment> <extracomment>Combo box value for landscape orientation</extracomment>
<translation>Landscape</translation> <translation>Landscape</translation>
</message> </message>
<message id="harbour-books-book-browser_link-title">
<source>Link</source>
<extracomment>External link menu title</extracomment>
<translation>Link</translation>
</message>
<message id="harbour-books-book-browser_link-open_in_browser">
<source>Open in browser</source>
<extracomment>Open link in browser</extracomment>
<translation>Open in browser</translation>
</message>
<message id="harbour-books-book-browser_link-copy_to_clipboard">
<source>Copy to clipboard</source>
<extracomment>Copy link to clipboard</extracomment>
<translation>Copy to clipboard</translation>
</message>
</context> </context>
</TS> </TS>