2015-06-28 14:22:35 +03:00
|
|
|
/*
|
2016-06-23 18:34:13 +03:00
|
|
|
Copyright (C) 2015-2016 Jolla Ltd.
|
2015-06-28 14:22:35 +03:00
|
|
|
Contact: Slava Monich <slava.monich@jolla.com>
|
|
|
|
|
2016-06-23 18:34:13 +03:00
|
|
|
You may use this file under the terms of the BSD license as follows:
|
2015-06-28 14:22:35 +03:00
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
|
modification, are permitted provided that the following conditions
|
|
|
|
are met:
|
2016-06-23 18:34:13 +03:00
|
|
|
|
2015-06-28 14:22:35 +03:00
|
|
|
* 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
|
2016-06-23 18:34:13 +03:00
|
|
|
notice, this list of conditions and the following disclaimer in
|
|
|
|
the documentation and/or other materials provided with the
|
|
|
|
distribution.
|
|
|
|
* Neither the name of Jolla Ltd nor the names of its contributors
|
|
|
|
may be used to endorse or promote products derived from this
|
|
|
|
software without specific prior written permission.
|
2015-06-28 14:22:35 +03:00
|
|
|
|
2016-06-23 18:34:13 +03:00
|
|
|
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.
|
2015-06-28 14:22:35 +03:00
|
|
|
*/
|
|
|
|
|
|
|
|
import QtQuick 2.0
|
|
|
|
import Sailfish.Silica 1.0
|
|
|
|
import harbour.books 1.0
|
|
|
|
|
2015-12-01 20:00:55 +03:00
|
|
|
Item {
|
2015-06-28 14:22:35 +03:00
|
|
|
id: shelfView
|
|
|
|
|
|
|
|
property int shelfIndex
|
|
|
|
property bool singleStorage
|
|
|
|
property bool removableStorage
|
|
|
|
property bool editMode
|
|
|
|
property bool deleteAllRequest
|
|
|
|
property real cellWidth
|
|
|
|
property real cellHeight
|
|
|
|
property alias view: grid
|
|
|
|
property alias shelf: shelfModel
|
|
|
|
property alias device: shelfModel.device
|
|
|
|
property alias dummyItemIndex: shelfModel.dummyItemIndex
|
|
|
|
|
|
|
|
signal openBook(var book)
|
|
|
|
signal dropItem(var mouseX, var mouseY)
|
|
|
|
signal startEditing()
|
|
|
|
signal stopEditing()
|
|
|
|
signal cancelDeleteAll()
|
|
|
|
signal scrollRight()
|
|
|
|
signal scrollLeft()
|
|
|
|
|
2015-11-08 14:20:25 +03:00
|
|
|
property bool _haveBooks: shelfModel && shelfModel.count
|
2015-06-28 14:22:35 +03:00
|
|
|
property int _cellsPerRow: Math.floor(width/cellWidth)
|
|
|
|
readonly property int _remorseTimeout: 5000
|
2015-11-08 14:20:25 +03:00
|
|
|
property bool _loading: !shelfModel || shelfModel.loading || startAnimationTimer.running
|
2015-06-28 14:22:35 +03:00
|
|
|
property var _remorse
|
|
|
|
|
|
|
|
on_HaveBooksChanged: if (!_haveBooks) shelfView.stopEditing()
|
|
|
|
|
|
|
|
Shelf {
|
|
|
|
id: shelfModel
|
|
|
|
property bool needDummyItem: dragInProgress && dragItem.shelfIndex !== shelfView.shelfIndex
|
2016-06-23 18:34:13 +03:00
|
|
|
property bool _completed // Received Component.onCompleted
|
2015-06-28 14:22:35 +03:00
|
|
|
onNeedDummyItemChanged: if (needDummyItem) hasDummyItem = true
|
|
|
|
editMode: shelfView.editMode
|
2015-11-08 14:20:25 +03:00
|
|
|
onRelativePathChanged: longStartTimer.restart()
|
2016-06-23 18:34:13 +03:00
|
|
|
onPathChanged: {
|
|
|
|
if (globalSettings.currentStorage === device && _completed) {
|
|
|
|
globalSettings.currentFolder = path
|
|
|
|
}
|
|
|
|
}
|
|
|
|
onDeviceChanged: {
|
|
|
|
if (device === globalSettings.currentStorage) {
|
|
|
|
relativePath = globalSettings.relativePath
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Component.onCompleted: _completed = true
|
2015-11-08 14:20:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
BooksPathModel {
|
|
|
|
id: pathModel
|
|
|
|
path: shelfModel.relativePath
|
2015-06-28 14:22:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
onEditModeChanged: {
|
|
|
|
dragItem.visible = false
|
|
|
|
shelf.cancelAllDeleteRequests()
|
|
|
|
dragArea.draggedItemIndex = -1
|
|
|
|
fadeAnimation.stop()
|
|
|
|
if (!editMode) {
|
|
|
|
dragArea.resetPressState()
|
|
|
|
dragScrollAnimation.stop()
|
|
|
|
if (_remorse) _remorse.cancel()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function doDeleteAll() {
|
|
|
|
if (deleteAllRequest) {
|
|
|
|
fadeAnimation.stop()
|
|
|
|
shelf.removeAll()
|
|
|
|
shelf.cancelAllDeleteRequests()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onDeleteAllRequestChanged: {
|
|
|
|
if (deleteAllRequest) {
|
|
|
|
if (!_remorse) _remorse = remorseComponent.createObject(shelfView)
|
|
|
|
fadeAnimation.restart()
|
|
|
|
//% "Deleting all books"
|
|
|
|
_remorse.execute(qsTrId("shelf-view-about-to-delete-all"),
|
|
|
|
doDeleteAll, _remorseTimeout)
|
|
|
|
} else if (_remorse) {
|
|
|
|
fadeAnimation.stop()
|
|
|
|
_remorse.cancel()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Component {
|
|
|
|
id: remorseComponent
|
|
|
|
RemorsePopup {
|
|
|
|
onCanceled: {
|
|
|
|
shelfView.cancelDeleteAll()
|
|
|
|
shelf.cancelAllDeleteRequests()
|
|
|
|
fadeAnimation.stop()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Connections {
|
|
|
|
target: dragItem
|
|
|
|
onAnimatingChanged: if (!dragItem.animating && !dragArea.pressed) dragArea.finishDrag()
|
|
|
|
}
|
|
|
|
|
|
|
|
BooksStorageHeader {
|
|
|
|
id: storageHeader
|
|
|
|
removable: removableStorage
|
2015-11-08 14:20:25 +03:00
|
|
|
count: shelfModel.bookCount
|
2015-06-28 14:22:35 +03:00
|
|
|
showCount: !_loading
|
2015-11-08 14:20:25 +03:00
|
|
|
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
|
2015-06-28 14:22:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
SilicaGridView {
|
|
|
|
id: grid
|
|
|
|
anchors {
|
2015-11-08 14:20:25 +03:00
|
|
|
top: storageHeader.bottom
|
2015-06-28 14:22:35 +03:00
|
|
|
left: parent.left
|
|
|
|
right: parent.right
|
|
|
|
bottom: parent.bottom
|
2015-08-18 21:06:46 +03:00
|
|
|
leftMargin: Math.floor((shelfView.width - _cellsPerRow * shelfView.cellWidth)/2)
|
2015-06-28 14:22:35 +03:00
|
|
|
}
|
|
|
|
model: shelfModel
|
2015-11-08 14:20:25 +03:00
|
|
|
interactive: !dragInProgress && !scrollToTopAnimation.running
|
2015-06-28 14:22:35 +03:00
|
|
|
clip: true
|
|
|
|
cellWidth: shelfView.cellWidth
|
|
|
|
cellHeight: shelfView.cellHeight
|
|
|
|
flickableDirection: Flickable.VerticalFlick
|
2015-11-08 14:20:25 +03:00
|
|
|
header: Column {
|
|
|
|
Repeater {
|
|
|
|
model: pathModel
|
|
|
|
BooksShelfTitle {
|
|
|
|
width: grid.width
|
|
|
|
text: model.name
|
2015-12-01 01:00:56 +03:00
|
|
|
editable: editMode && currentFolder
|
2015-11-30 00:18:09 +03:00
|
|
|
currentFolder: model.index === (pathModel.count-1)
|
2015-11-08 14:20:25 +03:00
|
|
|
onClicked: {
|
2015-12-01 01:00:56 +03:00
|
|
|
if (!currentFolder) {
|
2015-11-30 00:18:09 +03:00
|
|
|
console.log("switching to", model.path)
|
2015-11-30 18:57:27 +03:00
|
|
|
shelfView.stopEditing()
|
2015-11-30 00:18:09 +03:00
|
|
|
shelfModel.relativePath = model.path
|
|
|
|
}
|
2015-11-08 14:20:25 +03:00
|
|
|
}
|
2015-11-30 18:57:27 +03:00
|
|
|
onRename: {
|
|
|
|
console.log(to)
|
2015-12-01 01:00:56 +03:00
|
|
|
shelfModel.name = to
|
2015-11-30 18:57:27 +03:00
|
|
|
}
|
2015-12-01 01:00:56 +03:00
|
|
|
onStartEdit: shelfView.startEditing()
|
2015-11-08 14:20:25 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-06-28 14:22:35 +03:00
|
|
|
delegate: BooksShelfItem {
|
|
|
|
editMode: shelfView.editMode
|
|
|
|
dropped: dragItem.dropShelfIndex >= 0 &&
|
|
|
|
dragItem.dropShelfIndex === shelfView.shelfIndex &&
|
|
|
|
dragItem.dropItemIndex >= 0 &&
|
|
|
|
dragItem.dropItemIndex === model.index
|
|
|
|
dragged: dragItem.shelfIndex === shelfView.shelfIndex &&
|
|
|
|
dragArea.draggedItemIndex === model.index
|
|
|
|
visible: !model.dummy && !dragged && !dropped
|
|
|
|
pressed: model.accessible && (model.index === dragArea.pressedItemIndex) && (model.index !== dragArea.pressedDeleteItemIndex)
|
|
|
|
deleting: model.deleteRequested
|
|
|
|
deletingAll: shelfView.deleteAllRequest
|
|
|
|
deleteAllOpacity: grid.itemOpacity
|
|
|
|
width: shelfView.cellWidth
|
|
|
|
height: shelfView.cellHeight
|
|
|
|
book: model.book
|
|
|
|
name: model.name
|
|
|
|
copyingIn: model.copyingIn
|
|
|
|
copyingOut: model.copyingOut
|
2015-12-03 02:00:45 +03:00
|
|
|
copyProgress: model.copyProgress
|
2015-06-28 14:22:35 +03:00
|
|
|
remorseTimeout: _remorseTimeout
|
|
|
|
onScalingChanged: updateLastPressedItemScalingIndex()
|
|
|
|
onPressedChanged: updateLastPressedItemScalingIndex()
|
|
|
|
onDeleteMe: shelfView.shelf.remove(model.index)
|
|
|
|
function updateLastPressedItemScalingIndex() {
|
|
|
|
if (pressed && scaling) {
|
|
|
|
dragArea.lastPressedItemScalingIndex = model.index
|
|
|
|
} else if (dragArea.lastPressedItemScalingIndex === model.index) {
|
|
|
|
dragArea.lastPressedItemScalingIndex = -1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-08 14:20:25 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-06-28 14:22:35 +03:00
|
|
|
property real itemOpacity: 1
|
2015-11-08 14:20:25 +03:00
|
|
|
property real minContentY: -headerItem.height
|
2015-06-28 14:22:35 +03:00
|
|
|
|
|
|
|
moveDisplaced: Transition {
|
|
|
|
SmoothedAnimation { properties: "x,y"; duration: 150 }
|
|
|
|
}
|
|
|
|
|
|
|
|
removeDisplaced: Transition {
|
|
|
|
SmoothedAnimation { properties: "x,y"; duration: 150 }
|
|
|
|
}
|
|
|
|
|
|
|
|
NumberAnimation {
|
|
|
|
id: fadeAnimation
|
|
|
|
target: grid
|
|
|
|
property: "itemOpacity"
|
|
|
|
from: 1
|
|
|
|
to: 0
|
|
|
|
duration: _remorseTimeout
|
|
|
|
easing.type: Easing.OutCubic
|
|
|
|
}
|
|
|
|
|
2015-12-01 20:00:55 +03:00
|
|
|
onActiveFocusChanged: console.log("BooksShelfView.grid", activeFocus)
|
2015-06-28 14:22:35 +03:00
|
|
|
|
|
|
|
Behavior on y { SpringAnimation {} }
|
|
|
|
VerticalScrollDecorator {}
|
|
|
|
}
|
|
|
|
|
2015-12-01 20:00:55 +03:00
|
|
|
BooksDragArea {
|
|
|
|
id: dragArea
|
|
|
|
dragParent: storageView
|
|
|
|
gridView: grid
|
|
|
|
onDeleteItemAt: {
|
|
|
|
if (!shelfView.deleteAllRequest) {
|
|
|
|
shelfView.shelf.setDeleteRequested(index, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
onDropItem: shelfView.dropItem(mouseX, mouseY)
|
|
|
|
onActiveFocusChanged: console.log("BooksShelfView.grid.dragArea", activeFocus)
|
|
|
|
Component.onCompleted: {
|
|
|
|
console.log("BooksDragArea created")
|
|
|
|
grid.focus = true
|
|
|
|
}
|
|
|
|
}
|
2015-06-28 14:22:35 +03:00
|
|
|
|
|
|
|
Timer {
|
|
|
|
id: longStartTimer
|
|
|
|
interval: 500
|
|
|
|
running: true
|
|
|
|
onTriggered: {
|
|
|
|
if (shelf.loading) {
|
|
|
|
console.log(shelfModel.path, "startup is taking too long")
|
|
|
|
startAnimationTimer.start()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Timer {
|
|
|
|
id: startAnimationTimer
|
|
|
|
interval: 2000
|
|
|
|
}
|
2015-07-12 17:11:55 +03:00
|
|
|
|
|
|
|
Loader {
|
|
|
|
id: leftSwipeHintLoader
|
|
|
|
anchors.fill: parent
|
|
|
|
active: globalHints.storageLeftSwipe < MaximumHintCount || running
|
|
|
|
property bool running
|
|
|
|
sourceComponent: Component {
|
|
|
|
BooksStorageLeftSwipeHint {
|
|
|
|
property bool hintCanBeEnabled: !_loading &&
|
|
|
|
storageView.visible &&
|
|
|
|
shelfIndex == storageListWatcher.currentIndex &&
|
|
|
|
(shelfIndex+1) < storageModel.count &&
|
|
|
|
globalHints.storageLeftSwipe < MaximumHintCount
|
|
|
|
|
|
|
|
hintEnabled: hintCanBeEnabled && !hintDelayTimer.running
|
|
|
|
onHintShown: globalHints.storageLeftSwipe++
|
|
|
|
onHintRunningChanged: leftSwipeHintLoader.running = hintRunning
|
|
|
|
onHintCanBeEnabledChanged: if (hintCanBeEnabled) hintDelayTimer.restart()
|
|
|
|
Component.onCompleted: if (hintCanBeEnabled) hintDelayTimer.restart()
|
|
|
|
Timer {
|
|
|
|
id: hintDelayTimer
|
|
|
|
interval: 1000
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-06-28 14:22:35 +03:00
|
|
|
}
|