Implemented history (position stack)

Allows the user to return back after selecting a cross-page link
This commit is contained in:
Slava Monich 2017-08-03 18:48:17 +03:00
parent 59f49be0c3
commit 9ac726c523
17 changed files with 1183 additions and 329 deletions

View file

@ -109,6 +109,7 @@ SOURCES += \
src/BooksImportModel.cpp \ src/BooksImportModel.cpp \
src/BooksListWatcher.cpp \ src/BooksListWatcher.cpp \
src/BooksLoadingProperty.cpp \ src/BooksLoadingProperty.cpp \
src/BooksPageStack.cpp \
src/BooksPageWidget.cpp \ src/BooksPageWidget.cpp \
src/BooksPaintContext.cpp \ src/BooksPaintContext.cpp \
src/BooksPathModel.cpp \ src/BooksPathModel.cpp \
@ -150,6 +151,7 @@ HEADERS += \
src/BooksItem.h \ src/BooksItem.h \
src/BooksListWatcher.h \ src/BooksListWatcher.h \
src/BooksLoadingProperty.h \ src/BooksLoadingProperty.h \
src/BooksPageStack.h \
src/BooksPageWidget.h \ src/BooksPageWidget.h \
src/BooksPaintContext.h \ src/BooksPaintContext.h \
src/BooksPathModel.h \ src/BooksPathModel.h \

