[app] Added limited support for folders
UI doesn't allow creating or renaming the folders yet and there's no easy way to move books between folders, but if folders are there, they will be shown and the user will be able to navigate between them. Folders can be deleted though.
This commit is contained in:
parent
ef19a5c2e7
commit
89f79e2492
22 changed files with 1029 additions and 243 deletions
|
@ -117,6 +117,7 @@ SOURCES += \
|
||||||
src/BooksLoadingProperty.cpp \
|
src/BooksLoadingProperty.cpp \
|
||||||
src/BooksPageWidget.cpp \
|
src/BooksPageWidget.cpp \
|
||||||
src/BooksPaintContext.cpp \
|
src/BooksPaintContext.cpp \
|
||||||
|
src/BooksPathModel.cpp \
|
||||||
src/BooksSaveTimer.cpp \
|
src/BooksSaveTimer.cpp \
|
||||||
src/BooksSettings.cpp \
|
src/BooksSettings.cpp \
|
||||||
src/BooksShelf.cpp \
|
src/BooksShelf.cpp \
|
||||||
|
@ -152,6 +153,7 @@ HEADERS += \
|
||||||
src/BooksLoadingProperty.h \
|
src/BooksLoadingProperty.h \
|
||||||
src/BooksPageWidget.h \
|
src/BooksPageWidget.h \
|
||||||
src/BooksPaintContext.h \
|
src/BooksPaintContext.h \
|
||||||
|
src/BooksPathModel.h \
|
||||||
src/BooksPos.h \
|
src/BooksPos.h \
|
||||||
src/BooksSaveTimer.h \
|
src/BooksSaveTimer.h \
|
||||||
src/BooksSettings.h \
|
src/BooksSettings.h \
|
||||||
|
|
|
@ -36,6 +36,7 @@ MouseArea {
|
||||||
id: root
|
id: root
|
||||||
parent: dragInProgress ? dragParent : gridView
|
parent: dragInProgress ? dragParent : gridView
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
propagateComposedEvents: true
|
||||||
|
|
||||||
signal deleteItemAt(var index)
|
signal deleteItemAt(var index)
|
||||||
signal dropItem(var mouseX, var mouseY)
|
signal dropItem(var mouseX, var mouseY)
|
||||||
|
@ -84,13 +85,23 @@ MouseArea {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
index = gridView.indexAt(mouseX + gridView.contentX, mouseY + currentShelfView.contentY)
|
index = gridView.indexAt(mouseX + gridView.contentX, mouseY + currentShelfView.contentY)
|
||||||
if (index >= 0 && index === lastReleasedItemIndex) {
|
if (index >= 0) {
|
||||||
|
if (index === lastReleasedItemIndex) {
|
||||||
var item = shelf.get(index);
|
var item = shelf.get(index);
|
||||||
if (item.book && item.accessible) {
|
if (item.accessible) {
|
||||||
|
if (item.book) {
|
||||||
shelfView.openBook(item.book)
|
shelfView.openBook(item.book)
|
||||||
|
} else if (item.shelf) {
|
||||||
|
var path = shelfView.shelf.relativePath
|
||||||
|
shelfView.shelf.relativePath = path ? (path + "/" + item.shelf.name) : item.shelf.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (mouseY + gridView.contentY < 0) {
|
||||||
|
// Let the header item handle it
|
||||||
|
mouse.accepted = false
|
||||||
|
}
|
||||||
|
}
|
||||||
resetPressState()
|
resetPressState()
|
||||||
dragScrollAnimation.stop()
|
dragScrollAnimation.stop()
|
||||||
}
|
}
|
||||||
|
@ -105,9 +116,17 @@ MouseArea {
|
||||||
} else {
|
} else {
|
||||||
pressedDeleteItemIndex = -1
|
pressedDeleteItemIndex = -1
|
||||||
}
|
}
|
||||||
|
if (mouseY + gridView.contentY < 0) {
|
||||||
|
// Let the header item handle it
|
||||||
|
mouse.accepted = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onReleased: {
|
onReleased: {
|
||||||
stopDrag(mouseX, mouseY)
|
stopDrag(mouseX, mouseY)
|
||||||
|
if (mouseY + gridView.contentY < 0) {
|
||||||
|
// Let the header item handle it
|
||||||
|
mouse.accepted = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onPressAndHold: {
|
onPressAndHold: {
|
||||||
if (!shelfView.editMode) {
|
if (!shelfView.editMode) {
|
||||||
|
|
62
app/qml/BooksShelfFooter.qml
Normal file
62
app/qml/BooksShelfFooter.qml
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2015 Jolla Ltd.
|
||||||
|
Contact: Slava Monich <slava.monich@jolla.com>
|
||||||
|
|
||||||
|
You may use this file under the terms of BSD license as follows:
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the Jolla Ltd nor the
|
||||||
|
names of its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
|
||||||
|
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||||
|
THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.0
|
||||||
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
|
||||||
|
// 0: nothing
|
||||||
|
// 1: loading
|
||||||
|
// 2: no books
|
||||||
|
property int footerState
|
||||||
|
property bool allowBusyIndicator
|
||||||
|
|
||||||
|
implicitHeight: Math.max(noBooks.visible ? noBooks.implicitHeight : 0, busy.visible ? busy.implicitHeight : 0) * 2
|
||||||
|
|
||||||
|
InfoLabel {
|
||||||
|
id: noBooks
|
||||||
|
|
||||||
|
//% "No books"
|
||||||
|
text: qsTrId("shelf-view-no-books")
|
||||||
|
visible: footerState == 2
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
id: busy
|
||||||
|
visible: opacity > 0
|
||||||
|
anchors.centerIn: parent
|
||||||
|
size: BusyIndicatorSize.Large
|
||||||
|
running: footerState == 1 && allowBusyIndicator
|
||||||
|
Behavior on opacity { enabled: false }
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,19 +64,31 @@ Item {
|
||||||
readonly property bool animating: scaling || moving
|
readonly property bool animating: scaling || moving
|
||||||
|
|
||||||
property bool _deleting: deleting && !deletingAll
|
property bool _deleting: deleting && !deletingAll
|
||||||
property real _borderRadius: Theme.paddingSmall
|
readonly property real _borderRadius: Theme.paddingSmall
|
||||||
property color _borderColor: Theme.primaryColor
|
readonly property color _borderColor: Theme.primaryColor
|
||||||
|
readonly property real _borderWidth: 2
|
||||||
|
|
||||||
property bool scaledDown: (editMode && !dragged && !pressed && !dropped)
|
property bool scaledDown: (editMode && !dragged && !pressed && !dropped)
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors {
|
||||||
|
margins: root.margins
|
||||||
|
fill: parent
|
||||||
|
}
|
||||||
|
visible: !cover.book
|
||||||
|
source: "images/bookshelf.svg"
|
||||||
|
sourceSize.width: width
|
||||||
|
sourceSize.height: height
|
||||||
|
}
|
||||||
|
|
||||||
BookCover {
|
BookCover {
|
||||||
id: cover
|
id: cover
|
||||||
anchors {
|
anchors {
|
||||||
margins: root.margins
|
margins: root.margins
|
||||||
fill: parent
|
fill: parent
|
||||||
}
|
}
|
||||||
borderWidth: 2
|
|
||||||
borderRadius: _borderRadius
|
borderRadius: _borderRadius
|
||||||
|
borderWidth: book ? _borderWidth : 0
|
||||||
borderColor: _borderColor
|
borderColor: _borderColor
|
||||||
opacity: (copyingIn || copyingOut) ? 0.1 : 1
|
opacity: (copyingIn || copyingOut) ? 0.1 : 1
|
||||||
Behavior on opacity { FadeAnimation { } }
|
Behavior on opacity { FadeAnimation { } }
|
||||||
|
|
132
app/qml/BooksShelfTitle.qml
Normal file
132
app/qml/BooksShelfTitle.qml
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2015 Jolla Ltd.
|
||||||
|
Contact: Slava Monich <slava.monich@jolla.com>
|
||||||
|
|
||||||
|
You may use this file under the terms of BSD license as follows:
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the Jolla Ltd nor the
|
||||||
|
names of its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
|
||||||
|
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||||
|
THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.0
|
||||||
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
|
BackgroundItem {
|
||||||
|
id: root
|
||||||
|
implicitHeight: column.implicitHeight
|
||||||
|
property alias text: label.text
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: column
|
||||||
|
width: parent.width
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
height: Theme.paddingSmall
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: labelItem
|
||||||
|
width: Math.min(label.implicitWidth + icon.width + 3*Theme.paddingMedium, parent.width)
|
||||||
|
height: label.implicitHeight
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: icon
|
||||||
|
height: label.height*3/4
|
||||||
|
sourceSize.height: height
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
source: "images/folder.svg"
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: Theme.paddingMedium
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: label
|
||||||
|
truncationMode: TruncationMode.Fade
|
||||||
|
width: Math.min(parent.width - 2*Theme.paddingMedium, implicitWidth)
|
||||||
|
anchors {
|
||||||
|
left: icon.right
|
||||||
|
leftMargin: Theme.paddingMedium
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
color: (!root.enabled || pressed) ? Theme.highlightColor : Theme.primaryColor
|
||||||
|
Behavior on color { ColorAnimation { duration: 100 } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
height: Theme.paddingSmall
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Item {
|
||||||
|
width: labelItem.width
|
||||||
|
height: 3*Math.floor(PointsPerInch/100)
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: shelfLeft
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: Theme.paddingMedium
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
height: parent.height
|
||||||
|
sourceSize.height: height
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
source: "images/shelf-left.svg"
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: shelfRight
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: Theme.paddingMedium
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
height: parent.height
|
||||||
|
sourceSize.height: height
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
source: "images/shelf-right.svg"
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors {
|
||||||
|
left: shelfLeft.right
|
||||||
|
right: shelfRight.left
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
height: parent.height
|
||||||
|
sourceSize.height: height
|
||||||
|
sourceSize.width: width
|
||||||
|
source: "images/shelf-middle.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,10 +56,10 @@ SilicaFlickable {
|
||||||
signal scrollRight()
|
signal scrollRight()
|
||||||
signal scrollLeft()
|
signal scrollLeft()
|
||||||
|
|
||||||
property bool _haveBooks: shelf && shelf.count
|
property bool _haveBooks: shelfModel && shelfModel.count
|
||||||
property int _cellsPerRow: Math.floor(width/cellWidth)
|
property int _cellsPerRow: Math.floor(width/cellWidth)
|
||||||
readonly property int _remorseTimeout: 5000
|
readonly property int _remorseTimeout: 5000
|
||||||
property bool _loading: !shelf || shelf.loading || startAnimationTimer.running
|
property bool _loading: !shelfModel || shelfModel.loading || startAnimationTimer.running
|
||||||
property var _remorse
|
property var _remorse
|
||||||
|
|
||||||
on_HaveBooksChanged: if (!_haveBooks) shelfView.stopEditing()
|
on_HaveBooksChanged: if (!_haveBooks) shelfView.stopEditing()
|
||||||
|
@ -69,6 +69,12 @@ SilicaFlickable {
|
||||||
property bool needDummyItem: dragInProgress && dragItem.shelfIndex !== shelfView.shelfIndex
|
property bool needDummyItem: dragInProgress && dragItem.shelfIndex !== shelfView.shelfIndex
|
||||||
onNeedDummyItemChanged: if (needDummyItem) hasDummyItem = true
|
onNeedDummyItemChanged: if (needDummyItem) hasDummyItem = true
|
||||||
editMode: shelfView.editMode
|
editMode: shelfView.editMode
|
||||||
|
onRelativePathChanged: longStartTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
BooksPathModel {
|
||||||
|
id: pathModel
|
||||||
|
path: shelfModel.relativePath
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditModeChanged: {
|
onEditModeChanged: {
|
||||||
|
@ -123,27 +129,60 @@ SilicaFlickable {
|
||||||
BooksStorageHeader {
|
BooksStorageHeader {
|
||||||
id: storageHeader
|
id: storageHeader
|
||||||
removable: removableStorage
|
removable: removableStorage
|
||||||
count: shelfModel.count
|
count: shelfModel.bookCount
|
||||||
showCount: !_loading
|
showCount: !_loading
|
||||||
|
enabled: grid.contentY > grid.minContentY || pathModel.count > 0
|
||||||
|
needed: !singleStorage || pathModel.count > 0
|
||||||
|
onClicked: {
|
||||||
|
if (!scrollToTopAnimation.running) {
|
||||||
|
if (grid.contentY > grid.minContentY) {
|
||||||
|
scrollToTopAnimation.start()
|
||||||
|
} else {
|
||||||
|
animationEnabled = true
|
||||||
|
shelfModel.relativePath = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
id: scrollToTopAnimation
|
||||||
|
target: grid
|
||||||
|
property: "contentY"
|
||||||
|
duration: 500
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
to: grid.minContentY
|
||||||
}
|
}
|
||||||
|
|
||||||
SilicaGridView {
|
SilicaGridView {
|
||||||
id: grid
|
id: grid
|
||||||
anchors {
|
anchors {
|
||||||
top: singleStorage ? parent.top : storageHeader.bottom
|
top: storageHeader.bottom
|
||||||
left: parent.left
|
left: parent.left
|
||||||
right: parent.right
|
right: parent.right
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
leftMargin: Math.floor((shelfView.width - _cellsPerRow * shelfView.cellWidth)/2)
|
leftMargin: Math.floor((shelfView.width - _cellsPerRow * shelfView.cellWidth)/2)
|
||||||
}
|
}
|
||||||
model: shelfModel
|
model: shelfModel
|
||||||
interactive: !dragInProgress
|
interactive: !dragInProgress && !scrollToTopAnimation.running
|
||||||
clip: true
|
clip: true
|
||||||
opacity: (!_loading && _haveBooks) ? 1 : 0
|
|
||||||
visible: opacity > 0
|
|
||||||
cellWidth: shelfView.cellWidth
|
cellWidth: shelfView.cellWidth
|
||||||
cellHeight: shelfView.cellHeight
|
cellHeight: shelfView.cellHeight
|
||||||
flickableDirection: Flickable.VerticalFlick
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
header: Column {
|
||||||
|
Repeater {
|
||||||
|
model: pathModel
|
||||||
|
BooksShelfTitle {
|
||||||
|
width: grid.width
|
||||||
|
text: model.name
|
||||||
|
enabled: model.index < (pathModel.count-1)
|
||||||
|
onClicked: {
|
||||||
|
console.log("switching to", model.path)
|
||||||
|
shelfModel.relativePath = model.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
delegate: BooksShelfItem {
|
delegate: BooksShelfItem {
|
||||||
editMode: shelfView.editMode
|
editMode: shelfView.editMode
|
||||||
dropped: dragItem.dropShelfIndex >= 0 &&
|
dropped: dragItem.dropShelfIndex >= 0 &&
|
||||||
|
@ -176,7 +215,16 @@ SilicaFlickable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
footer: BooksShelfFooter {
|
||||||
|
width: grid.width
|
||||||
|
height: visible ? Math.max(implicitHeight, (grid.height > grid.headerItem.height) ? (grid.height - grid.headerItem.height) : 0) : 0
|
||||||
|
allowBusyIndicator: !longStartTimer.running
|
||||||
|
footerState: _haveBooks ? 0 : _loading ? 1 : 2
|
||||||
|
visible: !_haveBooks
|
||||||
|
}
|
||||||
|
|
||||||
property real itemOpacity: 1
|
property real itemOpacity: 1
|
||||||
|
property real minContentY: -headerItem.height
|
||||||
|
|
||||||
moveDisplaced: Transition {
|
moveDisplaced: Transition {
|
||||||
SmoothedAnimation { properties: "x,y"; duration: 150 }
|
SmoothedAnimation { properties: "x,y"; duration: 150 }
|
||||||
|
@ -209,33 +257,9 @@ SilicaFlickable {
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on y { SpringAnimation {} }
|
Behavior on y { SpringAnimation {} }
|
||||||
Behavior on opacity { FadeAnimation {} }
|
|
||||||
VerticalScrollDecorator {}
|
VerticalScrollDecorator {}
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewPlaceholder {
|
|
||||||
//% "No books"
|
|
||||||
text: qsTrId("shelf-view-no-books")
|
|
||||||
enabled: !_loading && !_haveBooks
|
|
||||||
PulleyAnimationHint {
|
|
||||||
id: pulleyAnimationHint
|
|
||||||
flickable: storageView
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: parent.enabled && !editMode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property Item _busyIndicator
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: busyIndicatorComponent
|
|
||||||
BusyIndicator {
|
|
||||||
visible: opacity > 0
|
|
||||||
anchors.centerIn: parent
|
|
||||||
size: BusyIndicatorSize.Large
|
|
||||||
running: _loading && !longStartTimer.running
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: longStartTimer
|
id: longStartTimer
|
||||||
|
@ -245,7 +269,6 @@ SilicaFlickable {
|
||||||
if (shelf.loading) {
|
if (shelf.loading) {
|
||||||
console.log(shelfModel.path, "startup is taking too long")
|
console.log(shelfModel.path, "startup is taking too long")
|
||||||
startAnimationTimer.start()
|
startAnimationTimer.start()
|
||||||
if (!_busyIndicator) _busyIndicator = busyIndicatorComponent.createObject(shelfView)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,65 +33,97 @@ import QtQuick 2.0
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
id: root
|
||||||
anchors {
|
anchors {
|
||||||
top: parent.top
|
|
||||||
left: parent.left
|
left: parent.left
|
||||||
right: parent.right
|
right: parent.right
|
||||||
topMargin: Theme.paddingMedium
|
topMargin: Theme.paddingMedium
|
||||||
}
|
}
|
||||||
spacing: 0
|
spacing: 0
|
||||||
visible: opacity > 0
|
y: needed ? Theme.paddingMedium : -height
|
||||||
opacity: singleStorage ? 0 : 1
|
property alias animationEnabled: yBehavior.enabled
|
||||||
Behavior on opacity { FadeAnimation {} }
|
|
||||||
|
|
||||||
|
Behavior on y {
|
||||||
|
id: yBehavior
|
||||||
|
enabled: false
|
||||||
|
NumberAnimation { duration: 200 }
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool needed
|
||||||
property bool removable
|
property bool removable
|
||||||
property int count
|
property int count
|
||||||
property bool showCount: true
|
property bool showCount: true
|
||||||
|
|
||||||
|
property int _shownCount
|
||||||
|
|
||||||
|
signal clicked()
|
||||||
|
|
||||||
|
function updateShownCount() {
|
||||||
|
if (count > 0) {
|
||||||
|
_shownCount = count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCountChanged: updateShownCount()
|
||||||
|
|
||||||
|
Component.onCompleted: updateShownCount()
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: Math.max(left.height, right.height)
|
height: Math.max(storageLabel.height, bookCount.height)
|
||||||
Row {
|
|
||||||
id: left
|
BooksSDCardIcon {
|
||||||
|
id: icon
|
||||||
anchors {
|
anchors {
|
||||||
left: parent.left
|
left: parent.left
|
||||||
|
leftMargin: Theme.paddingMedium
|
||||||
verticalCenter: parent.verticalCenter
|
verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
spacing: 0
|
|
||||||
Item {
|
|
||||||
width: Theme.paddingMedium
|
|
||||||
height: parent.height
|
|
||||||
}
|
|
||||||
BooksSDCardIcon {
|
|
||||||
visible: removableStorage
|
visible: removableStorage
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
height: storageLabel.height*3/4
|
height: storageLabel.height*3/4
|
||||||
}
|
}
|
||||||
Item {
|
|
||||||
visible: removableStorage
|
|
||||||
width: Theme.paddingMedium
|
|
||||||
height: parent.height
|
|
||||||
}
|
|
||||||
Label {
|
Label {
|
||||||
id: storageLabel
|
id: storageLabel
|
||||||
anchors.bottom: parent.bottom
|
anchors {
|
||||||
color: Theme.highlightColor
|
left: removableStorage ? icon.right : parent.left
|
||||||
|
right: bookCount.visible ? bookCount.left : parent.right
|
||||||
|
leftMargin: Theme.paddingMedium
|
||||||
|
rightMargin: Theme.paddingMedium
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
color: (root.enabled && !mouseArea.pressed) ? Theme.primaryColor : Theme.highlightColor
|
||||||
text: removable ?
|
text: removable ?
|
||||||
//% "Memory card"
|
//% "Memory card"
|
||||||
qsTrId("storage-removable") :
|
qsTrId("storage-removable") :
|
||||||
//% "Internal storage"
|
//% "Internal storage"
|
||||||
qsTrId("storage-internal")
|
qsTrId("storage-internal")
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.clicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 100 } }
|
||||||
|
|
||||||
|
// The label overlaps with the Sailfish 2.0 pulley menu which
|
||||||
|
// doesn't look great. Hide it when it's not needed. The book
|
||||||
|
// count can be left there, it doesn't overlap with anything
|
||||||
|
opacity: (needed) ? 1 : 0
|
||||||
|
visible: opacity > 0
|
||||||
|
Behavior on opacity { FadeAnimation {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: right
|
id: bookCount
|
||||||
anchors {
|
anchors {
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
right: parent.right
|
right: parent.right
|
||||||
rightMargin: Theme.paddingMedium
|
rightMargin: Theme.paddingMedium
|
||||||
}
|
}
|
||||||
//% "%0 book(s)"
|
//% "%0 book(s)"
|
||||||
text: qsTrId("storage-book-count",count).arg(count)
|
text: qsTrId("storage-book-count",_shownCount).arg(_shownCount)
|
||||||
font.pixelSize: Theme.fontSizeExtraSmall
|
font.pixelSize: Theme.fontSizeExtraSmall
|
||||||
color: Theme.highlightColor
|
color: Theme.highlightColor
|
||||||
opacity: (showCount && count > 0) ? 1 : 0
|
opacity: (showCount && count > 0) ? 1 : 0
|
||||||
|
|
|
@ -48,7 +48,8 @@ SilicaFlickable {
|
||||||
property var currentShelfView
|
property var currentShelfView
|
||||||
property int currentShelfIndex: storageListWatcher.currentIndex
|
property int currentShelfIndex: storageListWatcher.currentIndex
|
||||||
readonly property bool dragInProgress: draggedItem ? true : false
|
readonly property bool dragInProgress: draggedItem ? true : false
|
||||||
readonly property real maxContentY: currentShelfView ? Math.max(0, currentShelfView.contentHeight - currentShelfView.height) : 0
|
readonly property real maxContentY: currentShelfView ? Math.max(0, currentShelfView.contentHeight - currentShelfView.height) -
|
||||||
|
(currentShelfView.headerItem ? currentShelfView.headerItem.height : 0) : 0
|
||||||
readonly property real verticalScrollThreshold: _cellHeight/2
|
readonly property real verticalScrollThreshold: _cellHeight/2
|
||||||
readonly property real horizontalScrollThreshold: _cellWidth/2
|
readonly property real horizontalScrollThreshold: _cellWidth/2
|
||||||
|
|
||||||
|
|
91
app/qml/images/bookshelf.svg
Normal file
91
app/qml/images/bookshelf.svg
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
sodipodi:docname="bookshelf.svg"
|
||||||
|
viewBox="0 0 200 300"
|
||||||
|
sodipodi:version="0.32"
|
||||||
|
inkscape:export-xdpi="90"
|
||||||
|
version="1.0"
|
||||||
|
inkscape:export-ydpi="90"
|
||||||
|
inkscape:version="0.48.4 r9939"
|
||||||
|
width="100%"
|
||||||
|
height="100%">
|
||||||
|
<defs
|
||||||
|
id="defs4">
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient3497"
|
||||||
|
y2="-255.63425"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x2="349.17249"
|
||||||
|
gradientTransform="matrix(2.0000003,0,0,-0.86134847,-271.73873,-18.503609)"
|
||||||
|
y1="-260.72025"
|
||||||
|
x1="269.13498"
|
||||||
|
inkscape:collect="always">
|
||||||
|
<stop
|
||||||
|
id="stop4084"
|
||||||
|
style="stop-color:#502d16"
|
||||||
|
offset="0" />
|
||||||
|
<stop
|
||||||
|
id="stop4086"
|
||||||
|
style="stop-color:#7b4d2f"
|
||||||
|
offset="1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
inkscape:label="Capa 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
transform="translate(-261.64,-40.64)">
|
||||||
|
<g
|
||||||
|
id="g3354"
|
||||||
|
transform="matrix(1.1869435,0,0,2.2826699,-54.717604,-419.74382)">
|
||||||
|
<rect
|
||||||
|
id="rect3299"
|
||||||
|
style="fill:#784421"
|
||||||
|
height="127.04422"
|
||||||
|
width="168.5"
|
||||||
|
y="206.06738"
|
||||||
|
x="266.53131"
|
||||||
|
ry="15.942534"
|
||||||
|
rx="0" />
|
||||||
|
<path
|
||||||
|
id="rect3301"
|
||||||
|
sodipodi:nodetypes="ccccc"
|
||||||
|
style="fill:url(#linearGradient3497)"
|
||||||
|
d="m 266.53131,206.06739 168.50002,0 -8.42501,-4.38084 -151.65001,0 z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<rect
|
||||||
|
id="rect4090"
|
||||||
|
style="fill:#422511"
|
||||||
|
height="122.66337"
|
||||||
|
width="160.07504"
|
||||||
|
y="208.2578"
|
||||||
|
x="270.7438" />
|
||||||
|
<rect
|
||||||
|
id="rect4106"
|
||||||
|
style="fill:#3b2312"
|
||||||
|
height="122.66337"
|
||||||
|
width="4.2124925"
|
||||||
|
y="208.2578"
|
||||||
|
x="426.60632" />
|
||||||
|
<rect
|
||||||
|
id="rect4104"
|
||||||
|
style="fill:#3d200b"
|
||||||
|
height="122.66337"
|
||||||
|
width="4.2124901"
|
||||||
|
y="208.2578"
|
||||||
|
x="270.7438" />
|
||||||
|
<path
|
||||||
|
id="path4098"
|
||||||
|
sodipodi:nodetypes="ccccc"
|
||||||
|
style="fill:#502d16"
|
||||||
|
d="m 270.74381,330.92118 160.07501,0 -4.2125,-6.57125 -151.65001,0 z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
35
app/qml/images/folder.svg
Normal file
35
app/qml/images/folder.svg
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
viewBox="0 -256 100 86"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.48.4 r9939"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
sodipodi:docname="folder.svg">
|
||||||
|
<metadata
|
||||||
|
id="metadata12">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<path
|
||||||
|
d="m 97.499974,-225.88637 0,40.5 c 0,3.5284 -1.256001,6.55821 -3.768025,9.08947 -2.512015,2.53124 -5.518827,3.79687 -9.020429,3.79687 l -69.423042,0 c -3.501602,0 -6.5084135,-1.26563 -9.0204269,-3.79687 -2.5120233,-2.53126 -3.7680251,-5.56107 -3.7680251,-9.08947 l 0,-55.22725 c 0,-3.5284 1.2560018,-6.55821 3.7680251,-9.08948 2.5120134,-2.53123 5.5188249,-3.79687 9.0204269,-3.79687 l 18.269222,0 c 3.501603,0 6.508405,1.26564 9.020427,3.79687 2.512013,2.53127 3.768025,5.56108 3.768025,9.08948 l 0,1.84091 38.365368,0 c 3.501602,0 6.508414,1.26564 9.020429,3.79688 2.512024,2.53127 3.768025,5.56108 3.768025,9.08946 z"
|
||||||
|
id="path6-5"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#422511;fill-opacity:1;stroke:#784421;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||||
|
sodipodi:nodetypes="ssssssssssssscsss"
|
||||||
|
inkscape:transform-center-x="990.36694"
|
||||||
|
inkscape:transform-center-y="-12.926792" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -129,14 +129,14 @@ bool BooksBook::CoverPaintContext::gotIt() const
|
||||||
class BooksBook::CoverTask : public BooksTask
|
class BooksBook::CoverTask : public BooksTask
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CoverTask(BooksStorage aStorage, shared_ptr<Book> aBook) :
|
CoverTask(QString aStateDir, shared_ptr<Book> aBook) :
|
||||||
iStorage(aStorage), iBook(aBook), iCoverMissing(false) {}
|
iStateDir(aStateDir), iBook(aBook), iCoverMissing(false) {}
|
||||||
|
|
||||||
bool hasImage() const;
|
bool hasImage() const;
|
||||||
QString cachedImagePath() const;
|
QString cachedImagePath() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BooksStorage iStorage;
|
QString iStateDir;
|
||||||
shared_ptr<Book> iBook;
|
shared_ptr<Book> iBook;
|
||||||
QImage iCoverImage;
|
QImage iCoverImage;
|
||||||
bool iCoverMissing;
|
bool iCoverMissing;
|
||||||
|
@ -149,8 +149,8 @@ inline bool BooksBook::CoverTask::hasImage() const
|
||||||
|
|
||||||
QString BooksBook::CoverTask::cachedImagePath() const
|
QString BooksBook::CoverTask::cachedImagePath() const
|
||||||
{
|
{
|
||||||
if (iStorage.isValid()) {
|
if (!iStateDir.isEmpty()) {
|
||||||
return iStorage.configDir().path() + "/" +
|
return iStateDir + "/" +
|
||||||
QString::fromStdString(iBook->file().name(false)) +
|
QString::fromStdString(iBook->file().name(false)) +
|
||||||
BOOK_COVER_SUFFIX + "jpg";
|
BOOK_COVER_SUFFIX + "jpg";
|
||||||
}
|
}
|
||||||
|
@ -164,9 +164,9 @@ QString BooksBook::CoverTask::cachedImagePath() const
|
||||||
class BooksBook::LoadCoverTask : public BooksBook::CoverTask
|
class BooksBook::LoadCoverTask : public BooksBook::CoverTask
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LoadCoverTask(BooksStorage aStorage, shared_ptr<Book> aBook,
|
LoadCoverTask(QString aStateDir, shared_ptr<Book> aBook,
|
||||||
shared_ptr<FormatPlugin> aFormatPlugin) :
|
shared_ptr<FormatPlugin> aFormatPlugin) :
|
||||||
BooksBook::CoverTask(aStorage, aBook),
|
BooksBook::CoverTask(aStateDir, aBook),
|
||||||
iFormatPlugin(aFormatPlugin) {}
|
iFormatPlugin(aFormatPlugin) {}
|
||||||
|
|
||||||
virtual void performTask();
|
virtual void performTask();
|
||||||
|
@ -179,10 +179,10 @@ void BooksBook::LoadCoverTask::performTask()
|
||||||
{
|
{
|
||||||
if (!isCanceled()) {
|
if (!isCanceled()) {
|
||||||
// Try to load cached (or custom) cover
|
// Try to load cached (or custom) cover
|
||||||
if (iStorage.isValid()) {
|
if (!iStateDir.isEmpty()) {
|
||||||
QString coverPrefix(QString::fromStdString(
|
QString coverPrefix(QString::fromStdString(
|
||||||
iBook->file().name(false)) + BOOK_COVER_SUFFIX);
|
iBook->file().name(false)) + BOOK_COVER_SUFFIX);
|
||||||
QDirIterator it(iStorage.configDir());
|
QDirIterator it(iStateDir);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
QString path(it.next());
|
QString path(it.next());
|
||||||
if (it.fileName().startsWith(coverPrefix)) {
|
if (it.fileName().startsWith(coverPrefix)) {
|
||||||
|
@ -231,8 +231,8 @@ void BooksBook::LoadCoverTask::performTask()
|
||||||
class BooksBook::GuessCoverTask : public BooksBook::CoverTask
|
class BooksBook::GuessCoverTask : public BooksBook::CoverTask
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GuessCoverTask(BooksStorage aStorage, shared_ptr<Book> aBook) :
|
GuessCoverTask(QString aStateDir, shared_ptr<Book> aBook) :
|
||||||
BooksBook::CoverTask(aStorage, aBook) {}
|
BooksBook::CoverTask(aStateDir, aBook) {}
|
||||||
|
|
||||||
virtual void performTask();
|
virtual void performTask();
|
||||||
};
|
};
|
||||||
|
@ -262,8 +262,17 @@ void BooksBook::GuessCoverTask::performTask()
|
||||||
|
|
||||||
// Save the extracted image
|
// Save the extracted image
|
||||||
QString coverPath(cachedImagePath());
|
QString coverPath(cachedImagePath());
|
||||||
if (!coverPath.isEmpty() && iCoverImage.save(coverPath)) {
|
if (!coverPath.isEmpty()) {
|
||||||
|
QFileInfo file(coverPath);
|
||||||
|
QDir dir(file.dir());
|
||||||
|
if (!dir.mkpath(dir.absolutePath())) {
|
||||||
|
HWARN("failed to create" << qPrintable(dir.absolutePath()));
|
||||||
|
}
|
||||||
|
if (iCoverImage.save(coverPath)) {
|
||||||
HDEBUG("saved cover to" << qPrintable(coverPath));
|
HDEBUG("saved cover to" << qPrintable(coverPath));
|
||||||
|
} else {
|
||||||
|
HWARN("failed to save" << qPrintable(coverPath));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (isCanceled()) {
|
} else if (isCanceled()) {
|
||||||
HDEBUG("cancelled" << iBook->title().c_str());
|
HDEBUG("cancelled" << iBook->title().c_str());
|
||||||
|
@ -295,7 +304,8 @@ BooksBook::BooksBook(QObject* aParent) :
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
BooksBook::BooksBook(const BooksStorage& aStorage, shared_ptr<Book> aBook) :
|
BooksBook::BooksBook(const BooksStorage& aStorage, QString aRelativePath,
|
||||||
|
shared_ptr<Book> aBook) :
|
||||||
QObject(NULL),
|
QObject(NULL),
|
||||||
iRef(1),
|
iRef(1),
|
||||||
iStorage(aStorage),
|
iStorage(aStorage),
|
||||||
|
@ -315,8 +325,8 @@ BooksBook::BooksBook(const BooksStorage& aStorage, shared_ptr<Book> aBook) :
|
||||||
iAuthors += QString::fromStdString(authors[i]->name());
|
iAuthors += QString::fromStdString(authors[i]->name());
|
||||||
}
|
}
|
||||||
if (iStorage.isValid()) {
|
if (iStorage.isValid()) {
|
||||||
iStateFilePath = iStorage.configDir().path() + "/" +
|
iStateDir = iStorage.configDir().path() + "/" + aRelativePath;
|
||||||
iFileName + BOOKS_STATE_FILE_SUFFIX;
|
iStateFilePath = iStateDir + "/" + iFileName + BOOKS_STATE_FILE_SUFFIX;
|
||||||
// Load the state
|
// Load the state
|
||||||
QVariantMap state;
|
QVariantMap state;
|
||||||
if (HarbourJson::load(iStateFilePath, state)) {
|
if (HarbourJson::load(iStateFilePath, state)) {
|
||||||
|
@ -382,6 +392,11 @@ QString BooksBook::fileName() const
|
||||||
return iFileName;
|
return iFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BooksBook::accessible() const
|
||||||
|
{
|
||||||
|
return !iCopyingOut;
|
||||||
|
}
|
||||||
|
|
||||||
void BooksBook::setLastPos(const BooksPos& aPos)
|
void BooksBook::setLastPos(const BooksPos& aPos)
|
||||||
{
|
{
|
||||||
if (iLastPos != aPos) {
|
if (iLastPos != aPos) {
|
||||||
|
@ -430,7 +445,7 @@ bool BooksBook::requestCoverImage()
|
||||||
if (!iBook.isNull() && !iFormatPlugin.isNull() &&
|
if (!iBook.isNull() && !iFormatPlugin.isNull() &&
|
||||||
!iCoverTasksDone && !iCoverTask) {
|
!iCoverTasksDone && !iCoverTask) {
|
||||||
HDEBUG(iTitle);
|
HDEBUG(iTitle);
|
||||||
iCoverTask = new LoadCoverTask(iStorage, iBook, iFormatPlugin);
|
iCoverTask = new LoadCoverTask(iStateDir, iBook, iFormatPlugin);
|
||||||
connect(iCoverTask, SIGNAL(done()), SLOT(onLoadCoverTaskDone()));
|
connect(iCoverTask, SIGNAL(done()), SLOT(onLoadCoverTaskDone()));
|
||||||
iTaskQueue->submit(iCoverTask);
|
iTaskQueue->submit(iCoverTask);
|
||||||
Q_EMIT loadingCoverChanged();
|
Q_EMIT loadingCoverChanged();
|
||||||
|
@ -469,7 +484,7 @@ void BooksBook::onLoadCoverTaskDone()
|
||||||
iCoverTasksDone = true;
|
iCoverTasksDone = true;
|
||||||
Q_EMIT loadingCoverChanged();
|
Q_EMIT loadingCoverChanged();
|
||||||
} else {
|
} else {
|
||||||
iCoverTask = new GuessCoverTask(iStorage, iBook);
|
iCoverTask = new GuessCoverTask(iStateDir, iBook);
|
||||||
connect(iCoverTask, SIGNAL(done()), SLOT(onGuessCoverTaskDone()));
|
connect(iCoverTask, SIGNAL(done()), SLOT(onGuessCoverTaskDone()));
|
||||||
iTaskQueue->submit(iCoverTask);
|
iTaskQueue->submit(iCoverTask);
|
||||||
}
|
}
|
||||||
|
@ -514,7 +529,7 @@ void BooksBook::deleteFiles()
|
||||||
} else {
|
} else {
|
||||||
HWARN("failed to delete" << qPrintable(iPath));
|
HWARN("failed to delete" << qPrintable(iPath));
|
||||||
}
|
}
|
||||||
QDirIterator it(iStorage.configDir());
|
QDirIterator it(iStateDir);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
QString path(it.next());
|
QString path(it.next());
|
||||||
if (it.fileName().startsWith(iFileName)) {
|
if (it.fileName().startsWith(iFileName)) {
|
||||||
|
|
|
@ -65,8 +65,9 @@ class BooksBook : public QObject, public BooksItem
|
||||||
Q_PROPERTY(bool copyingOut READ copyingOut NOTIFY copyingOutChanged)
|
Q_PROPERTY(bool copyingOut READ copyingOut NOTIFY copyingOutChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BooksBook(QObject* aParent = NULL);
|
explicit BooksBook(QObject* aParent = NULL);
|
||||||
BooksBook(const BooksStorage& aStorage, shared_ptr<Book> aBook);
|
BooksBook(const BooksStorage& aStorage, QString aRelativePath,
|
||||||
|
shared_ptr<Book> aBook);
|
||||||
~BooksBook();
|
~BooksBook();
|
||||||
|
|
||||||
QString path() const { return iPath; }
|
QString path() const { return iPath; }
|
||||||
|
@ -75,7 +76,7 @@ public:
|
||||||
BooksPos lastPos() const { return iLastPos; }
|
BooksPos lastPos() const { return iLastPos; }
|
||||||
void setLastPos(const BooksPos& aPos);
|
void setLastPos(const BooksPos& aPos);
|
||||||
shared_ptr<Book> bookRef() const { return iBook; }
|
shared_ptr<Book> bookRef() const { return iBook; }
|
||||||
bool accessible() const { return !iCopyingOut; }
|
|
||||||
bool copyingOut() const { return iCopyingOut; }
|
bool copyingOut() const { return iCopyingOut; }
|
||||||
bool loadingCover() const { return !iCoverTasksDone; }
|
bool loadingCover() const { return !iCoverTasksDone; }
|
||||||
bool hasCoverImage() const;
|
bool hasCoverImage() const;
|
||||||
|
@ -85,9 +86,8 @@ public:
|
||||||
QImage coverImage();
|
QImage coverImage();
|
||||||
|
|
||||||
void setCopyingOut(bool aValue);
|
void setCopyingOut(bool aValue);
|
||||||
void deleteFiles();
|
|
||||||
|
|
||||||
// BooksListItem
|
// BooksItem
|
||||||
virtual BooksItem* retain();
|
virtual BooksItem* retain();
|
||||||
virtual void release();
|
virtual void release();
|
||||||
virtual QObject* object();
|
virtual QObject* object();
|
||||||
|
@ -95,6 +95,8 @@ public:
|
||||||
virtual BooksBook* book();
|
virtual BooksBook* book();
|
||||||
virtual QString name() const;
|
virtual QString name() const;
|
||||||
virtual QString fileName() const;
|
virtual QString fileName() const;
|
||||||
|
virtual bool accessible() const;
|
||||||
|
virtual void deleteFiles();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void coverImageChanged();
|
void coverImageChanged();
|
||||||
|
@ -128,6 +130,7 @@ private:
|
||||||
QString iAuthors;
|
QString iAuthors;
|
||||||
QString iFileName;
|
QString iFileName;
|
||||||
QString iPath;
|
QString iPath;
|
||||||
|
QString iStateDir;
|
||||||
QString iStateFilePath;
|
QString iStateFilePath;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -227,6 +227,7 @@ void BooksImportModel::Task::scanDir(QDir aDir)
|
||||||
// Files first
|
// Files first
|
||||||
if (!isCanceled()) {
|
if (!isCanceled()) {
|
||||||
HDEBUG("checking" << aDir.canonicalPath());
|
HDEBUG("checking" << aDir.canonicalPath());
|
||||||
|
BooksStorage dummy;
|
||||||
QFileInfoList fileList = aDir.entryInfoList(QDir::Files |
|
QFileInfoList fileList = aDir.entryInfoList(QDir::Files |
|
||||||
QDir::Readable, QDir::Time);
|
QDir::Readable, QDir::Time);
|
||||||
const int n = fileList.count();
|
const int n = fileList.count();
|
||||||
|
@ -238,7 +239,7 @@ void BooksImportModel::Task::scanDir(QDir aDir)
|
||||||
if (!book.isNull()) {
|
if (!book.isNull()) {
|
||||||
if (!isDuplicate(filePath, iDestFiles) &&
|
if (!isDuplicate(filePath, iDestFiles) &&
|
||||||
!isDuplicate(filePath, iSrcFiles)) {
|
!isDuplicate(filePath, iSrcFiles)) {
|
||||||
BooksBook* newBook = new BooksBook(BooksStorage(), book);
|
BooksBook* newBook = new BooksBook(dummy, QString(), book);
|
||||||
newBook->moveToThread(thread());
|
newBook->moveToThread(thread());
|
||||||
iBooks.append(newBook);
|
iBooks.append(newBook);
|
||||||
iSrcFiles.append(fileInfo);
|
iSrcFiles.append(fileInfo);
|
||||||
|
|
|
@ -52,6 +52,8 @@ public:
|
||||||
virtual BooksBook* book() = 0;
|
virtual BooksBook* book() = 0;
|
||||||
virtual QString name() const = 0;
|
virtual QString name() const = 0;
|
||||||
virtual QString fileName() const = 0;
|
virtual QString fileName() const = 0;
|
||||||
|
virtual bool accessible() const = 0;
|
||||||
|
virtual void deleteFiles() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BOOKS_ITEM_H
|
#endif // BOOKS_ITEM_H
|
||||||
|
|
125
app/src/BooksPathModel.cpp
Normal file
125
app/src/BooksPathModel.cpp
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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 Nemo Mobile 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 "BooksPathModel.h"
|
||||||
|
|
||||||
|
#include "HarbourDebug.h"
|
||||||
|
|
||||||
|
enum BooksPathModelRole {
|
||||||
|
BooksPathModelName = Qt::UserRole,
|
||||||
|
BooksPathModelPath
|
||||||
|
};
|
||||||
|
|
||||||
|
BooksPathModel::BooksPathModel(QObject* aParent) :
|
||||||
|
QAbstractListModel(aParent)
|
||||||
|
{
|
||||||
|
#if QT_VERSION < 0x050000
|
||||||
|
setRoleNames(roleNames());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void BooksPathModel::setPath(QString aPath)
|
||||||
|
{
|
||||||
|
HDEBUG(aPath);
|
||||||
|
if (iPath != aPath) {
|
||||||
|
iPath = aPath;
|
||||||
|
|
||||||
|
QStringList newNames = aPath.split('/', QString::SkipEmptyParts);
|
||||||
|
const int oldSize = iList.size();
|
||||||
|
const int newSize = newNames.size();
|
||||||
|
|
||||||
|
int i;
|
||||||
|
QString path;
|
||||||
|
QStringList newPaths;
|
||||||
|
for (i=0; i<newSize; i++) {
|
||||||
|
if (!path.isEmpty()) path += "/";
|
||||||
|
path += newNames.at(i);
|
||||||
|
newPaths.append(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldSize < newSize) {
|
||||||
|
beginInsertRows(QModelIndex(), oldSize, newSize-1);
|
||||||
|
for (int i=oldSize; i<newSize; i++) {
|
||||||
|
iList.append(Data(newNames.at(i), newPaths.at(i)));
|
||||||
|
}
|
||||||
|
endInsertRows();
|
||||||
|
Q_EMIT countChanged();
|
||||||
|
} else if (oldSize > newSize) {
|
||||||
|
beginRemoveRows(QModelIndex(), newSize, oldSize-1);
|
||||||
|
do iList.removeLast(); while (iList.size() > newSize);
|
||||||
|
endRemoveRows();
|
||||||
|
Q_EMIT countChanged();
|
||||||
|
}
|
||||||
|
for (i=0; i<newSize; i++) {
|
||||||
|
bool changed = false;
|
||||||
|
if (iList.at(i).iName != newNames.at(i)) {
|
||||||
|
iList[i].iName = newNames.at(i);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (iList.at(i).iPath != newPaths.at(i)) {
|
||||||
|
iList[i].iPath = newPaths.at(i);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
QModelIndex index = createIndex(i, 0);
|
||||||
|
Q_EMIT dataChanged(index, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Q_EMIT pathChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int,QByteArray> BooksPathModel::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roles;
|
||||||
|
roles.insert(BooksPathModelName, "name");
|
||||||
|
roles.insert(BooksPathModelPath, "path");
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
int BooksPathModel::rowCount(const QModelIndex&) const
|
||||||
|
{
|
||||||
|
return iList.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant BooksPathModel::data(const QModelIndex& aIndex, int aRole) const
|
||||||
|
{
|
||||||
|
const int i = aIndex.row();
|
||||||
|
if (validIndex(i)) {
|
||||||
|
switch (aRole) {
|
||||||
|
case BooksPathModelName: return iList.at(i).iName;
|
||||||
|
case BooksPathModelPath: return iList.at(i).iPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QVariant();
|
||||||
|
}
|
86
app/src/BooksPathModel.h
Normal file
86
app/src/BooksPathModel.h
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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 Nemo Mobile 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_PATH_MODEL_H
|
||||||
|
#define BOOKS_PATH_MODEL_H
|
||||||
|
|
||||||
|
#include "BooksTypes.h"
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QtQml>
|
||||||
|
|
||||||
|
class BooksPathModel: public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
||||||
|
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BooksPathModel(QObject* aParent = NULL);
|
||||||
|
|
||||||
|
int count() const { return iList.count(); }
|
||||||
|
QString path() const { return iPath; }
|
||||||
|
void setPath(QString aPath);
|
||||||
|
|
||||||
|
// QAbstractListModel
|
||||||
|
virtual QHash<int,QByteArray> roleNames() const;
|
||||||
|
virtual int rowCount(const QModelIndex& aParent) const;
|
||||||
|
virtual QVariant data(const QModelIndex& aIndex, int aRole) const;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void countChanged();
|
||||||
|
void pathChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool validIndex(int aIndex) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Data {
|
||||||
|
public:
|
||||||
|
QString iName;
|
||||||
|
QString iPath;
|
||||||
|
Data(QString aName, QString aPath) : iName(aName), iPath(aPath) {}
|
||||||
|
};
|
||||||
|
QList<Data> iList;
|
||||||
|
QString iPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
QML_DECLARE_TYPE(BooksPathModel)
|
||||||
|
|
||||||
|
inline bool BooksPathModel::validIndex(int aIndex) const
|
||||||
|
{ return aIndex >= 0 && aIndex < iList.count(); }
|
||||||
|
|
||||||
|
#endif // BOOKS_PATH_MODEL_H
|
|
@ -364,12 +364,13 @@ BooksSettings::updateCurrentBook()
|
||||||
} else if (!iCurrentBook || iCurrentBook->path() != path) {
|
} else if (!iCurrentBook || iCurrentBook->path() != path) {
|
||||||
shared_ptr<Book> book = BooksUtil::bookFromFile(path);
|
shared_ptr<Book> book = BooksUtil::bookFromFile(path);
|
||||||
if (!book.isNull()) {
|
if (!book.isNull()) {
|
||||||
|
QString rel;
|
||||||
QFileInfo info(path);
|
QFileInfo info(path);
|
||||||
BooksStorageManager* mgr = BooksStorageManager::instance();
|
BooksStorageManager* mgr = BooksStorageManager::instance();
|
||||||
BooksStorage storage = mgr->storageForPath(info.path());
|
BooksStorage storage = mgr->storageForPath(info.path(), &rel);
|
||||||
if (storage.isValid()) {
|
if (storage.isValid()) {
|
||||||
if (iCurrentBook) iCurrentBook->release();
|
if (iCurrentBook) iCurrentBook->release();
|
||||||
iCurrentBook = new BooksBook(storage, book);
|
iCurrentBook = new BooksBook(storage, rel, book);
|
||||||
iCurrentBook->requestCoverImage();
|
iCurrentBook->requestCoverImage();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,11 +91,10 @@ public:
|
||||||
|
|
||||||
class BooksShelf::LoadTask : public BooksTask
|
class BooksShelf::LoadTask : public BooksTask
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LoadTask(BooksStorage aStorage, QString aPath, QString aStateFilePath) :
|
LoadTask(BooksStorage aStorage, QString aRelPath, QString aStateFile) :
|
||||||
iStorage(aStorage), iPath(aPath), iStateFilePath(aStateFilePath) {}
|
iStorage(aStorage), iRelativePath(aRelPath),
|
||||||
|
iStateFilePath(aStateFile) {}
|
||||||
~LoadTask();
|
~LoadTask();
|
||||||
|
|
||||||
void performTask();
|
void performTask();
|
||||||
|
@ -103,20 +102,17 @@ public:
|
||||||
int findBook(QString aFileName) const;
|
int findBook(QString aFileName) const;
|
||||||
static int find(QFileInfoList aList, QString aFileName, int aStart);
|
static int find(QFileInfoList aList, QString aFileName, int aStart);
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void bookFound(BooksBook* aBook);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BooksStorage iStorage;
|
BooksStorage iStorage;
|
||||||
QString iPath;
|
QString iRelativePath;
|
||||||
QString iStateFilePath;
|
QString iStateFilePath;
|
||||||
QList<BooksBook*> iBooks;
|
QList<BooksItem*> iItems;
|
||||||
};
|
};
|
||||||
|
|
||||||
BooksShelf::LoadTask::~LoadTask()
|
BooksShelf::LoadTask::~LoadTask()
|
||||||
{
|
{
|
||||||
const int n = iBooks.count();
|
const int n = iItems.count();
|
||||||
for (int i=0; i<n; i++) iBooks.at(i)->release();
|
for (int i=0; i<n; i++) iItems.at(i)->release();
|
||||||
}
|
}
|
||||||
|
|
||||||
int BooksShelf::LoadTask::find(QFileInfoList aList, QString aName, int aStart)
|
int BooksShelf::LoadTask::find(QFileInfoList aList, QString aName, int aStart)
|
||||||
|
@ -135,9 +131,10 @@ int BooksShelf::LoadTask::find(QFileInfoList aList, QString aName, int aStart)
|
||||||
int BooksShelf::LoadTask::findBook(QString aFileName) const
|
int BooksShelf::LoadTask::findBook(QString aFileName) const
|
||||||
{
|
{
|
||||||
if (!aFileName.isEmpty()) {
|
if (!aFileName.isEmpty()) {
|
||||||
const int n = iBooks.count();
|
const int n = iItems.count();
|
||||||
for (int i=0; i<n; i++) {
|
for (int i=0; i<n; i++) {
|
||||||
if (iBooks.at(i)->fileName() == aFileName) {
|
BooksItem* item = iItems.at(i);
|
||||||
|
if (item->book() && item->fileName() == aFileName) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,9 +145,11 @@ int BooksShelf::LoadTask::findBook(QString aFileName) const
|
||||||
void BooksShelf::LoadTask::performTask()
|
void BooksShelf::LoadTask::performTask()
|
||||||
{
|
{
|
||||||
if (!isCanceled()) {
|
if (!isCanceled()) {
|
||||||
QDir dir(iPath);
|
QString path(iStorage.fullPath(iRelativePath));
|
||||||
HDEBUG("checking" << iPath);
|
HDEBUG("checking" << path);
|
||||||
QFileInfoList list = dir.entryInfoList(QDir::Files, QDir::Time);
|
QDir dir(path);
|
||||||
|
QFileInfoList list = dir.entryInfoList(QDir::Files |
|
||||||
|
QDir::Dirs | QDir::NoDotAndDotDot, QDir::Time);
|
||||||
|
|
||||||
// Restore the order
|
// Restore the order
|
||||||
QVariantMap state;
|
QVariantMap state;
|
||||||
|
@ -175,18 +174,31 @@ void BooksShelf::LoadTask::performTask()
|
||||||
|
|
||||||
const int n = list.count();
|
const int n = list.count();
|
||||||
for (int i=0; i<n && !isCanceled(); i++) {
|
for (int i=0; i<n && !isCanceled(); i++) {
|
||||||
std::string path(list.at(i).filePath().toStdString());
|
const QFileInfo& info = list.at(i);
|
||||||
|
QString path(info.filePath());
|
||||||
|
if (info.isDir()) {
|
||||||
|
HDEBUG("directory:" << qPrintable(path));
|
||||||
|
QString folderPath(iRelativePath);
|
||||||
|
if (!folderPath.isEmpty() && !folderPath.endsWith('/')) {
|
||||||
|
folderPath += '/';
|
||||||
|
}
|
||||||
|
folderPath += info.fileName();
|
||||||
|
BooksShelf* newShelf = new BooksShelf(iStorage, folderPath);
|
||||||
|
newShelf->moveToThread(thread());
|
||||||
|
iItems.append(newShelf);
|
||||||
|
} else {
|
||||||
shared_ptr<Book> book = BooksUtil::bookFromFile(path);
|
shared_ptr<Book> book = BooksUtil::bookFromFile(path);
|
||||||
if (!book.isNull()) {
|
if (!book.isNull()) {
|
||||||
BooksBook* newBook = new BooksBook(iStorage, book);
|
BooksBook* newBook = new BooksBook(iStorage,
|
||||||
|
iRelativePath, book);
|
||||||
newBook->moveToThread(thread());
|
newBook->moveToThread(thread());
|
||||||
iBooks.append(newBook);
|
iItems.append(newBook);
|
||||||
HDEBUG("[" << iBooks.size() << "]" <<
|
HDEBUG("[" << iItems.size() << "]" <<
|
||||||
qPrintable(newBook->fileName()) <<
|
qPrintable(newBook->fileName()) <<
|
||||||
newBook->title());
|
newBook->title());
|
||||||
Q_EMIT bookFound(newBook);
|
|
||||||
} else {
|
} else {
|
||||||
HDEBUG("not a book:" << path.c_str());
|
HDEBUG("not a book:" << qPrintable(path));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,7 +243,9 @@ public:
|
||||||
QObject* object() { return iItem ? iItem->object() : NULL; }
|
QObject* object() { return iItem ? iItem->object() : NULL; }
|
||||||
BooksBook* book() { return iItem ? iItem->book() : NULL; }
|
BooksBook* book() { return iItem ? iItem->book() : NULL; }
|
||||||
BooksShelf* shelf() { return iItem ? iItem->shelf() : NULL; }
|
BooksShelf* shelf() { return iItem ? iItem->shelf() : NULL; }
|
||||||
bool accessible();
|
bool accessible() const { return !iCopyTask && iItem && iItem->accessible(); }
|
||||||
|
bool isBook() const { return iItem && iItem->book(); }
|
||||||
|
bool isShelf() const { return iItem && iItem->shelf(); }
|
||||||
bool copyingOut();
|
bool copyingOut();
|
||||||
bool copyingIn() { return iCopyTask != NULL; }
|
bool copyingIn() { return iCopyTask != NULL; }
|
||||||
int copyPercent() { return iCopyTask ? iCopyTask->iCopyPercent : 0; }
|
int copyPercent() { return iCopyTask ? iCopyTask->iCopyPercent : 0; }
|
||||||
|
@ -298,16 +312,6 @@ void BooksShelf::Data::setBook(BooksBook* aBook, bool aExternal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool BooksShelf::Data::accessible()
|
|
||||||
{
|
|
||||||
if (iCopyTask) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
BooksBook* bookItem = book();
|
|
||||||
return bookItem && bookItem->accessible();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool BooksShelf::Data::copyingOut()
|
inline bool BooksShelf::Data::copyingOut()
|
||||||
{
|
{
|
||||||
BooksBook* bookItem = book();
|
BooksBook* bookItem = book();
|
||||||
|
@ -429,33 +433,83 @@ class BooksShelf::DeleteTask : public BooksTask
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
DeleteTask(BooksBook* aBook);
|
DeleteTask(BooksItem* aItem);
|
||||||
~DeleteTask();
|
~DeleteTask();
|
||||||
void performTask();
|
void performTask();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BooksBook* iBook;
|
BooksItem* iItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
BooksShelf::DeleteTask::DeleteTask(BooksBook* aBook) :
|
BooksShelf::DeleteTask::DeleteTask(BooksItem* aItem) :
|
||||||
iBook(aBook)
|
iItem(aItem)
|
||||||
{
|
{
|
||||||
iBook->retain();
|
iItem->retain();
|
||||||
iBook->cancelCoverRequest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BooksShelf::DeleteTask::~DeleteTask()
|
BooksShelf::DeleteTask::~DeleteTask()
|
||||||
{
|
{
|
||||||
iBook->release();
|
iItem->release();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BooksShelf::DeleteTask::performTask()
|
void BooksShelf::DeleteTask::performTask()
|
||||||
{
|
{
|
||||||
if (isCanceled()) {
|
if (isCanceled()) {
|
||||||
HDEBUG("cancelled" << iBook->title());
|
HDEBUG("cancelled" << iItem->fileName());
|
||||||
} else {
|
} else {
|
||||||
HDEBUG(iBook->title());
|
iItem->deleteFiles();
|
||||||
iBook->deleteFiles();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// BooksShelf::Counts
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
class BooksShelf::Counts {
|
||||||
|
public:
|
||||||
|
Counts(BooksShelf* aShelf);
|
||||||
|
void count(BooksShelf* aShelf);
|
||||||
|
void emitSignals(BooksShelf* aShelf);
|
||||||
|
|
||||||
|
int iTotalCount;
|
||||||
|
int iBookCount;
|
||||||
|
int iShelfCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
BooksShelf::Counts::Counts(BooksShelf* aShelf)
|
||||||
|
{
|
||||||
|
count(aShelf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BooksShelf::Counts::count(BooksShelf* aShelf)
|
||||||
|
{
|
||||||
|
iTotalCount = aShelf->iList.count(),
|
||||||
|
iBookCount = 0;
|
||||||
|
iShelfCount = 0;
|
||||||
|
for (int i=0; i<iTotalCount; i++) {
|
||||||
|
const Data* data = aShelf->iList.at(i);
|
||||||
|
if (data->isBook()) {
|
||||||
|
iBookCount++;
|
||||||
|
} else if (data->isShelf()) {
|
||||||
|
iShelfCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BooksShelf::Counts::emitSignals(BooksShelf* aShelf)
|
||||||
|
{
|
||||||
|
const int oldTotalCount = iTotalCount;
|
||||||
|
const int oldBookCount = iBookCount;
|
||||||
|
const int oldShelfCount = iShelfCount;
|
||||||
|
count(aShelf);
|
||||||
|
if (oldBookCount != iBookCount) {
|
||||||
|
Q_EMIT aShelf->bookCountChanged();
|
||||||
|
}
|
||||||
|
if (oldShelfCount != iShelfCount) {
|
||||||
|
Q_EMIT aShelf->shelfCountChanged();
|
||||||
|
}
|
||||||
|
if (oldTotalCount != iTotalCount) {
|
||||||
|
Q_EMIT aShelf->countChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,33 +526,48 @@ BooksShelf::BooksShelf(QObject* aParent) :
|
||||||
iSaveTimer(new BooksSaveTimer(this)),
|
iSaveTimer(new BooksSaveTimer(this)),
|
||||||
iTaskQueue(BooksTaskQueue::instance())
|
iTaskQueue(BooksTaskQueue::instance())
|
||||||
{
|
{
|
||||||
#if QT_VERSION < 0x050000
|
init();
|
||||||
setRoleNames(roleNames());
|
|
||||||
#endif
|
|
||||||
QQmlEngine::setObjectOwnership(&iStorage, QQmlEngine::CppOwnership);
|
|
||||||
connect(iSaveTimer, SIGNAL(save()), SLOT(saveState()));
|
connect(iSaveTimer, SIGNAL(save()), SLOT(saveState()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BooksShelf::BooksShelf(BooksStorage aStorage, QString aRelativePath) :
|
||||||
|
iLoadTask(NULL),
|
||||||
|
iRelativePath(aRelativePath),
|
||||||
|
iStorage(aStorage),
|
||||||
|
iDummyItemIndex(-1),
|
||||||
|
iEditMode(false),
|
||||||
|
iRef(1),
|
||||||
|
iSaveTimer(NULL),
|
||||||
|
iTaskQueue(BooksTaskQueue::instance())
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
// Refcounted BooksShelf objects are managed by C++ code
|
||||||
|
// They also don't need to read the content of the directory -
|
||||||
|
// only the objects allocated by QML do that.
|
||||||
|
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
|
||||||
|
int slashPos = aRelativePath.lastIndexOf('/');
|
||||||
|
iFileName = (slashPos >= 0) ?
|
||||||
|
aRelativePath.right(aRelativePath.length() - slashPos - 1) :
|
||||||
|
aRelativePath;
|
||||||
|
updatePath();
|
||||||
|
}
|
||||||
|
|
||||||
BooksShelf::~BooksShelf()
|
BooksShelf::~BooksShelf()
|
||||||
{
|
{
|
||||||
const int n = iDeleteTasks.count();
|
const int n = iDeleteTasks.count();
|
||||||
for (int i=0; i<n; i++) iDeleteTasks.at(i)->release(this);
|
for (int i=0; i<n; i++) iDeleteTasks.at(i)->release(this);
|
||||||
if (iLoadTask) iLoadTask->release(this);
|
if (iLoadTask) iLoadTask->release(this);
|
||||||
if (iSaveTimer->saveRequested()) saveState();
|
if (iSaveTimer && iSaveTimer->saveRequested()) saveState();
|
||||||
removeAllBooks();
|
qDeleteAll(iList);
|
||||||
HDEBUG("destroyed");
|
HDEBUG("destroyed");
|
||||||
}
|
}
|
||||||
|
|
||||||
void BooksShelf::removeAllBooks()
|
void BooksShelf::init()
|
||||||
{
|
{
|
||||||
while (!iList.isEmpty()) {
|
#if QT_VERSION < 0x050000
|
||||||
Data* data = iList.takeLast();
|
setRoleNames(roleNames());
|
||||||
BooksBook* book = data->book();
|
#endif
|
||||||
if (book) {
|
QQmlEngine::setObjectOwnership(&iStorage, QQmlEngine::CppOwnership);
|
||||||
Q_EMIT bookRemoved(book);
|
|
||||||
}
|
|
||||||
delete data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BooksShelf::setRelativePath(QString aPath)
|
void BooksShelf::setRelativePath(QString aPath)
|
||||||
|
@ -521,25 +590,31 @@ void BooksShelf::setDevice(QString aDevice)
|
||||||
|
|
||||||
void BooksShelf::updatePath()
|
void BooksShelf::updatePath()
|
||||||
{
|
{
|
||||||
|
BooksLoadingSignalBlocker block(this);
|
||||||
const QString oldPath = iPath;
|
const QString oldPath = iPath;
|
||||||
iPath.clear();
|
iPath.clear();
|
||||||
if (iStorage.isValid()) {
|
if (iStorage.isValid()) {
|
||||||
QString newPath(iStorage.root());
|
iPath = iStorage.fullPath(iRelativePath);
|
||||||
if (!iRelativePath.isEmpty()) {
|
|
||||||
if (!newPath.endsWith('/')) newPath += '/';
|
|
||||||
newPath += iRelativePath;
|
|
||||||
}
|
|
||||||
iPath = QDir::cleanPath(newPath);
|
|
||||||
}
|
}
|
||||||
if (oldPath != iPath) {
|
if (oldPath != iPath) {
|
||||||
const int oldCount = iList.count();
|
|
||||||
const int oldDummyItemIndex = iDummyItemIndex;
|
const int oldDummyItemIndex = iDummyItemIndex;
|
||||||
beginResetModel();
|
Counts counts(this);
|
||||||
HDEBUG(iPath);
|
HDEBUG(iPath);
|
||||||
removeAllBooks();
|
// Clear the model
|
||||||
|
if (!iList.isEmpty()) {
|
||||||
|
beginRemoveRows(QModelIndex(), 0, iList.size()-1);
|
||||||
|
while (!iList.isEmpty()) {
|
||||||
|
Data* data = iList.takeLast();
|
||||||
|
BooksBook* book = data->book();
|
||||||
|
if (book) {
|
||||||
|
Q_EMIT bookRemoved(book);
|
||||||
|
}
|
||||||
|
delete data;
|
||||||
|
}
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
iDummyItemIndex = -1;
|
iDummyItemIndex = -1;
|
||||||
if (!iPath.isEmpty()) loadBookList();
|
if (!iPath.isEmpty()) loadBookList();
|
||||||
endResetModel();
|
|
||||||
Q_EMIT pathChanged();
|
Q_EMIT pathChanged();
|
||||||
if (oldDummyItemIndex != iDummyItemIndex) {
|
if (oldDummyItemIndex != iDummyItemIndex) {
|
||||||
Q_EMIT dummyItemIndexChanged();
|
Q_EMIT dummyItemIndexChanged();
|
||||||
|
@ -547,9 +622,7 @@ void BooksShelf::updatePath()
|
||||||
Q_EMIT hasDummyItemChanged();
|
Q_EMIT hasDummyItemChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldCount != iList.count()) {
|
counts.emitSignals(this);
|
||||||
Q_EMIT countChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -557,45 +630,42 @@ void BooksShelf::onLoadTaskDone()
|
||||||
{
|
{
|
||||||
HASSERT(iLoadTask);
|
HASSERT(iLoadTask);
|
||||||
HASSERT(iLoadTask == sender());
|
HASSERT(iLoadTask == sender());
|
||||||
|
if (iLoadTask && iLoadTask == sender()) {
|
||||||
|
BooksLoadingSignalBlocker block(this);
|
||||||
|
const int oldSize = iList.size();
|
||||||
|
const int newSize = iLoadTask->iItems.size();
|
||||||
|
HASSERT(iList.isEmpty());
|
||||||
|
if (newSize > 0) {
|
||||||
|
Counts counts(this);
|
||||||
|
beginInsertRows(QModelIndex(), oldSize, oldSize + newSize - 1);
|
||||||
|
for (int i=0; i<newSize; i++) {
|
||||||
|
BooksItem* item = iLoadTask->iItems.at(i);
|
||||||
|
BooksBook* book = item->book();
|
||||||
|
if (book) {
|
||||||
|
Q_EMIT bookAdded(book);
|
||||||
|
}
|
||||||
|
iList.append(new Data(this, item->retain(), false));
|
||||||
|
}
|
||||||
|
endInsertRows();
|
||||||
|
counts.emitSignals(this);
|
||||||
|
}
|
||||||
iLoadTask->release(this);
|
iLoadTask->release(this);
|
||||||
iLoadTask = NULL;
|
iLoadTask = NULL;
|
||||||
Q_EMIT loadingChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void BooksShelf::onBookFound(BooksBook* aBook)
|
|
||||||
{
|
|
||||||
if (iLoadTask && iLoadTask == sender()) {
|
|
||||||
beginInsertRows(QModelIndex(), iList.count(), iList.count());
|
|
||||||
iList.append(new Data(this, aBook->retain(), false));
|
|
||||||
endInsertRows();
|
|
||||||
Q_EMIT bookAdded(aBook);
|
|
||||||
Q_EMIT countChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BooksShelf::loadBookList()
|
void BooksShelf::loadBookList()
|
||||||
{
|
{
|
||||||
if (!iList.isEmpty()) {
|
BooksLoadingSignalBlocker block(this);
|
||||||
beginResetModel();
|
|
||||||
removeAllBooks();
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool wasLoading = loading();
|
|
||||||
if (iLoadTask) iLoadTask->release(this);
|
if (iLoadTask) iLoadTask->release(this);
|
||||||
if (iPath.isEmpty()) {
|
if (iPath.isEmpty()) {
|
||||||
iLoadTask = NULL;
|
iLoadTask = NULL;
|
||||||
} else {
|
} else {
|
||||||
HDEBUG(iPath);
|
HDEBUG(iPath);
|
||||||
iLoadTask = new LoadTask(iStorage, iPath, stateFileName());
|
iLoadTask = new LoadTask(iStorage, iRelativePath, stateFileName());
|
||||||
connect(iLoadTask, SIGNAL(bookFound(BooksBook*)),
|
|
||||||
SLOT(onBookFound(BooksBook*)), Qt::QueuedConnection);
|
|
||||||
connect(iLoadTask, SIGNAL(done()), SLOT(onLoadTaskDone()));
|
connect(iLoadTask, SIGNAL(done()), SLOT(onLoadTaskDone()));
|
||||||
iTaskQueue->submit(iLoadTask);
|
iTaskQueue->submit(iLoadTask);
|
||||||
}
|
}
|
||||||
if (wasLoading != loading()) {
|
|
||||||
Q_EMIT loadingChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BooksShelf::saveState()
|
void BooksShelf::saveState()
|
||||||
|
@ -614,7 +684,7 @@ void BooksShelf::saveState()
|
||||||
|
|
||||||
void BooksShelf::queueStateSave()
|
void BooksShelf::queueStateSave()
|
||||||
{
|
{
|
||||||
if (iEditMode) {
|
if (iEditMode && iSaveTimer) {
|
||||||
iSaveTimer->requestSave();
|
iSaveTimer->requestSave();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -622,7 +692,7 @@ void BooksShelf::queueStateSave()
|
||||||
QString BooksShelf::stateFileName() const
|
QString BooksShelf::stateFileName() const
|
||||||
{
|
{
|
||||||
return iStorage.isValid() ?
|
return iStorage.isValid() ?
|
||||||
iStorage.configDir().path() + ("/" SHELF_STATE_FILE) :
|
iStorage.configDir().path() + "/" + iRelativePath + ("/" SHELF_STATE_FILE) :
|
||||||
QString();
|
QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -672,7 +742,7 @@ void BooksShelf::setEditMode(bool aEditMode)
|
||||||
if (iEditMode != aEditMode) {
|
if (iEditMode != aEditMode) {
|
||||||
iEditMode = aEditMode;
|
iEditMode = aEditMode;
|
||||||
HDEBUG(iEditMode);
|
HDEBUG(iEditMode);
|
||||||
if (iSaveTimer->saveRequested()) {
|
if (iSaveTimer && iSaveTimer->saveRequested()) {
|
||||||
iSaveTimer->cancelSave();
|
iSaveTimer->cancelSave();
|
||||||
saveState();
|
saveState();
|
||||||
}
|
}
|
||||||
|
@ -723,7 +793,7 @@ BooksBook* BooksShelf::book()
|
||||||
|
|
||||||
QString BooksShelf::name() const
|
QString BooksShelf::name() const
|
||||||
{
|
{
|
||||||
return iName;
|
return iFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString BooksShelf::fileName() const
|
QString BooksShelf::fileName() const
|
||||||
|
@ -731,11 +801,34 @@ QString BooksShelf::fileName() const
|
||||||
return iFileName;
|
return iFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BooksShelf::accessible() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int BooksShelf::count() const
|
int BooksShelf::count() const
|
||||||
{
|
{
|
||||||
return iList.count();
|
return iList.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int BooksShelf::bookCount() const
|
||||||
|
{
|
||||||
|
int n=0, total = iList.count();
|
||||||
|
for(int i=0; i<total; i++) {
|
||||||
|
if (iList.at(i)->book()) n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int BooksShelf::shelfCount() const
|
||||||
|
{
|
||||||
|
int n=0, total = iList.count();
|
||||||
|
for(int i=0; i<total; i++) {
|
||||||
|
if (iList.at(i)->shelf()) n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
QObject* BooksShelf::get(int aIndex) const
|
QObject* BooksShelf::get(int aIndex) const
|
||||||
{
|
{
|
||||||
if (validIndex(aIndex)) {
|
if (validIndex(aIndex)) {
|
||||||
|
@ -802,25 +895,28 @@ void BooksShelf::move(int aFrom, int aTo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BooksShelf::remove(int aIndex)
|
void BooksShelf::submitDeleteTask(int aIndex)
|
||||||
{
|
{
|
||||||
BooksBook* book = removeBook(aIndex);
|
BooksItem* item = iList.at(aIndex)->iItem;
|
||||||
|
if (item) {
|
||||||
|
DeleteTask* task = new DeleteTask(item);
|
||||||
|
iDeleteTasks.append(task);
|
||||||
|
iTaskQueue->submit(task);
|
||||||
|
BooksBook* book = item->book();
|
||||||
if (book) {
|
if (book) {
|
||||||
|
book->cancelCoverRequest();
|
||||||
Q_EMIT bookRemoved(book);
|
Q_EMIT bookRemoved(book);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BooksBook* BooksShelf::removeBook(int aIndex)
|
void BooksShelf::remove(int aIndex)
|
||||||
{
|
{
|
||||||
if (validIndex(aIndex)) {
|
if (validIndex(aIndex)) {
|
||||||
|
Counts counts(this);
|
||||||
HDEBUG(iList.at(aIndex)->name());
|
HDEBUG(iList.at(aIndex)->name());
|
||||||
beginRemoveRows(QModelIndex(), aIndex, aIndex);
|
beginRemoveRows(QModelIndex(), aIndex, aIndex);
|
||||||
BooksBook* book = iList.at(aIndex)->book();
|
submitDeleteTask(aIndex);
|
||||||
if (book) {
|
|
||||||
DeleteTask* task = new DeleteTask(book);
|
|
||||||
iDeleteTasks.append(task);
|
|
||||||
iTaskQueue->submit(task);
|
|
||||||
}
|
|
||||||
if (iDummyItemIndex == aIndex) {
|
if (iDummyItemIndex == aIndex) {
|
||||||
iDummyItemIndex = -1;
|
iDummyItemIndex = -1;
|
||||||
Q_EMIT hasDummyItemChanged();
|
Q_EMIT hasDummyItemChanged();
|
||||||
|
@ -828,26 +924,19 @@ BooksBook* BooksShelf::removeBook(int aIndex)
|
||||||
}
|
}
|
||||||
delete iList.takeAt(aIndex);
|
delete iList.takeAt(aIndex);
|
||||||
queueStateSave();
|
queueStateSave();
|
||||||
Q_EMIT countChanged();
|
counts.emitSignals(this);
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
return book;
|
|
||||||
}
|
}
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BooksShelf::removeAll()
|
void BooksShelf::removeAll()
|
||||||
{
|
{
|
||||||
if (!iList.isEmpty()) {
|
if (!iList.isEmpty()) {
|
||||||
|
Counts counts(this);
|
||||||
beginRemoveRows(QModelIndex(), 0, iList.count()-1);
|
beginRemoveRows(QModelIndex(), 0, iList.count()-1);
|
||||||
const int n = iList.count();
|
const int n = iList.count();
|
||||||
for (int i=0; i<n; i++) {
|
for (int i=0; i<n; i++) {
|
||||||
BooksBook* book = iList.at(i)->book();
|
submitDeleteTask(i);
|
||||||
if (book) {
|
|
||||||
DeleteTask* task = new DeleteTask(book);
|
|
||||||
iDeleteTasks.append(task);
|
|
||||||
iTaskQueue->submit(task);
|
|
||||||
Q_EMIT bookRemoved(book);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (iDummyItemIndex >= 0) {
|
if (iDummyItemIndex >= 0) {
|
||||||
iDummyItemIndex = -1;
|
iDummyItemIndex = -1;
|
||||||
|
@ -857,7 +946,7 @@ void BooksShelf::removeAll()
|
||||||
qDeleteAll(iList);
|
qDeleteAll(iList);
|
||||||
iList.clear();
|
iList.clear();
|
||||||
queueStateSave();
|
queueStateSave();
|
||||||
Q_EMIT countChanged();
|
counts.emitSignals(this);
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -908,11 +997,12 @@ void BooksShelf::importBook(QObject* aBook)
|
||||||
} else {
|
} else {
|
||||||
HDEBUG(qPrintable(book->path()) << "->" << qPrintable(iPath));
|
HDEBUG(qPrintable(book->path()) << "->" << qPrintable(iPath));
|
||||||
beginInsertRows(QModelIndex(), 0, 0);
|
beginInsertRows(QModelIndex(), 0, 0);
|
||||||
|
Counts counts(this);
|
||||||
Data* data = new Data(this, book->retain(), true);
|
Data* data = new Data(this, book->retain(), true);
|
||||||
iList.insert(0, data);
|
iList.insert(0, data);
|
||||||
iTaskQueue->submit(new CopyTask(data, book));
|
iTaskQueue->submit(new CopyTask(data, book));
|
||||||
|
counts.emitSignals(this);
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
Q_EMIT countChanged();
|
|
||||||
saveState();
|
saveState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -990,7 +1080,7 @@ void BooksShelf::onCopyTaskDone()
|
||||||
if (task->iSuccess) {
|
if (task->iSuccess) {
|
||||||
shared_ptr<Book> book = BooksUtil::bookFromFile(task->iDest);
|
shared_ptr<Book> book = BooksUtil::bookFromFile(task->iDest);
|
||||||
if (!book.isNull()) {
|
if (!book.isNull()) {
|
||||||
copy = new BooksBook(iStorage, book);
|
copy = new BooksBook(iStorage, iRelativePath, book);
|
||||||
copy->setLastPos(src->lastPos());
|
copy->setLastPos(src->lastPos());
|
||||||
copy->setCoverImage(src->coverImage());
|
copy->setCoverImage(src->coverImage());
|
||||||
copy->requestCoverImage();
|
copy->requestCoverImage();
|
||||||
|
@ -1024,7 +1114,7 @@ void BooksShelf::onCopyTaskDone()
|
||||||
QModelIndex index(createIndex(row, 0));
|
QModelIndex index(createIndex(row, 0));
|
||||||
Q_EMIT dataChanged(index, index);
|
Q_EMIT dataChanged(index, index);
|
||||||
} else {
|
} else {
|
||||||
removeBook(row);
|
remove(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1039,6 +1129,22 @@ void BooksShelf::onDeleteTaskDone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BooksShelf::deleteFiles()
|
||||||
|
{
|
||||||
|
if (iStorage.isValid()) {
|
||||||
|
QString path(iStorage.fullPath(iRelativePath));
|
||||||
|
HDEBUG("removing" << path);
|
||||||
|
if (!QDir(path).removeRecursively()) {
|
||||||
|
HWARN("some content couldn't be deleted under" << path);
|
||||||
|
}
|
||||||
|
path = iStorage.configDir().path() + "/" + iRelativePath;
|
||||||
|
HDEBUG("removing" << path);
|
||||||
|
if (!QDir(path).removeRecursively()) {
|
||||||
|
HWARN("some content couldn't be deleted under" << path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QHash<int,QByteArray> BooksShelf::roleNames() const
|
QHash<int,QByteArray> BooksShelf::roleNames() const
|
||||||
{
|
{
|
||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
#include "BooksSaveTimer.h"
|
#include "BooksSaveTimer.h"
|
||||||
#include "BooksTask.h"
|
#include "BooksTask.h"
|
||||||
#include "BooksTaskQueue.h"
|
#include "BooksTaskQueue.h"
|
||||||
|
#include "BooksLoadingProperty.h"
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
@ -46,11 +47,14 @@
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QtQml>
|
#include <QtQml>
|
||||||
|
|
||||||
class BooksShelf: public QAbstractListModel, public BooksItem
|
class BooksShelf: public QAbstractListModel, public BooksItem, public BooksLoadingProperty
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
||||||
|
Q_PROPERTY(int bookCount READ bookCount NOTIFY bookCountChanged)
|
||||||
|
Q_PROPERTY(int shelfCount READ shelfCount NOTIFY shelfCountChanged)
|
||||||
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
|
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
|
||||||
|
Q_PROPERTY(bool accessible READ accessible CONSTANT)
|
||||||
Q_PROPERTY(QString path READ path NOTIFY pathChanged)
|
Q_PROPERTY(QString path READ path NOTIFY pathChanged)
|
||||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||||
Q_PROPERTY(QString device READ device WRITE setDevice NOTIFY deviceChanged)
|
Q_PROPERTY(QString device READ device WRITE setDevice NOTIFY deviceChanged)
|
||||||
|
@ -59,10 +63,12 @@ class BooksShelf: public QAbstractListModel, public BooksItem
|
||||||
Q_PROPERTY(bool hasDummyItem READ hasDummyItem WRITE setHasDummyItem NOTIFY hasDummyItemChanged)
|
Q_PROPERTY(bool hasDummyItem READ hasDummyItem WRITE setHasDummyItem NOTIFY hasDummyItemChanged)
|
||||||
Q_PROPERTY(int dummyItemIndex READ dummyItemIndex WRITE setDummyItemIndex NOTIFY dummyItemIndexChanged)
|
Q_PROPERTY(int dummyItemIndex READ dummyItemIndex WRITE setDummyItemIndex NOTIFY dummyItemIndexChanged)
|
||||||
Q_PROPERTY(BooksBook* book READ book CONSTANT)
|
Q_PROPERTY(BooksBook* book READ book CONSTANT)
|
||||||
|
Q_PROPERTY(BooksShelf* shelf READ shelf CONSTANT)
|
||||||
Q_PROPERTY(QObject* storage READ storage CONSTANT)
|
Q_PROPERTY(QObject* storage READ storage CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BooksShelf(QObject* aParent = NULL);
|
explicit BooksShelf(QObject* aParent = NULL);
|
||||||
|
BooksShelf(BooksStorage aStorage, QString aRelativePath);
|
||||||
~BooksShelf();
|
~BooksShelf();
|
||||||
|
|
||||||
Q_INVOKABLE QObject* get(int aIndex) const;
|
Q_INVOKABLE QObject* get(int aIndex) const;
|
||||||
|
@ -77,6 +83,8 @@ public:
|
||||||
|
|
||||||
bool loading() const { return iLoadTask != NULL; }
|
bool loading() const { return iLoadTask != NULL; }
|
||||||
int count() const;
|
int count() const;
|
||||||
|
int bookCount() const;
|
||||||
|
int shelfCount() const;
|
||||||
QString path() const { return iPath; }
|
QString path() const { return iPath; }
|
||||||
QString relativePath() const { return iRelativePath; }
|
QString relativePath() const { return iRelativePath; }
|
||||||
void setRelativePath(QString aPath);
|
void setRelativePath(QString aPath);
|
||||||
|
@ -100,7 +108,7 @@ public:
|
||||||
virtual int rowCount(const QModelIndex& aParent) const;
|
virtual int rowCount(const QModelIndex& aParent) const;
|
||||||
virtual QVariant data(const QModelIndex& aIndex, int aRole) const;
|
virtual QVariant data(const QModelIndex& aIndex, int aRole) const;
|
||||||
|
|
||||||
// BooksListItem
|
// BooksItem
|
||||||
virtual BooksItem* retain();
|
virtual BooksItem* retain();
|
||||||
virtual void release();
|
virtual void release();
|
||||||
virtual QObject* object();
|
virtual QObject* object();
|
||||||
|
@ -108,10 +116,14 @@ public:
|
||||||
virtual BooksBook* book();
|
virtual BooksBook* book();
|
||||||
virtual QString name() const;
|
virtual QString name() const;
|
||||||
virtual QString fileName() const;
|
virtual QString fileName() const;
|
||||||
|
virtual bool accessible() const;
|
||||||
|
virtual void deleteFiles();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void loadingChanged();
|
void loadingChanged();
|
||||||
void countChanged();
|
void countChanged();
|
||||||
|
void bookCountChanged();
|
||||||
|
void shelfCountChanged();
|
||||||
void pathChanged();
|
void pathChanged();
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
void deviceChanged();
|
void deviceChanged();
|
||||||
|
@ -124,7 +136,6 @@ Q_SIGNALS:
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void onLoadTaskDone();
|
void onLoadTaskDone();
|
||||||
void onBookFound(BooksBook* aBook);
|
|
||||||
void onBookAccessibleChanged();
|
void onBookAccessibleChanged();
|
||||||
void onBookCopyingOutChanged();
|
void onBookCopyingOutChanged();
|
||||||
void onBookMovedAway();
|
void onBookMovedAway();
|
||||||
|
@ -134,6 +145,7 @@ private Q_SLOTS:
|
||||||
void saveState();
|
void saveState();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void init();
|
||||||
QString stateFileName() const;
|
QString stateFileName() const;
|
||||||
int bookIndex(BooksBook* aBook) const;
|
int bookIndex(BooksBook* aBook) const;
|
||||||
int itemIndex(QString aFileName, int aStartIndex = 0) const;
|
int itemIndex(QString aFileName, int aStartIndex = 0) const;
|
||||||
|
@ -142,19 +154,20 @@ private:
|
||||||
void queueStateSave();
|
void queueStateSave();
|
||||||
void loadBookList();
|
void loadBookList();
|
||||||
void updatePath();
|
void updatePath();
|
||||||
void removeAllBooks();
|
void submitDeleteTask(int aIndex);
|
||||||
BooksBook* removeBook(int aIndex);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class Data;
|
class Data;
|
||||||
|
class Counts;
|
||||||
class CopyTask;
|
class CopyTask;
|
||||||
class LoadTask;
|
class LoadTask;
|
||||||
class ImportTask;
|
class ImportTask;
|
||||||
class DeleteTask;
|
class DeleteTask;
|
||||||
|
friend class Counts;
|
||||||
|
|
||||||
QList<Data*> iList;
|
QList<Data*> iList;
|
||||||
QList<DeleteTask*> iDeleteTasks;
|
QList<DeleteTask*> iDeleteTasks;
|
||||||
LoadTask* iLoadTask;
|
LoadTask* iLoadTask;
|
||||||
QString iName;
|
|
||||||
QString iFileName;
|
QString iFileName;
|
||||||
QString iPath;
|
QString iPath;
|
||||||
QString iRelativePath;
|
QString iRelativePath;
|
||||||
|
|
|
@ -203,6 +203,19 @@ bool BooksStorage::isPresent() const
|
||||||
return iPrivate && iPrivate->iPresent;
|
return iPrivate && iPrivate->iPresent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString BooksStorage::fullPath(QString aRelativePath) const
|
||||||
|
{
|
||||||
|
if (iPrivate) {
|
||||||
|
QString path(booksDir().path());
|
||||||
|
if (!aRelativePath.isEmpty()) {
|
||||||
|
if (!path.endsWith('/')) path += '/';
|
||||||
|
path += aRelativePath;
|
||||||
|
}
|
||||||
|
return QDir::cleanPath(path);
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
bool BooksStorage::equal(const BooksStorage& aStorage) const
|
bool BooksStorage::equal(const BooksStorage& aStorage) const
|
||||||
{
|
{
|
||||||
if (iPrivate == aStorage.iPrivate) {
|
if (iPrivate == aStorage.iPrivate) {
|
||||||
|
@ -224,7 +237,6 @@ BooksStorage& BooksStorage::operator = (const BooksStorage& aStorage)
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// BooksStorageManager::Private
|
// BooksStorageManager::Private
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
@ -250,7 +262,7 @@ public:
|
||||||
~Private();
|
~Private();
|
||||||
|
|
||||||
int findDevice(QString aDevice) const;
|
int findDevice(QString aDevice) const;
|
||||||
int findPath(QString aPath) const;
|
int findPath(QString aPath, QString* aRelPath) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QList<BooksStorage> iStorageList;
|
QList<BooksStorage> iStorageList;
|
||||||
|
@ -335,13 +347,22 @@ int BooksStorageManager::Private::findDevice(QString aDevice) const
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int BooksStorageManager::Private::findPath(QString aPath) const
|
int BooksStorageManager::Private::findPath(QString aPath, QString* aRelPath) const
|
||||||
{
|
{
|
||||||
if (!aPath.isEmpty()) {
|
if (!aPath.isEmpty()) {
|
||||||
const int n = iStorageList.count();
|
const int n = iStorageList.count();
|
||||||
for (int i=0; i<n; i++) {
|
for (int i=0; i<n; i++) {
|
||||||
BooksStorage::Private* data = iStorageList.at(i).iPrivate;
|
BooksStorage::Private* data = iStorageList.at(i).iPrivate;
|
||||||
if (aPath.startsWith(data->iBooksDir.path())) {
|
if (aPath.startsWith(data->iBooksDir.path())) {
|
||||||
|
if (aRelPath) {
|
||||||
|
int i = data->iBooksDir.path().length();
|
||||||
|
while (aPath.length() > i && aPath.at(i) == '/') i++;
|
||||||
|
if (aPath.length() > i) {
|
||||||
|
*aRelPath = aPath.right(aPath.length() - i);
|
||||||
|
} else {
|
||||||
|
*aRelPath = QString();
|
||||||
|
}
|
||||||
|
}
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -414,9 +435,9 @@ BooksStorage BooksStorageManager::storageForDevice(QString aDevice) const
|
||||||
return (index >= 0) ? iPrivate->iStorageList.at(index) : BooksStorage();
|
return (index >= 0) ? iPrivate->iStorageList.at(index) : BooksStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
BooksStorage BooksStorageManager::storageForPath(QString aPath) const
|
BooksStorage BooksStorageManager::storageForPath(QString aPath, QString* aRelPath) const
|
||||||
{
|
{
|
||||||
int index = iPrivate->findPath(aPath);
|
int index = iPrivate->findPath(aPath, aRelPath);
|
||||||
return (index >= 0) ? iPrivate->iStorageList.at(index) : BooksStorage();
|
return (index >= 0) ? iPrivate->iStorageList.at(index) : BooksStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,8 @@ public:
|
||||||
QDir configDir() const;
|
QDir configDir() const;
|
||||||
QString label() const { return booksDir().dirName(); }
|
QString label() const { return booksDir().dirName(); }
|
||||||
QString root() const { return booksDir().path(); }
|
QString root() const { return booksDir().path(); }
|
||||||
|
QString fullConfigPath(QString aRelativePath) const;
|
||||||
|
QString fullPath(QString aRelativePath) const;
|
||||||
|
|
||||||
bool isValid() const { return iPrivate != NULL; }
|
bool isValid() const { return iPrivate != NULL; }
|
||||||
bool isInternal() const;
|
bool isInternal() const;
|
||||||
|
@ -93,7 +95,7 @@ public:
|
||||||
int count() const;
|
int count() const;
|
||||||
QList<BooksStorage> storageList() const;
|
QList<BooksStorage> storageList() const;
|
||||||
BooksStorage storageForDevice(QString aDevice) const;
|
BooksStorage storageForDevice(QString aDevice) const;
|
||||||
BooksStorage storageForPath(QString aPath) const;
|
BooksStorage storageForPath(QString aPath, QString* aRelPath = NULL) const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void storageAdded(BooksStorage aStorage);
|
void storageAdded(BooksStorage aStorage);
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
#include "BooksConfig.h"
|
#include "BooksConfig.h"
|
||||||
#include "BooksSettings.h"
|
#include "BooksSettings.h"
|
||||||
#include "BooksImportModel.h"
|
#include "BooksImportModel.h"
|
||||||
|
#include "BooksPathModel.h"
|
||||||
#include "BooksStorageModel.h"
|
#include "BooksStorageModel.h"
|
||||||
#include "BooksPageWidget.h"
|
#include "BooksPageWidget.h"
|
||||||
#include "BooksListWatcher.h"
|
#include "BooksListWatcher.h"
|
||||||
|
@ -73,6 +74,7 @@ Q_DECL_EXPORT int main(int argc, char **argv)
|
||||||
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(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");
|
||||||
|
|
Loading…
Reference in a new issue