[app] Added bookPos role to the book model

And fixed a bunch of paging/rotation issues (or at least tried to)
This commit is contained in:
Slava Monich 2020-09-28 02:37:14 +03:00
parent e4cbab0301
commit 04e5a721e8
10 changed files with 204 additions and 175 deletions

View file

@ -84,6 +84,13 @@ SilicaFlickable {
if (footnoteView) footnoteView.hide() if (footnoteView) footnoteView.hide()
} }
function bzzz() {
if (!hapticFeedback) {
hapticFeedback = hapticFeedbackComponent.createObject(root)
}
hapticFeedback.play()
}
onOrientationChanged: { onOrientationChanged: {
if (footnoteView) { if (footnoteView) {
footnoteView.cancel() footnoteView.cancel()
@ -94,10 +101,7 @@ SilicaFlickable {
onSelectingChanged: { onSelectingChanged: {
if (selecting) { if (selecting) {
if (!hapticFeedback) { bzzz()
hapticFeedback = hapticFeedbackComponent.createObject(root)
}
hapticFeedback.play()
} else if (!selectionEmpty) { } else if (!selectionEmpty) {
notification.publish() notification.publish()
} }
@ -105,21 +109,25 @@ SilicaFlickable {
Component { Component {
id: linkMenuComponent id: linkMenuComponent
BooksLinkMenu { } BooksLinkMenu { }
} }
Component { Component {
id: imageViewComponent id: imageViewComponent
BooksImageView { } BooksImageView { }
} }
Component { Component {
id: footnoteViewComponent id: footnoteViewComponent
BooksFootnoteView { } BooksFootnoteView { }
} }
Component { Component {
id: hapticFeedbackComponent id: hapticFeedbackComponent
ThemeEffect { effect: ThemeEffect.Press } ThemeEffect { effect: ThemeEffect.Press }
} }
@ -127,27 +135,32 @@ SilicaFlickable {
MenuItem { MenuItem {
//% "Back to library" //% "Back to library"
text: qsTrId("harbour-books-book-view-back") text: qsTrId("harbour-books-book-view-back")
onClicked: root.closeBook() onClicked: root.closeBook()
} }
} }
BookModel { BookModel {
id: bookModel id: bookModel
book: root.book ? root.book : null book: root.book ? root.book : null
size: bookViewWatcher.size size: bookViewWatcher.size
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(page) onJumpToPage: bookView.jumpTo(page)
} }
Notification { Notification {
id: notification id: notification
//: Pop-up notification //: Pop-up notification
//% "Copied to clipboard" //% "Copied to clipboard"
previewBody: qsTrId("harbour-books-book-view-copied_to_clipboard") previewBody: qsTrId("harbour-books-book-view-copied_to_clipboard")
expireTimeout: 2000 expireTimeout: 2000
Component.onCompleted: { Component.onCompleted: {
if ("icon" in notification) { if ("icon" in notification) {
notification.icon = "icon-s-clipboard" notification.icon = "icon-s-clipboard"
@ -157,6 +170,7 @@ SilicaFlickable {
SilicaListView { SilicaListView {
id: bookView id: bookView
model: bookModel model: bookModel
anchors.fill: parent anchors.fill: parent
flickDeceleration: maximumFlickVelocity flickDeceleration: maximumFlickVelocity
@ -167,12 +181,14 @@ SilicaFlickable {
opacity: loading ? 0 : 1 opacity: loading ? 0 : 1
visible: opacity > 0 visible: opacity > 0
interactive: root.interactive interactive: root.interactive
readonly property real maxContentX: Math.max(0, contentWidth - width) readonly property real maxContentX: Math.max(0, contentWidth - width)
readonly property int currentPage: stackModel.currentPage readonly property int currentPage: stackModel.currentPage
property bool completed property bool completed
Component.onCompleted: { Component.onCompleted: {
bookViewWatcher.positionViewAtIndex(currentPage) bookViewWatcher.positionViewAtIndex(currentPage)
pager.setPage(currentPage)
completed = true completed = true
} }
@ -180,6 +196,7 @@ SilicaFlickable {
if (completed && !moving && !scrollAnimation.running) { if (completed && !moving && !scrollAnimation.running) {
bookViewWatcher.positionViewAtIndex(currentPage) bookViewWatcher.positionViewAtIndex(currentPage)
} }
pager.setPage(currentPage)
} }
onCurrentIndexChanged: { onCurrentIndexChanged: {
@ -189,17 +206,19 @@ SilicaFlickable {
} }
onMovingChanged: { onMovingChanged: {
if (!moving) { if (!moving && currentIndex >= 0) {
updateModel() updateModel()
} }
} }
delegate: BooksPageView { delegate: BooksPageView {
id: pageView id: pageView
width: bookView.width width: bookView.width
height: bookView.height height: bookView.height
model: bookModel bookModel: bookView.model
page: index page: model.pageIndex
bookPos: model.bookPos
leftMargin: bookModel.leftMargin leftMargin: bookModel.leftMargin
rightMargin: bookModel.rightMargin rightMargin: bookModel.rightMargin
topMargin: bookModel.topMargin topMargin: bookModel.topMargin
@ -210,6 +229,7 @@ SilicaFlickable {
pageNumberVisible: _currentState.page pageNumberVisible: _currentState.page
currentPage: ListView.isCurrentItem currentPage: ListView.isCurrentItem
title: bookModel.title title: bookModel.title
onJumpToPage: bookView.jumpTo(page) onJumpToPage: bookView.jumpTo(page)
onPushPosition: stackModel.pushPosition(position) // bookView.jumpTo(page) onPushPosition: stackModel.pushPosition(position) // bookView.jumpTo(page)
onPageClicked: { onPageClicked: {
@ -248,17 +268,11 @@ SilicaFlickable {
} }
} }
property int jumpingTo: -1
function jumpTo(page) { function jumpTo(page) {
if (book && page >=0 && page !== currentIndex) { if (book && page >=0) {
jumpingTo = page console.log("showing page", page)
bookViewWatcher.positionViewAtIndex(page) bookViewWatcher.positionViewAtIndex(page)
pager.currentPage = page stackModel.currentPage = page
jumpingTo = -1
if (currentIndex !== page) {
console.log("oops, still at", currentPage)
resetPager.restart()
}
} }
} }
@ -282,19 +296,18 @@ SilicaFlickable {
function updateModel() { function updateModel() {
hideViews() hideViews()
stackModel.currentPage = currentIndex stackModel.currentPage = bookViewWatcher.currentIndex
if (!pager.pressed) {
pager.currentPage = currentIndex
}
} }
ListWatcher { ListWatcher {
id: bookViewWatcher id: bookViewWatcher
listView: bookView listView: bookView
} }
NumberAnimation { NumberAnimation {
id: scrollAnimation id: scrollAnimation
target: bookView target: bookView
property: "contentX" property: "contentX"
duration: 500 duration: 500
@ -303,17 +316,9 @@ SilicaFlickable {
Behavior on opacity { FadeAnimation {} } Behavior on opacity { FadeAnimation {} }
Timer {
id: resetPager
interval: 0
onTriggered: {
console.log("resetting pager to", bookView.currentIndex)
pager.currentPage = bookView.currentIndex
}
}
BooksPageTools { BooksPageTools {
id: pageTools id: pageTools
anchors { anchors {
top: parent.top top: parent.top
left: parent.left left: parent.left
@ -323,13 +328,16 @@ SilicaFlickable {
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 {} }
onIncreaseFontSize: bookModel.increaseFontSize() onIncreaseFontSize: bookModel.increaseFontSize()
onDecreaseFontSize: bookModel.decreaseFontSize() onDecreaseFontSize: bookModel.decreaseFontSize()
Behavior on opacity { FadeAnimation {} }
} }
BooksPager { BooksPager {
id: pager id: pager
anchors { anchors {
left: parent.left left: parent.left
right: parent.right right: parent.right
@ -341,15 +349,19 @@ SilicaFlickable {
stack: stackModel 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 && pageCount) ? 0.75 : 0
visible: opacity > 0 visible: opacity > 0
onPageChanged: bookView.jumpTo(page) onPageChanged: bookView.jumpTo(page)
onFeedback: bzzz()
Behavior on opacity { FadeAnimation {} } Behavior on opacity { FadeAnimation {} }
} }
} }
BooksTitleLabel { BooksTitleLabel {
id: titleLabel id: titleLabel
anchors { anchors {
top: parent.top top: parent.top
left: parent.left left: parent.left
@ -365,6 +377,7 @@ SilicaFlickable {
BusyIndicator { BusyIndicator {
id: busyIndicator id: busyIndicator
anchors.centerIn: parent anchors.centerIn: parent
size: BusyIndicatorSize.Large size: BusyIndicatorSize.Large
running: loading running: loading
@ -384,10 +397,12 @@ SilicaFlickable {
bottom: parent.bottom bottom: parent.bottom
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
} }
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
onClicked: root.closeBook()
Behavior on opacity { FadeAnimation { } } Behavior on opacity { FadeAnimation { } }
} }
@ -402,7 +417,6 @@ SilicaFlickable {
color: Theme.highlightColor color: Theme.highlightColor
opacity: loading ? 1 : 0 opacity: loading ? 1 : 0
visible: opacity > 0 visible: opacity > 0
Behavior on opacity { FadeAnimation {} }
text: bookModel ? (bookModel.resetReason == BookModel.ReasonLoading ? text: bookModel ? (bookModel.resetReason == BookModel.ReasonLoading ?
//% "Loading..." //% "Loading..."
qsTrId("harbour-books-book-view-loading") : qsTrId("harbour-books-book-view-loading") :
@ -414,10 +428,11 @@ SilicaFlickable {
qsTrId("harbour-books-book-view-applying_smaller_fonts") : qsTrId("harbour-books-book-view-applying_smaller_fonts") :
//% "Formatting..." //% "Formatting..."
qsTrId("harbour-books-book-view-formatting")) : "" qsTrId("harbour-books-book-view-formatting")) : ""
Behavior on opacity { FadeAnimation {} }
} }
function performAction(action) function performAction(action) {
{
switch (action) { switch (action) {
case BooksSettings.ActionPreviousPage: case BooksSettings.ActionPreviousPage:
bookView.prevPage() bookView.prevPage()
@ -431,8 +446,10 @@ SilicaFlickable {
MediaKey { MediaKey {
enabled: viewActive && haveVolumeUpAction enabled: viewActive && haveVolumeUpAction
key: Qt.Key_VolumeUp key: Qt.Key_VolumeUp
onPressed: volumeUpAction() onPressed: volumeUpAction()
onRepeat: volumeUpAction() onRepeat: volumeUpAction()
function volumeUpAction() { function volumeUpAction() {
performAction(Settings.volumeUpAction) performAction(Settings.volumeUpAction)
} }
@ -441,8 +458,10 @@ SilicaFlickable {
MediaKey { MediaKey {
enabled: viewActive && haveVolumeDownAction enabled: viewActive && haveVolumeDownAction
key: Qt.Key_VolumeDown key: Qt.Key_VolumeDown
onPressed: volumeDownAction() onPressed: volumeDownAction()
onRepeat: volumeDownAction() onRepeat: volumeDownAction()
function volumeDownAction() { function volumeDownAction() {
performAction(Settings.volumeDownAction) performAction(Settings.volumeDownAction)
} }
@ -455,6 +474,7 @@ SilicaFlickable {
Resource { Resource {
id: volumeKeysResource id: volumeKeysResource
type: Resource.ScaleButton type: Resource.ScaleButton
optional: true optional: true
} }

View file

@ -42,8 +42,9 @@ Rectangle {
color: Settings.pageBackgroundColor color: Settings.pageBackgroundColor
property alias model: widget.model
property alias page: widget.page property alias page: widget.page
property alias bookPos: widget.bookPos
property alias bookModel: widget.model
property alias leftMargin: widget.leftMargin property alias leftMargin: widget.leftMargin
property alias rightMargin: widget.rightMargin property alias rightMargin: widget.rightMargin
property alias topMargin: widget.topMargin property alias topMargin: widget.topMargin

View file

@ -1,6 +1,6 @@
/* /*
Copyright (C) 2015-2017 Jolla Ltd. Copyright (C) 2015-2020 Jolla Ltd.
Contact: Slava Monich <slava.monich@jolla.com> Copyright (C) 2015-2020 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:
@ -8,14 +8,15 @@
modification, are permitted provided that the following conditions modification, are permitted provided that the following conditions
are met: are met:
* Redistributions of source code must retain the above copyright 1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright 2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer
documentation and/or other materials provided with the distribution. in the documentation and/or other materials provided with the
* Neither the name of Jolla Ltd nor the names of its contributors may distribution.
be used to endorse or promote products derived from this software 3. Neither the names of the copyright holders nor the names of its
without specific prior written permission. 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" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
@ -36,6 +37,7 @@ import harbour.books 1.0
Item { Item {
id: root id: root
height: slider.height height: slider.height
property var stack property var stack
@ -45,10 +47,24 @@ Item {
readonly property bool canGoForward: haveHistory && (stack.currentIndex < (stack.count - 1)) readonly property bool canGoForward: haveHistory && (stack.currentIndex < (stack.count - 1))
property real leftMargin: Theme.horizontalPageMargin property real leftMargin: Theme.horizontalPageMargin
property real rightMargin: Theme.horizontalPageMargin property real rightMargin: Theme.horizontalPageMargin
property alias currentPage: slider.value
property alias pressed: slider.pressed property alias pressed: slider.pressed
property bool _externalChange
signal pageChanged(var page) signal pageChanged(var page)
signal feedback()
function clearStack() {
if (haveHistory) {
stack.clear()
feedback()
}
}
function setPage(page) {
_externalChange = true
slider.value = page
_externalChange = false
}
states: [ states: [
State { State {
@ -71,23 +87,25 @@ Item {
MouseArea { MouseArea {
id: navigateBackArea id: navigateBackArea
property bool down: pressed && containsMouse property bool down: pressed && containsMouse
width: navigateBack.width + root.leftMargin width: navigateBack.width + root.leftMargin
height: navigateBack.height height: navigateBack.height
visible: navigateBack.visible
anchors { anchors {
left: parent.left left: parent.left
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
onClicked: stack.back() onClicked: stack.back()
onPressAndHold: stack.clear() onPressAndHold: clearStack()
} }
IconButton { IconButton {
id: navigateBack id: navigateBack
icon.source: "image://theme/icon-m-left?" + Settings.primaryPageToolColor icon.source: "image://theme/icon-m-left?" + Settings.primaryPageToolColor
opacity: canGoBack ? 1 : 0 opacity: canGoBack ? 1 : 0
visible: opacity > 0 visible: opacity > 0
Behavior on opacity { FadeAnimation {} }
down: navigateBackArea.down || (pressed && containsMouse) down: navigateBackArea.down || (pressed && containsMouse)
anchors { anchors {
left: parent.left left: parent.left
@ -95,56 +113,58 @@ Item {
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
onClicked: stack.back() onClicked: stack.back()
onPressAndHold: stack.clear() onPressAndHold: clearStack()
Behavior on opacity { FadeAnimation {} }
} }
BooksPageSlider { BooksPageSlider {
id: slider id: slider
anchors {
bottom: parent.bottom anchors.bottom: parent.bottom
}
width: parent.width - 2*x width: parent.width - 2*x
stepSize: 1 stepSize: 1
minimumValue: 0 minimumValue: 0
maximumValue: pageCount > 0 ? pageCount - 1 : 0 maximumValue: pageCount > 0 ? pageCount - 1 : 0
valueText: ""
label: ""
leftMargin: Theme.horizontalPageMargin leftMargin: Theme.horizontalPageMargin
rightMargin: 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) onMaximumValueChanged: _updateHighlightToValue()
onSliderValueChanged: if (!_externalChange) pageChanged(value)
Behavior on x { SmoothedAnimation { duration: 250 } } Behavior on x { SmoothedAnimation { duration: 250 } }
} }
MouseArea { MouseArea {
id: navigateForwardArea id: navigateForwardArea
property bool down: pressed && containsMouse property bool down: pressed && containsMouse
width: navigateForward.width + root.rightMargin width: navigateForward.width + root.rightMargin
height: navigateForward.height height: navigateForward.height
visible: navigateForward.visible
anchors { anchors {
right: parent.right right: parent.right
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
onClicked: stack.forward() onClicked: stack.forward()
onPressAndHold: stack.clear() onPressAndHold: clearStack()
} }
IconButton { IconButton {
id: navigateForward id: navigateForward
icon.source: "image://theme/icon-m-right?" + Settings.primaryPageToolColor icon.source: "image://theme/icon-m-right?" + Settings.primaryPageToolColor
down: navigateForwardArea.down || (pressed && containsMouse) down: navigateForwardArea.down || (pressed && containsMouse)
opacity: canGoForward ? 1 : 0 opacity: canGoForward ? 1 : 0
visible: opacity > 0 visible: opacity > 0
Behavior on opacity { FadeAnimation {} }
anchors { anchors {
right: parent.right right: parent.right
rightMargin: root.rightMargin rightMargin: root.rightMargin
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
onClicked: stack.forward() onClicked: stack.forward()
onPressAndHold: stack.clear() onPressAndHold: clearStack()
Behavior on opacity { FadeAnimation {} }
} }
} }

View file

@ -1,6 +1,6 @@
/* /*
Copyright (C) 2015-2019 Jolla Ltd. Copyright (C) 2015-2020 Jolla Ltd.
Copyright (C) 2015-2019 Slava Monich <slava.monich@jolla.com> Copyright (C) 2015-2020 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:
@ -172,6 +172,7 @@ SilicaFlickable {
flickDeceleration: maximumFlickVelocity flickDeceleration: maximumFlickVelocity
orientation: ListView.Horizontal orientation: ListView.Horizontal
snapMode: ListView.SnapOneItem snapMode: ListView.SnapOneItem
highlightRangeMode: ListView.StrictlyEnforceRange
spacing: Theme.paddingMedium spacing: Theme.paddingMedium
interactive: !dragInProgress && !dragScrollAnimation.running interactive: !dragInProgress && !dragScrollAnimation.running

View file

@ -299,7 +299,8 @@ void BooksBookModel::PagingTask::performTask()
// ========================================================================== // ==========================================================================
enum BooksBookModelRole { enum BooksBookModelRole {
BooksBookModelPageIndex = Qt::UserRole BooksBookModelPageIndex = Qt::UserRole,
BooksBookModelBookPos
}; };
BooksBookModel::BooksBookModel(QObject* aParent) : BooksBookModel::BooksBookModel(QObject* aParent) :
@ -528,11 +529,23 @@ void BooksBookModel::setBottomMargin(int aMargin)
} }
} }
void BooksBookModel::emitBookPosChanged()
{
const int n = pageCount();
if (n > 0) {
const QModelIndex topLeft(index(0));
const QModelIndex bottomRight(index(n - 1));
const QVector<int> roles(1, BooksBookModelBookPos);
Q_EMIT dataChanged(topLeft, bottomRight, roles);
}
}
void BooksBookModel::updateModel(int aPrevPageCount) void BooksBookModel::updateModel(int aPrevPageCount)
{ {
const int newPageCount = pageCount(); const int newPageCount = pageCount();
if (aPrevPageCount != newPageCount) { if (aPrevPageCount != newPageCount) {
HDEBUG(aPrevPageCount << "->" << newPageCount); HDEBUG(aPrevPageCount << "->" << newPageCount);
emitBookPosChanged();
if (newPageCount > aPrevPageCount) { if (newPageCount > aPrevPageCount) {
beginInsertRows(QModelIndex(), aPrevPageCount, newPageCount-1); beginInsertRows(QModelIndex(), aPrevPageCount, newPageCount-1);
endInsertRows(); endInsertRows();
@ -695,6 +708,7 @@ QHash<int,QByteArray> BooksBookModel::roleNames() const
{ {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles.insert(BooksBookModelPageIndex, "pageIndex"); roles.insert(BooksBookModelPageIndex, "pageIndex");
roles.insert(BooksBookModelBookPos, "bookPos");
return roles; return roles;
} }
@ -705,10 +719,13 @@ int BooksBookModel::rowCount(const QModelIndex&) const
QVariant BooksBookModel::data(const QModelIndex& aIndex, int aRole) const QVariant BooksBookModel::data(const QModelIndex& aIndex, int aRole) const
{ {
const int i = aIndex.row(); const int row = aIndex.row();
if (i >= 0 && i < pageCount()) { if (row >= 0 && row < pageCount()) {
switch (aRole) { switch ((BooksBookModelRole)aRole) {
case BooksBookModelPageIndex: return i; case BooksBookModelPageIndex:
return row;
case BooksBookModelBookPos:
return QVariant::fromValue(iData->iPageMarks.at(row));
} }
} }
return QVariant(); return QVariant();

View file

@ -124,14 +124,15 @@ public:
int fontSizeAdjust() const; int fontSizeAdjust() const;
// QAbstractListModel // QAbstractListModel
virtual QHash<int,QByteArray> roleNames() const; virtual QHash<int,QByteArray> roleNames() const Q_DECL_OVERRIDE;
virtual int rowCount(const QModelIndex& aParent) const; virtual int rowCount(const QModelIndex& aParent) const Q_DECL_OVERRIDE;
virtual QVariant data(const QModelIndex& aIndex, int aRole) const; virtual QVariant data(const QModelIndex& aIndex, int aRole) const Q_DECL_OVERRIDE;
private: private:
void updateSize(); void updateSize();
void updateModel(int aPrevPageCount); void updateModel(int aPrevPageCount);
void startReset(ResetReason aReason = ReasonUnknown, bool aFull = true); void startReset(ResetReason aReason = ReasonUnknown, bool aFull = true);
void emitBookPosChanged();
private Q_SLOTS: private Q_SLOTS:
void onResetProgress(int aProgress); void onResetProgress(int aProgress);
@ -141,11 +142,11 @@ private Q_SLOTS:
void onHashChanged(); void onHashChanged();
Q_SIGNALS: Q_SIGNALS:
void loadingChanged() Q_DECL_OVERRIDE;
void sizeChanged(); void sizeChanged();
void bookChanged(); void bookChanged();
void bookModelChanged(); void bookModelChanged();
void titleChanged(); void titleChanged();
void loadingChanged();
void pageCountChanged(); void pageCountChanged();
void pageMarksChanged(); void pageMarksChanged();
void progressChanged(); void progressChanged();

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2015-2017 Jolla Ltd. * Copyright (C) 2015-2020 Jolla Ltd.
* Contact: Slava Monich <slava.monich@jolla.com> * Copyright (C) 2015-2020 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:
* *
@ -8,15 +8,15 @@
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
* are met: * are met:
* *
* * Redistributions of source code must retain the above copyright * 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer. * notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright * 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in * notice, this list of conditions and the following disclaimer
* the documentation and/or other materials provided with the * in the documentation and/or other materials provided with the
* distribution. * distribution.
* * Neither the name of Jolla Ltd nor the names of its contributors * 3. Neither the names of the copyright holders nor the names of its
* may be used to endorse or promote products derived from this * contributors may be used to endorse or promote products derived
* software without specific prior written permission. * from this software without specific prior written permission.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@ -27,7 +27,7 @@
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE ``USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
@ -49,7 +49,7 @@ BooksListWatcher::BooksListWatcher(QObject* aParent) :
iContentY(0), iContentY(0),
iListView(NULL), iListView(NULL),
iCenterMode(-1), iCenterMode(-1),
iPositionIsChanging(false), iPositionIsChanging(0),
iResizeTimer(new QTimer(this)) iResizeTimer(new QTimer(this))
{ {
iResizeTimer->setSingleShot(true); iResizeTimer->setSingleShot(true);
@ -121,6 +121,7 @@ void BooksListWatcher::positionViewAtIndex(int aIndex)
if (iListView) { if (iListView) {
HDEBUG(aIndex); HDEBUG(aIndex);
doPositionViewAtIndex(aIndex); doPositionViewAtIndex(aIndex);
updateCurrentIndex();
} }
} }
@ -146,21 +147,15 @@ void BooksListWatcher::doPositionViewAtIndex(int aIndex)
iCenterMode = 1; iCenterMode = 1;
} }
} }
iPositionIsChanging = true; iPositionIsChanging++;
positionViewAtIndex(aIndex, iCenterMode); positionViewAtIndex(aIndex, iCenterMode);
// This is probably a bug in QQuickListView - it first calculates const int currentIndex = getCurrentIndex();
// the item position and then starts instantiating the delegates. if (currentIndex != aIndex) {
// 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 // Didn't work from the first try, give it another go
HDEBUG("retrying..."); HDEBUG("still" << currentIndex << ", retrying...");
positionViewAtIndex(aIndex, iCenterMode); positionViewAtIndex(aIndex, iCenterMode);
} }
iPositionIsChanging = false; iPositionIsChanging--;
updateCurrentIndex();
} }
void BooksListWatcher::positionViewAtIndex(int aIndex, int aMode) void BooksListWatcher::positionViewAtIndex(int aIndex, int aMode)
@ -208,10 +203,12 @@ int BooksListWatcher::getCurrentIndex()
void BooksListWatcher::tryToRestoreCurrentIndex() void BooksListWatcher::tryToRestoreCurrentIndex()
{ {
HASSERT(!iPositionIsChanging); HASSERT(!iPositionIsChanging);
const int index = getCurrentIndex(); if (iCurrentIndex >= 0 && !iPositionIsChanging) {
if (iCurrentIndex != index) { const int index = getCurrentIndex();
if (iCurrentIndex >= 0) { if (iCurrentIndex != index) {
HDEBUG(index << "->" << iCurrentIndex);
doPositionViewAtIndex(iCurrentIndex); doPositionViewAtIndex(iCurrentIndex);
HDEBUG(getCurrentIndex());
} }
} }
} }
@ -219,9 +216,9 @@ void BooksListWatcher::tryToRestoreCurrentIndex()
void BooksListWatcher::updateCurrentIndex() void BooksListWatcher::updateCurrentIndex()
{ {
HASSERT(!iPositionIsChanging); HASSERT(!iPositionIsChanging);
if (contentWidth() > 0 || contentHeight() > 0) { if (!iPositionIsChanging && (contentWidth() > 0 || contentHeight() > 0)) {
const int index = getCurrentIndex(); const int index = getCurrentIndex();
if (iCurrentIndex != index) { if (iCurrentIndex != index && index >= 0) {
iCurrentIndex = index; iCurrentIndex = index;
HDEBUG(index << contentWidth() << "x" << contentHeight()); HDEBUG(index << contentWidth() << "x" << contentHeight());
Q_EMIT currentIndexChanged(); Q_EMIT currentIndexChanged();
@ -234,7 +231,7 @@ void BooksListWatcher::updateCurrentIndex()
void BooksListWatcher::updateSize() void BooksListWatcher::updateSize()
{ {
const QSize size(iListView->width(), iListView->height()); const QSize size(iListView->width(), iListView->height());
HDEBUG(size); HDEBUG(size << getCurrentIndex());
if (iSize != size) { if (iSize != size) {
const QSize oldSize(iSize); const QSize oldSize(iSize);
iSize = size; iSize = size;
@ -282,7 +279,7 @@ void BooksListWatcher::onContentXChanged()
{ {
HASSERT(sender() == iListView); HASSERT(sender() == iListView);
iContentX = contentX(); iContentX = contentX();
if (!iPositionIsChanging) { if (!iPositionIsChanging && !iResizeTimer->isActive()) {
updateCurrentIndex(); updateCurrentIndex();
} }
} }
@ -291,7 +288,7 @@ void BooksListWatcher::onContentYChanged()
{ {
HASSERT(sender() == iListView); HASSERT(sender() == iListView);
iContentY = contentY(); iContentY = contentY();
if (!iPositionIsChanging) { if (!iPositionIsChanging && !iResizeTimer->isActive()) {
updateCurrentIndex(); updateCurrentIndex();
} }
} }
@ -299,7 +296,7 @@ void BooksListWatcher::onContentYChanged()
void BooksListWatcher::onContentSizeChanged() void BooksListWatcher::onContentSizeChanged()
{ {
HASSERT(sender() == iListView); HASSERT(sender() == iListView);
if (!iPositionIsChanging) { if (!iPositionIsChanging && !iResizeTimer->isActive()) {
tryToRestoreCurrentIndex(); tryToRestoreCurrentIndex();
updateCurrentIndex(); updateCurrentIndex();
} }

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2015-2017 Jolla Ltd. * Copyright (C) 2015-2020 Jolla Ltd.
* Contact: Slava Monich <slava.monich@jolla.com> * Copyright (C) 2015-2020 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:
* *
@ -8,15 +8,15 @@
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
* are met: * are met:
* *
* * Redistributions of source code must retain the above copyright * 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer. * notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright * 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in * notice, this list of conditions and the following disclaimer
* the documentation and/or other materials provided with the * in the documentation and/or other materials provided with the
* distribution. * distribution.
* * Neither the name of Jolla Ltd nor the names of its contributors * 3. Neither the names of the copyright holders nor the names of its
* may be used to endorse or promote products derived from this * contributors may be used to endorse or promote products derived
* software without specific prior written permission. * from this software without specific prior written permission.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@ -96,7 +96,7 @@ private:
qreal iContentY; qreal iContentY;
QQuickItem* iListView; QQuickItem* iListView;
int iCenterMode; int iCenterMode;
bool iPositionIsChanging; int iPositionIsChanging;
QTimer* iResizeTimer; QTimer* iResizeTimer;
}; };

View file

@ -546,7 +546,7 @@ void BooksPageWidget::setPressed(bool aPressed)
if (iPressed != aPressed) { if (iPressed != aPressed) {
iPressed = aPressed; iPressed = aPressed;
if (!iPressed && iSelecting) { if (!iPressed && iSelecting) {
HDEBUG("leaving selection mode"); HDEBUG("page" << iPage << "leaving selection mode");
iSelecting = false; iSelecting = false;
Q_EMIT selectingChanged(); Q_EMIT selectingChanged();
} }
@ -558,7 +558,7 @@ void BooksPageWidget::setCurrentPage(bool aCurrentPage)
{ {
if (iCurrentPage != aCurrentPage) { if (iCurrentPage != aCurrentPage) {
iCurrentPage = aCurrentPage; iCurrentPage = aCurrentPage;
HDEBUG(iCurrentPage); HDEBUG("page" << iPage << iCurrentPage);
if (!iCurrentPage) clearSelection(); if (!iCurrentPage) clearSelection();
Q_EMIT currentPageChanged(); Q_EMIT currentPageChanged();
} }
@ -578,19 +578,11 @@ void BooksPageWidget::setModel(BooksBookModel* aModel)
} }
#endif // HARBOUR_DEBUG #endif // HARBOUR_DEBUG
iTextStyle = iModel->textStyle(); iTextStyle = iModel->textStyle();
iPageMark = iModel->pageMark(iPage);
connect(iModel, SIGNAL(bookModelChanged()),
SLOT(onBookModelChanged()));
connect(iModel, SIGNAL(destroyed()), connect(iModel, SIGNAL(destroyed()),
SLOT(onBookModelDestroyed())); SLOT(onBookModelDestroyed()));
connect(iModel, SIGNAL(pageMarksChanged()),
SLOT(onBookModelPageMarksChanged()));
connect(iModel, SIGNAL(loadingChanged()),
SLOT(onBookModelLoadingChanged()));
connect(iModel, SIGNAL(textStyleChanged()), connect(iModel, SIGNAL(textStyleChanged()),
SLOT(onTextStyleChanged())); SLOT(onTextStyleChanged()));
} else { } else {
iPageMark.invalidate();
iTextStyle = BooksTextStyle::defaults(); iTextStyle = BooksTextStyle::defaults();
} }
resetView(); resetView();
@ -613,14 +605,6 @@ void BooksPageWidget::onColorsChanged()
scheduleRepaint(); scheduleRepaint();
} }
void BooksPageWidget::onBookModelChanged()
{
HDEBUG(iModel->title());
BooksLoadingSignalBlocker block(this);
iPageMark = iModel->pageMark(iPage);
resetView();
}
void BooksPageWidget::onBookModelDestroyed() void BooksPageWidget::onBookModelDestroyed()
{ {
HDEBUG("model destroyed"); HDEBUG("model destroyed");
@ -631,46 +615,26 @@ void BooksPageWidget::onBookModelDestroyed()
resetView(); resetView();
} }
void BooksPageWidget::onBookModelPageMarksChanged()
{
const BooksPos pos = iModel->pageMark(iPage);
if (iPageMark != pos) {
BooksLoadingSignalBlocker block(this);
iPageMark = pos;
HDEBUG("page" << iPage);
resetView();
}
}
void BooksPageWidget::onBookModelLoadingChanged()
{
BooksLoadingSignalBlocker block(this);
if (!iModel->loading()) {
HDEBUG("page" << iPage);
const BooksPos pos = iModel->pageMark(iPage);
if (iPageMark != pos) {
iPageMark = pos;
resetView();
}
}
}
void BooksPageWidget::setPage(int aPage) void BooksPageWidget::setPage(int aPage)
{ {
if (iPage != aPage) { if (iPage != aPage) {
BooksLoadingSignalBlocker block(this); BooksLoadingSignalBlocker block(this);
iPage = aPage; iPage = aPage;
HDEBUG(iPage); HDEBUG(iPage);
const BooksPos pos = iModel->pageMark(iPage);
if (iPageMark != pos) {
iPageMark = pos;
resetView();
}
resetView();
Q_EMIT pageChanged(); Q_EMIT pageChanged();
} }
} }
void BooksPageWidget::setBookPos(const BooksPos& aBookPos)
{
if (iBookPos != aBookPos) {
iBookPos = aBookPos;
HDEBUG("page" << iPage << iBookPos);
resetView();
Q_EMIT bookPosChanged();
}
}
void BooksPageWidget::setLeftMargin(int aMargin) void BooksPageWidget::setLeftMargin(int aMargin)
{ {
if (iMargins.iLeft != aMargin) { if (iMargins.iLeft != aMargin) {
@ -717,7 +681,7 @@ void BooksPageWidget::paint(QPainter* aPainter)
HDEBUG("page" << iPage); HDEBUG("page" << iPage);
aPainter->drawImage(0, 0, iImage); aPainter->drawImage(0, 0, iImage);
iEmpty = false; iEmpty = false;
} else if (iPage >= 0 && iPageMark.valid() && !iData.isNull()) { } else if (iPage >= 0 && iBookPos.valid() && !iData.isNull()) {
if (!iRenderTask) { if (!iRenderTask) {
HDEBUG("page" << iPage << "(scheduled)"); HDEBUG("page" << iPage << "(scheduled)");
scheduleRepaint(); scheduleRepaint();
@ -755,18 +719,19 @@ void BooksPageWidget::resetView()
iFootnoteTask->release(this); iFootnoteTask->release(this);
iFootnoteTask = NULL; iFootnoteTask = NULL;
} }
iImage = QImage();
iData.reset(); iData.reset();
if (iPage >= 0 && iPageMark.valid() && if (iPage >= 0 && iBookPos.valid() &&
width() > 0 && height() > 0 && iModel) { width() > 0 && height() > 0 && iModel) {
shared_ptr<ZLTextModel> textModel = iModel->bookTextModel(); shared_ptr<ZLTextModel> textModel = iModel->bookTextModel();
if (!textModel.isNull()) { if (!textModel.isNull()) {
(iResetTask = new ResetTask(iTaskQueue->pool(), textModel, iTextStyle, (iResetTask = new ResetTask(iTaskQueue->pool(), textModel, iTextStyle,
width(), height(), iMargins, iPageMark))-> width(), height(), iMargins, iBookPos))->
submit(this, SLOT(onResetTaskDone())); submit(this, SLOT(onResetTaskDone()));
cancelRepaint(); cancelRepaint();
} }
} }
if (!iResetTask && !iEmpty) { if (!iEmpty) {
update(); update();
} }
} }
@ -788,8 +753,7 @@ void BooksPageWidget::scheduleRepaint()
const int h = height(); const int h = height();
if (w > 0 && h > 0 && !iData.isNull() && !iData->iView.isNull()) { if (w > 0 && h > 0 && !iData.isNull() && !iData->iView.isNull()) {
const shared_ptr<BooksTextView> view(iData->iView); const shared_ptr<BooksTextView> view(iData->iView);
BooksSettings* settings = iSettings.data(); view->setInvertColors(iSettings->invertColors());
view->setInvertColors(settings->invertColors());
(iRenderTask = new RenderTask(iTaskQueue->pool(), iData, w, h))-> (iRenderTask = new RenderTask(iTaskQueue->pool(), iData, w, h))->
submit(this, SLOT(onRenderTaskDone())); submit(this, SLOT(onRenderTaskDone()));
} else { } else {
@ -1013,6 +977,8 @@ void BooksPageWidget::onWidthChanged()
HVERBOSE((int)width()); HVERBOSE((int)width());
// Width change will probably be followed by height change // Width change will probably be followed by height change
iResizeTimer->start(); iResizeTimer->start();
iImage = QImage();
update();
} }
void BooksPageWidget::onHeightChanged() void BooksPageWidget::onHeightChanged()
@ -1024,6 +990,8 @@ void BooksPageWidget::onHeightChanged()
updateSize(); updateSize();
} else { } else {
iResizeTimer->start(); iResizeTimer->start();
iImage = QImage();
update();
} }
} }

View file

@ -62,6 +62,7 @@ class BooksPageWidget: public QQuickPaintedItem, private BooksLoadingProperty
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(BooksBookModel* model READ model WRITE setModel NOTIFY modelChanged) Q_PROPERTY(BooksBookModel* model READ model WRITE setModel NOTIFY modelChanged)
Q_PROPERTY(BooksPos bookPos READ bookPos WRITE setBookPos NOTIFY bookPosChanged)
public: public:
class Data; class Data;
@ -85,6 +86,9 @@ public:
BooksBookModel* model() const; BooksBookModel* model() const;
void setModel(BooksBookModel* aModel); void setModel(BooksBookModel* aModel);
const BooksPos& bookPos() const;
void setBookPos(const BooksPos& aBookPos);
int leftMargin() const; int leftMargin() const;
int rightMargin() const; int rightMargin() const;
int topMargin() const; int topMargin() const;
@ -103,13 +107,14 @@ public:
Q_INVOKABLE void clearSelection(); Q_INVOKABLE void clearSelection();
Q_SIGNALS: Q_SIGNALS:
void loadingChanged(); void loadingChanged() Q_DECL_OVERRIDE;
void pressedChanged(); void pressedChanged();
void selectingChanged(); void selectingChanged();
void selectionEmptyChanged(); void selectionEmptyChanged();
void currentPageChanged(); void currentPageChanged();
void pageChanged(); void pageChanged();
void modelChanged(); void modelChanged();
void bookPosChanged();
void leftMarginChanged(); void leftMarginChanged();
void rightMarginChanged(); void rightMarginChanged();
void topMarginChanged(); void topMarginChanged();
@ -125,10 +130,7 @@ private Q_SLOTS:
void onWidthChanged(); void onWidthChanged();
void onHeightChanged(); void onHeightChanged();
void onResizeTimeout(); void onResizeTimeout();
void onBookModelChanged();
void onBookModelDestroyed(); void onBookModelDestroyed();
void onBookModelPageMarksChanged();
void onBookModelLoadingChanged();
void onTextStyleChanged(); void onTextStyleChanged();
void onColorsChanged(); void onColorsChanged();
void onResetTaskDone(); void onResetTaskDone();
@ -141,7 +143,7 @@ private Q_SLOTS:
void onFootnoteTaskDone(); void onFootnoteTaskDone();
private: private:
void paint(QPainter *painter); void paint(QPainter *painter) Q_DECL_OVERRIDE;
void updateSize(); void updateSize();
void resetView(); void resetView();
void releaseExtendSelectionTasks(); void releaseExtendSelectionTasks();
@ -160,7 +162,7 @@ private:
QSharedPointer<BooksSettings> iSettings; QSharedPointer<BooksSettings> iSettings;
shared_ptr<BooksTaskQueue> iTaskQueue; shared_ptr<BooksTaskQueue> iTaskQueue;
shared_ptr<ZLTextStyle> iTextStyle; shared_ptr<ZLTextStyle> iTextStyle;
BooksPos iPageMark; BooksPos iBookPos;
QTimer* iResizeTimer; QTimer* iResizeTimer;
BooksBookModel* iModel; BooksBookModel* iModel;
BooksMargins iMargins; BooksMargins iMargins;
@ -192,6 +194,8 @@ inline bool BooksPageWidget::currentPage() const
{ return iCurrentPage; } { return iCurrentPage; }
inline int BooksPageWidget::page() const inline int BooksPageWidget::page() const
{ return iPage; } { return iPage; }
inline const BooksPos& BooksPageWidget::bookPos() const
{ return iBookPos; }
inline BooksBookModel* BooksPageWidget::model() const inline BooksBookModel* BooksPageWidget::model() const
{ return iModel; } { return iModel; }
inline int BooksPageWidget::leftMargin() const inline int BooksPageWidget::leftMargin() const