View file

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2015-2016 Jolla Ltd. Copyright (C) 2015-2017 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 BSD license as follows: You may use this file under the terms of BSD license as follows:
@ -43,8 +43,8 @@ SilicaFlickable {
signal pageClicked(var page) signal pageClicked(var page)
property int orientation: Orientation.Portrait property int orientation: Orientation.Portrait
property int _currentPage: bookListWatcher.currentIndex property alias stackModel: bookModel.pageStack
property bool _loading: minLoadingDelay.running || bookModel.loading property bool loading: bookModel.loading
property var _currentState: _visibilityStates[Settings.pageDetails % _visibilityStates.length] property var _currentState: _visibilityStates[Settings.pageDetails % _visibilityStates.length]
readonly property var _visibilityStates: [ readonly property var _visibilityStates: [
{ pager: false, page: false, title: false, tools: false }, { pager: false, page: false, title: false, tools: false },
@ -52,18 +52,6 @@ SilicaFlickable {
{ pager: true, page: true, title: true, tools: true } { pager: true, page: true, title: true, tools: true }
] ]
// NOTE: These have to match ResetReason in BooksBookModel
readonly property var _loadingTextLabel: [
//% "Formatting..."
qsTrId("harbour-books-book-view-formatting"),
//% "Loading..."
qsTrId("harbour-books-book-view-loading"),
//% "Applying larger fonts..."
qsTrId("harbour-books-book-view-applying_larger_fonts"),
//% "Applying smaller fonts..."
qsTrId("harbour-books-book-view-applying_smaller_fonts")
]
interactive: (!linkMenu || !linkMenu.visible) && interactive: (!linkMenu || !linkMenu.visible) &&
(!imageView || !imageView.visible) && (!imageView || !imageView.visible) &&
(!footnoteView || !footnoteView.visible) (!footnoteView || !footnoteView.visible)
@ -101,49 +89,14 @@ SilicaFlickable {
} }
} }
Timer {
id: minLoadingDelay
interval: 1000
}
Timer {
id: resetPager
interval: 0
onTriggered: {
if (_currentPage >= 0) {
console.log("resetting pager to", _currentPage)
pager.currentPage = _currentPage
}
}
}
BookModel { BookModel {
id: bookModel id: bookModel
book: root.book ? root.book : null book: root.book ? root.book : null
size: bookListWatcher.size size: bookViewWatcher.size
currentPage: _currentPage
leftMargin: Theme.horizontalPageMargin leftMargin: Theme.horizontalPageMargin
rightMargin: Theme.horizontalPageMargin rightMargin: Theme.horizontalPageMargin
topMargin: Theme.itemSizeSmall topMargin: Theme.itemSizeSmall
bottomMargin: Theme.itemSizeSmall bottomMargin: Theme.itemSizeSmall
onJumpToPage: bookView.jumpTo(index)
onCurrentPageChanged: {
if (linkMenu) linkMenu.hide()
if (currentPage >= 0 && bookView._jumpingTo < 0) {
pager.currentPage = currentPage
}
}
onLoadingChanged: {
if (loading && !pageCount) {
minLoadingDelay.start()
bookView._jumpingTo = -1
}
}
}
ListWatcher {
id: bookListWatcher
listView: bookView
} }
SilicaListView { SilicaListView {
@ -154,9 +107,50 @@ SilicaFlickable {
orientation: ListView.Horizontal orientation: ListView.Horizontal
snapMode: ListView.SnapOneItem snapMode: ListView.SnapOneItem
spacing: Theme.paddingMedium spacing: Theme.paddingMedium
opacity: _loading ? 0 : 1 opacity: loading ? 0 : 1
visible: opacity > 0 visible: opacity > 0
interactive: root.interactive interactive: root.interactive
readonly property int currentPage: stackModel.currentPage
property bool completed
Component.onCompleted: {
//console.log(currentPage)
bookViewWatcher.positionViewAtIndex(currentPage)
completed = true
}
onCurrentPageChanged: {
//console.log(currentPage, completed, flicking)
if (completed && !flicking) {
bookViewWatcher.positionViewAtIndex(currentPage)
}
}
onFlickingChanged: {
if (!flicking) {
bookViewWatcher.updateModel()
}
}
ListWatcher {
id: bookViewWatcher
listView: bookView
onCurrentIndexChanged: {
if (listView.completed && !listView.flicking && currentIndex >= 0) {
//console.log(currentIndex, listView.completed, listView.flicking)
updateModel()
}
}
function updateModel() {
if (linkMenu) linkMenu.hide()
//console.trace()
stackModel.currentPage = currentIndex
if (!pager.pressed) {
pager.currentPage = currentIndex
}
}
}
delegate: BooksPageView { delegate: BooksPageView {
width: bookView.width width: bookView.width
height: bookView.height height: bookView.height
@ -172,12 +166,13 @@ SilicaFlickable {
pageNumberVisible: _currentState.page pageNumberVisible: _currentState.page
title: bookModel.title title: bookModel.title
onJumpToPage: bookView.jumpTo(page) onJumpToPage: bookView.jumpTo(page)
onPushPosition: stackModel.pushPosition(position) // bookView.jumpTo(page)
onPageClicked: { onPageClicked: {
root.pageClicked(index) root.pageClicked(index)
Settings.pageDetails = (Settings.pageDetails+ 1) % _visibilityStates.length Settings.pageDetails = (Settings.pageDetails + 1) % _visibilityStates.length
} }
onImagePressed: { onImagePressed: {
if (_currentPage == index) { if (bookViewWatcher.currentIndex == index) {
if (!imageView) { if (!imageView) {
imageView = imageViewComponent.createObject(root) imageView = imageViewComponent.createObject(root)
} }
@ -185,7 +180,7 @@ SilicaFlickable {
} }
} }
onBrowserLinkPressed: { onBrowserLinkPressed: {
if (_currentPage == index) { if (bookViewWatcher.currentIndex == index) {
if (!linkMenu) { if (!linkMenu) {
linkMenu = linkMenuComponent.createObject(root) linkMenu = linkMenuComponent.createObject(root)
} }
@ -193,7 +188,7 @@ SilicaFlickable {
} }
} }
onFootnotePressed: { onFootnotePressed: {
if (_currentPage == index) { if (bookViewWatcher.currentIndex == index) {
if (!footnoteView) { if (!footnoteView) {
footnoteView = footnoteViewComponent.createObject(root) footnoteView = footnoteViewComponent.createObject(root)
} }
@ -202,15 +197,15 @@ SilicaFlickable {
} }
} }
property int _jumpingTo: -1 property int jumpingTo: -1
function jumpTo(page) { function jumpTo(page) {
if (page >=0 && page !== _currentPage) { if (page >=0 && page !== bookViewWatcher.currentIndex) {
_jumpingTo = page jumpingTo = page
positionViewAtIndex(page, ListView.Center) bookViewWatcher.positionViewAtIndex(page)
pager.currentPage = page pager.currentPage = page
_jumpingTo = -1 jumpingTo = -1
if (_currentPage !== page) { if (bookViewWatcher.currentIndex !== page) {
console.log("oops, still at", _currentPage) console.log("oops, still at", currentPage)
resetPager.restart() resetPager.restart()
} }
} }
@ -218,6 +213,15 @@ SilicaFlickable {
Behavior on opacity { FadeAnimation {} } Behavior on opacity { FadeAnimation {} }
Timer {
id: resetPager
interval: 0
onTriggered: {
console.log("resetting pager to", bookViewWatcher.currentIndex)
pager.currentPage = bookViewWatcher.currentIndex
}
}
BooksPageTools { BooksPageTools {
id: pageTools id: pageTools
anchors { anchors {
@ -228,7 +232,7 @@ SilicaFlickable {
leftMargin: bookModel.leftMargin leftMargin: bookModel.leftMargin
rightMargin: bookModel.rightMargin rightMargin: bookModel.rightMargin
opacity: _currentState.tools ? 1 : 0 opacity: _currentState.tools ? 1 : 0
visible: opacity > 0 && book && bookModel.pageCount && !_loading visible: opacity > 0 && book && bookModel.pageCount && !loading
Behavior on opacity { FadeAnimation {} } Behavior on opacity { FadeAnimation {} }
onIncreaseFontSize: bookModel.increaseFontSize() onIncreaseFontSize: bookModel.increaseFontSize()
onDecreaseFontSize: bookModel.decreaseFontSize() onDecreaseFontSize: bookModel.decreaseFontSize()
@ -237,9 +241,14 @@ SilicaFlickable {
BooksPager { BooksPager {
id: pager id: pager
anchors { anchors {
left: parent.left
right: parent.right
bottom: parent.bottom bottom: parent.bottom
bottomMargin: (Theme.itemSizeExtraSmall + 2*(bookModel.bottomMargin - height))/4 bottomMargin: (Theme.itemSizeExtraSmall + 2*(bookModel.bottomMargin - height))/4
} }
leftMargin: bookModel.leftMargin
rightMargin: bookModel.rightMargin
stack: stackModel
pageCount: bookModel.pageCount pageCount: bookModel.pageCount
width: parent.width width: parent.width
opacity: (_currentState.pager && book && bookModel.pageCount) ? 0.75 : 0 opacity: (_currentState.pager && book && bookModel.pageCount) ? 0.75 : 0
@ -261,20 +270,20 @@ SilicaFlickable {
text: bookModel.title text: bookModel.title
height: Theme.itemSizeExtraSmall height: Theme.itemSizeExtraSmall
color: Theme.highlightColor color: Theme.highlightColor
opacity: _loading ? 0.6 : 0 opacity: loading ? 0.6 : 0
} }
BusyIndicator { BusyIndicator {
id: busyIndicator id: busyIndicator
anchors.centerIn: parent anchors.centerIn: parent
size: BusyIndicatorSize.Large size: BusyIndicatorSize.Large
running: _loading running: loading
} }
BooksFitLabel { BooksFitLabel {
anchors.fill: busyIndicator anchors.fill: busyIndicator
text: bookModel.progress > 0 ? bookModel.progress : "" text: bookModel.progress > 0 ? bookModel.progress : ""
opacity: (_loading && bookModel.progress > 0) ? 1 : 0 opacity: (loading && bookModel.progress > 0) ? 1 : 0
} }
Button { Button {
@ -286,7 +295,7 @@ SilicaFlickable {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
} }
onClicked: root.closeBook() onClicked: root.closeBook()
enabled: _loading && bookModel.resetReason === BookModel.ReasonLoading enabled: loading && bookModel.resetReason === BookModel.ReasonLoading
visible: opacity > 0 visible: opacity > 0
opacity: enabled ? 1.0 : 0.0 opacity: enabled ? 1.0 : 0.0
Behavior on opacity { FadeAnimation { } } Behavior on opacity { FadeAnimation { } }
@ -301,9 +310,19 @@ SilicaFlickable {
} }
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
color: Theme.highlightColor color: Theme.highlightColor
opacity: _loading ? 1 : 0 opacity: loading ? 1 : 0
visible: opacity > 0 visible: opacity > 0
Behavior on opacity { FadeAnimation {} } Behavior on opacity { FadeAnimation {} }
text: bookModel ? _loadingTextLabel[bookModel.resetReason] : "" text: bookModel ? (bookModel.resetReason == BookModel.ReasonLoading ?
//% "Loading..."
qsTrId("harbour-books-book-view-loading") :
bookModel.resetReason == BookModel.ReasonIncreasingFontSize ?
//% "Applying larger fonts..."
qsTrId("harbour-books-book-view-applying_larger_fonts") :
bookModel.resetReason == BookModel.ReasonDecreasingFontSize ?
//% "Applying smaller fonts..."
qsTrId("harbour-books-book-view-applying_smaller_fonts") :
//% "Formatting..."
qsTrId("harbour-books-book-view-formatting")) : ""
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2015-2016 Jolla Ltd. Copyright (C) 2015-2017 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 BSD license as follows: You may use this file under the terms of BSD license as follows:
@ -54,6 +54,7 @@ Item {
signal footnotePressed(var touchX, var touchY, var text, var url) signal footnotePressed(var touchX, var touchY, var text, var url)
signal browserLinkPressed(var url) signal browserLinkPressed(var url)
signal jumpToPage(var page) signal jumpToPage(var page)
signal pushPosition(var position)
PageWidget { PageWidget {
id: widget id: widget
@ -63,6 +64,7 @@ Item {
onImagePressed: view.imagePressed(imageId, rect) onImagePressed: view.imagePressed(imageId, rect)
onActiveTouch: pressImage.animate(touchX, touchY) onActiveTouch: pressImage.animate(touchX, touchY)
onJumpToPage: view.jumpToPage(page) onJumpToPage: view.jumpToPage(page)
onPushPosition: view.pushPosition(position)
onShowFootnote: view.footnotePressed(touchX,touchY,text,imageId) onShowFootnote: view.footnotePressed(touchX,touchY,text,imageId)
} }

View file

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2015-2016 Jolla Ltd. Copyright (C) 2015-2017 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 BSD license as follows: You may use this file under the terms of BSD license as follows:
@ -38,29 +38,82 @@ Item {
id: root id: root
height: slider.height height: slider.height
property alias pageCount: slider.maximumValue property var stack
property int pageCount
property real leftMargin: Theme.horizontalPageMargin
property real rightMargin: Theme.horizontalPageMargin
property alias currentPage: slider.value property alias currentPage: slider.value
property alias pressed: slider.pressed property alias pressed: slider.pressed
property alias leftMargin: slider.leftMargin
property alias rightMargin: slider.rightMargin
signal pageChanged(var page) signal pageChanged(var page)
MouseArea {
id: navigateBackArea
property bool down: pressed && containsMouse
width: navigateBack.width + root.leftMargin
height: navigateBack.height
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
onClicked: stack.back()
}
IconButton {
id: navigateBack
icon.source: "image://theme/icon-m-left?" + Settings.primaryPageToolColor
down: navigateBackArea.down || (pressed && containsMouse)
anchors {
left: parent.left
leftMargin: root.leftMargin
verticalCenter: parent.verticalCenter
}
onClicked: stack.back()
}
BooksPageSlider { BooksPageSlider {
id: slider id: slider
anchors { anchors {
left: parent.left left: navigateBack.right
right: parent.right right: navigateForwardArea.left
bottom: parent.bottom bottom: parent.bottom
} }
stepSize: 1 stepSize: 1
minimumValue: 0 minimumValue: 0
maximumValue: pageCount > 0 ? pageCount - 1 : 0
valueText: "" valueText: ""
label: "" label: ""
leftMargin: Theme.horizontalPageMargin
rightMargin: Theme.horizontalPageMargin
primaryColor: Settings.primaryPageToolColor primaryColor: Settings.primaryPageToolColor
secondaryColor: Settings.primaryPageToolColor secondaryColor: Settings.primaryPageToolColor
highlightColor: Settings.highlightPageToolColor highlightColor: Settings.highlightPageToolColor
secondaryHighlightColor: Settings.highlightPageToolColor secondaryHighlightColor: Settings.highlightPageToolColor
onSliderValueChanged: root.pageChanged(value) onSliderValueChanged: root.pageChanged(value)
} }
MouseArea {
id: navigateForwardArea
property bool down: pressed && containsMouse
width: navigateForward.width + root.rightMargin
height: navigateForward.height
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
}
onClicked: stack.forward()
}
IconButton {
id: navigateForward
icon.source: "image://theme/icon-m-right?" + Settings.primaryPageToolColor
down: navigateForwardArea.down || (pressed && containsMouse)
anchors {
right: parent.right
rightMargin: root.rightMargin
verticalCenter: parent.verticalCenter
}
onClicked: stack.forward()
}
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015 Jolla Ltd. * Copyright (C) 2015-2017 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.
* *
@ -58,6 +58,7 @@
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#define BOOK_STATE_PAGE_STACK_INDEX "pageStackIndex"
#define BOOK_STATE_FONT_SIZE_ADJUST "fontSizeAdjust" #define BOOK_STATE_FONT_SIZE_ADJUST "fontSizeAdjust"
#define BOOK_STATE_POSITION "position" #define BOOK_STATE_POSITION "position"
#define BOOK_COVER_SUFFIX ".cover." #define BOOK_COVER_SUFFIX ".cover."
@ -328,10 +329,40 @@ BooksBook::BooksBook(const BooksStorage& aStorage, QString aRelativePath,
// Load the state // Load the state
QVariantMap state; QVariantMap state;
if (HarbourJson::load(iStateFilePath, state)) { if (HarbourJson::load(iStateFilePath, state)) {
iLastPos = BooksPos::fromVariant(state.value(BOOK_STATE_POSITION));
iFontSizeAdjust = state.value(BOOK_STATE_FONT_SIZE_ADJUST).toInt(); iFontSizeAdjust = state.value(BOOK_STATE_FONT_SIZE_ADJUST).toInt();
#ifdef BOOK_STATE_PAGE_STACK_INDEX
iPageStackPos = state.value(BOOK_STATE_PAGE_STACK_INDEX).toInt();
#endif
// Current position can be stored in two formats - a single
// position (older format) or a list of position (newer one).
// We have to detect which one we are dealing with
QVariant position(state.value(BOOK_STATE_POSITION));
BooksPos bookPos(BooksPos::fromVariant(position));
if (bookPos.valid()) {
// Old format (single position)
iPageStack.append(bookPos);
} else {
// New format (list of positions)
QVariantList list(position.toList());
const int count = list.count();
for (int k=0; k<count; k++) {
bookPos = BooksPos::fromVariant(list.at(k));
if (bookPos.valid()) {
iPageStack.append(bookPos);
}
}
}
} }
} }
// Validate the state
if (iPageStack.isEmpty()) {
iPageStack.append(BooksPos(0,0,0));
}
if (iPageStackPos < 0) {
iPageStackPos = 0;
} else if (iPageStackPos >= iPageStack.count()) {
iPageStackPos = iPageStack.count() - 1;
}
// Refcounted BooksBook objects are managed by C++ code // Refcounted BooksBook objects are managed by C++ code
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
} }
@ -339,6 +370,7 @@ BooksBook::BooksBook(const BooksStorage& aStorage, QString aRelativePath,
void BooksBook::init() void BooksBook::init()
{ {
iFontSizeAdjust = 0; iFontSizeAdjust = 0;
iPageStackPos = 0;
iCoverTask = NULL; iCoverTask = NULL;
iCoverTasksDone = false; iCoverTasksDone = false;
iCopyingOut = false; iCopyingOut = false;
@ -419,10 +451,23 @@ bool BooksBook::setFontSizeAdjust(int aFontSizeAdjust)
} }
} }
void BooksBook::setLastPos(const BooksPos& aPos) void BooksBook::setPageStack(BooksPos::List aStack, int aStackPos)
{ {
if (iLastPos != aPos) { if (aStackPos < 0) {
iLastPos = aPos; aStackPos = 0;
} else if (aStackPos >= aStack.count()) {
aStackPos = aStack.count() - 1;
}
bool changed = false;
if (iPageStack != aStack) {
iPageStack = aStack;
changed = true;
}
if (iPageStackPos != aStackPos) {
iPageStackPos = aStackPos;
changed = true;
}
if (changed) {
requestSave(); requestSave();
} }
} }
@ -536,8 +581,14 @@ void BooksBook::saveState()
if (!iStateFilePath.isEmpty()) { if (!iStateFilePath.isEmpty()) {
QVariantMap state; QVariantMap state;
HarbourJson::load(iStateFilePath, state); HarbourJson::load(iStateFilePath, state);
state.insert(BOOK_STATE_POSITION, iLastPos.toVariant()); QVariantList positions;
const int n = iPageStack.count();
for (int i=0; i<n; i++) positions.append(iPageStack.at(i).toVariant());
state.insert(BOOK_STATE_POSITION, positions);
state.insert(BOOK_STATE_FONT_SIZE_ADJUST, iFontSizeAdjust); state.insert(BOOK_STATE_FONT_SIZE_ADJUST, iFontSizeAdjust);
#ifdef BOOK_STATE_PAGE_STACK_INDEX
state.insert(BOOK_STATE_PAGE_STACK_INDEX, iPageStackPos);
#endif
if (HarbourJson::save(iStateFilePath, state)) { if (HarbourJson::save(iStateFilePath, state)) {
HDEBUG("wrote" << iStateFilePath); HDEBUG("wrote" << iStateFilePath);
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015 Jolla Ltd. * Copyright (C) 2015-2017 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.
* *
@ -74,16 +74,17 @@ public:
static BooksBook* newBook(const BooksStorage& aStorage, QString aRelPath, static BooksBook* newBook(const BooksStorage& aStorage, QString aRelPath,
QString aFileName); QString aFileName);
QString title() const { return iTitle; } QString title() const;
QString authors() const { return iAuthors; } QString authors() const;
int fontSizeAdjust() const { return iFontSizeAdjust; } int fontSizeAdjust() const;
bool setFontSizeAdjust(int aFontSizeAdjust); bool setFontSizeAdjust(int aFontSizeAdjust);
BooksPos lastPos() const { return iLastPos; } int pageStackPos() const;
void setLastPos(const BooksPos& aPos); BooksPos::List pageStack() const;
shared_ptr<Book> bookRef() const { return iBook; } void setPageStack(BooksPos::List aStack, int aStackPos);
shared_ptr<Book> bookRef() const;
bool copyingOut() const { return iCopyingOut; } bool copyingOut() const;
bool loadingCover() const { return !iCoverTasksDone; } bool loadingCover() const;
bool hasCoverImage() const; bool hasCoverImage() const;
bool requestCoverImage(); bool requestCoverImage();
void cancelCoverRequest(); void cancelCoverRequest();
@ -129,7 +130,8 @@ private:
private: private:
QAtomicInt iRef; QAtomicInt iRef;
int iFontSizeAdjust; int iFontSizeAdjust;
BooksPos iLastPos; int iPageStackPos;
BooksPos::List iPageStack;
BooksStorage iStorage; BooksStorage iStorage;
shared_ptr<Book> iBook; shared_ptr<Book> iBook;
QImage iCoverImage; QImage iCoverImage;
@ -149,6 +151,22 @@ private:
QML_DECLARE_TYPE(BooksBook) QML_DECLARE_TYPE(BooksBook)
inline QString BooksBook::title() const
{ return iTitle; }
inline QString BooksBook::authors() const
{ return iAuthors; }
inline int BooksBook::fontSizeAdjust() const
{ return iFontSizeAdjust; }
inline int BooksBook::pageStackPos() const
{ return iPageStackPos; }
inline BooksPos::List BooksBook::pageStack() const
{ return iPageStack; }
inline shared_ptr<Book> BooksBook::bookRef() const
{ return iBook; }
inline bool BooksBook::copyingOut() const
{ return iCopyingOut; }
inline bool BooksBook::loadingCover() const
{ return !iCoverTasksDone; }
inline bool BooksBook::isCanceled(CopyOperation* aObserver) inline bool BooksBook::isCanceled(CopyOperation* aObserver)
{ return aObserver && aObserver->isCanceled(); } { return aObserver && aObserver->isCanceled(); }
inline QImage BooksBook::coverImage() const inline QImage BooksBook::coverImage() const

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015-2016 Jolla Ltd. * Copyright (C) 2015-2017 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:
@ -45,10 +45,6 @@ class BooksBookModel::Data {
public: public:
Data(int aWidth, int aHeight) : iWidth(aWidth), iHeight(aHeight) {} Data(int aWidth, int aHeight) : iWidth(aWidth), iHeight(aHeight) {}
int pickPage(const BooksPos& aPagePos) const;
int pickPage(const BooksPos& aPagePos, const BooksPos& aNextPagePos,
int aPageCount) const;
public: public:
int iWidth; int iWidth;
int iHeight; int iHeight;
@ -56,65 +52,17 @@ public:
BooksPos::List iPageMarks; BooksPos::List iPageMarks;
}; };
int BooksBookModel::Data::pickPage(const BooksPos& aPagePos) const
{
int page = 0;
if (aPagePos.valid()) {
BooksPos::ConstIterator it = qFind(iPageMarks, aPagePos);
if (it == iPageMarks.end()) {
it = qUpperBound(iPageMarks, aPagePos);
page = (int)(it - iPageMarks.begin()) - 1;
HDEBUG("using page" << page << "for" << aPagePos);
} else {
page = it - iPageMarks.begin();
HDEBUG("found" << aPagePos << "at page" << page);
}
}
return page;
}
int BooksBookModel::Data::pickPage(const BooksPos& aPagePos,
const BooksPos& aNextPagePos, int aPageCount) const
{
int page = 0;
if (aPagePos.valid()) {
if (!aNextPagePos.valid()) {
// Last page stays the last
page = iPageMarks.count() - 1;
HDEBUG("last page" << page);
} else {
BooksPos::ConstIterator it = qFind(iPageMarks, aPagePos);
if (it == iPageMarks.end()) {
// Two 90-degrees rotations should return the reader
// back to the same page. That's what this is about.
const BooksPos& pos = (iPageMarks.count() > aPageCount) ?
aPagePos : aNextPagePos;
it = qUpperBound(iPageMarks, pos);
page = (int)(it - iPageMarks.begin());
if (page > 0) page--;
HDEBUG("using page" << page << "for" << pos);
} else {
page = it - iPageMarks.begin();
HDEBUG("found" << aPagePos << "at page" << page);
}
}
}
return page;
}
// ========================================================================== // ==========================================================================
// BooksBookModel::Task // BooksBookModel::PagingTask
// ========================================================================== // ==========================================================================
class BooksBookModel::Task : public BooksTask class BooksBookModel::PagingTask : public BooksTask
{ {
Q_OBJECT Q_OBJECT
public: public:
Task(BooksBookModel* aReceiver, shared_ptr<Book> aBook, PagingTask(BooksBookModel* aReceiver, shared_ptr<Book> aBook);
const BooksPos& aPagePos, const BooksPos& aNextPagePos, ~PagingTask();
const BooksPos& aLastPos, int aPageCount);
~Task();
void performTask(); void performTask();
@ -127,38 +75,27 @@ public:
BooksMargins iMargins; BooksMargins iMargins;
BooksPaintContext iPaint; BooksPaintContext iPaint;
BooksBookModel::Data* iData; BooksBookModel::Data* iData;
BooksPos iPagePos;
BooksPos iNextPagePos;
BooksPos iLastPos;
int iOldPageCount;
int iPage;
}; };
BooksBookModel::Task::Task(BooksBookModel* aModel, BooksBookModel::PagingTask::PagingTask(BooksBookModel* aModel,
shared_ptr<Book> aBook, const BooksPos& aPagePos, shared_ptr<Book> aBook) :
const BooksPos& aNextPagePos, const BooksPos& aLastPos, int aPageCount) :
iBook(aBook), iBook(aBook),
iTextStyle(aModel->textStyle()), iTextStyle(aModel->textStyle()),
iMargins(aModel->margins()), iMargins(aModel->margins()),
iPaint(aModel->width(), aModel->height()), iPaint(aModel->width(), aModel->height()),
iData(NULL), iData(NULL)
iPagePos(aPagePos),
iNextPagePos(aNextPagePos),
iLastPos(aLastPos),
iOldPageCount(aPageCount),
iPage(-1)
{ {
aModel->connect(this, SIGNAL(done()), SLOT(onResetDone())); aModel->connect(this, SIGNAL(done()), SLOT(onResetDone()));
aModel->connect(this, SIGNAL(progress(int)), SLOT(onResetProgress(int)), aModel->connect(this, SIGNAL(progress(int)), SLOT(onResetProgress(int)),
Qt::QueuedConnection); Qt::QueuedConnection);
} }
BooksBookModel::Task::~Task() BooksBookModel::PagingTask::~PagingTask()
{ {
delete iData; delete iData;
} }
void BooksBookModel::Task::performTask() void BooksBookModel::PagingTask::performTask()
{ {
if (!isCanceled()) { if (!isCanceled()) {
iData = new BooksBookModel::Data(iPaint.width(), iPaint.height()); iData = new BooksBookModel::Data(iPaint.width(), iPaint.height());
@ -183,9 +120,6 @@ void BooksBookModel::Task::performTask()
if (!isCanceled()) { if (!isCanceled()) {
HDEBUG(iData->iPageMarks.count() << "page(s)" << qPrintable( HDEBUG(iData->iPageMarks.count() << "page(s)" << qPrintable(
QString("%1x%2").arg(iData->iWidth).arg(iData->iHeight))); QString("%1x%2").arg(iData->iWidth).arg(iData->iHeight)));
iPage = iPagePos.valid() ?
iData->pickPage(iPagePos, iNextPagePos, iOldPageCount) :
iData->pickPage(iLastPos);
} else { } else {
HDEBUG("giving up" << qPrintable(QString("%1x%2").arg(iPaint.width()). HDEBUG("giving up" << qPrintable(QString("%1x%2").arg(iPaint.width()).
arg(iPaint.height())) << "paging"); arg(iPaint.height())) << "paging");
@ -203,17 +137,19 @@ enum BooksBookModelRole {
BooksBookModel::BooksBookModel(QObject* aParent) : BooksBookModel::BooksBookModel(QObject* aParent) :
QAbstractListModel(aParent), QAbstractListModel(aParent),
iResetReason(ReasonUnknown), iResetReason(ReasonUnknown),
iCurrentPage(-1),
iProgress(0), iProgress(0),
iBook(NULL), iBook(NULL),
iTask(NULL), iPagingTask(NULL),
iData(NULL), iData(NULL),
iData2(NULL), iData2(NULL),
iSettings(BooksSettings::sharedInstance()), iSettings(BooksSettings::sharedInstance()),
iTaskQueue(BooksTaskQueue::defaultQueue()) iTaskQueue(BooksTaskQueue::defaultQueue()),
iPageStack(new BooksPageStack(this))
{ {
iTextStyle = iSettings->textStyle(fontSizeAdjust()); iTextStyle = iSettings->textStyle(fontSizeAdjust());
connect(iSettings.data(), SIGNAL(textStyleChanged()), SLOT(onTextStyleChanged())); connect(iSettings.data(), SIGNAL(textStyleChanged()), SLOT(onTextStyleChanged()));
connect(iPageStack, SIGNAL(changed()), SLOT(onPageStackChanged()));
connect(iPageStack, SIGNAL(currentIndexChanged()), SLOT(onPageStackChanged()));
HDEBUG("created"); HDEBUG("created");
#if QT_VERSION < 0x050000 #if QT_VERSION < 0x050000
setRoleNames(roleNames()); setRoleNames(roleNames());
@ -222,7 +158,7 @@ BooksBookModel::BooksBookModel(QObject* aParent) :
BooksBookModel::~BooksBookModel() BooksBookModel::~BooksBookModel()
{ {
if (iTask) iTask->release(this); if (iPagingTask) iPagingTask->release(this);
if (iBook) { if (iBook) {
iBook->disconnect(this); iBook->disconnect(this);
iBook->release(); iBook->release();
@ -235,7 +171,6 @@ BooksBookModel::~BooksBookModel()
void BooksBookModel::setBook(BooksBook* aBook) void BooksBookModel::setBook(BooksBook* aBook)
{ {
shared_ptr<Book> oldBook;
shared_ptr<Book> newBook; shared_ptr<Book> newBook;
if (iBook != aBook) { if (iBook != aBook) {
const QString oldTitle(iTitle); const QString oldTitle(iTitle);
@ -246,14 +181,16 @@ void BooksBookModel::setBook(BooksBook* aBook)
if (aBook) { if (aBook) {
(iBook = aBook)->retain(); (iBook = aBook)->retain();
iBookRef = newBook; iBookRef = newBook;
iTitle = iBook->title(); iTitle = aBook->title();
iTextStyle = iSettings->textStyle(fontSizeAdjust()); iTextStyle = iSettings->textStyle(fontSizeAdjust());
connect(iBook, SIGNAL(fontSizeAdjustChanged()), SLOT(onTextStyleChanged())); iPageStack->setStack(aBook->pageStack(), aBook->pageStackPos());
connect(aBook, SIGNAL(fontSizeAdjustChanged()), SLOT(onTextStyleChanged()));
HDEBUG(iTitle); HDEBUG(iTitle);
} else { } else {
iBook = NULL; iBook = NULL;
iBookRef.reset(); iBookRef.reset();
iTitle = QString(); iTitle = QString();
iPageStack->clear();
HDEBUG("<none>"); HDEBUG("<none>");
} }
startReset(ReasonLoading, true); startReset(ReasonLoading, true);
@ -268,7 +205,7 @@ void BooksBookModel::setBook(BooksBook* aBook)
bool BooksBookModel::loading() const bool BooksBookModel::loading() const
{ {
return (iTask != NULL); return (iPagingTask != NULL);
} }
bool BooksBookModel::increaseFontSize() bool BooksBookModel::increaseFontSize()
@ -281,19 +218,12 @@ bool BooksBookModel::decreaseFontSize()
return iBook && iBook->setFontSizeAdjust(iBook->fontSizeAdjust()-1); return iBook && iBook->setFontSizeAdjust(iBook->fontSizeAdjust()-1);
} }
void BooksBookModel::setCurrentPage(int aPage) void BooksBookModel::onPageStackChanged()
{ {
if (iCurrentPage != aPage) { if (iBook) {
iCurrentPage = aPage; BooksPos::Stack stack = iPageStack->getStack();
if (iData && HDEBUG(stack.iList << stack.iPos);
iCurrentPage >= 0 && iBook->setPageStack(stack.iList, stack.iPos);
iCurrentPage < iData->iPageMarks.count()) {
iBook->setLastPos(iData->iPageMarks.at(iCurrentPage));
HDEBUG(aPage << iBook->lastPos());
} else {
HDEBUG(aPage);
}
Q_EMIT currentPageChanged();
} }
} }
@ -314,24 +244,18 @@ int BooksBookModel::fontSizeAdjust() const
BooksPos BooksBookModel::pageMark(int aPage) const BooksPos BooksBookModel::pageMark(int aPage) const
{ {
if (aPage >= 0 && iData) { return iData ? BooksPos::posAt(iData->iPageMarks, aPage) : BooksPos();
const int n = iData->iPageMarks.count();
if (aPage < n) {
return iData->iPageMarks.at(aPage);
}
}
return BooksPos();
} }
int BooksBookModel::linkToPage(const std::string& aLink) const BooksPos BooksBookModel::linkPosition(const std::string& aLink) const
{ {
if (iData && !iData->iBookModel.isNull()) { if (iData && !iData->iBookModel.isNull()) {
BookModel::Label label = iData->iBookModel->label(aLink); BookModel::Label label = iData->iBookModel->label(aLink);
if (label.ParagraphNumber >= 0) { if (label.ParagraphNumber >= 0) {
return iData->pickPage(BooksPos(label.ParagraphNumber, 0, 0)); return BooksPos(label.ParagraphNumber, 0, 0);
} }
} }
return -1; return BooksPos();
} }
shared_ptr<BookModel> BooksBookModel::bookModel() const shared_ptr<BookModel> BooksBookModel::bookModel() const
@ -410,6 +334,7 @@ void BooksBookModel::updateModel(int aPrevPageCount)
{ {
const int newPageCount = pageCount(); const int newPageCount = pageCount();
if (aPrevPageCount != newPageCount) { if (aPrevPageCount != newPageCount) {
HDEBUG(aPrevPageCount << "->" << newPageCount);
if (newPageCount > aPrevPageCount) { if (newPageCount > aPrevPageCount) {
beginInsertRows(QModelIndex(), aPrevPageCount, newPageCount-1); beginInsertRows(QModelIndex(), aPrevPageCount, newPageCount-1);
endInsertRows(); endInsertRows();
@ -433,36 +358,20 @@ void BooksBookModel::setSize(QSize aSize)
} else if (iData2 && iData2->iWidth == w && iData2->iHeight == h) { } else if (iData2 && iData2->iWidth == w && iData2->iHeight == h) {
HDEBUG("switching to backup layout"); HDEBUG("switching to backup layout");
const int oldModelPageCount = pageCount(); const int oldModelPageCount = pageCount();
int oldPageCount;
BooksPos page1, page2;
if (iTask) {
// Layout has been switched back before the paging task
// has completed
HDEBUG("not so fast please...");
oldPageCount = iTask->iOldPageCount;
page1 = iTask->iPagePos;
page2 = iTask->iNextPagePos;
} else {
oldPageCount = oldModelPageCount;
page1 = pageMark(iCurrentPage);
page2 = pageMark(iCurrentPage+1);
}
Data* tmp = iData; Data* tmp = iData;
iData = iData2; iData = iData2;
iData2 = tmp; iData2 = tmp;
if (iData) { // Cancel unnecessary paging task
// Cancel unnecessary paging task BooksLoadingSignalBlocker block(this);
if (iTask) { if (iPagingTask) {
BooksLoadingSignalBlocker block(this); HDEBUG("not so fast please...");
iTask->release(this); iPagingTask->release(this);
iTask = NULL; iPagingTask = NULL;
}
updateModel(oldModelPageCount);
Q_EMIT pageMarksChanged();
Q_EMIT jumpToPage(iData->pickPage(page1, page2, oldPageCount));
} else {
startReset(ReasonUnknown, false);
} }
updateModel(oldModelPageCount);
iPageStack->setPageMarks(iData->iPageMarks);
Q_EMIT pageMarksChanged();
Q_EMIT jumpToPage(iPageStack->currentPage());
} else { } else {
startReset(ReasonUnknown, false); startReset(ReasonUnknown, false);
} }
@ -487,9 +396,8 @@ void BooksBookModel::onTextStyleChanged()
void BooksBookModel::startReset(ResetReason aResetReason, bool aFullReset) void BooksBookModel::startReset(ResetReason aResetReason, bool aFullReset)
{ {
BooksPos dummy;
BooksLoadingSignalBlocker block(this); BooksLoadingSignalBlocker block(this);
const BooksPos thisPage = pageMark(iCurrentPage);
const BooksPos nextPage = pageMark(iCurrentPage+1);
if (aResetReason == ReasonUnknown) { if (aResetReason == ReasonUnknown) {
if (iResetReason == ReasonUnknown) { if (iResetReason == ReasonUnknown) {
if (!iData && !iData2) { if (!iData && !iData2) {
@ -499,9 +407,9 @@ void BooksBookModel::startReset(ResetReason aResetReason, bool aFullReset)
aResetReason = iResetReason; aResetReason = iResetReason;
} }
} }
if (iTask) { if (iPagingTask) {
iTask->release(this); iPagingTask->release(this);
iTask = NULL; iPagingTask = NULL;
} }
const int oldPageCount(pageCount()); const int oldPageCount(pageCount());
if (oldPageCount > 0) { if (oldPageCount > 0) {
@ -520,9 +428,8 @@ void BooksBookModel::startReset(ResetReason aResetReason, bool aFullReset)
if (iBook && width() > 0 && height() > 0) { if (iBook && width() > 0 && height() > 0) {
HDEBUG("starting" << qPrintable(QString("%1x%2").arg(width()). HDEBUG("starting" << qPrintable(QString("%1x%2").arg(width()).
arg(height())) << "paging"); arg(height())) << "paging");
iTask = new Task(this, iBook->bookRef(), thisPage, nextPage, iPagingTask = new PagingTask(this, iBook->bookRef());
iBook->lastPos(), oldPageCount); iTaskQueue->submit(iPagingTask);
iTaskQueue->submit(iTask);
} }
if (oldPageCount > 0) { if (oldPageCount > 0) {
@ -531,11 +438,6 @@ void BooksBookModel::startReset(ResetReason aResetReason, bool aFullReset)
Q_EMIT pageCountChanged(); Q_EMIT pageCountChanged();
} }
if (iCurrentPage != 0) {
iCurrentPage = 0;
Q_EMIT currentPageChanged();
}
if (iProgress != 0) { if (iProgress != 0) {
iProgress = 0; iProgress = 0;
Q_EMIT progressChanged(); Q_EMIT progressChanged();
@ -551,7 +453,7 @@ void BooksBookModel::onResetProgress(int aProgress)
{ {
// progress -> onResetProgress is a queued connection, we may received // progress -> onResetProgress is a queued connection, we may received
// this event from the task that has already been canceled. // this event from the task that has already been canceled.
if (iTask == sender() && aProgress > iProgress) { if (iPagingTask == sender() && aProgress > iProgress) {
iProgress = aProgress; iProgress = aProgress;
Q_EMIT progressChanged(); Q_EMIT progressChanged();
} }
@ -559,22 +461,22 @@ void BooksBookModel::onResetProgress(int aProgress)
void BooksBookModel::onResetDone() void BooksBookModel::onResetDone()
{ {
HASSERT(sender() == iTask); HASSERT(sender() == iPagingTask);
HASSERT(iTask->iData); HASSERT(iPagingTask->iData);
HASSERT(!iData); HASSERT(!iData);
const int oldPageCount(pageCount()); const int oldPageCount(pageCount());
shared_ptr<BookModel> oldBookModel(bookModel()); shared_ptr<BookModel> oldBookModel(bookModel());
BooksLoadingSignalBlocker block(this); BooksLoadingSignalBlocker block(this);
int page = iTask->iPage;
iData = iTask->iData; iData = iPagingTask->iData;
iTask->iData = NULL; iPagingTask->iData = NULL;
iTask->release(this); iPagingTask->release(this);
iTask = NULL; iPagingTask = NULL;
updateModel(oldPageCount); updateModel(oldPageCount);
Q_EMIT jumpToPage(page); iPageStack->setPageMarks(iData->iPageMarks);
Q_EMIT jumpToPage(iPageStack->currentPage());
Q_EMIT pageMarksChanged(); Q_EMIT pageMarksChanged();
if (oldBookModel != bookModel()) { if (oldBookModel != bookModel()) {
Q_EMIT bookModelChanged(); Q_EMIT bookModelChanged();

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015-2016 Jolla Ltd. * Copyright (C) 2015-2017 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:
@ -42,6 +42,7 @@
#include "BooksPos.h" #include "BooksPos.h"
#include "BooksPaintContext.h" #include "BooksPaintContext.h"
#include "BooksLoadingProperty.h" #include "BooksLoadingProperty.h"
#include "BooksPageStack.h"
#include "ZLTextStyle.h" #include "ZLTextStyle.h"
#include "bookmodel/BookModel.h" #include "bookmodel/BookModel.h"
@ -63,11 +64,11 @@ class BooksBookModel: public QAbstractListModel, private BooksLoadingProperty
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
Q_PROPERTY(int progress READ progress NOTIFY progressChanged) Q_PROPERTY(int progress READ progress NOTIFY progressChanged)
Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged) Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged)
Q_PROPERTY(int currentPage READ currentPage WRITE setCurrentPage NOTIFY currentPageChanged)
Q_PROPERTY(int leftMargin READ leftMargin WRITE setLeftMargin NOTIFY leftMarginChanged) Q_PROPERTY(int leftMargin READ leftMargin WRITE setLeftMargin NOTIFY leftMarginChanged)
Q_PROPERTY(int rightMargin READ rightMargin WRITE setRightMargin NOTIFY rightMarginChanged) Q_PROPERTY(int rightMargin READ rightMargin WRITE setRightMargin NOTIFY rightMarginChanged)
Q_PROPERTY(int topMargin READ topMargin WRITE setTopMargin NOTIFY topMarginChanged) Q_PROPERTY(int topMargin READ topMargin WRITE setTopMargin NOTIFY topMarginChanged)
Q_PROPERTY(int bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY bottomMarginChanged) Q_PROPERTY(int bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY bottomMarginChanged)
Q_PROPERTY(BooksPageStack* pageStack READ pageStack CONSTANT)
Q_PROPERTY(BooksBook* book READ book WRITE setBook NOTIFY bookChanged) Q_PROPERTY(BooksBook* book READ book WRITE setBook NOTIFY bookChanged)
Q_PROPERTY(ResetReason resetReason READ resetReason NOTIFY resetReasonChanged) Q_PROPERTY(ResetReason resetReason READ resetReason NOTIFY resetReasonChanged)
@ -88,25 +89,23 @@ public:
bool loading() const; bool loading() const;
int pageCount() const; int pageCount() const;
int progress() const { return iProgress; } int progress() const;
QString title() const { return iTitle; } QString title() const;
int width() const { return iSize.width(); } int width() const;
int height() const { return iSize.height(); } int height() const;
ResetReason resetReason() const { return iResetReason; } ResetReason resetReason() const;
BooksPageStack* pageStack() const;
QSize size() const { return iSize; } QSize size() const;
void setSize(QSize aSize); void setSize(QSize aSize);
int currentPage() const { return iCurrentPage; } BooksBook* book() const;
void setCurrentPage(int aPage);
BooksBook* book() const { return iBook; }
void setBook(BooksBook* aBook); void setBook(BooksBook* aBook);
int leftMargin() const { return iMargins.iLeft; } int leftMargin() const;
int rightMargin() const { return iMargins.iRight; } int rightMargin() const;
int topMargin() const { return iMargins.iTop; } int topMargin() const;
int bottomMargin() const { return iMargins.iBottom; } int bottomMargin() const;
void setLeftMargin(int aMargin); void setLeftMargin(int aMargin);
void setRightMargin(int aMargin); void setRightMargin(int aMargin);
@ -115,14 +114,14 @@ public:
BooksPos::List pageMarks() const; BooksPos::List pageMarks() const;
BooksPos pageMark(int aPage) const; BooksPos pageMark(int aPage) const;
BooksMargins margins() const { return iMargins; } BooksMargins margins() const;
shared_ptr<Book> bookRef() const { return iBookRef; } shared_ptr<Book> bookRef() const;
shared_ptr<BookModel> bookModel() const; shared_ptr<BookModel> bookModel() const;
shared_ptr<ZLTextModel> bookTextModel() const; shared_ptr<ZLTextModel> bookTextModel() const;
shared_ptr<ZLTextModel> contentsModel() const; shared_ptr<ZLTextModel> contentsModel() const;
shared_ptr<ZLTextModel> footnoteModel(const std::string& aId) const; shared_ptr<ZLTextModel> footnoteModel(const std::string& aId) const;
shared_ptr<ZLTextStyle> textStyle() const { return iTextStyle; } shared_ptr<ZLTextStyle> textStyle() const;
int linkToPage(const std::string& aLink) const; BooksPos linkPosition(const std::string& aLink) const;
int fontSizeAdjust() const; int fontSizeAdjust() const;
// QAbstractListModel // QAbstractListModel
@ -139,6 +138,7 @@ private Q_SLOTS:
void onResetProgress(int aProgress); void onResetProgress(int aProgress);
void onResetDone(); void onResetDone();
void onTextStyleChanged(); void onTextStyleChanged();
void onPageStackChanged();
Q_SIGNALS: Q_SIGNALS:
void sizeChanged(); void sizeChanged();
@ -149,7 +149,6 @@ Q_SIGNALS:
void pageCountChanged(); void pageCountChanged();
void pageMarksChanged(); void pageMarksChanged();
void progressChanged(); void progressChanged();
void currentPageChanged();
void leftMarginChanged(); void leftMarginChanged();
void rightMarginChanged(); void rightMarginChanged();
void topMarginChanged(); void topMarginChanged();
@ -160,24 +159,55 @@ Q_SIGNALS:
private: private:
class Data; class Data;
class Task; class PagingTask;
ResetReason iResetReason; ResetReason iResetReason;
int iCurrentPage;
int iProgress; int iProgress;
QSize iSize; QSize iSize;
QString iTitle; QString iTitle;
BooksMargins iMargins; BooksMargins iMargins;
BooksBook* iBook; BooksBook* iBook;
shared_ptr<Book> iBookRef; shared_ptr<Book> iBookRef;
Task* iTask; PagingTask* iPagingTask;
Data* iData; Data* iData;
Data* iData2; Data* iData2;
QSharedPointer<BooksSettings> iSettings; QSharedPointer<BooksSettings> iSettings;
shared_ptr<BooksTaskQueue> iTaskQueue; shared_ptr<BooksTaskQueue> iTaskQueue;
shared_ptr<ZLTextStyle> iTextStyle; shared_ptr<ZLTextStyle> iTextStyle;
BooksPageStack* iPageStack;
}; };
QML_DECLARE_TYPE(BooksBookModel) QML_DECLARE_TYPE(BooksBookModel)
inline int BooksBookModel::progress() const
{ return iProgress; }
inline QString BooksBookModel::title() const
{ return iTitle; }
inline int BooksBookModel::width() const
{ return iSize.width(); }
inline int BooksBookModel::height() const
{ return iSize.height(); }
inline BooksBookModel::ResetReason BooksBookModel::resetReason() const
{ return iResetReason; }
inline BooksPageStack* BooksBookModel::pageStack() const
{ return iPageStack; }
inline QSize BooksBookModel::size() const
{ return iSize; }
inline BooksBook* BooksBookModel::book() const
{ return iBook; }
inline int BooksBookModel::leftMargin() const
{ return iMargins.iLeft; }
inline int BooksBookModel::rightMargin() const
{ return iMargins.iRight; }
inline int BooksBookModel::topMargin() const
{ return iMargins.iTop; }
inline int BooksBookModel::bottomMargin() const
{ return iMargins.iBottom; }
inline BooksMargins BooksBookModel::margins() const
{ return iMargins; }
inline shared_ptr<Book> BooksBookModel::bookRef() const
{ return iBookRef; }
inline shared_ptr<ZLTextStyle> BooksBookModel::textStyle() const
{ return iTextStyle; }
#endif // BOOKS_BOOK_MODEL_H #endif // BOOKS_BOOK_MODEL_H

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015-2016 Jolla Ltd. * Copyright (C) 2015-2017 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:
@ -37,6 +37,8 @@
#define LISTVIEW_CONTENT_X "contentX" #define LISTVIEW_CONTENT_X "contentX"
#define LISTVIEW_CONTENT_Y "contentY" #define LISTVIEW_CONTENT_Y "contentY"
#define LISTVIEW_CONTENT_WIDTH "contentWidth"
#define LISTVIEW_CONTENT_HEIGHT "contentHeight"
#define LISTVIEW_INDEX_AT "indexAt" #define LISTVIEW_INDEX_AT "indexAt"
#define LISTVIEW_POSITION_VIEW_AT_INDEX "positionViewAtIndex" #define LISTVIEW_POSITION_VIEW_AT_INDEX "positionViewAtIndex"
@ -48,7 +50,6 @@ BooksListWatcher::BooksListWatcher(QObject* aParent) :
iListView(NULL), iListView(NULL),
iCenterMode(-1), iCenterMode(-1),
iPositionIsChanging(false), iPositionIsChanging(false),
iCanRetry(true),
iResizeTimer(new QTimer(this)) iResizeTimer(new QTimer(this))
{ {
iResizeTimer->setSingleShot(true); iResizeTimer->setSingleShot(true);
@ -63,7 +64,6 @@ void BooksListWatcher::setListView(QQuickItem* aView)
if (iListView) iListView->disconnect(this); if (iListView) iListView->disconnect(this);
iListView = aView; iListView = aView;
iCenterMode = -1; iCenterMode = -1;
iCanRetry = true;
if (iListView) { if (iListView) {
connect(iListView, connect(iListView,
SIGNAL(widthChanged()), SIGNAL(widthChanged()),
@ -120,46 +120,47 @@ void BooksListWatcher::positionViewAtIndex(int aIndex)
{ {
if (iListView) { if (iListView) {
HDEBUG(aIndex); HDEBUG(aIndex);
if (iCenterMode < 0) { doPositionViewAtIndex(aIndex);
bool ok = false; }
const QMetaObject* metaObject = iListView->metaObject(); }
if (metaObject) {
int index = metaObject->indexOfEnumerator("PositionMode"); void BooksListWatcher::doPositionViewAtIndex(int aIndex)
if (index >= 0) { {
QMetaEnum metaEnum = metaObject->enumerator(index); if (iCenterMode < 0) {
int value = metaEnum.keyToValue("Center", &ok); bool ok = false;
if (ok) { const QMetaObject* metaObject = iListView->metaObject();
iCenterMode = value; if (metaObject) {
HDEBUG("Center =" << iCenterMode); int index = metaObject->indexOfEnumerator("PositionMode");
} if (index >= 0) {
QMetaEnum metaEnum = metaObject->enumerator(index);
int value = metaEnum.keyToValue("Center", &ok);
if (ok) {
iCenterMode = value;
HDEBUG("Center =" << iCenterMode);
} }
} }
HASSERT(ok);
if (!ok) {
// This is what it normally is
iCenterMode = 1;
}
} }
iPositionIsChanging = true; HASSERT(ok);
positionViewAtIndex(aIndex, iCenterMode); if (!ok) {
if (iCanRetry) { // This is what it normally is
// This is probably a bug in QQuickListView - it first calculates iCenterMode = 1;
// the item position and then starts instantiating the delegates.
// The very first time the item position always turns out to be
// zero because the average item size isn't known yet. So if we
// are trying to position the list at a non-zero index and instead
// we got positioned at zero, try it again. It doesn't make sense
// to retry more than once though.
if (aIndex > 0 && getCurrentIndex() == 0) {
// Didn't work from the first try, give it another go
HDEBUG("retrying...");
positionViewAtIndex(aIndex, iCenterMode);
}
iCanRetry = false;
} }
iPositionIsChanging = false;
updateCurrentIndex();
} }
iPositionIsChanging = true;
positionViewAtIndex(aIndex, iCenterMode);
// This is probably a bug in QQuickListView - it first calculates
// the item position and then starts instantiating the delegates.
// If there are no delegates yet, then the average item size is zero
// and the resulting item position will always turn out to be zero.
// So if we are trying to position the list at a non-zero index and
// instead we got positioned at zero, try it again.
if (aIndex > 0 && getCurrentIndex() == 0) {
// Didn't work from the first try, give it another go
HDEBUG("retrying...");
positionViewAtIndex(aIndex, iCenterMode);
}
iPositionIsChanging = false;
updateCurrentIndex();
} }
void BooksListWatcher::positionViewAtIndex(int aIndex, int aMode) void BooksListWatcher::positionViewAtIndex(int aIndex, int aMode)
@ -181,6 +182,16 @@ qreal BooksListWatcher::contentY()
return getRealProperty(LISTVIEW_CONTENT_Y); return getRealProperty(LISTVIEW_CONTENT_Y);
} }
qreal BooksListWatcher::contentWidth()
{
return getRealProperty(LISTVIEW_CONTENT_WIDTH);
}
qreal BooksListWatcher::contentHeight()
{
return getRealProperty(LISTVIEW_CONTENT_HEIGHT);
}
int BooksListWatcher::getCurrentIndex() int BooksListWatcher::getCurrentIndex()
{ {
if (iListView) { if (iListView) {
@ -194,13 +205,29 @@ int BooksListWatcher::getCurrentIndex()
return -1; return -1;
} }
void BooksListWatcher::updateCurrentIndex() void BooksListWatcher::tryToRestoreCurrentIndex()
{ {
HASSERT(!iPositionIsChanging);
const int index = getCurrentIndex(); const int index = getCurrentIndex();
if (iCurrentIndex != index) { if (iCurrentIndex != index) {
HDEBUG(index); if (iCurrentIndex >= 0) {
iCurrentIndex = index; doPositionViewAtIndex(iCurrentIndex);
Q_EMIT currentIndexChanged(); }
}
}
void BooksListWatcher::updateCurrentIndex()
{
HASSERT(!iPositionIsChanging);
if (contentWidth() > 0 || contentHeight() > 0) {
const int index = getCurrentIndex();
if (iCurrentIndex != index) {
iCurrentIndex = index;
HDEBUG(index << contentWidth() << "x" << contentHeight());
Q_EMIT currentIndexChanged();
}
} else {
HDEBUG(contentWidth() << "x" << contentHeight());
} }
} }
@ -211,6 +238,7 @@ void BooksListWatcher::updateSize()
if (iSize != size) { if (iSize != size) {
const QSize oldSize(iSize); const QSize oldSize(iSize);
iSize = size; iSize = size;
tryToRestoreCurrentIndex();
Q_EMIT sizeChanged(); Q_EMIT sizeChanged();
if (oldSize.width() != iSize.width()) { if (oldSize.width() != iSize.width()) {
Q_EMIT widthChanged(); Q_EMIT widthChanged();
@ -272,6 +300,7 @@ void BooksListWatcher::onContentSizeChanged()
{ {
HASSERT(sender() == iListView); HASSERT(sender() == iListView);
if (!iPositionIsChanging) { if (!iPositionIsChanging) {
tryToRestoreCurrentIndex();
updateCurrentIndex(); updateCurrentIndex();
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015-2016 Jolla Ltd. * Copyright (C) 2015-2017 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:
@ -64,10 +64,14 @@ public:
private: private:
qreal contentX(); qreal contentX();
qreal contentY(); qreal contentY();
qreal contentWidth();
qreal contentHeight();
qreal getRealProperty(const char *name, qreal defaultValue = 0.0); qreal getRealProperty(const char *name, qreal defaultValue = 0.0);
int getCurrentIndex(); int getCurrentIndex();
void doPositionViewAtIndex(int aIndex);
void positionViewAtIndex(int aIndex, int aMode); void positionViewAtIndex(int aIndex, int aMode);
void updateCurrentIndex(); void updateCurrentIndex();
void tryToRestoreCurrentIndex();
void updateSize(); void updateSize();
private Q_SLOTS: private Q_SLOTS:
@ -93,7 +97,6 @@ private:
QQuickItem* iListView; QQuickItem* iListView;
int iCenterMode; int iCenterMode;
bool iPositionIsChanging; bool iPositionIsChanging;
bool iCanRetry;
QTimer* iResizeTimer; QTimer* iResizeTimer;
}; };

633
app/src/BooksPageStack.cpp Normal file
View file

@ -0,0 +1,633 @@
/*
* Copyright (C) 2016-2017 Jolla Ltd.
* Contact: Slava Monich <slava.monich@jolla.com>
*
* You may use this file under the terms of the 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
* OWNER 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.
*/
#include "BooksPageStack.h"
#include "HarbourDebug.h"
#define SignalModelChanged (1 << 0)
#define SignalCountChanged (1 << 1)
#define SignalCurrentIndexChanged (1 << 2)
#define SignalCurrentPageChanged (1 << 3)
// ==========================================================================
// BooksPageStack::Entry
// ==========================================================================
class BooksPageStack::Entry {
public:
BooksPos iPos;
int iPage;
public:
Entry() : iPos(0, 0, 0), iPage(0) {}
Entry(const BooksPos& aPos, int aPage) : iPos(aPos), iPage(aPage) {}
Entry(const Entry& aEntry) : iPos(aEntry.iPos), iPage(aEntry.iPage) {}
const Entry& operator = (const Entry& Entry)
{
iPage = Entry.iPage;
iPos = Entry.iPos;
return *this;
}
bool operator == (const Entry& aEntry) const
{
return iPage == aEntry.iPage && iPos == aEntry.iPos;
}
bool operator != (const Entry& aEntry) const
{
return iPage != aEntry.iPage || iPos != aEntry.iPos;
}
};
// ==========================================================================
// BooksPageStack::Private
// ==========================================================================
class BooksPageStack::Private : public QObject {
Q_OBJECT
public:
QList<Entry> iEntries;
int iCurrentIndex;
int iQueuedSignals;
enum {
MAX_DEPTH = 10
};
private:
BooksPos::List iPageMarks;
BooksPageStack* iModel;
public:
Private(BooksPageStack* aModel);
BooksPos::Stack getStack() const;
bool isValidIndex(int aIndex) const;
void setCurrentIndex(int aIndex);
int currentPage() const;
int pageAt(int aIndex) const;
void setCurrentPage(int aPage);
void setPageAt(int aIndex, int aPage);
void setStack(BooksPos::List aStack, int aCurrentPos);
void setPageMarks(BooksPos::List aPageMarks);
void push(BooksPos aPos, int aPage);
void push(BooksPos);
void push(int aPage);
void pop();
void clear();
void emitQueuedSignals();
private Q_SLOTS:
void onModelChanged();
private:
BooksPos getPosAt(int aIndex) const;
int findPage(BooksPos aPos) const;
int makeIndexValid(int aIndex) const;
void pageChanged(int aIndex);
void checkCurrentIndex(int aLastIndex);
void checkCurrentPage(int aLastCurrentPage);
void checkCount(int aLastCount);
int validateCurrentIndex();
void queueSignals(int aSignals);
};
BooksPageStack::Private::Private(BooksPageStack* aModel) :
QObject(aModel),
iCurrentIndex(0),
iQueuedSignals(0),
iModel(aModel)
{
iEntries.append(Entry());
connect(aModel,
SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),
SLOT(onModelChanged()));
connect(aModel,
SIGNAL(rowsInserted(QModelIndex,int,int)),
SLOT(onModelChanged()));
connect(aModel,
SIGNAL(rowsRemoved(QModelIndex,int,int)),
SLOT(onModelChanged()));
connect(aModel,
SIGNAL(modelReset()),
SLOT(onModelChanged()));
}
void BooksPageStack::Private::emitQueuedSignals()
{
static const struct SignalInfo {
int signal;
void (BooksPageStack::*fn)();
} signalInfo [] = {
{ SignalModelChanged, &BooksPageStack::changed },
{ SignalCountChanged, &BooksPageStack::countChanged },
{ SignalCurrentIndexChanged, &BooksPageStack::currentIndexChanged },
{ SignalCurrentPageChanged, &BooksPageStack::currentPageChanged}
};
const uint n = sizeof(signalInfo)/sizeof(signalInfo[0]);
for (uint i=0; i<n && iQueuedSignals; i++) {
if (iQueuedSignals & signalInfo[i].signal) {
iQueuedSignals &= ~signalInfo[i].signal;
Q_EMIT (iModel->*(signalInfo[i].fn))();
}
}
}
void BooksPageStack::Private::onModelChanged()
{
queueSignals(SignalModelChanged);
}
inline void BooksPageStack::Private::queueSignals(int aSignals)
{
iQueuedSignals |= aSignals;
}
int BooksPageStack::Private::makeIndexValid(int aIndex) const
{
if (aIndex < 0) {
return 0;
} else if (aIndex >= iEntries.count()) {
return iEntries.count() - 1;
}
return aIndex;
}
inline bool BooksPageStack::Private::isValidIndex(int aIndex) const
{
return aIndex >= 0 && aIndex < iEntries.count();
}
inline void BooksPageStack::Private::checkCurrentIndex(int aLastIndex)
{
if (validateCurrentIndex() != aLastIndex) {
queueSignals(SignalCurrentIndexChanged);
}
}
inline void BooksPageStack::Private::checkCurrentPage(int aLastCurrentPage)
{
if (currentPage() != aLastCurrentPage) {
queueSignals(SignalCurrentPageChanged);
}
}
inline void BooksPageStack::Private::checkCount(int aLastCount)
{
if (iEntries.count() != aLastCount) {
queueSignals(SignalCountChanged);
}
}
int BooksPageStack::Private::validateCurrentIndex()
{
const int validIndex = makeIndexValid(iCurrentIndex);
if (iCurrentIndex != validIndex) {
iCurrentIndex = validIndex;
queueSignals(SignalCurrentIndexChanged);
}
return iCurrentIndex;
}
inline int BooksPageStack::Private::currentPage() const
{
return iEntries.at(iCurrentIndex).iPage;
}
int BooksPageStack::Private::pageAt(int aIndex) const
{
if (isValidIndex(aIndex)) {
return iEntries.at(aIndex).iPage;
}
return 0;
}
void BooksPageStack::Private::setCurrentIndex(int aIndex)
{
const int newIndex = makeIndexValid(aIndex);
if (iCurrentIndex != newIndex) {
const int oldCurrentPage = currentPage();
iCurrentIndex = newIndex;
HDEBUG(iCurrentIndex);
checkCurrentPage(oldCurrentPage);
queueSignals(SignalCurrentIndexChanged);
}
}
void BooksPageStack::Private::setPageAt(int aIndex, int aPage)
{
Entry entry = iEntries.at(aIndex);
if (entry.iPage != aPage) {
entry.iPage = aPage;
const int np = iPageMarks.count();
if (np > 0) {
entry.iPos = (aPage < 0) ? iPageMarks.at(0) :
(aPage >= np) ? iPageMarks.at(np-1) :
iPageMarks.at(aPage);
} else {
entry.iPos.set(0, 0, 0);
}
iEntries[aIndex] = entry;
pageChanged(aIndex);
}
}
inline void BooksPageStack::Private::setCurrentPage(int aPage)
{
setPageAt(iCurrentIndex, aPage);
}
void BooksPageStack::Private::pageChanged(int aIndex)
{
QVector<int> roles;
roles.append(PageRole);
QModelIndex modelIndex(iModel->createIndex(aIndex, 0));
Q_EMIT iModel->dataChanged(modelIndex, modelIndex, roles);
}
void BooksPageStack::Private::setStack(BooksPos::List aStack, int aStackPos)
{
if (aStack.isEmpty()) {
aStack = BooksPos::List();
aStack.append(BooksPos(0, 0, 0));
}
// First entry (always exists)
BooksPos pos = aStack.at(0);
Entry lastEntry(pos, findPage(pos));
if (iEntries.at(0) != lastEntry) {
iEntries[0] = lastEntry;
pageChanged(0);
} else {
iEntries[0] = lastEntry;
}
// Update other entries
int entryIndex = 1, stackIndex = 1;
const int oldEntryCount = iEntries.count();
while (entryIndex < oldEntryCount && stackIndex < aStack.count()) {
pos = aStack.at(stackIndex++);
Entry entry(pos, findPage(pos));
if (iEntries.at(entryIndex) != entry) {
iEntries[entryIndex] = entry;
pageChanged(entryIndex);
} else {
iEntries[entryIndex] = entry;
}
lastEntry = entry;
entryIndex++;
}
if (entryIndex < oldEntryCount) {
// We have run out of stack entries, remove remainig rows
iModel->beginRemoveRows(QModelIndex(), entryIndex, oldEntryCount-1);
while (iEntries.count() > entryIndex) {
iEntries.removeLast();
}
iModel->endRemoveRows();
Q_EMIT iModel->countChanged();
} else {
// Add new entries if necessary
while (stackIndex < aStack.count()) {
pos = aStack.at(stackIndex++);
Entry entry(pos, findPage(pos));
if (entry != lastEntry) {
iEntries.append(entry);
lastEntry = entry;
}
}
const int n = iEntries.count();
if (n > oldEntryCount) {
// We have added some entries, update the model
iModel->beginInsertRows(QModelIndex(), oldEntryCount, n-1);
iModel->endInsertRows();
Q_EMIT iModel->countChanged();
}
}
setCurrentIndex(aStackPos);
validateCurrentIndex();
}
void BooksPageStack::Private::setPageMarks(BooksPos::List aPageMarks)
{
if (iPageMarks != aPageMarks) {
iPageMarks = aPageMarks;
const int prevCurrentPage = currentPage();
HDEBUG(iPageMarks);
const int n = iEntries.count();
for (int i=0; i<n; i++) {
Entry entry = iEntries.at(i);
const int page = findPage(entry.iPos);
if (entry.iPage != page) {
entry.iPage = page;
iEntries[i] = entry;
pageChanged(i);
}
}
checkCurrentPage(prevCurrentPage);
}
}
int BooksPageStack::Private::findPage(BooksPos aPos) const
{
BooksPos::ConstIterator it = qBinaryFind(iPageMarks, aPos);
if (it == iPageMarks.end()) {
it = qLowerBound(iPageMarks, aPos);
if (it == iPageMarks.end()) {
return iPageMarks.count() - 1;
} else if (it == iPageMarks.begin()) {
return 0;
} else {
return (it - iPageMarks.begin()) - 1;
}
} else {
return it - iPageMarks.begin();
}
}
BooksPos BooksPageStack::Private::getPosAt(int aIndex) const
{
if (iPageMarks.isEmpty()) {
return BooksPos(0, 0, 0);
} else if (aIndex < 0) {
return iPageMarks.first();
} else if (aIndex >= iPageMarks.count()) {
return iPageMarks.last();
} else {
return iPageMarks.at(aIndex);
}
}
void BooksPageStack::Private::push(BooksPos aPos, int aPage)
{
Entry last = iEntries.last();
if (last.iPos != aPos || last.iPage != aPage) {
// We push on top of the current position. If we have reached
// the depth limit, we push the entire stack down
const int n = iEntries.count();
if (n >= MAX_DEPTH) {
for (int i=1; i<n; i++) {
iEntries[i-1] = iEntries[i];
pageChanged(i-1);
}
iEntries[n-1] = Entry(aPos, aPage);
pageChanged(n-1);
} else {
if (n >= iCurrentIndex+2) {
if (n > iCurrentIndex+2) {
// Drop unnecessary entries
iModel->beginRemoveRows(QModelIndex(), iCurrentIndex+2, n-1);
while (iEntries.count() > iCurrentIndex+2) {
iEntries.removeLast();
}
iModel->endRemoveRows();
Q_EMIT iModel->countChanged();
queueSignals(SignalCountChanged);
}
// And replace the next one
setPageAt(iCurrentIndex+1, aPage);
} else {
// Just push the new one
const int i = iCurrentIndex+1;
iModel->beginInsertRows(QModelIndex(), i, i);
iEntries.append(Entry(aPos, aPage));
iModel->endInsertRows();
queueSignals(SignalCountChanged);
}
// Move the current index
setCurrentIndex(iCurrentIndex+1);
}
}
}
void BooksPageStack::Private::push(BooksPos aPos)
{
if (iEntries.last().iPos != aPos) {
push(aPos, findPage(aPos));
}
}
void BooksPageStack::Private::push(int aPage)
{
if (iEntries.last().iPage != aPage) {
push(getPosAt(aPage), aPage);
}
}
void BooksPageStack::Private::pop()
{
const int n = iEntries.count();
if (n > 1) {
iModel->beginRemoveRows(QModelIndex(), n-1, n-1);
iEntries.removeLast();
validateCurrentIndex();
iModel->endRemoveRows();
queueSignals(SignalCountChanged);
}
}
void BooksPageStack::Private::clear()
{
const int n = iEntries.count();
if (n > 1) {
Entry currentEntry = iEntries.at(iCurrentIndex);
iModel->beginRemoveRows(QModelIndex(), 1, n-1);
while (iEntries.count() > 1) {
iEntries.removeLast();
}
validateCurrentIndex();
iModel->endRemoveRows();
if (iEntries.at(0) != currentEntry) {
iEntries[0] = currentEntry;
pageChanged(0);
}
queueSignals(SignalCountChanged);
}
}
BooksPos::Stack BooksPageStack::Private::getStack() const
{
const int n = iEntries.count();
BooksPos::Stack stack;
stack.iList.reserve(n);
for (int i=0; i<n; i++) {
stack.iList.append(iEntries.at(i).iPos);
}
stack.iPos = iCurrentIndex;
return stack;
}
// ==========================================================================
// BooksPageStack
// ==========================================================================
BooksPageStack::BooksPageStack(QObject* aParent) :
QAbstractListModel(aParent),
iPrivate(new Private(this))
{
#if QT_VERSION < 0x050000
setRoleNames(roleNames());
#endif
}
int BooksPageStack::count() const
{
return iPrivate->iEntries.count();
}
int BooksPageStack::currentIndex() const
{
return iPrivate->iCurrentIndex;
}
int BooksPageStack::currentPage() const
{
return iPrivate->currentPage();
}
void BooksPageStack::setCurrentIndex(int aIndex)
{
iPrivate->setCurrentIndex(aIndex);
iPrivate->emitQueuedSignals();
}
void BooksPageStack::setCurrentPage(int aPage)
{
iPrivate->setCurrentPage(aPage);
iPrivate->emitQueuedSignals();
}
void BooksPageStack::back()
{
iPrivate->setCurrentIndex(iPrivate->iCurrentIndex - 1);
iPrivate->emitQueuedSignals();
}
void BooksPageStack::forward()
{
iPrivate->setCurrentIndex(iPrivate->iCurrentIndex + 1);
iPrivate->emitQueuedSignals();
}
BooksPos::Stack BooksPageStack::getStack() const
{
return iPrivate->getStack();
}
void BooksPageStack::setStack(BooksPos::List aStack, int aCurrentPos)
{
iPrivate->setStack(aStack, aCurrentPos);
iPrivate->emitQueuedSignals();
}
void BooksPageStack::setPageMarks(BooksPos::List aPageMarks)
{
iPrivate->setPageMarks(aPageMarks);
iPrivate->emitQueuedSignals();
}
int BooksPageStack::pageAt(int aIndex)
{
return iPrivate->pageAt(aIndex);
}
void BooksPageStack::pushPage(int aPage)
{
HDEBUG(aPage);
iPrivate->push(aPage);
iPrivate->emitQueuedSignals();
}
void BooksPageStack::pushPosition(BooksPos aPos)
{
HDEBUG("" << aPos);
iPrivate->push(aPos);
iPrivate->emitQueuedSignals();
}
void BooksPageStack::pop()
{
HDEBUG("");
iPrivate->pop();
iPrivate->emitQueuedSignals();
}
void BooksPageStack::clear()
{
HDEBUG("");
iPrivate->clear();
iPrivate->emitQueuedSignals();
}
QHash<int,QByteArray> BooksPageStack::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(PageRole, "page");
return roles;
}
int BooksPageStack::rowCount(const QModelIndex&) const
{
return iPrivate->iEntries.count();
}
QVariant BooksPageStack::data(const QModelIndex& aIndex, int aRole) const
{
const int row = aIndex.row();
if (iPrivate->isValidIndex(row) && aRole == PageRole) {
return iPrivate->iEntries.at(row).iPage;
}
return QVariant();
}
bool BooksPageStack::setData(const QModelIndex& aIndex, const QVariant& aValue,
int aRole)
{
const int row = aIndex.row();
if (iPrivate->isValidIndex(row) && aRole == PageRole) {
bool ok = false;
const int page = aValue.toInt(&ok);
if (page >= 0 && ok) {
iPrivate->setPageAt(row, page);
iPrivate->emitQueuedSignals();
return true;
}
}
return false;
}
#include "BooksPageStack.moc"

97
app/src/BooksPageStack.h Normal file
View file

@ -0,0 +1,97 @@
/*
* Copyright (C) 2016-2017 Jolla Ltd.
* Contact: Slava Monich <slava.monich@jolla.com>
*
* You may use this file under the terms of the 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
* OWNER 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.
*/
#ifndef BOOKS_STACK_MODEL_H
#define BOOKS_STACK_MODEL_H
#include "BooksTypes.h"
#include "BooksPos.h"
#include <QAbstractListModel>
#include <QtQml>
class BooksPageStack: public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(int currentPage READ currentPage WRITE setCurrentPage NOTIFY currentPageChanged)
Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
enum Role {
PageRole = Qt::UserRole // "page"
};
public:
explicit BooksPageStack(QObject* aParent = NULL);
int count() const;
int currentIndex() const;
void setCurrentIndex(int aIndex);
int currentPage() const;
void setCurrentPage(int aPage);
BooksPos::Stack getStack() const;
void setStack(BooksPos::List aStack, int aCurrentPos);
void setPageMarks(BooksPos::List aPageMarks);
// QAbstractListModel
QHash<int,QByteArray> roleNames() const;
int rowCount(const QModelIndex& aParent) const;
QVariant data(const QModelIndex& aIndex, int aRole) const;
bool setData(const QModelIndex& aIndex, const QVariant& aValue, int aRole);
Q_INVOKABLE int pageAt(int aIndex);
Q_INVOKABLE void pushPage(int aPage);
Q_INVOKABLE void pushPosition(BooksPos aPos);
Q_INVOKABLE void pop();
Q_INVOKABLE void clear();
Q_INVOKABLE void back();
Q_INVOKABLE void forward();
Q_SIGNALS:
void changed();
void countChanged();
void currentIndexChanged();
void currentPageChanged();
private:
class Entry;
class Private;
Private* iPrivate;
};
QML_DECLARE_TYPE(BooksPageStack)
#endif // BOOKS_STACK_MODEL_H

View file

@ -723,10 +723,10 @@ void BooksPageWidget::onLongPressTaskDone()
} }
} else if (task->iKind == INTERNAL_HYPERLINK) { } else if (task->iKind == INTERNAL_HYPERLINK) {
if (iModel) { if (iModel) {
int page = iModel->linkToPage(task->iLink); BooksPos pos = iModel->linkPosition(task->iLink);
if (page >= 0) { if (pos.valid()) {
HDEBUG("link to page" << page); HDEBUG("link to" << pos);
Q_EMIT jumpToPage(page); Q_EMIT pushPosition(pos);
} }
} }
} else if (task->iKind == FOOTNOTE) { } else if (task->iKind == FOOTNOTE) {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015-2016 Jolla Ltd. * Copyright (C) 2015-2017 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:
@ -101,6 +101,7 @@ Q_SIGNALS:
void activeTouch(int touchX, int touchY); void activeTouch(int touchX, int touchY);
void jumpToPage(int page); void jumpToPage(int page);
void showFootnote(int touchX, int touchY, QString text, QString imageId); void showFootnote(int touchX, int touchY, QString text, QString imageId);
void pushPosition(BooksPos position);
private Q_SLOTS: private Q_SLOTS:
void onWidthChanged(); void onWidthChanged();

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015-2016 Jolla Ltd. * Copyright (C) 2015-2017 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:
@ -34,6 +34,7 @@
#ifndef BOOKS_POSITION_H #ifndef BOOKS_POSITION_H
#define BOOKS_POSITION_H #define BOOKS_POSITION_H
#include <QMetaType>
#include <QVariant> #include <QVariant>
#include <QDebug> #include <QDebug>
#include <QList> #include <QList>
@ -46,6 +47,7 @@ struct BooksPos {
typedef QList<BooksPos> List; typedef QList<BooksPos> List;
typedef QList<BooksPos>::iterator Iterator; typedef QList<BooksPos>::iterator Iterator;
typedef QList<BooksPos>::const_iterator ConstIterator; typedef QList<BooksPos>::const_iterator ConstIterator;
struct Stack { List iList; int iPos; };
BooksPos() : BooksPos() :
iParagraphIndex(-1), iParagraphIndex(-1),
@ -80,6 +82,13 @@ struct BooksPos {
return iParagraphIndex >= 0 && iElementIndex >= 0 && iCharIndex >= 0; return iParagraphIndex >= 0 && iElementIndex >= 0 && iCharIndex >= 0;
} }
void set(int aParagraphIndex, int aElementIndex, int aCharIndex)
{
iParagraphIndex = aParagraphIndex;
iElementIndex = aElementIndex;
iCharIndex = aCharIndex;
}
QVariant toVariant() const QVariant toVariant() const
{ {
QVariantList list; QVariantList list;
@ -124,8 +133,7 @@ struct BooksPos {
(iParagraphIndex > aPos.iParagraphIndex) ? false : (iParagraphIndex > aPos.iParagraphIndex) ? false :
(iElementIndex < aPos.iElementIndex) ? true : (iElementIndex < aPos.iElementIndex) ? true :
(iElementIndex > aPos.iElementIndex) ? false : (iElementIndex > aPos.iElementIndex) ? false :
(iCharIndex < aPos.iCharIndex) ? true : (iCharIndex < aPos.iCharIndex);
(iCharIndex > aPos.iCharIndex) ? false : true;
} }
bool operator > (const BooksPos& aPos) const bool operator > (const BooksPos& aPos) const
@ -159,7 +167,7 @@ struct BooksPos {
QString toString() const QString toString() const
{ {
return QString("BooksPos(%1,%2,%3)").arg(iParagraphIndex). return QString("(%1,%2,%3)").arg(iParagraphIndex).
arg(iElementIndex).arg(iCharIndex); arg(iElementIndex).arg(iCharIndex);
} }
@ -173,4 +181,6 @@ struct BooksPos {
inline QDebug& operator<<(QDebug& aDebug, const BooksPos& aPos) inline QDebug& operator<<(QDebug& aDebug, const BooksPos& aPos)
{ aDebug << qPrintable(aPos.toString()); return aDebug; } { aDebug << qPrintable(aPos.toString()); return aDebug; }
Q_DECLARE_METATYPE(BooksPos)
#endif /* BOOKS_POSITION_H */ #endif /* BOOKS_POSITION_H */

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015-2016 Jolla Ltd. * Copyright (C) 2015-2017 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:
@ -1119,7 +1119,7 @@ void BooksShelf::onCopyTaskDone()
copy = task->iDestItem->book(); copy = task->iDestItem->book();
if (copy) { if (copy) {
copy->retain(); copy->retain();
copy->setLastPos(src->lastPos()); copy->setPageStack(src->pageStack(), src->pageStackPos());
copy->setCoverImage(src->coverImage()); copy->setCoverImage(src->coverImage());
copy->requestCoverImage(); copy->requestCoverImage();
} else { } else {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015-2016 Jolla Ltd. * Copyright (C) 2015-2017 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:
@ -32,6 +32,7 @@
*/ */
#include "BooksDefs.h" #include "BooksDefs.h"
#include "BooksPos.h"
#include "BooksShelf.h" #include "BooksShelf.h"
#include "BooksBook.h" #include "BooksBook.h"
#include "BooksBookModel.h" #include "BooksBookModel.h"
@ -39,6 +40,7 @@
#include "BooksConfig.h" #include "BooksConfig.h"
#include "BooksImportModel.h" #include "BooksImportModel.h"
#include "BooksPathModel.h" #include "BooksPathModel.h"
#include "BooksPageStack.h"
#include "BooksStorageModel.h" #include "BooksStorageModel.h"
#include "BooksPageWidget.h" #include "BooksPageWidget.h"
#include "BooksListWatcher.h" #include "BooksListWatcher.h"
@ -68,12 +70,14 @@
Q_DECL_EXPORT int main(int argc, char **argv) Q_DECL_EXPORT int main(int argc, char **argv)
{ {
QGuiApplication* app = SailfishApp::application(argc, argv); QGuiApplication* app = SailfishApp::application(argc, argv);
qRegisterMetaType<BooksPos>();
BOOKS_QML_REGISTER(BooksShelf, "Shelf"); BOOKS_QML_REGISTER(BooksShelf, "Shelf");
BOOKS_QML_REGISTER(BooksBook, "Book"); BOOKS_QML_REGISTER(BooksBook, "Book");
BOOKS_QML_REGISTER(BooksBookModel, "BookModel"); BOOKS_QML_REGISTER(BooksBookModel, "BookModel");
BOOKS_QML_REGISTER(BooksCoverModel, "CoverModel"); BOOKS_QML_REGISTER(BooksCoverModel, "CoverModel");
BOOKS_QML_REGISTER(BooksImportModel, "BooksImportModel"); BOOKS_QML_REGISTER(BooksImportModel, "BooksImportModel");
BOOKS_QML_REGISTER(BooksPathModel, "BooksPathModel"); BOOKS_QML_REGISTER(BooksPathModel, "BooksPathModel");
BOOKS_QML_REGISTER(BooksPageStack, "BooksPageStack");
BOOKS_QML_REGISTER(BooksStorageModel, "BookStorage"); BOOKS_QML_REGISTER(BooksStorageModel, "BookStorage");
BOOKS_QML_REGISTER(BooksPageWidget, "PageWidget"); BOOKS_QML_REGISTER(BooksPageWidget, "PageWidget");
BOOKS_QML_REGISTER(BooksListWatcher, "ListWatcher"); BOOKS_QML_REGISTER(BooksListWatcher, "ListWatcher");