diff --git a/app/qml/BooksBookView.qml b/app/qml/BooksBookView.qml index 7b199c8..357fa76 100644 --- a/app/qml/BooksBookView.qml +++ b/app/qml/BooksBookView.qml @@ -7,14 +7,15 @@ 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 the 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. + * 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 @@ -62,6 +63,16 @@ SilicaFlickable { qsTrId("harbour-books-book-view-applying_smaller_fonts") ] + interactive: !linkMenu || !linkMenu.visible + + property var linkMenu + + Component { + id: linkMenuComponent + BooksLinkMenu { + } + } + PullDownMenu { MenuItem { //% "Back to library" @@ -98,6 +109,7 @@ SilicaFlickable { settings: globalSettings onJumpToPage: bookView.jumpTo(index) onCurrentPageChanged: { + if (linkMenu) linkMenu.hide() if (currentPage >= 0 && bookView._jumpingTo < 0) { pager.currentPage = currentPage } @@ -125,6 +137,7 @@ SilicaFlickable { spacing: Theme.paddingMedium opacity: _loading ? 0 : 1 visible: opacity > 0 + interactive: root.interactive delegate: BooksPageView { width: bookView.width height: bookView.height @@ -143,6 +156,15 @@ SilicaFlickable { root.pageClicked(index) 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 diff --git a/app/qml/BooksLinkMenu.qml b/app/qml/BooksLinkMenu.qml new file mode 100644 index 0000000..4ab96f3 --- /dev/null +++ b/app/qml/BooksLinkMenu.qml @@ -0,0 +1,142 @@ +/* + Copyright (C) 2016 Jolla Ltd. + Contact: Slava Monich + + 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() + } +} diff --git a/app/qml/BooksMenuItem.qml b/app/qml/BooksMenuItem.qml new file mode 100644 index 0000000..7db6334 --- /dev/null +++ b/app/qml/BooksMenuItem.qml @@ -0,0 +1,50 @@ +/* + Copyright (C) 2016 Jolla Ltd. + Contact: Slava Monich + + 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() + } +} diff --git a/app/qml/BooksPageView.qml b/app/qml/BooksPageView.qml index 3fad3ee..fce780a 100644 --- a/app/qml/BooksPageView.qml +++ b/app/qml/BooksPageView.qml @@ -49,12 +49,14 @@ Item { property bool pageNumberVisible signal pageClicked() + signal browserLinkPressed(var url) PageWidget { id: widget anchors.fill: parent settings: globalSettings model: bookModel + onBrowserLinkPressed: view.browserLinkPressed(url) } BooksTitleLabel { @@ -99,5 +101,6 @@ Item { MouseArea { anchors.fill: parent onClicked: view.pageClicked() + onPressAndHold: widget.handleLongPress(mouseX, mouseY) } } diff --git a/app/src/BooksPageWidget.cpp b/app/src/BooksPageWidget.cpp index 7587159..7b469a0 100644 --- a/app/src/BooksPageWidget.cpp +++ b/app/src/BooksPageWidget.cpp @@ -35,6 +35,9 @@ #include "BooksTextStyle.h" #include "BooksDefs.h" +#include "bookmodel/FBTextKind.h" +#include "ZLStringUtil.h" + #include "HarbourDebug.h" #include @@ -152,6 +155,57 @@ void BooksPageWidget::RenderTask::performTask() } } +// ========================================================================== +// BooksPageWidget::LongPressTask +// ========================================================================== + +class BooksPageWidget::LongPressTask : public BooksTask { +public: + LongPressTask(shared_ptr aData, int aX, int aY) : + iData(aData), iX(aX), iY(aY) {} + + void performTask(); + +public: + shared_ptr 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; iElementIndex && !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 // ========================================================================== @@ -165,6 +219,7 @@ BooksPageWidget::BooksPageWidget(QQuickItem* aParent) : iSettings(NULL), iResetTask(NULL), iRenderTask(NULL), + iLongPressTask(NULL), iEmpty(false), iPage(-1) { @@ -182,6 +237,7 @@ BooksPageWidget::~BooksPageWidget() HDEBUG("page" << iPage); if (iResetTask) iResetTask->release(this); if (iRenderTask) iRenderTask->release(this); + if (iLongPressTask) iLongPressTask->release(this); } void BooksPageWidget::setModel(BooksBookModel* aModel) @@ -461,6 +517,26 @@ void BooksPageWidget::onRenderTaskDone() 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() { HDEBUG("page" << iPage << QSize(width(), height())); @@ -494,3 +570,13 @@ void BooksPageWidget::onResizeTimeout() // setHeight() method 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())); + } +} diff --git a/app/src/BooksPageWidget.h b/app/src/BooksPageWidget.h index c26e826..de8460b 100644 --- a/app/src/BooksPageWidget.h +++ b/app/src/BooksPageWidget.h @@ -89,6 +89,8 @@ public: BooksMargins margins() const { return iMargins; } + Q_INVOKABLE void handleLongPress(int aX, int aY); + Q_SIGNALS: void loadingChanged(); void pageChanged(); @@ -98,6 +100,7 @@ Q_SIGNALS: void rightMarginChanged(); void topMarginChanged(); void bottomMarginChanged(); + void browserLinkPressed(QString url); private Q_SLOTS: void onWidthChanged(); @@ -111,6 +114,7 @@ private Q_SLOTS: void onInvertColorsChanged(); void onResetTaskDone(); void onRenderTaskDone(); + void onLongPressTaskDone(); private: void paint(QPainter *painter); @@ -123,6 +127,7 @@ private: private: class ResetTask; class RenderTask; + class LongPressTask; shared_ptr iTaskQueue; shared_ptr iTextStyle; @@ -135,6 +140,7 @@ private: shared_ptr iImage; ResetTask* iResetTask; RenderTask* iRenderTask; + LongPressTask* iLongPressTask; bool iEmpty; int iPage; }; diff --git a/app/translations/harbour-books-de.ts b/app/translations/harbour-books-de.ts index 00c4d66..7c1e22c 100644 --- a/app/translations/harbour-books-de.ts +++ b/app/translations/harbour-books-de.ts @@ -134,5 +134,21 @@ Combo box value for landscape orientation Querformat + + Link + External link + External link menu title + Link + + + Open in browser + Open link in browser + Im Browser öffnen + + + Copy to clipboard + Copy link to clipboard + In Zwischenablage kopieren + diff --git a/app/translations/harbour-books-fi.ts b/app/translations/harbour-books-fi.ts index 29eb9a8..48dfe83 100644 --- a/app/translations/harbour-books-fi.ts +++ b/app/translations/harbour-books-fi.ts @@ -134,5 +134,21 @@ Combo box value for landscape orientation Vaaka + + Link + External link + External link menu title + Linkki + + + Open in browser + Open link in browser + Avaa selaimessa + + + Copy to clipboard + Copy link to clipboard + Kopioi leikepöydälle + diff --git a/app/translations/harbour-books-ru.ts b/app/translations/harbour-books-ru.ts index d3343e9..a9e6420 100644 --- a/app/translations/harbour-books-ru.ts +++ b/app/translations/harbour-books-ru.ts @@ -136,5 +136,20 @@ Combo box value for landscape orientation Альбомная + + Link + External link menu title + Ссылка + + + Open in browser + Open link in browser + Открыть в браузере + + + Copy to clipboard + Copy link to clipboard + Скопировать + diff --git a/app/translations/harbour-books-sv.ts b/app/translations/harbour-books-sv.ts index d93af3b..4aced20 100644 --- a/app/translations/harbour-books-sv.ts +++ b/app/translations/harbour-books-sv.ts @@ -134,5 +134,21 @@ Combo box value for landscape orientation Liggande + + Link + External link + External link menu title + Länk + + + Open in browser + Open link in browser + Öppna i webbläsaren + + + Copy to clipboard + Copy link to clipboard + Kopiera till urklipp + diff --git a/app/translations/harbour-books.ts b/app/translations/harbour-books.ts index cbc8a9d..ba91d0d 100644 --- a/app/translations/harbour-books.ts +++ b/app/translations/harbour-books.ts @@ -134,5 +134,20 @@ Combo box value for landscape orientation Landscape + + Link + External link menu title + Link + + + Open in browser + Open link in browser + Open in browser + + + Copy to clipboard + Copy link to clipboard + Copy to clipboard +