diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30a4648 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +harbour-books.pro.user +build diff --git a/.gitmodules b/.gitmodules index 73ccf99..65918b4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "fbreader/upstream"] path = fbreader/upstream url = https://github.com/geometer/FBReader.git +[submodule "harbour-lib"] + path = harbour-lib + url = https://github.com/monich/harbour-lib.git diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a59509 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +## E-book reader for Sailfish OS + +![icon](app/harbour-books.png) + +The core functionality is based on [FBReader](https://github.com/geometer/FBReader) source code with very few modifications. Books are imported from the Downloads folder, where they are saved by the browser or email client. Alternatively, you can manually copy your books to the Books directory under the home folder. Removable storage is supported as well. In theory, it should be able to handle all E-book formats supported by FBReader. Tested mostly with epub and fb2. diff --git a/app/app.pro b/app/app.pro new file mode 100644 index 0000000..a54d7ce --- /dev/null +++ b/app/app.pro @@ -0,0 +1,138 @@ +TARGET = harbour-books +CONFIG += sailfishapp link_pkgconfig +PKGCONFIG += sailfishapp mlite5 expat glib-2.0 +#QT += dbus + +!include(../common.pri) + +CONFIG(debug, debug|release) { + DEFINES += HARBOUR_DEBUG=1 +} + +# Directories +FBREADER_DIR = $$_PRO_FILE_PWD_/../fbreader +FRIBIDI_DIR = $$_PRO_FILE_PWD_/../fribidi +LINEBREAK_DIR = $$_PRO_FILE_PWD_/../linebreak +HARBOUR_LIB_DIR = $$_PRO_FILE_PWD_/../harbour-lib + +# Libraries +FBREADER_LIB = $$OUT_PWD/../fbreader/libfbreader.a +FRIBIDI_LIB = $$OUT_PWD/../fribidi/libfribidi.a +LINEBREAK_LIB = $$OUT_PWD/../linebreak/liblinebreak.a +HARBOUR_LIB = $$OUT_PWD/../harbour-lib/libharbour-lib.a + +PRE_TARGETDEPS += \ + $$FBREADER_LIB \ + $$FRIBIDI_LIB \ + $$LINEBREAK_LIB \ + $$HARBOUR_LIB +LIBS += \ + $$FBREADER_LIB \ + $$FRIBIDI_LIB \ + $$LINEBREAK_LIB \ + $$HARBOUR_LIB \ + -lbz2 -lz -ldl + +OTHER_FILES += \ + harbour-books.png \ + harbour-books.desktop \ + qml/*.qml \ + qml/images/* \ + data/default/* \ + data/zlibrary/core/encodings/* \ + data/zlibrary/core/resources/* \ + translations/*.ts + +TARGET_DATA_DIR = /usr/share/harbour-books +TARGET_DEFAULT_DATA_DIR = $$TARGET_DATA_DIR/data +TARGET_ZLIBRARY_DATA_DIR = $$TARGET_DEFAULT_DATA_DIR + +core_data.files = \ + data/zlibrary/core/*.gz \ + data/zlibrary/core/*.zip \ + data/zlibrary/core/encodings \ + data/zlibrary/core/resources +core_data.path = $$TARGET_ZLIBRARY_DATA_DIR +INSTALLS += core_data + +text_data.files = data/zlibrary/text/*.zip +text_data.path = $$TARGET_ZLIBRARY_DATA_DIR +INSTALLS += text_data + +defaults.files = data/default/* +defaults.path = $$TARGET_ZLIBRARY_DATA_DIR +INSTALLS += defaults + +formats.files = data/formats/* +formats.path = $$TARGET_DEFAULT_DATA_DIR/formats +INSTALLS += formats + +CONFIG += sailfishapp_i18n sailfishapp_i18n_idbased +TRANSLATIONS += \ + translations/harbour-books.ts \ + translations/harbour-books-ru.ts + +INCLUDEPATH += \ + src \ + $$HARBOUR_LIB_DIR/include \ + $$FBREADER_DIR/fbreader/fbreader/src \ + $$FBREADER_DIR/fbreader/zlibrary/text/include \ + $$FBREADER_DIR/fbreader/zlibrary/core/include \ + $$FBREADER_DIR/fbreader/zlibrary/core/src/view \ + $$FBREADER_DIR/fbreader/zlibrary/core/src/dialogs \ + $$FBREADER_DIR/fbreader/zlibrary/core/src/application \ + $$FBREADER_DIR/fbreader/zlibrary/core/src/options \ + $$FBREADER_DIR/fbreader/zlibrary/core/src/unix \ + $$FBREADER_DIR/fbreader/zlibrary/ui/src/qt4 + +SOURCES += \ + src/BooksBook.cpp \ + src/BooksBookModel.cpp \ + src/BooksConfig.cpp \ + src/BooksCoverModel.cpp \ + src/BooksCoverWidget.cpp \ + src/BooksDialogManager.cpp \ + src/BooksImportModel.cpp \ + src/BooksListWatcher.cpp \ + src/BooksLoadingProperty.cpp \ + src/BooksPageWidget.cpp \ + src/BooksPaintContext.cpp \ + src/BooksSaveTimer.cpp \ + src/BooksSettings.cpp \ + src/BooksShelf.cpp \ + src/BooksStorage.cpp \ + src/BooksStorageModel.cpp \ + src/BooksTask.cpp \ + src/BooksTextStyle.cpp \ + src/BooksTaskQueue.cpp \ + src/BooksTextView.cpp \ + src/libudev.c \ + src/main.cpp \ + src/ZLApplication.cpp \ + src/ZLibrary.cpp + +HEADERS += \ + src/BooksBook.h \ + src/BooksBookModel.h \ + src/BooksConfig.h \ + src/BooksCoverModel.h \ + src/BooksCoverWidget.h \ + src/BooksDefs.h \ + src/BooksDialogManager.h \ + src/BooksImportModel.h \ + src/BooksItem.h \ + src/BooksListWatcher.h \ + src/BooksLoadingProperty.h \ + src/BooksPageWidget.h \ + src/BooksPaintContext.h \ + src/BooksPos.h \ + src/BooksSaveTimer.h \ + src/BooksSettings.h \ + src/BooksShelf.h \ + src/BooksStorage.h \ + src/BooksStorageModel.h \ + src/BooksTask.h \ + src/BooksTaskQueue.h \ + src/BooksTextView.h \ + src/BooksTextStyle.h \ + src/BooksTypes.h diff --git a/app/data/default/styles.xml b/app/data/default/styles.xml new file mode 120000 index 0000000..c98aff4 --- /dev/null +++ b/app/data/default/styles.xml @@ -0,0 +1 @@ +../../../fbreader/fbreader/fbreader/data/default/styles.desktop.xml \ No newline at end of file diff --git a/app/data/formats b/app/data/formats new file mode 120000 index 0000000..45fdfef --- /dev/null +++ b/app/data/formats @@ -0,0 +1 @@ +../../fbreader/fbreader/fbreader/data/formats \ No newline at end of file diff --git a/app/data/zlibrary/core b/app/data/zlibrary/core new file mode 120000 index 0000000..a4785f6 --- /dev/null +++ b/app/data/zlibrary/core @@ -0,0 +1 @@ +../../../fbreader/fbreader/zlibrary/core/data \ No newline at end of file diff --git a/app/data/zlibrary/text b/app/data/zlibrary/text new file mode 120000 index 0000000..c9282cd --- /dev/null +++ b/app/data/zlibrary/text @@ -0,0 +1 @@ +../../../fbreader/fbreader/zlibrary/text/data \ No newline at end of file diff --git a/app/harbour-books.desktop b/app/harbour-books.desktop new file mode 100644 index 0000000..389dfde --- /dev/null +++ b/app/harbour-books.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=Books +Name[ru]=Книги +Comment=E-Book Reader +Exec=harbour-books +Icon=harbour-books +X-Nemo-Application-Type=silica-qt5 diff --git a/app/harbour-books.png b/app/harbour-books.png new file mode 100644 index 0000000..b5dfd76 Binary files /dev/null and b/app/harbour-books.png differ diff --git a/app/harbour-books.svg b/app/harbour-books.svg new file mode 100644 index 0000000..6ed6560 --- /dev/null +++ b/app/harbour-books.svg @@ -0,0 +1,52 @@ + + + +image/svg+xml + + + + + + + + diff --git a/app/qml/BooksBookView.qml b/app/qml/BooksBookView.qml new file mode 100644 index 0000000..a9b236a --- /dev/null +++ b/app/qml/BooksBookView.qml @@ -0,0 +1,253 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 +import harbour.books 1.0 + +SilicaFlickable { + id: root + + property variant book + property variant settings + + signal closeBook() + signal pageClicked(var page) + + property int _currentPage: bookListWatcher.currentIndex + property bool _loading: minLoadingDelay.running || !bookModel.pageCount + property bool _pagerVisible: (_currentState.pager && book && bookModel.pageCount) ? 1 : 0 + property var _currentState: _visibilityStates[settings.pageDetails % _visibilityStates.length] + readonly property var _visibilityStates: [ + { pager: false, page: false, title: false, tools: false }, + { pager: false, page: true, title: true, tools: false }, + { pager: true, page: true, title: true, tools: true } + ] + + PullDownMenu { + MenuItem { + id: defaultFontMenuItem + //% "Use default fonts" + text: qsTrId("book-font-default") + enabled: settings.fontSize != Settings.DefaultFontSize + onClicked: settings.fontSize = Settings.DefaultFontSize + } + MenuItem { + id: smallerFontMenuItem + //% "Use smaller fonts" + text: qsTrId("book-font-smaller") + enabled: settings.fontSize >= Settings.MinFontSize + onClicked: settings.fontSize -= 1 + } + MenuItem { + id: largerFontMenuItem + //% "Use larger fonts" + text: qsTrId("book-font-larger") + enabled: settings.fontSize <= Settings.MaxFontSize + onClicked: settings.fontSize += 1 + } + MenuItem { + //% "Back to library" + text: qsTrId("book-view-back") + onClicked: root.closeBook() + } + + // Remove disabled items from the menu when we have a chance + onActiveChanged: if (!active) hideDisabledItems() + Component.onCompleted: hideDisabledItems() + + function hideDisabledItems() { + defaultFontMenuItem.visible = defaultFontMenuItem.enabled + smallerFontMenuItem.visible = smallerFontMenuItem.enabled + largerFontMenuItem.visible = largerFontMenuItem.enabled + } + } + + Timer { + id: minLoadingDelay + interval: 1000 + } + + Timer { + id: resetPager + interval: 0 + onTriggered: { + if (_currentPage >= 0) { + console.log("resetting pager to", _currentPage) + pager.currentPage = _currentPage + } + } + } + + BookModel { + id: bookModel + book: root.book ? root.book : null + size: bookListWatcher.size + currentPage: _currentPage + leftMargin: Theme.horizontalPageMargin + rightMargin: Theme.horizontalPageMargin + topMargin: Theme.itemSizeSmall + bottomMargin: Theme.itemSizeSmall + settings: root.settings + onJumpToPage: bookView.jumpTo(index) + onCurrentPageChanged: { + if (currentPage >= 0 && bookView._jumpingTo < 0) { + pager.currentPage = currentPage + } + } + onLoadingChanged: { + if (loading && !pageCount) { + minLoadingDelay.start() + bookView._jumpingTo = -1 + } + } + } + + ListWatcher { + id: bookListWatcher + listView: bookView + } + + SilicaListView { + id: bookView + model: bookModel + anchors.fill: parent + flickDeceleration: maximumFlickVelocity + orientation: ListView.Horizontal + snapMode: ListView.SnapOneItem + spacing: Theme.paddingMedium + opacity: _loading ? 0 : 1 + visible: opacity > 0 + delegate: BooksPageView { + width: bookView.width + height: bookView.height + model: bookModel + page: index + settings: root.settings + leftMargin: bookModel.leftMargin + rightMargin: bookModel.rightMargin + topMargin: bookModel.topMargin + bottomMargin: bookModel.bottomMargin + titleVisible: _currentState.title + pageNumberVisible: _currentState.page + title: bookModel.title + onPageClicked: { + root.pageClicked(index) + settings.pageDetails = (settings.pageDetails+ 1) % _visibilityStates.length + } + } + + property int _jumpingTo: -1 + function jumpTo(page) { + if (page >=0 && page !== _currentPage) { + _jumpingTo = page + positionViewAtIndex(page, ListView.Center) + _jumpingTo = -1 + if (_currentPage !== page) { + console.log("oops, still at", _currentPage) + resetPager.restart() + } + } + } + + Behavior on opacity { FadeAnimation {} } + } + +/* + BooksPageTools { + anchors { + top: parent.top + left: parent.left + right: parent.right + leftMargin: bookModel.leftMargin + rightMargin: bookModel.rightMargin + } + opacity: _currentState.tools ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { FadeAnimation {} } + } +*/ + + BooksPager { + id: pager + anchors { + bottom: parent.bottom + bottomMargin: (Theme.itemSizeExtraSmall + 2*(bookModel.bottomMargin - height))/4 + } + pageCount: bookModel.pageCount + width: parent.width + opacity: _pagerVisible ? 1 : 0 + visible: opacity > 0 + onPageChanged: bookView.jumpTo(page) + Behavior on opacity { FadeAnimation {} } + } + + Column { + id: loadingAnimation + opacity: _loading ? 1 : 0 + visible: opacity > 0 + anchors.centerIn: parent + spacing: Theme.paddingLarge + Behavior on opacity { FadeAnimation {} } + Item { + width: busyIndicator.width + height: busyIndicator.height + anchors.horizontalCenter: parent.horizontalCenter + BusyIndicator { + id: busyIndicator + anchors.centerIn: parent + size: BusyIndicatorSize.Large + running: _loading + } + BooksFitLabel { + anchors { + fill: parent + margins: Theme.paddingMedium + } + maxFontSize: Theme.fontSizeMedium + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + color: Theme.highlightColor + text: bookModel.progress > 0 ? bookModel.progress : "" + visible: opacity > 0 + opacity: bookModel.progress > 0 ? 1 : 0 + Behavior on opacity { FadeAnimation {} } + } + } + Label { + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + color: Theme.highlightColor + //% "Loading..." + text: qsTrId("book-view-loading") + } + } +} diff --git a/app/qml/BooksCoverPage.qml b/app/qml/BooksCoverPage.qml new file mode 100644 index 0000000..d439e9a --- /dev/null +++ b/app/qml/BooksCoverPage.qml @@ -0,0 +1,109 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 +import harbour.books 1.0 + +CoverBackground { + id: root + transparent: !_haveBook || grid.count < 6 + + property variant book + property variant shelf + + property bool _haveBook: book ? true : false + + CoverModel { + id: coverModel + source: shelf + } + + GridView { + id: grid + visible: !_haveBook + anchors.fill: parent + interactive: false + cellWidth: Math.floor(width/2.0) + cellHeight: Math.ceil(height/((grid.count > 4) ? 3.0 : 2.0)) + model: coverModel + delegate: BookCover { + book: model.book + width: grid.cellWidth + height: grid.cellHeight + borderWidth: 0 + stretch: true + } + } + + Rectangle { + anchors.centerIn: parent + visible: !_haveBook && !coverModel.count + color: "#cc0000" + height: Math.ceil(parent.height*3/10) + width: height + radius: height/2 + + Image { + anchors.centerIn: parent + anchors.horizontalCenter: parent.horizontalCenter + source: "images/cover-image.svg" + width: parent.width - Math.max(Math.ceil(parent.width/20),2) + sourceSize.width: width + sourceSize.height: height + fillMode: Image.PreserveAspectFit + } + } + + BookCover { + id: bookCover + visible: _haveBook + anchors.fill: parent + anchors.centerIn: parent + width: Theme.coverSizeLarge.width + height: Theme.coverSizeLarge.height + borderRadius: Theme.paddingSmall + borderColor: Theme.primaryColor + synchronous: true + book: root.book ? root.book : null + defaultCover: "images/default-cover.jpg" + stretch: true + } + + BooksTitleText { + anchors { + margins: Theme.paddingMedium + fill: parent + } + text: book ? book.title : "" + visible: bookCover.empty + } +} diff --git a/app/qml/BooksDeleteButton.qml b/app/qml/BooksDeleteButton.qml new file mode 100644 index 0000000..a6dc356 --- /dev/null +++ b/app/qml/BooksDeleteButton.qml @@ -0,0 +1,66 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 + +Rectangle { + id: root + + signal clicked() + + property real size + property real margin: Theme.paddingMedium + + width: size + height: size + color: Theme.highlightColor + radius: size/2 + + opacity: enabled ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { FadeAnimation {} } + + Image { + anchors.fill: parent + sourceSize.width: width + sourceSize.height: height + source: "images/close-x.svg" + } + + MouseArea { + anchors { + fill: parent + margins: -root.margin + } + onClicked: root.clicked() + } +} diff --git a/app/qml/BooksDragArea.qml b/app/qml/BooksDragArea.qml new file mode 100644 index 0000000..d376466 --- /dev/null +++ b/app/qml/BooksDragArea.qml @@ -0,0 +1,260 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 + +MouseArea { + id: root + parent: dragInProgress ? dragParent : gridView + anchors.fill: parent + + signal deleteItemAt(var index) + signal dropItem(var mouseX, var mouseY) + + property var dragParent + property var gridView + property int draggedItemIndex: -1 + property int pressedItemIndex: -1 + property int pressedDeleteItemIndex: -1 + property int lastPressedItemScalingIndex: -1 + property int lastReleasedItemIndex: -1 + property int lastReleasedDeleteItemIndex: -1 + property bool pressedItemScaling: (pressedItemIndex >= 0 && lastPressedItemScalingIndex === pressedItemIndex) + property bool dragPositionChanged + property bool dragCloseToTheLeftEdge + property bool dragCloseToTheRightEdge + property real dragLastX + property real dragLastY + + onDraggedItemIndexChanged: { + draggedItem = (draggedItemIndex >= 0) ? shelf.get(draggedItemIndex) : null + } + + function itemX(index) { return shelfView.cellWidth * (index % shelfView._cellsPerRow) - gridView.contentX } + function itemY(index) { return shelfView.cellHeight * Math.floor(index / shelfView._cellsPerRow) - gridView.contentY } + + onCanceled: { + stopDrag() + } + onClicked: { + var index + if (shelfView.editMode) { + if (!dragInProgress || !dragPositionChanged) { + index = gridView.indexAt(mouseX + gridView.contentX, mouseY + currentShelfView.contentY) + if (index >= 0 && + index === lastReleasedDeleteItemIndex && + dragItem.withinDeleteButton(mouseX - itemX(index), mouseY - itemY(index))) { + lastReleasedDeleteItemIndex = -1 + root.deleteItemAt(index) + dragScrollAnimation.stop() + } else if (shelfView.shelf.deleteRequested(index)) { + shelfView.shelf.setDeleteRequested(index, false); + } else { + shelfView.stopEditing() + } + } + } else { + index = gridView.indexAt(mouseX + gridView.contentX, mouseY + currentShelfView.contentY) + if (index >= 0 && index === lastReleasedItemIndex) { + var item = shelf.get(index); + if (item.book && item.accessible) { + shelfView.openBook(item.book) + } + } + } + resetPressState() + dragScrollAnimation.stop() + } + onPressed: { + var index = gridView.indexAt(mouseX + gridView.contentX, mouseY + currentShelfView.contentY) + lastReleasedItemIndex = -1 + lastReleasedDeleteItemIndex = -1 + pressedItemIndex = index + if (pressedDeleteItemIndex < 0 && + dragItem.withinDeleteButton(mouseX - itemX(index), mouseY - itemY(index))) { + pressedDeleteItemIndex = index + } else { + pressedDeleteItemIndex = -1 + } + } + onReleased: { + stopDrag(mouseX, mouseY) + } + onPressAndHold: { + if (!shelfView.editMode) { + var index = gridView.indexAt(mouseX + gridView.contentX, mouseY + currentShelfView.contentY) + if (index === pressedItemIndex) { + shelfView.startEditing() + } + } + } + onPositionChanged: { + if (shelfView.editMode) { + if (!pressedItemScaling && !dragInProgress && pressedDeleteItemIndex < 0) { + startDrag(mouseX, mouseY) + } else { + doDrag(mouseX, mouseY) + } + } + } + + function startDrag(x, y) { + var index = gridView.indexAt(x + gridView.contentX, y + currentShelfView.contentY) + if (index >= 0) { + var item = shelf.get(index) + if (item.accessible) { + var delta = gridView.mapToItem(dragParent,0,0) + console.log(shelf.path, "dragging", item.name) + dragLastX = x + delta.x + dragLastY = y + delta.y + dragItem.moveAnimationEnabled = false + dragItem.shelfIndex = shelfView.shelfIndex + dragItem.dropShelfIndex = -1 + dragItem.dropItemIndex = -1 + dragItem.x = itemX(index) + delta.x + dragItem.y = itemY(index) + delta.y + dragCloseToTheLeftEdge = (x < horizontalScrollThreshold) + dragCloseToTheRightEdge = (x > (gridView.width - horizontalScrollThreshold)) + dragPositionChanged = false + draggedItemIndex = index + pressedItemIndex = -1 + dragItem.visible = true + } else { + console.log(item.name, "is not draggable") + } + } + } + + function doDrag(x, y) { + if (dragInProgress && !dragItem.moving) { + var dx = x - dragLastX + var dy = y - dragLastY + if (dx !== 0 || dy !== 0) { + dragPositionChanged = true + dragItem.x += dx + dragItem.y += dy + dragLastX = x + dragLastY = y + var newIndex = currentShelfView.indexAt(x + gridView.contentX, Math.max(y + currentShelfView.contentY, 0)) + if (newIndex < 0) newIndex = currentShelf.count - 1 + if (newIndex >= 0) { + if (currentShelf.hasDummyItem) { + currentShelf.dummyItemIndex = newIndex + } else { + currentShelf.move(draggedItemIndex, newIndex) + draggedItemIndex = newIndex + } + } + if (y < verticalScrollThreshold && currentShelfView.contentY > 0) { + if (!dragScrollAnimation.running) { + dragScrollAnimation.from = currentShelfView.contentY + dragScrollAnimation.to = 0 + dragScrollAnimation.duration = currentShelfView.contentY*1000/gridView.height + dragScrollAnimation.start() + } + } else if (y > (gridView.height - verticalScrollThreshold) && (currentShelfView.contentY < maxContentY)) { + if (!dragScrollAnimation.running) { + dragScrollAnimation.from = currentShelfView.contentY + dragScrollAnimation.to = maxContentY + dragScrollAnimation.duration = (maxContentY - currentShelfView.contentY)*1000/gridView.height + dragScrollAnimation.start() + } + } else { + if (dragScrollAnimation.running) { + dragScrollAnimation.stop() + } + if (x < horizontalScrollThreshold) { + if (!dragCloseToTheLeftEdge) { + dragCloseToTheLeftEdge = true + shelfView.scrollLeft() + } + } else { + dragCloseToTheLeftEdge = false + } + if (x > (gridView.width - horizontalScrollThreshold)) { + if (!dragCloseToTheRightEdge) { + dragCloseToTheRightEdge = true + shelfView.scrollRight() + } + } else { + dragCloseToTheRightEdge = false + } + } + } + } + } + + function stopDrag(x, y) { + lastReleasedItemIndex = pressedItemIndex + lastReleasedDeleteItemIndex = pressedDeleteItemIndex + if (draggedItemIndex >= 0) { + if (x !== undefined && y !== undefined) doDrag(x, y) + var delta = gridView.mapToItem(dragParent,0,0) + dragItem.moveAnimationEnabled = true + dragItem.x = itemX(draggedItemIndex) + delta.x + dragItem.y = itemY(draggedItemIndex) + delta.y + // Allow the listener to modify dragItem coordinates + if (x !== undefined && y !== undefined) root.dropItem(x,y) + dragItem.dragged = false + // Normally we would finish things up once all the + // animations have completed + if (!dragItem.animating) finishDrag() + } + pressedItemIndex = -1 + dragScrollAnimation.stop() + } + + function finishDrag() { + console.log(shelf.path, "done with drag animation") + draggedItemIndex = -1 + dragItem.dropShelfIndex = -1 + dragItem.dropItemIndex = -1 + dragItem.moveAnimationEnabled = false + dragItem.visible = false + } + + function resetPressState() { + pressedItemIndex = -1 + pressedDeleteItemIndex = -1 + lastReleasedItemIndex = -1 + lastReleasedDeleteItemIndex = -1 + dragCloseToTheLeftEdge = false + dragCloseToTheRightEdge = false + } + + NumberAnimation { + id: dragScrollAnimation + target: currentShelfView + property: "contentY" + easing.type: Easing.InOutQuad + } +} diff --git a/app/qml/BooksFitLabel.qml b/app/qml/BooksFitLabel.qml new file mode 100644 index 0000000..8f6d6ab --- /dev/null +++ b/app/qml/BooksFitLabel.qml @@ -0,0 +1,66 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 + +Label { + property int minFontSize: Theme.fontSizeTiny + property int maxFontSize: Theme.fontSizeHuge + + smooth: true + + Component.onCompleted: refitText() + + onWidthChanged: refitText() + onHeightChanged: refitText() + onTextChanged: refitText() + onMaxFontSizeChanged: refitText() + onMinFontSizeChanged: refitText() + + function refitText() { + if (paintedHeight > 0 && paintedWidth > 0) { + if (font.pixelSize % 2) font.pixelSize++ + while (paintedWidth > width || paintedHeight > height) { + if ((font.pixelSize -= 2) <= minFontSize) + break + } + while (paintedWidth < width && paintedHeight < height) { + font.pixelSize += 2 + } + font.pixelSize -= 2 + if (font.pixelSize >= maxFontSize) { + font.pixelSize = maxFontSize + return + } + } + } +} diff --git a/app/qml/BooksImport.qml b/app/qml/BooksImport.qml new file mode 100644 index 0000000..81a9f9e --- /dev/null +++ b/app/qml/BooksImport.qml @@ -0,0 +1,118 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 +import harbour.books 1.0 + +Dialog { + id: root + allowedOrientations: window.allowedOrientations + canAccept: importModel.selectedCount > 0 + + property alias selectedCount: importModel.selectedCount + property alias destination: importModel.destination + + property bool _loading: importModel.busy || startTimer.running + + function selectedBook(index) { return importModel.selectedBook(index) } + + BooksImportModel { + id: importModel + } + + SilicaFlickable { + anchors.fill: parent + + DialogHeader { + id: dialogHeader + spacing: 0 + acceptText: (_loading || !importModel.count) ? "" : + //% "Import %0 book(s)" + importModel.selectedCount ? qsTrId("import-view-import-n-books",importModel.selectedCount).arg(importModel.selectedCount) : + //% "Select books" + qsTrId("import-view-select-books") + } + + SilicaListView { + anchors { + top: dialogHeader.bottom + bottom: parent.bottom + left: parent.left + right: parent.right + } + + model: importModel + clip: true + + opacity: (importModel.count > 0) ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { FadeAnimation {} } + + delegate: BooksImportItem { + width: root.width + highlighted: down || model.selected + contentHeight: Theme.itemSizeExtraLarge + onClicked: importModel.setSelected(model.index, !model.selected) + book: model.book + } + VerticalScrollDecorator {} + } + + ViewPlaceholder { + //% "No new books found" + text: qsTrId("import-view-no-new-books-found") + enabled: !_loading && !importModel.count + } + } + + BusyIndicator { + visible: opacity > 0 + anchors.centerIn: parent + size: BusyIndicatorSize.Large + running: _loading && !importModel.count + } + + // Give the dialog 1 second to initialize and finish the transition + // then ask the model to actually examine the downloads + Timer { + interval: 1000 + running: true + onTriggered: importModel.refresh() + } + + // Then show the spinner for at least one more second + Timer { + id: startTimer + interval: 2000 + running: true + } +} diff --git a/app/qml/BooksImportItem.qml b/app/qml/BooksImportItem.qml new file mode 100644 index 0000000..8a76e0d --- /dev/null +++ b/app/qml/BooksImportItem.qml @@ -0,0 +1,101 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 +import harbour.books 1.0 + +BackgroundItem { + id: root + width: parent.width + height: contentHeight + enabled: book ? true : false + + property var book + property string _title: book ? book.title : "" + property string _authors: book ? book.authors : "" + + BooksShelfItem { + id: cover + visible: root.enabled + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + leftMargin: Theme.horizontalPageMargin + } + width: Math.round(height*3/4) + book: root.book ? root.book : null + name: _title + } + + Column { + visible: root.enabled + anchors { + left: cover.right + leftMargin: Theme.paddingMedium + right: parent.right + rightMargin: Theme.horizontalPageMargin + verticalCenter: parent.verticalCenter + } + Label { + width: parent.width + text: _authors + color: root.highlighted ? Theme.highlightColor : Theme.primaryColor + truncationMode: TruncationMode.Fade + font.pixelSize: Theme.fontSizeExtraSmall + } + Label { + width: parent.width + text: _title + color: root.highlighted ? Theme.highlightColor : Theme.primaryColor + truncationMode: TruncationMode.Fade + } + } + + Image { + anchors { + top: parent.top + topMargin: Theme.paddingSmall + right: parent.right + rightMargin: Theme.paddingSmall + } + visible: highlighted + source: "image://theme/icon-s-installed" + (highlighted ? "?" + Theme.highlightColor : "") + } + + BusyIndicator { + anchors.centerIn: parent + visible: !root.enabled + running: visible + size: BusyIndicatorSize.Medium + } +} diff --git a/app/qml/BooksMain.qml b/app/qml/BooksMain.qml new file mode 100644 index 0000000..2f36c1f --- /dev/null +++ b/app/qml/BooksMain.qml @@ -0,0 +1,55 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 +import harbour.books 1.0 + +ApplicationWindow { + id: window + allowedOrientations: Orientation.All + + property variant currentShelf: mainPage.currentShelf + + Settings { id: globalSettings } + + initialPage: BooksMainPage { + id: mainPage + settings: globalSettings + } + + cover: Component { + BooksCoverPage { + book: globalSettings.currentBook + shelf: currentShelf + } + } +} diff --git a/app/qml/BooksMainPage.qml b/app/qml/BooksMainPage.qml new file mode 100644 index 0000000..0fc4006 --- /dev/null +++ b/app/qml/BooksMainPage.qml @@ -0,0 +1,82 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 +import harbour.books 1.0 + +Page { + id: root + + allowedOrientations: window.allowedOrientations + + //property variant shelf + property variant settings + property variant currentShelf: storageView.currentShelf + + property Item _bookView + + function createBookViewIfNeeded() { + if (settings.currentBook && !_bookView) { + _bookView = bookViewComponent.createObject(root) + } + } + + Connections { + target: settings + onCurrentBookChanged: createBookViewIfNeeded() + } + + Component { + id: bookViewComponent + BooksBookView { + anchors.fill: parent + opacity: book ? 1 : 0 + visible: opacity > 0 + settings: root.settings + book: settings.currentBook ? settings.currentBook : null + onCloseBook: settings.currentBook = null + Behavior on opacity { FadeAnimation {} } + } + } + + BooksStorageView { + id: storageView + anchors.fill: parent + settings: root.settings + opacity: settings.currentBook ? 0 : 1 + visible: opacity > 0 + Behavior on opacity { FadeAnimation {} } + onOpenBook: settings.currentBook = book + } + + Component.onCompleted: createBookViewIfNeeded() +} diff --git a/app/qml/BooksPageSlider.qml b/app/qml/BooksPageSlider.qml new file mode 100644 index 0000000..c6f0aa5 --- /dev/null +++ b/app/qml/BooksPageSlider.qml @@ -0,0 +1,287 @@ +/**************************************************************************************** +** +** Copyright (C) 2013 Jolla Ltd. +** Contact: Martin Jones +** All rights reserved. +** +** This file is part of Sailfish Silica UI component package. +** +** 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 + +MouseArea { + id: slider + + // Same as Silica Slider except that its colors are configurable + property color primaryColor: Theme.primaryColor + property color secondaryColor: Theme.secondaryColor + property color highlightColor: Theme.highlightColor + property color secondaryHighlightColor: Theme.secondaryHighlightColor + + property real maximumValue: 1.0 + property real minimumValue: 0.0 + property real stepSize + property real value: 0.0 + readonly property real sliderValue: Math.max(minimumValue, Math.min(maximumValue, value)) + property bool handleVisible: true + property string valueText + property alias label: labelText.text + property bool down: pressed + property bool highlighted: down + property real leftMargin: Math.round(Screen.width/8) + property real rightMargin: Math.round(Screen.width/8) + + property bool _hasValueLabel: false + property real _oldValue + property bool _tracking: true + property real _precFactor: 1.0 + + property real _grooveWidth: Math.max(0, width - leftMargin - rightMargin) + property bool _widthChanged + property bool _cancel + + property bool _componentComplete + + onStepSizeChanged: { + // Avoid rounding errors. We assume that the range will + // be sensibly related to stepSize + var decimial = Math.floor(stepSize) - stepSize + if (decimial < 0.001) { + _precFactor = Math.pow(10, 7) + } else if (decimial < 0.1) { + _precFactor = Math.pow(10, 4) + } else { + _precFactor = 1.0 + } + } + + height: valueText !== "" ? Theme.itemSizeExtraLarge : label !== "" ? Theme.itemSizeMedium : Theme.itemSizeSmall + + onWidthChanged: updateWidth() + onLeftMarginChanged: updateWidth() + onRightMarginChanged: updateWidth() + + // changing the width of the slider shouldn't animate the slider bar/handle + function updateWidth() { + _widthChanged = true + _grooveWidth = Math.max(0, width - leftMargin - rightMargin) + _widthChanged = false + } + + function cancel() { + _cancel = true + value = _oldValue + _updateHighlightToValue() + } + + drag { + target: draggable + minimumX: leftMargin - highlight.width/2 + maximumX: slider.width - leftMargin - highlight.width/2 + axis: Drag.XAxis + } + + function _updateHighlightToValue() { + if (maximumValue > minimumValue) { + highlight.x = (sliderValue - minimumValue) / (maximumValue - minimumValue) * _grooveWidth - highlight.width/2 + leftMargin + } else { + highlight.x = leftMargin - highlight.width/2 + } + } + + function _updateValueToDraggable() { + if (width > (leftMargin + rightMargin)) { + highlight.x = draggable.x + var pos = draggable.x + highlight.width/2 - leftMargin + value = _calcValue((pos / _grooveWidth) * (maximumValue - minimumValue) + minimumValue) + } + } + + function _calcValue(newVal) { + if (newVal <= minimumValue) { + return minimumValue + } + + if (stepSize > 0.0) { + var offset = newVal - minimumValue + var intervals = Math.round(offset / stepSize) + newVal = Math.round((minimumValue + (intervals * stepSize)) * _precFactor) / _precFactor + } + + if (newVal > maximumValue) { + return maximumValue + } + + return newVal + } + + onPressed: { + _cancel = false + _oldValue = value + draggable.x = Math.min(Math.max(drag.minimumX, mouseX - highlight.width/2), drag.maximumX) + } + + onReleased: { + if (!_cancel) { + _tracking = false + _updateValueToDraggable() + if (stepSize != 0.0) { + // on release make sure that we settle on a step boundary + _updateHighlightToValue() + } + _oldValue = value + } + } + + onCanceled: value = _oldValue + + onValueTextChanged: { + if (valueText && !_hasValueLabel) { + _hasValueLabel = true + var valueIndicatorComponent = Qt.createComponent("private/SliderValueLabel.qml") + if (valueIndicatorComponent.status === Component.Ready) { + valueIndicatorComponent.createObject(slider) + } else { + console.log(valueIndicatorComponent.errorString()) + } + } + } + + onSliderValueChanged: { + if (!slider.drag.active) { + _tracking = false + _updateHighlightToValue() + } + } + + GlassItem { + id: background + // extra painting margins (Theme.paddingMedium on both sides) are needed, + // because glass item doesn't visibly paint across the full width of the item + x: slider.leftMargin-Theme.paddingMedium + width: slider._grooveWidth + 2*Theme.paddingMedium + height: Theme.itemSizeExtraSmall/2 + + anchors.verticalCenter: parent.verticalCenter + dimmed: true + radius: 0.06 + falloffRadius: 0.09 + ratio: 0.0 + onWidthChanged: { _tracking = true; _updateHighlightToValue() } + color: slider.highlighted ? highlightColor : secondaryColor + states: State { + name: "hasText"; when: slider.valueText !== "" || text !== "" + AnchorChanges { target: background; anchors.verticalCenter: undefined; anchors.bottom: slider.bottom } + PropertyChanges { target: background; anchors.bottomMargin: Theme.fontSizeSmall + Theme.paddingSmall + 1 } + } + } + + GlassItem { + id: progressBar + x: background.x // some margin at each end + anchors.verticalCenter: background.verticalCenter + // height added as GlassItem will not display correctly with width < height + width: (sliderValue - minimumValue) / (maximumValue - minimumValue) * (background.width-height) + height + height: Theme.itemSizeExtraSmall/2 + visible: sliderValue > minimumValue + dimmed: false + radius: 0.05 + falloffRadius: 0.14 + ratio: 0.0 + color: slider.highlighted ? highlightColor : primaryColor + Behavior on width { + enabled: !_widthChanged + SmoothedAnimation { velocity: 1500 } + } + } + + Item { + id: draggable + width: highlight.width + height: highlight.height + onXChanged: { + if (_cancel) { + return + } + if (slider.drag.active) { + _updateValueToDraggable() + } + if (!_tracking && Math.abs(highlight.x - draggable.x) < 5) { + _tracking = true + } + } + } + GlassItem { + id: highlight + width: Theme.itemSizeMedium + height: Theme.itemSizeMedium + radius: 0.17 + falloffRadius: 0.17 + anchors.verticalCenter: background.verticalCenter + visible: handleVisible + color: slider.highlighted ? highlightColor : primaryColor + Behavior on x { + enabled: !_widthChanged + SmoothedAnimation { velocity: 1500 } + } + } + + Label { + id: labelText + visible: text.length + font.pixelSize: Theme.fontSizeSmall + color: slider.highlighted ? secondaryHighlightColor : secondaryColor + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: background.verticalCenter + anchors.topMargin: Theme.paddingMedium + width: Math.min(paintedWidth, parent.width - 2*Theme.paddingMedium) + truncationMode: TruncationMode.Fade + } + states: State { + name: "invalidRange" + when: _componentComplete && minimumValue >= maximumValue + PropertyChanges { + target: progressBar + width: progressBar.height + } + PropertyChanges { + target: slider + enabled: false + opacity: 0.6 + } + StateChangeScript { + script: console.log("Warning: Slider.maximumValue needs to be higher than Slider.minimumValue") + } + } + + Component.onCompleted: { + _componentComplete = true + _updateHighlightToValue() + } +} diff --git a/app/qml/BooksPageView.qml b/app/qml/BooksPageView.qml new file mode 100644 index 0000000..2256b03 --- /dev/null +++ b/app/qml/BooksPageView.qml @@ -0,0 +1,107 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 +import harbour.books 1.0 + +Item { + id: view + + property alias model: widget.model + property alias page: widget.page + property alias leftMargin: widget.leftMargin + property alias rightMargin: widget.rightMargin + property alias topMargin: widget.topMargin + property alias bottomMargin: widget.bottomMargin + property alias settings: widget.settings + property alias title: titleLabel.text + property bool titleVisible + property bool pageNumberVisible + property color extraInfoColor: "#808080" + + signal pageClicked() + + PageWidget { + id: widget + anchors.fill: parent + model: bookModel + } + + Label { + id: titleLabel + anchors { + top: parent.top + left: parent.left + right: parent.right + leftMargin: Theme.paddingLarge + rightMargin: Theme.paddingLarge + } + height: Theme.itemSizeExtraSmall + font.pixelSize: Theme.fontSizeSmall + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: extraInfoColor + elide: Text.ElideRight + opacity: titleVisible ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { FadeAnimation {} } + } + + BusyIndicator { + anchors.centerIn: parent + size: BusyIndicatorSize.Large + running: widget.loading + opacity: running ? 1 : 0 + visible: opacity > 0 + Behavior on opacity {} + } + + Label { + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + } + text: widget.page + 1 + height: Theme.itemSizeExtraSmall + font.pixelSize: Theme.fontSizeSmall + verticalAlignment: Text.AlignVCenter + color: extraInfoColor + opacity: pageNumberVisible ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { FadeAnimation {} } + } + + MouseArea { + anchors.fill: parent + onClicked: view.pageClicked() + } +} diff --git a/app/qml/BooksPager.qml b/app/qml/BooksPager.qml new file mode 100644 index 0000000..2ef8c55 --- /dev/null +++ b/app/qml/BooksPager.qml @@ -0,0 +1,68 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 +import harbour.books 1.0 + +Item { + id: root + height: slider.height + + property alias pageCount: slider.maximumValue + property alias currentPage: slider.value + property alias pressed: slider.pressed + property alias leftMargin: slider.leftMargin + property alias rightMargin: slider.rightMargin + + signal pageChanged(var page) + + BooksPageSlider { + id: slider + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + stepSize: 1 + minimumValue: 0 + valueText: "" + label: "" + //highlightColor: primaryColor + //secondaryHighlightColor: secondaryColor + //highlightColor: "#000000" + //secondaryHighlightColor: "#808080" + //primaryColor: "#808080" + primaryColor: highlightColor + secondaryColor: secondaryHighlightColor + onValueChanged: root.pageChanged(value) + } +} diff --git a/app/qml/BooksSDCardIcon.qml b/app/qml/BooksSDCardIcon.qml new file mode 100644 index 0000000..2792105 --- /dev/null +++ b/app/qml/BooksSDCardIcon.qml @@ -0,0 +1,54 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 + +Rectangle { + color: "transparent" + width: image.width + + Rectangle { + color: Theme.highlightColor + x: parent.width*3/80 + y: parent.height*3/60 + width: parent.width*74/80 + height: parent.height*47/60 + } + + Image { + id: image + height: parent.height + sourceSize.height: height + fillMode: Image.PreserveAspectFit + source: "images/sdcard.svg" + } +} diff --git a/app/qml/BooksShelfItem.qml b/app/qml/BooksShelfItem.qml new file mode 100644 index 0000000..4769a41 --- /dev/null +++ b/app/qml/BooksShelfItem.qml @@ -0,0 +1,205 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 +import harbour.books 1.0 + +Item { + id: root + + signal clicked() + signal deleteRequested() + signal deleteMe() + + opacity: deletingAll ? deleteAllOpacity : deleting ? scale : 1 + + property alias name: title.text + property alias book: cover.book + property alias synchronous: cover.synchronous + property alias remorseTimeout: deleteAnimation.duration + property real margins: Theme.paddingMedium + property real deleteAllOpacity: 1 + property bool editMode + property bool dropped + property bool dragged + property bool pressed + property bool deletingAll + property bool deleting + property bool copyingOut + property bool copyingIn + property bool scaleAnimationEnabled: true + property bool moveAnimationEnabled: false + + readonly property bool scaling: scaleUpAnimation.running || scaleDownAnimation.running || deleteAnimation.running + readonly property bool moving: moveAnimationX.running || moveAnimationY.running + readonly property bool animating: scaling || moving + + property bool _deleting: deleting && !deletingAll + property real _borderRadius: Theme.paddingSmall + property color _borderColor: Theme.primaryColor + + property bool scaledDown: (editMode && !dragged && !pressed && !dropped) + + BookCover { + id: cover + anchors { + margins: root.margins + fill: parent + } + borderWidth: 2 + borderRadius: _borderRadius + borderColor: _borderColor + opacity: (copyingIn || copyingOut) ? 0.1 : 1 + Behavior on opacity { FadeAnimation { } } + + BooksTitleText { + id: title + anchors.centerIn: parent + width: parent.width - 2*Theme.paddingMedium + height: parent.height - 2*Theme.paddingMedium + visible: parent.empty + } + + layer.enabled: root.pressed + layer.effect: ShaderEffect { // i.e. PressEffect from Lipstick + property variant source + property color color: Theme.rgba(Theme.highlightBackgroundColor, 0.4) + fragmentShader: " + uniform sampler2D source; + uniform highp vec4 color; + uniform lowp float qt_Opacity; + varying highp vec2 qt_TexCoord0; + void main(void) { + highp vec4 pixelColor = texture2D(source, qt_TexCoord0); + gl_FragColor = vec4(mix(pixelColor.rgb/max(pixelColor.a, 0.00390625), color.rgb/max(color.a, 0.00390625), color.a) * pixelColor.a, pixelColor.a) * qt_Opacity; + }" + } + } + + MouseArea { + anchors { + fill: parent + margins: -Theme.paddingSmall + } + onClicked: root.clicked() + } + + BooksDeleteButton { + id: deleteButton + x: cover.x + (cover.width - width)/2 + y: cover.y + cover.height - height/2 + size: 3*margins + enabled: editMode && !pressed && !deleting && !deletingAll && !copyingIn && !copyingOut + onClicked: root.deleteRequested() + } + + BusyIndicator { + id: busyIndicator + anchors.centerIn: parent + size: BusyIndicatorSize.Large + running: copyingIn || copyingOut + } + + function withinDeleteButton(x, y) { + return x >= deleteButton.x - deleteButton.margin && + x < deleteButton.x + deleteButton.width + deleteButton.margin && + y >= deleteButton.y - deleteButton.margin && + y < deleteButton.y + deleteButton.height + deleteButton.margin; + } + + function updateScaledDown() { + if (scaleAnimationEnabled) { + if (scaledDown) { + scaleUpAnimation.stop() + scaleDownAnimation.restart() + } else { + scaleDownAnimation.stop() + scaleUpAnimation.restart() + } + } else { + scale = scaledDown ? scaleDownAnimation.to : scaleUpAnimation.to + } + } + + onScaledDownChanged: if (!_deleting) updateScaledDown() + + onScaleAnimationEnabledChanged: { + if (!_deleting && !scaleAnimationEnabled) { + scale = scaledDown ? scaleDownAnimation.to : scaleUpAnimation.to + } + } + + on_DeletingChanged: { + if (_deleting) { + scaleUpAnimation.stop() + scaleDownAnimation.stop() + deleteAnimation.start() + } else { + deleteAnimation.stop() + updateScaledDown() + } + } + + NumberAnimation { + id: scaleUpAnimation + target: root + property: "scale" + to: 1 + duration: 250 + easing.type: Easing.InOutQuad + } + + NumberAnimation { + id: scaleDownAnimation + target: root + property: "scale" + to: 0.9 + duration: 250 + easing.type: Easing.InOutQuad + } + + NumberAnimation { + id: deleteAnimation + target: root + property: "scale" + to: 0 + duration: 2000 + easing.type: Easing.OutSine + onRunningChanged: if (!running && _deleting) root.deleteMe() + } + + NumberAnimation { id: moveAnimationX; duration: 150; easing.type: Easing.InOutQuad } + NumberAnimation { id: moveAnimationY; duration: 150; easing.type: Easing.InOutQuad } + + Behavior on x { animation: moveAnimationX; enabled: moveAnimationEnabled } + Behavior on y { animation: moveAnimationY; enabled: moveAnimationEnabled } +} diff --git a/app/qml/BooksShelfView.qml b/app/qml/BooksShelfView.qml new file mode 100644 index 0000000..79e51c6 --- /dev/null +++ b/app/qml/BooksShelfView.qml @@ -0,0 +1,258 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 +import harbour.books 1.0 + +SilicaFlickable { + id: shelfView + + property variant settings + 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() + + property bool _haveBooks: shelf && shelf.count + property int _cellsPerRow: Math.floor(width/cellWidth) + readonly property int _remorseTimeout: 5000 + property bool _loading: !shelf || shelf.loading || startAnimationTimer.running + property var _remorse + + on_HaveBooksChanged: if (!_haveBooks) shelfView.stopEditing() + + Shelf { + id: shelfModel + property bool needDummyItem: dragInProgress && dragItem.shelfIndex !== shelfView.shelfIndex + onNeedDummyItemChanged: if (needDummyItem) hasDummyItem = true + editMode: shelfView.editMode + } + + 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 + count: shelfModel.count + showCount: !_loading + } + + SilicaGridView { + id: grid + anchors { + top: singleStorage ? parent.top : storageHeader.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + leftMargin: Math.floor((shelfView.width - _cellsPerRow * shelfView.cellWidth)/_cellsPerRow/2) + } + model: shelfModel + interactive: !dragInProgress + clip: true + opacity: (!_loading && _haveBooks) ? 1 : 0 + visible: opacity > 0 + cellWidth: shelfView.cellWidth + cellHeight: shelfView.cellHeight + flickableDirection: Flickable.VerticalFlick + 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 + 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 + } + } + } + + property real itemOpacity: 1 + + 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 + } + + BooksDragArea { + id: dragArea + dragParent: storageView + gridView: grid + onDeleteItemAt: { + if (!shelfView.deleteAllRequest) { + shelfView.shelf.setDeleteRequested(index, true); + } + } + onDropItem: shelfView.dropItem(mouseX, mouseY) + } + + Behavior on y { SpringAnimation {} } + Behavior on opacity { FadeAnimation {} } + 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 { + id: longStartTimer + interval: 500 + running: true + onTriggered: { + if (shelf.loading) { + console.log(shelfModel.path, "startup is taking too long") + startAnimationTimer.start() + if (!_busyIndicator) _busyIndicator = busyIndicatorComponent.createObject(shelfView) + } + } + } + + Timer { + id: startAnimationTimer + interval: 2000 + } +} diff --git a/app/qml/BooksStorageHeader.qml b/app/qml/BooksStorageHeader.qml new file mode 100644 index 0000000..b051894 --- /dev/null +++ b/app/qml/BooksStorageHeader.qml @@ -0,0 +1,125 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 + +Column { + anchors { + top: parent.top + left: parent.left + right: parent.right + topMargin: Theme.paddingMedium + } + spacing: 0 + visible: opacity > 0 + opacity: singleStorage ? 0 : 1 + Behavior on opacity { FadeAnimation {} } + + property bool removable + property int count + property bool showCount: true + + Item { + width: parent.width + height: Math.max(left.height, right.height) + Row { + id: left + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + } + spacing: 0 + Item { + width: Theme.paddingMedium + height: parent.height + } + BooksSDCardIcon { + visible: removableStorage + anchors.bottom: parent.bottom + height: storageLabel.height*3/4 + } + Item { + visible: removableStorage + width: Theme.paddingMedium + height: parent.height + } + Label { + id: storageLabel + anchors.bottom: parent.bottom + color: Theme.highlightColor + text: removable ? + //% "Memory card" + qsTrId("storage-removable") : + //% "Internal storage" + qsTrId("storage-internal") + } + } + Label { + id: right + anchors { + bottom: parent.bottom + right: parent.right + rightMargin: Theme.paddingMedium + } + //% "%0 book(s)" + text: qsTrId("storage-book-count",count).arg(count) + font.pixelSize: Theme.fontSizeExtraSmall + color: Theme.highlightColor + opacity: (showCount && count > 0) ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { FadeAnimation {} } + } + } + + Item { + height: Theme.paddingSmall + width: parent.width + } + + Rectangle { + color: Theme.secondaryHighlightColor + height: 1 + anchors { + left: parent.left + right: parent.right + } + } + + Rectangle { + color: Theme.highlightColor + height: 1 + anchors { + left: parent.left + right: parent.right + } + } +} diff --git a/app/qml/BooksStorageView.qml b/app/qml/BooksStorageView.qml new file mode 100644 index 0000000..4a017d2 --- /dev/null +++ b/app/qml/BooksStorageView.qml @@ -0,0 +1,247 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 +import harbour.books 1.0 + +SilicaFlickable { + id: storageView + interactive: !dragInProgress + + property variant settings + property bool editMode: false + + signal openBook(var book) + + property real _cellWidth: calculateCellWidth() + property real _cellHeight: _cellWidth*8/5 + property var draggedItem + property var currentShelf + property var currentShelfView + property int currentShelfIndex: storageListWatcher.currentIndex + readonly property bool dragInProgress: draggedItem ? true : false + readonly property real maxContentY: currentShelfView ? Math.max(0, currentShelfView.contentHeight - currentShelfView.height) : 0 + readonly property real verticalScrollThreshold: _cellHeight/2 + readonly property real horizontalScrollThreshold: _cellWidth/2 + + readonly property real _minGridCellWidth: 10*Theme.paddingMedium + + // Books in the library shouldn't be too small or too big. + // At least 3 (or 5 in landscape) should fit in the horizontal direction. + // The width shouldn't be smaller than 10*Theme.paddingMedium or 0.88 inch + function calculateCellWidth() { + var result = 0 + if (width > 0) { + // At least 3 books in portrait, 5 in landscape + var n = (height > width) ? 4 : 6 + var cellSize = width/n + while (cellSize > _minGridCellWidth && (cellSize/PointsPerInch) > 0.88 && n < 11) { + cellSize = width/(++n) + } + result = Math.floor(width/(n-1)) + } + return result + } + + PullDownMenu { + MenuItem { + //% "Scan downloads" + text: qsTrId("storage-view-scan-downloads") + visible: !editMode + onClicked: pageStack.push(importComponent) + } + MenuItem { + //% "Delete all books" + text: qsTrId("storage-view-delete-everything") + visible: editMode + enabled: currentShelf && (currentShelf.count > 0) + onClicked: storageModel.setDeleteAllRequest(storageListWatcher.currentIndex, true) + } + } + + onEditModeChanged: { + storageModel.cancelDeleteAllRequests() + dragScrollAnimation.stop() + } + + Component { + id: importComponent + BooksImport { + destination: currentShelf ? currentShelf.path : "" + onAccepted: { + var count = selectedCount + for (var i=0; i maxContentX) { + dragScrollAnimation.to = maxContentX + dragScrollAnimation.restart() + } + } + + delegate: BooksShelfView { + width: storageList.width + height: storageList.height + cellWidth: storageView._cellWidth + cellHeight: storageView._cellHeight + settings: storageView.settings + singleStorage: storageModel.count < 2 + editMode: storageView.editMode + deleteAllRequest: model.deleteAllRequest + device: model.device + removableStorage: model.removable + shelfIndex: model.index + onStartEditing: storageView.editMode = true + onStopEditing: storageView.editMode = false + onScrollLeft: storageList.scrollOnePageLeft() + onScrollRight: storageList.scrollOnePageRight() + onCancelDeleteAll: storageModel.cancelDeleteAllRequests() + onDropItem: storageView.dropItem() + + property bool current: model.index === currentShelfIndex + Component.onCompleted: updateCurrentShelf() + onCurrentChanged: updateCurrentShelf() + function updateCurrentShelf() { + if (current) { + storageView.currentShelf = shelf + storageView.currentShelfView = view + } else { + // no need for dummy item anymore + shelf.hasDummyItem = false + } + } + onOpenBook: storageView.openBook(book) + } + + function scrollOnePageLeft() { + if (contentX > 0) { + dragScrollAnimation.from = contentX + dragScrollAnimation.to = Math.max(0, contentX - width - spacing) + dragScrollAnimation.start() + } + } + + function scrollOnePageRight() { + if (contentX < maxContentX) { + dragScrollAnimation.from = contentX + dragScrollAnimation.to = Math.min(maxContentX, contentX + width + spacing) + dragScrollAnimation.start() + } + } + + function scrollToPage(index) { + dragScrollAnimation.from = contentX + dragScrollAnimation.to = (width + spacing) * index + dragScrollAnimation.start() + } + } + + function dropItem() { + if (draggedItem && dragItem.shelfIndex !== currentShelfIndex && currentShelf) { + var targetIndex = currentShelf.dummyItemIndex + if (targetIndex >= 0 && currentShelf.drop(draggedItem)) { + console.log("drop accepted") + // Update coordinates of the drag item to make it move toward the drop target + var cellsPerRow = Math.floor(currentShelfView.width/_cellWidth) + var delta = currentShelfView.mapToItem(storageView,0,0) + var x = _cellWidth * (targetIndex % cellsPerRow) - currentShelfView.contentX + var y = _cellHeight * Math.floor(targetIndex / cellsPerRow) - currentShelfView.contentY + dragItem.x = x + delta.x + dragItem.y = y + delta.y + // This hides the target item until the grag item makes it to the destination: + dragItem.dropShelfIndex = currentShelfIndex + dragItem.dropItemIndex = targetIndex + } + } + } + + BooksShelfItem { + id: dragItem + visible: false + width: storageView._cellWidth + height: storageView._cellHeight + pressed: false + editMode: false + scaledDown: false + scaleAnimationEnabled: false + synchronous: true + book: draggedItem ? draggedItem.book : null + name: draggedItem ? draggedItem.name : "" + property int shelfIndex: -1 + property int dropShelfIndex: -1 + property int dropItemIndex: -1 + } + + NumberAnimation { + id: dragScrollAnimation + target: storageList + property: "contentX" + duration: 500 + easing.type: Easing.InOutQuad + } +} diff --git a/app/qml/BooksTitleText.qml b/app/qml/BooksTitleText.qml new file mode 100644 index 0000000..0a2adad --- /dev/null +++ b/app/qml/BooksTitleText.qml @@ -0,0 +1,63 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of 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 + +Text { + color: Theme.primaryColor + font.pixelSize: Theme.fontSizeSmall + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + onTextChanged: fitText() + onWidthChanged: fitText() + onHeightChanged: fitText() + onPaintedHeightChanged: fitText() + property bool _fittingText: false + + function fitText() { + if (!_fittingText) { + _fittingText = true + font.pixelSize = Theme.fontSizeSmall + maximumLineCount = 100000 + elide = Text.ElideNone + if (width > 0 && height > 0 && paintedHeight > height) { + font.pixelSize = Theme.fontSizeTiny + if (paintedHeight > height && lineHeight > 0) { + maximumLineCount = height/lineHeight + elide = Text.ElideRight + } + } + _fittingText = false + } + } +} diff --git a/app/qml/images/close-x.svg b/app/qml/images/close-x.svg new file mode 100644 index 0000000..49b31a5 --- /dev/null +++ b/app/qml/images/close-x.svg @@ -0,0 +1,33 @@ + + + +image/svg+xml diff --git a/app/qml/images/cover-image.svg b/app/qml/images/cover-image.svg new file mode 100644 index 0000000..9309f65 --- /dev/null +++ b/app/qml/images/cover-image.svg @@ -0,0 +1,97 @@ + + + +image/svg+xml diff --git a/app/qml/images/default-cover.jpg b/app/qml/images/default-cover.jpg new file mode 100644 index 0000000..c526287 Binary files /dev/null and b/app/qml/images/default-cover.jpg differ diff --git a/app/qml/images/sdcard.svg b/app/qml/images/sdcard.svg new file mode 100644 index 0000000..cb6a601 --- /dev/null +++ b/app/qml/images/sdcard.svg @@ -0,0 +1,50 @@ + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/app/src/BooksBook.cpp b/app/src/BooksBook.cpp new file mode 100644 index 0000000..724378b --- /dev/null +++ b/app/src/BooksBook.cpp @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksBook.h" +#include "BooksDefs.h" +#include "BooksTask.h" +#include "BooksTextView.h" +#include "BooksTextStyle.h" +#include "BooksPaintContext.h" + +#include "HarbourJson.h" +#include "HarbourDebug.h" + +#include "ZLImage.h" +#include "image/ZLQtImageManager.h" +#include "bookmodel/BookModel.h" +#include "library/Author.h" + +#include +#include +#include +#include + +#define BOOK_STATE_POSITION "position" +#define BOOK_COVER_SUFFIX ".cover." + +// ========================================================================== +// BooksBook::CoverContext +// ========================================================================== +class BooksBook::CoverPaintContext : public BooksPaintContext { +public: + CoverPaintContext(); + + void drawImage(int x, int y, const ZLImageData& image); + void drawImage(int x, int y, const ZLImageData& image, int width, int height, ScalingType type); + void handleImage(const ZLImageData& image); + bool gotIt() const; + +public: + static QSize gMaxScreenSize; + static bool gMaxScreenSizeKnown; + + QImage iImage; +}; + +QSize BooksBook::CoverPaintContext::gMaxScreenSize(480,640); +bool BooksBook::CoverPaintContext::gMaxScreenSizeKnown = false; + +BooksBook::CoverPaintContext::CoverPaintContext() +{ + if (!gMaxScreenSizeKnown) { + QList screens = qGuiApp->screens(); + const int n = screens.count(); + for (int i=0; isize(); + gMaxScreenSize.setWidth(qMax(s.width(), gMaxScreenSize.width())); + gMaxScreenSize.setHeight(qMax(s.width(), gMaxScreenSize.height())); + } + } + + setWidth(gMaxScreenSize.width()); + setHeight(gMaxScreenSize.height()); +} + +void BooksBook::CoverPaintContext::drawImage(int x, int y, const ZLImageData& image) +{ + handleImage(image); +} + +void BooksBook::CoverPaintContext::drawImage(int x, int y, const ZLImageData& image, + int width, int height, ScalingType type) +{ + handleImage(image); +} + +void BooksBook::CoverPaintContext::handleImage(const ZLImageData& image) +{ + const QImage* qImage = ((ZLQtImageData&)image).image(); + HDEBUG(image.width() << 'x' << image.height()); + if (qImage->height() > qImage->width() && + qImage->width() > iImage.width() && + qImage->height() > iImage.height()) { + iImage = *qImage; + } +} + +bool BooksBook::CoverPaintContext::gotIt() const +{ + return iImage.width() >= 50 && + iImage.height() >= 80 && + iImage.height() > iImage.width(); +} + +// ========================================================================== +// BooksBook::CoverTask +// ========================================================================== + +class BooksBook::CoverTask : public BooksTask +{ +public: + CoverTask(BooksStorage aStorage, shared_ptr aBook) : + iStorage(aStorage), iBook(aBook), iCoverMissing(false) {} + + bool hasImage() const; + +public: + BooksStorage iStorage; + shared_ptr iBook; + QImage iCoverImage; + bool iCoverMissing; +}; + +inline bool BooksBook::CoverTask::hasImage() const +{ + return iCoverImage.width() > 0 && iCoverImage.height() > 0; +} + +// ========================================================================== +// BooksBook::LoadCoverTask +// ========================================================================== + +class BooksBook::LoadCoverTask : public BooksBook::CoverTask +{ +public: + LoadCoverTask(BooksStorage aStorage, shared_ptr aBook, + shared_ptr aFormatPlugin) : + BooksBook::CoverTask(aStorage, aBook), + iFormatPlugin(aFormatPlugin) {} + + virtual void performTask(); + +public: + shared_ptr iFormatPlugin; +}; + +void BooksBook::LoadCoverTask::performTask() +{ + if (!isCanceled()) { + // Try to load cached (or custom) cover + if (iStorage.isValid()) { + QString coverPrefix(QString::fromStdString( + iBook->file().name(false)) + BOOK_COVER_SUFFIX); + QDirIterator it(iStorage.configDir()); + while (it.hasNext()) { + QString path(it.next()); + if (it.fileName().startsWith(coverPrefix)) { + if (QFile(path).size() == 0) { + HDEBUG("no cover for" << iBook->title().c_str()); + iCoverMissing = true; + return; + } else if (iCoverImage.load(path)) { + HDEBUG("loaded cover" << iCoverImage.width() << 'x' << + iCoverImage.height() << "for" << + iBook->title().c_str() << "from" << + qPrintable(path)); + return; + } + } + } + } + + // OK, fetch one from the book file + shared_ptr imageData; + shared_ptr image = iFormatPlugin->coverImage(iBook->file()); + if (!image.isNull()) { + imageData = ZLImageManager::Instance().imageData(*image); + } + if (!imageData.isNull()) { + QImage* qImage = (QImage*)((ZLQtImageData&)*imageData).image(); + if (qImage) iCoverImage = *qImage; + } + } +#if HARBOUR_DEBUG + if (hasImage()) { + HDEBUG("loaded cover" << iCoverImage.width() << 'x' << + iCoverImage.height() << "for" << iBook->title().c_str()); + } else if (isCanceled()) { + HDEBUG("cancelled" << iBook->title().c_str()); + } else { + HDEBUG("no cover found in" << iBook->title().c_str()); + } +#endif +} + +// ========================================================================== +// BooksBook::GuessCoverTask +// ========================================================================== + +class BooksBook::GuessCoverTask : public BooksBook::CoverTask +{ +public: + GuessCoverTask(BooksStorage aStorage, shared_ptr aBook) : + BooksBook::CoverTask(aStorage, aBook) {} + + virtual void performTask(); +}; + +void BooksBook::GuessCoverTask::performTask() +{ + if (!isCanceled()) { + BooksMargins margins; + CoverPaintContext context; + BookModel bookModel(iBook); + shared_ptr model(bookModel.bookTextModel()); + BooksTextView view(context, BooksTextStyle::defaults(), margins); + view.setModel(model); + view.rewind(); + if (!isCanceled()) { + view.rewind(); + if (!isCanceled()) { + view.paint(); + iCoverImage = context.iImage; + } + } + } + + QString coverPath; + if (iStorage.isValid()) { + coverPath = iStorage.configDir().path() + "/" + + QString::fromStdString(iBook->file().name(false)) + + BOOK_COVER_SUFFIX + "jpg"; + } + + if (hasImage()) { + HDEBUG("using image" << iCoverImage.width() << 'x' << + iCoverImage.width() << "as cover for" << iBook->title().c_str()); + + // Save the extracted image + if (!coverPath.isEmpty() && iCoverImage.save(coverPath)) { + HDEBUG("saved cover to" << qPrintable(coverPath)); + } + } else if (isCanceled()) { + HDEBUG("cancelled" << iBook->title().c_str()); + } else { + HDEBUG("no cover for" << iBook->title().c_str()); + iCoverMissing = true; + + // Create empty file. Guessing the cover image is an expensive task, + // we don't want to do it every time the application is started. + if (!coverPath.isEmpty() && + // Check if the book file still exists - the failure could've + // been caused by the SD-card removal. + QFile::exists(QString::fromStdString(iBook->file().path()))) { + QFile(coverPath).open(QIODevice::WriteOnly); + } + } +} + +// ========================================================================== +// BooksBook +// ========================================================================== + +// This constructor isn't really used, but is required by qmlRegisterType +BooksBook::BooksBook(QObject* aParent) : + QObject(aParent), + iRef(-1) +{ + init(); +} + +BooksBook::BooksBook(const BooksStorage& aStorage, shared_ptr aBook) : + QObject(NULL), + iRef(1), + iStorage(aStorage), + iBook(aBook), + iTaskQueue(BooksTaskQueue::instance()) +{ + init(); + HASSERT(!iBook.isNull()); + iTitle = QString::fromStdString(iBook->title()); + iPath = QString::fromStdString(iBook->file().path()); + iFileName = QString::fromStdString(iBook->file().name(false)); + iFormatPlugin = PluginCollection::Instance().plugin(*iBook); + AuthorList authors(iBook->authors()); + const int n = authors.size(); + for (int i=0; i 0) iAuthors += ", "; + iAuthors += QString::fromStdString(authors[i]->name()); + } + if (iStorage.isValid()) { + iStateFilePath = iStorage.configDir().path() + "/" + + iFileName + BOOKS_STATE_FILE_SUFFIX; + // Load the state + QVariantMap state; + if (HarbourJson::load(iStateFilePath, state)) { + iLastPos = BooksPos::fromVariant(state.value(BOOK_STATE_POSITION)); + } + } + // Refcounted BooksBook objects are managed by C++ code + QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); +} + +void BooksBook::init() +{ + iCoverTask = NULL; + iCoverTasksDone = false; + iCopyingOut = false; + iSaveTimer = NULL; +} + +BooksBook::~BooksBook() +{ + HDEBUG(qPrintable(iPath)); + HASSERT(!iRef.load()); + if (iCoverTask) iCoverTask->release(this); +} + +BooksItem* BooksBook::retain() +{ + if (iRef.load() >= 0) { + iRef.ref(); + } + return this; +} + +void BooksBook::release() +{ + if (iRef.load() >= 0 && !iRef.deref()) { + delete this; + } +} + +QObject* BooksBook::object() +{ + return this; +} + +BooksShelf* BooksBook::shelf() +{ + return NULL; +} + +BooksBook* BooksBook::book() +{ + return this; +} + +QString BooksBook::name() const +{ + return iTitle; +} + +QString BooksBook::fileName() const +{ + return iFileName; +} + +void BooksBook::setLastPos(const BooksPos& aPos) +{ + if (iLastPos != aPos) { + iLastPos = aPos; + // We only need save timer if we have the state file + if (!iSaveTimer && iStorage.isValid()) { + iSaveTimer = new BooksSaveTimer(this); + connect(iSaveTimer, SIGNAL(save()), SLOT(saveState())); + } + if (iSaveTimer) iSaveTimer->requestSave(); + } +} + +void BooksBook::setCopyingOut(bool aValue) +{ + if (iCopyingOut != aValue) { + const bool wasAccessible = accessible(); + iCopyingOut = aValue; + Q_EMIT copyingOutChanged(); + if (wasAccessible != accessible()) { + Q_EMIT accessibleChanged(); + } + } +} + +QImage BooksBook::coverImage() +{ + return iCoverImage; +} + +bool BooksBook::hasCoverImage() const +{ + return iCoverImage.width() > 0 && iCoverImage.height() > 0; +} + +void BooksBook::setCoverImage(QImage aImage) +{ + if (iCoverImage != aImage) { + iCoverImage = aImage; + Q_EMIT coverImageChanged(); + } +} + +bool BooksBook::requestCoverImage() +{ + if (!iBook.isNull() && !iFormatPlugin.isNull() && + !iCoverTasksDone && !iCoverTask) { + HDEBUG(iTitle); + iCoverTask = new LoadCoverTask(iStorage, iBook, iFormatPlugin); + connect(iCoverTask, SIGNAL(done()), SLOT(onLoadCoverTaskDone())); + iTaskQueue->submit(iCoverTask); + Q_EMIT loadingCoverChanged(); + } + return iCoverTask != NULL; +} + +void BooksBook::cancelCoverRequest() +{ + if (iCoverTask) { + iCoverTask->release(this); + iCoverTask = NULL; + } +} + +bool BooksBook::coverTaskDone() +{ + HASSERT(sender() == iCoverTask); + HASSERT(!iCoverTasksDone); + HDEBUG(iTitle << iCoverTask->hasImage()); + const bool gotCover = iCoverTask->hasImage(); + if (gotCover) { + iCoverImage = iCoverTask->iCoverImage; + Q_EMIT coverImageChanged(); + } + iCoverTask->release(this); + iCoverTask = NULL; + return gotCover; +} + +void BooksBook::onLoadCoverTaskDone() +{ + HDEBUG(iTitle); + const bool coverMissing = iCoverTask->iCoverMissing; + if (coverTaskDone() || coverMissing) { + iCoverTasksDone = true; + Q_EMIT loadingCoverChanged(); + } else { + iCoverTask = new GuessCoverTask(iStorage, iBook); + connect(iCoverTask, SIGNAL(done()), SLOT(onGuessCoverTaskDone())); + iTaskQueue->submit(iCoverTask); + } +} + +void BooksBook::onGuessCoverTaskDone() +{ + HDEBUG(iTitle); + coverTaskDone(); + iCoverTasksDone = true; + Q_EMIT loadingCoverChanged(); +} + +void BooksBook::saveState() +{ + if (!iStateFilePath.isEmpty()) { + QVariantMap state; + HarbourJson::load(iStateFilePath, state); + state.insert(BOOK_STATE_POSITION, iLastPos.toVariant()); + if (HarbourJson::save(iStateFilePath, state)) { + HDEBUG("wrote" << iStateFilePath); + } + } +} + +void BooksBook::deleteFiles() +{ + if (iStorage.isValid()) { + if (QFile::remove(iPath)) { + HDEBUG("deleted" << qPrintable(iPath)); + } else { + HWARN("failed to delete" << qPrintable(iPath)); + } + QDirIterator it(iStorage.configDir()); + while (it.hasNext()) { + QString path(it.next()); + if (it.fileName().startsWith(iFileName)) { + if (QFile::remove(path)) { + HDEBUG(qPrintable(path)); + } else { + HWARN("failed to delete" << qPrintable(path)); + } + } + } + } +} diff --git a/app/src/BooksBook.h b/app/src/BooksBook.h new file mode 100644 index 0000000..9af3afa --- /dev/null +++ b/app/src/BooksBook.h @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_BOOK_H +#define BOOKS_BOOK_H + +#include "BooksItem.h" +#include "BooksPos.h" +#include "BooksStorage.h" +#include "BooksSaveTimer.h" +#include "BooksTaskQueue.h" + +#include "ZLImageManager.h" +#include "formats/FormatPlugin.h" +#include "library/Book.h" + +#include +#include + +class BooksBook : public QObject, public BooksItem +{ + class CoverTask; + class LoadCoverTask; + class GuessCoverTask; + class CoverPaintContext; + + Q_OBJECT + Q_PROPERTY(QString path READ path CONSTANT) + Q_PROPERTY(QString name READ title CONSTANT) + Q_PROPERTY(QString title READ title CONSTANT) + Q_PROPERTY(QString authors READ authors CONSTANT) + Q_PROPERTY(BooksBook* book READ book CONSTANT) + Q_PROPERTY(bool accessible READ accessible NOTIFY accessibleChanged) + Q_PROPERTY(bool loadingCover READ loadingCover NOTIFY loadingCoverChanged) + Q_PROPERTY(bool copyingOut READ copyingOut NOTIFY copyingOutChanged) + +public: + BooksBook(QObject* aParent = NULL); + BooksBook(const BooksStorage& aStorage, shared_ptr aBook); + ~BooksBook(); + + QString path() const { return iPath; } + QString title() const { return iTitle; } + QString authors() const { return iAuthors; } + BooksPos lastPos() const { return iLastPos; } + void setLastPos(const BooksPos& aPos); + shared_ptr bookRef() const { return iBook; } + bool accessible() const { return !iCopyingOut; } + bool copyingOut() const { return iCopyingOut; } + bool loadingCover() const { return !iCoverTasksDone; } + bool hasCoverImage() const; + bool requestCoverImage(); + void cancelCoverRequest(); + void setCoverImage(QImage aImage); + QImage coverImage(); + + void setCopyingOut(bool aValue); + void deleteFiles(); + + // BooksListItem + virtual BooksItem* retain(); + virtual void release(); + virtual QObject* object(); + virtual BooksShelf* shelf(); + virtual BooksBook* book(); + virtual QString name() const; + virtual QString fileName() const; + +Q_SIGNALS: + void coverImageChanged(); + void loadingCoverChanged(); + void accessibleChanged(); + void copyingOutChanged(); + void movedAway(); + +private Q_SLOTS: + void onLoadCoverTaskDone(); + void onGuessCoverTaskDone(); + void saveState(); + +private: + void init(); + bool coverTaskDone(); + +private: + QAtomicInt iRef; + BooksPos iLastPos; + BooksStorage iStorage; + shared_ptr iBook; + QImage iCoverImage; + shared_ptr iFormatPlugin; + shared_ptr iTaskQueue; + BooksSaveTimer* iSaveTimer; + CoverTask* iCoverTask; + bool iCoverTasksDone; + bool iCopyingOut; + QString iTitle; + QString iAuthors; + QString iFileName; + QString iPath; + QString iStateFilePath; +}; + +QML_DECLARE_TYPE(BooksBook) + +#endif // BOOKS_BOOK_H diff --git a/app/src/BooksBookModel.cpp b/app/src/BooksBookModel.cpp new file mode 100644 index 0000000..f5f08f0 --- /dev/null +++ b/app/src/BooksBookModel.cpp @@ -0,0 +1,543 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksBookModel.h" +#include "BooksTextStyle.h" + +#include "HarbourDebug.h" + +#include "ZLTextHyphenator.h" + +// ========================================================================== +// BooksBookModel::Data +// ========================================================================== +class BooksBookModel::Data { +public: + Data(int aWidth, int aHeight) : iWidth(aWidth), iHeight(aHeight) {} + + int pickPage(const BooksPos& aPagePos) const; + int pickPage(const BooksPos& aPagePos, const BooksPos& aNextPagePos, + int aPageCount) const; + +public: + int iWidth; + int iHeight; + shared_ptr iBookModel; + BooksPos::List iPageMarks; +}; + +int BooksBookModel::Data::pickPage(const BooksPos& aPagePos) const +{ + int page = 0; + if (aPagePos.valid()) { + BooksPos::ConstIterator it = qFind(iPageMarks, aPagePos); + if (it == iPageMarks.end()) { + it = qUpperBound(iPageMarks, aPagePos); + page = (int)(it - iPageMarks.begin()) - 1; + HDEBUG("using page" << page << "for" << aPagePos); + } else { + page = it - iPageMarks.begin(); + HDEBUG("found" << aPagePos << "at page" << page); + } + } + return page; +} + +int BooksBookModel::Data::pickPage(const BooksPos& aPagePos, + const BooksPos& aNextPagePos, int aPageCount) const +{ + int page = 0; + if (aPagePos.valid()) { + if (!aNextPagePos.valid()) { + // Last page stays the last + page = iPageMarks.count() - 1; + HDEBUG("last page" << page); + } else { + BooksPos::ConstIterator it = qFind(iPageMarks, aPagePos); + if (it == iPageMarks.end()) { + // Two 90-degrees rotations should return the reader + // back to the same page. That's what this is about. + if (iPageMarks.count() > aPageCount) { + it = qUpperBound(iPageMarks, aPagePos); + page = (int)(it - iPageMarks.begin()) - 1; + HDEBUG("using page" << page << "for" << aPagePos); + } else { + it = qLowerBound(iPageMarks, aNextPagePos); + page = (int)(it - iPageMarks.begin()); + if (page > 0) page--; + HDEBUG("using page" << page << "for" << aNextPagePos); + } + } else { + page = it - iPageMarks.begin(); + HDEBUG("found" << aPagePos << "at page" << page); + } + } + } + return page; +} + +// ========================================================================== +// BooksBookModel::Task +// ========================================================================== + +class BooksBookModel::Task : public BooksTask +{ + Q_OBJECT + +public: + Task(BooksBookModel* aReceiver, shared_ptr aBook, + const BooksPos& aPagePos, const BooksPos& aNextPagePos, + const BooksPos& aLastPos, int aPageCount); + ~Task(); + + void performTask(); + +Q_SIGNALS: + void progress(int aProgress); + +public: + shared_ptr iBook; + shared_ptr iTextStyle; + BooksMargins iMargins; + BooksPaintContext iPaint; + BooksBookModel::Data* iData; + BooksPos iPagePos; + BooksPos iNextPagePos; + BooksPos iLastPos; + int iOldPageCount; + int iPage; +}; + +BooksBookModel::Task::Task(BooksBookModel* aModel, + shared_ptr aBook, const BooksPos& aPagePos, + const BooksPos& aNextPagePos, const BooksPos& aLastPos, int aPageCount) : + iBook(aBook), + iTextStyle(aModel->textStyle()), + iMargins(aModel->margins()), + iPaint(aModel->width(), aModel->height()), + iData(NULL), + iPagePos(aPagePos), + iNextPagePos(aNextPagePos), + iLastPos(aLastPos), + iOldPageCount(aPageCount), + iPage(-1) +{ + aModel->connect(this, SIGNAL(done()), SLOT(onResetDone())); + aModel->connect(this, SIGNAL(progress(int)), SLOT(onResetProgress(int)), + Qt::QueuedConnection); +} + +BooksBookModel::Task::~Task() +{ + delete iData; +} + +void BooksBookModel::Task::performTask() +{ + if (!isCanceled()) { + iData = new BooksBookModel::Data(iPaint.width(), iPaint.height()); + iData->iBookModel = new BookModel(iBook); + shared_ptr model(iData->iBookModel->bookTextModel()); + ZLTextHyphenator::Instance().load(iBook->language()); + if (!isCanceled()) { + BooksTextView view(iPaint, iTextStyle, iMargins); + view.setModel(model); + if (model->paragraphsNumber() > 0) { + BooksPos mark = view.rewind(); + iData->iPageMarks.append(mark); + Q_EMIT progress(iData->iPageMarks.count()); + while (!isCanceled() && view.nextPage()) { + mark = view.position(); + iData->iPageMarks.append(mark); + Q_EMIT progress(iData->iPageMarks.count()); + } + } + } + } + if (!isCanceled()) { + HDEBUG(iData->iPageMarks.count() << "page(s)" << qPrintable( + QString("%1x%2").arg(iData->iWidth).arg(iData->iHeight))); + iPage = iPagePos.valid() ? + iData->pickPage(iPagePos, iNextPagePos, iOldPageCount) : + iData->pickPage(iLastPos); + } else { + HDEBUG("giving up" << qPrintable(QString("%1x%2").arg(iPaint.width()). + arg(iPaint.height())) << "paging"); + } +} + +// ========================================================================== +// BooksBookModel +// ========================================================================== + +enum BooksBookModelRole { + BooksBookModelPageIndex = Qt::UserRole +}; + +BooksBookModel::BooksBookModel(QObject* aParent) : + QAbstractListModel(aParent), + iCurrentPage(-1), + iProgress(0), + iBook(NULL), + iSettings(NULL), + iTask(NULL), + iData(NULL), + iData2(NULL), + iTaskQueue(BooksTaskQueue::instance()), + iTextStyle(BooksTextStyle::defaults()) +{ + HDEBUG("created"); +#if QT_VERSION < 0x050000 + setRoleNames(roleNames()); +#endif +} + +BooksBookModel::~BooksBookModel() +{ + if (iTask) iTask->release(this); + delete iData; + delete iData2; + HDEBUG("destroyed"); +} + +void BooksBookModel::setBook(BooksBook* aBook) +{ + shared_ptr oldBook; + shared_ptr newBook; + if (iBook != aBook) { + const QString oldTitle(iTitle); + if (iBook) iBook->release(); + if (aBook) { + (iBook = aBook)->retain(); + iBookRef = newBook; + iTitle = iBook->title(); + HDEBUG(iTitle); + } else { + iBook = NULL; + iBookRef.reset(); + iTitle = QString(); + HDEBUG(""); + } + startReset(); + if (oldTitle != iTitle) { + Q_EMIT titleChanged(); + } + Q_EMIT bookModelChanged(); + Q_EMIT bookChanged(); + } +} + +bool BooksBookModel::loading() const +{ + return (iTask != NULL); +} + +void BooksBookModel::setSettings(BooksSettings* aSettings) +{ + if (iSettings != aSettings) { + shared_ptr oldTextStyle(iTextStyle); + if (iSettings) iSettings->disconnect(this); + iSettings = aSettings; + if (iSettings) { + iTextStyle = iSettings->textStyle(); + connect(iSettings, SIGNAL(textStyleChanged()), SLOT(onTextStyleChanged())); + } else { + iTextStyle = BooksTextStyle::defaults(); + } + if (!BooksTextStyle::equalLayout(oldTextStyle, iTextStyle)) { + startReset(); + } + Q_EMIT settingsChanged(); + } +} + +void BooksBookModel::setCurrentPage(int aPage) +{ + if (iCurrentPage != aPage) { + iCurrentPage = aPage; + if (iData && + iCurrentPage >= 0 && + iCurrentPage < iData->iPageMarks.count()) { + iBook->setLastPos(iData->iPageMarks.at(iCurrentPage)); + HDEBUG(aPage << iBook->lastPos()); + } else { + HDEBUG(aPage); + } + Q_EMIT currentPageChanged(); + } +} + +int BooksBookModel::pageCount() const +{ + return iData ? iData->iPageMarks.count() : 0; +} + +BooksPos::List BooksBookModel::pageMarks() const +{ + return iData ? iData->iPageMarks : BooksPos::List(); +} + +BooksPos BooksBookModel::pageMark(int aPage) const +{ + if (aPage >= 0 && iData) { + const int n = iData->iPageMarks.count(); + if (aPage < n) { + return iData->iPageMarks.at(aPage); + } + } + return BooksPos(); +} + +shared_ptr BooksBookModel::bookModel() const +{ + return iData ? iData->iBookModel : NULL; +} + +shared_ptr BooksBookModel::bookTextModel() const +{ + shared_ptr model; + if (iData && !iData->iBookModel.isNull()) { + model = iData->iBookModel->bookTextModel(); + } + return model; +} + +shared_ptr BooksBookModel::contentsModel() const +{ + shared_ptr model; + if (iData && !iData->iBookModel.isNull()) { + model = iData->iBookModel->contentsModel(); + } + return model; +} + +void BooksBookModel::setLeftMargin(int aMargin) +{ + if (iMargins.iLeft != aMargin) { + iMargins.iLeft = aMargin; + HDEBUG(aMargin); + startReset(); + Q_EMIT leftMarginChanged(); + } +} + +void BooksBookModel::setRightMargin(int aMargin) +{ + if (iMargins.iRight != aMargin) { + iMargins.iRight = aMargin; + HDEBUG(aMargin); + startReset(); + Q_EMIT rightMarginChanged(); + } +} + +void BooksBookModel::setTopMargin(int aMargin) +{ + if (iMargins.iTop != aMargin) { + iMargins.iTop = aMargin; + HDEBUG(aMargin); + startReset(); + Q_EMIT topMarginChanged(); + } +} + +void BooksBookModel::setBottomMargin(int aMargin) +{ + if (iMargins.iBottom != aMargin) { + iMargins.iBottom = aMargin; + HDEBUG(aMargin); + startReset(); + Q_EMIT bottomMarginChanged(); + } +} + +void BooksBookModel::updateModel(int aPrevPageCount) +{ + const int newPageCount = pageCount(); + if (aPrevPageCount != newPageCount) { + if (newPageCount > aPrevPageCount) { + beginInsertRows(QModelIndex(), aPrevPageCount, newPageCount-1); + endInsertRows(); + } else { + beginRemoveRows(QModelIndex(), newPageCount, aPrevPageCount-1); + endRemoveRows(); + } + Q_EMIT pageCountChanged(); + } +} + +void BooksBookModel::setSize(QSize aSize) +{ + if (iSize != aSize) { + iSize = aSize; + const int w = width(); + const int h = height(); + HDEBUG(aSize); + if (iData && iData->iWidth == w && iData->iHeight == h) { + HDEBUG("size didn't change"); + } else if (iData2 && iData2->iWidth == w && iData2->iHeight == h) { + HDEBUG("switching to backup layout"); + const int oldPageCount(pageCount()); + BooksPos page1 = pageMark(iCurrentPage); + BooksPos page2 = pageMark(iCurrentPage+1); + Data* tmp = iData; + iData = iData2; + iData2 = tmp; + if (iData) { + // Cancel unnecessary paging task + if (iTask) { + iTask->release(this); + iTask = NULL; + } + updateModel(oldPageCount); + Q_EMIT pageMarksChanged(); + Q_EMIT jumpToPage(iData->pickPage(page1, page2, oldPageCount)); + } else { + startReset(false); + } + } else { + startReset(false); + } + Q_EMIT sizeChanged(); + } +} + +void BooksBookModel::onTextStyleChanged() +{ + HDEBUG(iTitle); + iTextStyle = iSettings->textStyle(); + startReset(); +} + +void BooksBookModel::startReset(bool aFullReset) +{ + BooksLoadingSignalBlocker block(this); + const BooksPos thisPage = pageMark(iCurrentPage); + const BooksPos nextPage = pageMark(iCurrentPage+1); + if (iTask) { + iTask->release(this); + iTask = NULL; + } + const int oldPageCount(pageCount()); + if (oldPageCount > 0) { + beginResetModel(); + } + + delete iData2; + if (aFullReset) { + delete iData; + iData2 = NULL; + } else { + iData2 = iData; + } + iData = NULL; + + if (iBook && width() > 0 && height() > 0) { + iTask = new Task(this, iBook->bookRef(), thisPage, nextPage, + iBook->lastPos(), oldPageCount); + iTaskQueue->submit(iTask); + } + + if (oldPageCount > 0) { + endResetModel(); + Q_EMIT pageMarksChanged(); + Q_EMIT pageCountChanged(); + } + + if (iCurrentPage != 0) { + iCurrentPage = 0; + Q_EMIT currentPageChanged(); + } + + if (iProgress != 0) { + iProgress = 0; + Q_EMIT progressChanged(); + } +} + +void BooksBookModel::onResetProgress(int aProgress) +{ + if (iTask && aProgress > iProgress) { + iProgress = aProgress; + Q_EMIT progressChanged(); + } +} + +void BooksBookModel::onResetDone() +{ + HASSERT(sender() == iTask); + HASSERT(iTask->iData); + HASSERT(!iData); + + const int oldPageCount(pageCount()); + shared_ptr oldBookModel(bookModel()); + BooksLoadingSignalBlocker block(this); + int page = iTask->iPage; + + iData = iTask->iData; + iTask->iData = NULL; + iTask->release(this); + iTask = NULL; + + updateModel(oldPageCount); + Q_EMIT jumpToPage(page); + Q_EMIT pageMarksChanged(); + if (oldBookModel != bookModel()) { + Q_EMIT bookModelChanged(); + } +} + +QHash BooksBookModel::roleNames() const +{ + QHash roles; + roles.insert(BooksBookModelPageIndex, "pageIndex"); + return roles; +} + +int BooksBookModel::rowCount(const QModelIndex&) const +{ + return pageCount(); +} + +QVariant BooksBookModel::data(const QModelIndex& aIndex, int aRole) const +{ + const int i = aIndex.row(); + if (i >= 0 && i < pageCount()) { + switch (aRole) { + case BooksBookModelPageIndex: return i; + } + } + return QVariant(); +} + +#include "BooksBookModel.moc" diff --git a/app/src/BooksBookModel.h b/app/src/BooksBookModel.h new file mode 100644 index 0000000..7288720 --- /dev/null +++ b/app/src/BooksBookModel.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_BOOK_MODEL_H +#define BOOKS_BOOK_MODEL_H + +#include "BooksBook.h" +#include "BooksTask.h" +#include "BooksTaskQueue.h" +#include "BooksTextView.h" +#include "BooksSettings.h" +#include "BooksPos.h" +#include "BooksPaintContext.h" +#include "BooksLoadingProperty.h" + +#include "ZLTextStyle.h" +#include "bookmodel/BookModel.h" + +#include +#include +#include +#include +#include +#include +#include + +class BooksBookModel: public QAbstractListModel, private BooksLoadingProperty +{ + Q_OBJECT + Q_PROPERTY(QString title READ title NOTIFY titleChanged) + Q_PROPERTY(QSize size READ size WRITE setSize NOTIFY sizeChanged) + Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) + Q_PROPERTY(int progress READ progress NOTIFY progressChanged) + Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged) + Q_PROPERTY(int currentPage READ currentPage WRITE setCurrentPage NOTIFY currentPageChanged) + Q_PROPERTY(int leftMargin READ leftMargin WRITE setLeftMargin NOTIFY leftMarginChanged) + Q_PROPERTY(int rightMargin READ rightMargin WRITE setRightMargin NOTIFY rightMarginChanged) + Q_PROPERTY(int topMargin READ topMargin WRITE setTopMargin NOTIFY topMarginChanged) + Q_PROPERTY(int bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY bottomMarginChanged) + Q_PROPERTY(BooksBook* book READ book WRITE setBook NOTIFY bookChanged) + Q_PROPERTY(BooksSettings* settings READ settings WRITE setSettings NOTIFY settingsChanged) + +public: + explicit BooksBookModel(QObject* aParent = NULL); + ~BooksBookModel(); + + bool loading() const; + int pageCount() const; + int progress() const { return iProgress; } + QString title() const { return iTitle; } + int width() const { return iSize.width(); } + int height() const { return iSize.height(); } + + QSize size() const { return iSize; } + void setSize(QSize aSize); + + int currentPage() const { return iCurrentPage; } + void setCurrentPage(int aPage); + + BooksBook* book() const { return iBook; } + void setBook(BooksBook* aBook); + + BooksSettings* settings() const { return iSettings; } + void setSettings(BooksSettings* aModel); + + int leftMargin() const { return iMargins.iLeft; } + int rightMargin() const { return iMargins.iRight; } + int topMargin() const { return iMargins.iTop; } + int bottomMargin() const { return iMargins.iBottom; } + + void setLeftMargin(int aMargin); + void setRightMargin(int aMargin); + void setTopMargin(int aMargin); + void setBottomMargin(int aMargin); + + BooksPos::List pageMarks() const; + BooksPos pageMark(int aPage) const; + BooksMargins margins() const { return iMargins; } + shared_ptr bookRef() const { return iBookRef; } + shared_ptr bookModel() const; + shared_ptr bookTextModel() const; + shared_ptr contentsModel() const; + shared_ptr textStyle() const { return iTextStyle; } + + // QAbstractListModel + virtual QHash roleNames() const; + virtual int rowCount(const QModelIndex& aParent) const; + virtual QVariant data(const QModelIndex& aIndex, int aRole) const; + +private: + void updateSize(); + void updateModel(int aPrevPageCount); + void startReset(bool aFullReset = true); + +private Q_SLOTS: + void onResetProgress(int aProgress); + void onResetDone(); + void onTextStyleChanged(); + +Q_SIGNALS: + void sizeChanged(); + void bookChanged(); + void bookModelChanged(); + void titleChanged(); + void loadingChanged(); + void pageCountChanged(); + void pageMarksChanged(); + void progressChanged(); + void currentPageChanged(); + void settingsChanged(); + void leftMarginChanged(); + void rightMarginChanged(); + void topMarginChanged(); + void bottomMarginChanged(); + void jumpToPage(int index); + +private: + class Data; + class Task; + + int iCurrentPage; + int iProgress; + QSize iSize; + QString iTitle; + BooksMargins iMargins; + BooksBook* iBook; + shared_ptr iBookRef; + BooksSettings* iSettings; + Task* iTask; + Data* iData; + Data* iData2; + shared_ptr iTaskQueue; + shared_ptr iTextStyle; +}; + +QML_DECLARE_TYPE(BooksBookModel) + +#endif // BOOKS_BOOK_MODEL_H diff --git a/app/src/BooksConfig.cpp b/app/src/BooksConfig.cpp new file mode 100644 index 0000000..71b2952 --- /dev/null +++ b/app/src/BooksConfig.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksConfig.h" + +#include "HarbourDebug.h" + +// ========================================================================== +// BooksConfig::Private +// ========================================================================== + +class BooksConfig::Private +{ +public: +}; + +// ========================================================================== +// BooksConfig +// ========================================================================== + +BooksConfig::BooksConfig() : iPrivate(new Private) +{ +} + +BooksConfig::~BooksConfig() +{ + delete iPrivate; +} + +void +BooksConfig::listOptionNames( + const std::string& aGroup, + std::vector& names) +{ + HDEBUG(aGroup.c_str()); +} + +void +BooksConfig::listOptionGroups( + std::vector& aGroups) +{ + HDEBUG("sorry..."); +} + +void +BooksConfig::removeGroup( + const std::string& aName) +{ + HDEBUG(aName.c_str()); +} + +const std::string& +BooksConfig::getDefaultValue( + const std::string& aGroup, + const std::string& aName, + const std::string& aDefault) const +{ + HDEBUG(aGroup.c_str() << aName.c_str() << "(" << aDefault.c_str() << ")"); + return aDefault; +} + +const std::string& +BooksConfig::getValue( + const std::string& aGroup, + const std::string& aName, + const std::string& aDefault) const +{ + HDEBUG(aGroup.c_str() << aName.c_str() << "(" << aDefault.c_str() << ")"); + return aDefault; +} + +void +BooksConfig::setValue( + const std::string& aGroup, + const std::string& aName, + const std::string& aValue, + const std::string& aCategory) +{ + HDEBUG(aGroup.c_str() << aName.c_str() << aValue.c_str() << aCategory.c_str()); +} + +void +BooksConfig::unsetValue( + const std::string& aGroup, + const std::string& aName) +{ + HDEBUG(aGroup.c_str() << aName.c_str()); +} + +bool BooksConfig::isAutoSavingSupported() const +{ + HDEBUG("NO"); + return false; +} + +void BooksConfig::startAutoSave(int aSeconds) +{ + HDEBUG(aSeconds); +} + +// ========================================================================== +// BooksConfigManager +// ========================================================================== + +BooksConfigManager::BooksConfigManager() +{ + HASSERT(!ourInstance); + HASSERT(!ourIsInitialised); + ourInstance = this; +} + +BooksConfigManager::~BooksConfigManager() +{ + HASSERT(ourInstance == this); + if (ourInstance == this) { + ourInstance = NULL; + ourIsInitialised = false; + } +} + +ZLConfig* BooksConfigManager::createConfig() const +{ + ZLConfig* config = new BooksConfig(); + ourIsInitialised = true; + return config; +} diff --git a/app/src/BooksConfig.h b/app/src/BooksConfig.h new file mode 100644 index 0000000..11e6be1 --- /dev/null +++ b/app/src/BooksConfig.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_CONFIG_H +#define BOOKS_CONFIG_H + +#include "BooksTypes.h" +#include + +class BooksConfigManager : public ZLConfigManager { +public: + BooksConfigManager(); + ~BooksConfigManager(); + ZLConfig* createConfig() const; +}; + +class BooksConfig : public ZLConfig +{ +public: + BooksConfig(); + ~BooksConfig(); + + // ZLConfig + void listOptionNames(const std::string& aGroup, std::vector& names); + void listOptionGroups(std::vector& aGroups); + void removeGroup(const std::string& aName); + + const std::string& getDefaultValue(const std::string& aGroup, const std::string& aName, const std::string& aDefault) const; + const std::string& getValue(const std::string& aGroup, const std::string& aName, const std::string& aDefault) const; + void setValue(const std::string& aGroup, const std::string& aName, const std::string& aValue, const std::string& aCategory); + void unsetValue(const std::string& aGroup, const std::string& aName); + + bool isAutoSavingSupported() const; + void startAutoSave(int aSeconds); + +private: + void updateRenderType(); + +private: + class Private; + Private* iPrivate; +}; + +#endif // BOOKS_CONFIG_H diff --git a/app/src/BooksCoverModel.cpp b/app/src/BooksCoverModel.cpp new file mode 100644 index 0000000..6dd6cf3 --- /dev/null +++ b/app/src/BooksCoverModel.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksCoverModel.h" +#include "BooksShelf.h" +#include "BooksBook.h" + +#include "HarbourDebug.h" + +BooksCoverModel::BooksCoverModel(QObject* aParent) : + QSortFilterProxyModel(aParent) +{ + connect(this, SIGNAL(sourceModelChanged()), SIGNAL(sourceChanged())); +} + +bool BooksCoverModel::filterAcceptsRow(int aRow, const QModelIndex& aParent) const +{ + BooksShelf* shelf = qobject_cast(sourceModel()); + if (shelf) { + BooksBook* book = qobject_cast(shelf->get(aRow)); + if (book) { + return book->hasCoverImage(); + } + } + return true; +} + +int BooksCoverModel::count() const +{ + return rowCount(QModelIndex()); +} + +void BooksCoverModel::setSource(QObject* aSrc) +{ + QAbstractItemModel* oldM = sourceModel(); + if (aSrc != oldM) { + const int oldCount = count(); + if (oldM) { + BooksShelf* oldShelf = qobject_cast(oldM); + if (oldShelf) { + const int n = oldShelf->count(); + for (int i=0; ibookAt(i)); + } + } + oldM->disconnect(this); + } + if (aSrc) { + QAbstractItemModel* newM = qobject_cast(aSrc); + if (newM) { + setSourceModel(newM); + BooksShelf* newShelf = qobject_cast(newM); + if (newShelf) { + const int n = newShelf->count(); + for (int i=0; ibookAt(i)); + } + connect(newShelf, + SIGNAL(bookAdded(BooksBook*)), + SLOT(onBookAdded(BooksBook*))); + connect(newShelf, + SIGNAL(bookRemoved(BooksBook*)), + SLOT(onBookRemoved(BooksBook*))); + } + connect(newM, + SIGNAL(rowsInserted(QModelIndex,int,int)), + SIGNAL(countChanged())); + connect(newM, + SIGNAL(rowsRemoved(QModelIndex,int,int)), + SIGNAL(countChanged())); + connect(newM, + SIGNAL(modelReset()), + SIGNAL(countChanged())); + } else { + HDEBUG("unexpected source" << aSrc); + setSourceModel(NULL); + } + } else { + setSourceModel(NULL); + } + if (oldCount != count()) { + Q_EMIT countChanged(); + } + } +} + +void BooksCoverModel::onCoverImageChanged() +{ + beginResetModel(); + endResetModel(); + Q_EMIT countChanged(); +} + +void BooksCoverModel::onBookAdded(BooksBook* aBook) +{ + if (aBook) { + connect(aBook, + SIGNAL(coverImageChanged()), + SLOT(onCoverImageChanged())); + } +} + +void BooksCoverModel::onBookRemoved(BooksBook* aBook) +{ + if (aBook) { + aBook->disconnect(this); + } +} diff --git a/app/src/BooksCoverModel.h b/app/src/BooksCoverModel.h new file mode 100644 index 0000000..5791ca3 --- /dev/null +++ b/app/src/BooksCoverModel.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_COVER_MODEL_H +#define BOOKS_COVER_MODEL_H + +#include "BooksBook.h" + +#include +#include + +class BooksCoverModel: public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QObject* source READ sourceModel WRITE setSource NOTIFY sourceChanged) + +public: + explicit BooksCoverModel(QObject* aParent = NULL); + + int count() const; + void setSource(QObject* aSource); + bool filterAcceptsRow(int aRow, const QModelIndex& aParent) const; + +Q_SIGNALS: + void countChanged(); + void sourceChanged(); + +private Q_SLOTS: + void onCoverImageChanged(); + void onBookAdded(BooksBook* aBook); + void onBookRemoved(BooksBook* aBook); +}; + +QML_DECLARE_TYPE(BooksCoverModel) + +#endif // BOOKS_COVER_MODEL_H diff --git a/app/src/BooksCoverWidget.cpp b/app/src/BooksCoverWidget.cpp new file mode 100644 index 0000000..9fcfb20 --- /dev/null +++ b/app/src/BooksCoverWidget.cpp @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksCoverWidget.h" + +#include "HarbourDebug.h" + +#include "ZLibrary.h" + +#include + +// ========================================================================== +// BooksCoverWidget::ScaleTask +// ========================================================================== + +class BooksCoverWidget::ScaleTask : public BooksTask +{ +public: + ScaleTask(QImage aImage, int aWidth, int aHeight, bool aStretch); + static QImage scale(QImage aImage, int aWidth, int aHeight, bool aStretch); + void performTask(); + +public: + QImage iImage; + QImage iScaledImage; + int iWidth; + int iHeight; + bool iStretch; +}; + +BooksCoverWidget::ScaleTask::ScaleTask(QImage aImage, int aWidth, int aHeight, + bool aStretch) : + iImage(aImage), + iWidth(aWidth), + iHeight(aHeight), + iStretch(aStretch) +{ +} + +QImage BooksCoverWidget::ScaleTask::scale(QImage aImage, + int aWidth, int aHeight, bool aStretch) +{ + if (aStretch){ + return aImage.scaled(aWidth, aHeight, Qt::KeepAspectRatioByExpanding, + Qt::SmoothTransformation); + } else { + return (aWidth*aImage.height() > aImage.width()*aHeight) ? + aImage.scaledToHeight(aHeight, Qt::SmoothTransformation) : + aImage.scaledToWidth(aWidth, Qt::SmoothTransformation); + } +} + +void BooksCoverWidget::ScaleTask::performTask() +{ + if (!iImage.isNull() && !isCanceled()) { + iScaledImage = scale(iImage, iWidth, iHeight, iStretch); + } +} + +// ========================================================================== +// BooksCoverWidget::DefaultImage +// ========================================================================== + +// Image shared by all items in the bookshelf view +class BooksCoverWidget::DefaultImage +{ +public: + static QImage scaled(int aWidth, int aHeight); + static QImage* retain(); + static void release(QImage* aImage); + +private: + static const char* iImageName; + static QImage* iImage; + static QImage* iScaledImage; + static int iRefCount; + static bool iMissing; +}; + +const char* BooksCoverWidget::DefaultImage::iImageName = "default-cover.jpg"; +QImage* BooksCoverWidget::DefaultImage::iImage = NULL; +QImage* BooksCoverWidget::DefaultImage::iScaledImage = NULL; +int BooksCoverWidget::DefaultImage::iRefCount = 0; +bool BooksCoverWidget::DefaultImage::iMissing = false; + +QImage* BooksCoverWidget::DefaultImage::retain() +{ + if (!iImage && !iMissing) { + QString path(QString::fromStdString( + ZLibrary::DefaultFilesPathPrefix() + iImageName)); + iImage = new QImage(path); + if (iImage->isNull() || !iImage->width() || !iImage->height()) { + HWARN("Failed to load" << qPrintable(path)); + delete iImage; + iImage = NULL; + iMissing = true; + } else { + HDEBUG("loaded" << qPrintable(path)); + } + } + if (iImage) { + iRefCount++; + } + return iImage; +} + +QImage BooksCoverWidget::DefaultImage::scaled(int aWidth, int aHeight) +{ + QImage scaled; + HASSERT(iImage); + if (iImage) { + const int iw = iImage->width(); + const int ih = iImage->height(); + if (aWidth*ih > iw*aHeight) { + // Scaling to height + if (iScaledImage && iScaledImage->height() != aHeight) { + delete iScaledImage; + iScaledImage = NULL; + } + if (iScaledImage) { + scaled = *iScaledImage; + } else { + HDEBUG("scaling to height" << aHeight); + scaled = iImage->scaledToHeight(aHeight, + Qt::SmoothTransformation); + iScaledImage = new QImage(scaled); + } + } else { + // Scaling to width + if (iScaledImage && iScaledImage->width() != aWidth) { + delete iScaledImage; + iScaledImage = NULL; + } + if (iScaledImage) { + scaled = *iScaledImage; + } else { + HDEBUG("scaling to width" << aHeight); + scaled = iImage->scaledToWidth(aWidth, + Qt::SmoothTransformation); + iScaledImage = new QImage(scaled); + } + } + } + return scaled; +} + +void BooksCoverWidget::DefaultImage::release(QImage* aImage) +{ + if (aImage) { + HASSERT(aImage == iImage); + if (!(--iRefCount)) { + HDEBUG("deleting cached image"); + if (iImage) { + delete iImage; + iImage = NULL; + } + if (iScaledImage) { + delete iScaledImage; + iScaledImage = NULL; + } + } + } +} + +// ========================================================================== +// BooksViewWidget +// ========================================================================== + +BooksCoverWidget::BooksCoverWidget(QQuickItem* aParent) : + QQuickPaintedItem(aParent), + iTaskQueue(BooksTaskQueue::instance()), + iScaleTask(NULL), + iBook(NULL), + iDefaultImage(NULL), + iBorderWidth(0), + iBorderRadius(0), + iBorderColor(Qt::transparent), + iStretch(false), + iSynchronous(false) +{ + setFlag(ItemHasContents, true); + setFillColor(Qt::transparent); + connect(this, SIGNAL(widthChanged()), SLOT(onSizeChanged())); + connect(this, SIGNAL(heightChanged()), SLOT(onSizeChanged())); +} + +BooksCoverWidget::~BooksCoverWidget() +{ + HDEBUG(iTitle); + DefaultImage::release(iDefaultImage); + if (iScaleTask) iScaleTask->release(this); + if (iBook) iBook->release(); +} + +void BooksCoverWidget::setBook(BooksBook* aBook) +{ + if (iBook != aBook) { + const bool wasEmpty(empty()); + const bool wasLoading = loading(); + if (iBook) { + iBook->disconnect(this); + iBook->release(); + } + if (aBook) { + (iBook = aBook)->retain(); + iBook->requestCoverImage(); + iBookRef = iBook->bookRef(); + iCoverImage = iBook->coverImage(); + iTitle = iBook->title(); + connect(iBook, + SIGNAL(loadingCoverChanged()), + SIGNAL(loadingChanged())); + connect(iBook, + SIGNAL(coverImageChanged()), + SLOT(onCoverImageChanged())); + HDEBUG(iTitle); + } else { + iBook = NULL; + iBookRef.reset(); + iCoverImage = QImage(); + iTitle.clear(); + HDEBUG(""); + } + scaleImage(wasEmpty); + Q_EMIT bookChanged(); + if (wasLoading != loading()) { + Q_EMIT loadingChanged(); + } + } +} + +void BooksCoverWidget::onCoverImageChanged() +{ + HDEBUG(iTitle); + const bool wasEmpty(empty()); + iCoverImage = iBook->coverImage(); + scaleImage(wasEmpty); +} + +void BooksCoverWidget::setStretch(bool aValue) +{ + if (iStretch != aValue) { + iStretch = aValue; + HDEBUG(aValue); + scaleImage(); + Q_EMIT stretchChanged(); + } +} + +void BooksCoverWidget::setSynchronous(bool aValue) +{ + if (iSynchronous != aValue) { + iSynchronous = aValue; + HDEBUG(aValue); + Q_EMIT synchronousChanged(); + } +} + +void BooksCoverWidget::setBorderWidth(qreal aWidth) +{ + if (iBorderWidth != aWidth && aWidth >= 0) { + iBorderWidth = aWidth; + HDEBUG(iBorderWidth); + update(); + Q_EMIT borderWidthChanged(); + } +} + +void BooksCoverWidget::setBorderRadius(qreal aRadius) +{ + if (iBorderRadius != aRadius && aRadius >= 0) { + iBorderRadius = aRadius; + HDEBUG(iBorderRadius); + update(); + Q_EMIT borderRadiusChanged(); + } +} + +void BooksCoverWidget::setBorderColor(QColor aColor) +{ + if (iBorderColor != aColor) { + iBorderColor = aColor; + HDEBUG(iBorderColor); + update(); + Q_EMIT borderColorChanged(); + } +} + +void BooksCoverWidget::setDefaultCover(QUrl aUrl) +{ + if (iDefaultCover != aUrl) { + iDefaultCover = aUrl; + HDEBUG(iDefaultCover); + scaleImage(); + Q_EMIT defaultCoverChanged(); + } +} + +void BooksCoverWidget::onSizeChanged() +{ + scaleImage(); +} + +bool BooksCoverWidget::empty() const +{ + return !iBook || !iBook->hasCoverImage() || + iScaleTask || iScaledImage.isNull(); +} + +bool BooksCoverWidget::loading() const +{ + return iBook && iBook->loadingCover(); +} + +void BooksCoverWidget::scaleImage(bool aWasEmpty) +{ + const int w = width(); + const int h = height(); + + iScaledImage = QImage(); + if (iScaleTask) { + iScaleTask->release(this); + iScaleTask = NULL; + } + + if (w > 0 && h > 0) { + if ((!iBook || !iBook->hasCoverImage()) && iDefaultCover.isValid()) { + QString path(iDefaultCover.toLocalFile()); + if (!iCoverImage.load(path)) { + HWARN("Failed to load" << qPrintable(path)); + } + } + if (iCoverImage.isNull()) { + if (!iDefaultImage) iDefaultImage = DefaultImage::retain(); + if (iDefaultImage) iCoverImage = *iDefaultImage; + } + + if (!iCoverImage.isNull()) { + if (iSynchronous) { + iScaledImage = ScaleTask::scale(iCoverImage, w, h, iStretch); + update(); + } else { + iScaledImage = QImage(); + iScaleTask = new ScaleTask(iCoverImage, w, h, iStretch); + connect(iScaleTask, SIGNAL(done()), SLOT(onScaleTaskDone())); + iTaskQueue->submit(iScaleTask); + } + } else { + update(); + } + } + + if (aWasEmpty != empty()) { + Q_EMIT emptyChanged(); + } +} + +void BooksCoverWidget::onScaleTaskDone() +{ + const bool wasEmpty(empty()); + HASSERT(iScaleTask == sender()); + iScaledImage = iScaleTask->iScaledImage; + iScaleTask->release(this); + iScaleTask = NULL; + update(); + if (wasEmpty != empty()) { + Q_EMIT emptyChanged(); + } +} + +void BooksCoverWidget::paint(QPainter* aPainter) +{ + const qreal w = width(); + const qreal h = height(); + if (w > 0 && h > 0) { + qreal sw, sh; + if (!iScaledImage.isNull()) { + sw = iScaledImage.width(); + sh = iScaledImage.height(); + } else { + sw = w; + sh = h; + } + + const int x = (w - sw)/2; + const int y = h - sh; + + QPainterPath path; + qreal w1, h1, x1, y1; + + if (iBorderRadius > 0) { + // The border rectangle is no less that 3*radius + // and no more than the size of the item. + const qreal d = 2*iBorderRadius; + w1 = qMin(w, qMax(sw, 2*d)) - iBorderWidth; + h1 = qMin(h, qMax(sh, 3*d)) - iBorderWidth; + x1 = (w - w1)/2; + y1 = h - h1 - iBorderWidth/2; + + const qreal x2 = x1 + w1 - d; + const qreal y2 = y1 + h1 - d; + path.arcMoveTo(x1, y1, d, d, 180); + path.arcTo(x1, y1, d, d, 180, -90); + path.arcTo(x2, y1, d, d, 90, -90); + path.arcTo(x2, y2, d, d, 0, -90); + path.arcTo(x1, y2, d, d, 270, -90); + path.closeSubpath(); + aPainter->setClipPath(path); + } else { + w1 = sw - iBorderWidth; + h1 = sh - iBorderWidth; + x1 = (w - w1)/2; + y1 = h - h1 - iBorderWidth/2; + } + + if (!iScaledImage.isNull()) { + aPainter->drawImage(x, y, iScaledImage); + } + + if (iBorderColor.alpha() && iBorderWidth > 0) { + aPainter->setRenderHint(QPainter::Antialiasing); + aPainter->setRenderHint(QPainter::HighQualityAntialiasing); + aPainter->setBrush(Qt::NoBrush); + aPainter->setPen(QPen(iBorderColor, iBorderWidth)); + if (iBorderRadius > 0) { + aPainter->setClipping(false); + aPainter->drawPath(path); + } else { + aPainter->drawRect(x1, y1, w1, h1); + } + } + } +} diff --git a/app/src/BooksCoverWidget.h b/app/src/BooksCoverWidget.h new file mode 100644 index 0000000..bc9529e --- /dev/null +++ b/app/src/BooksCoverWidget.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_COVER_WIDGET_H +#define BOOKS_COVER_WIDGET_H + +#include "BooksTypes.h" +#include "BooksTask.h" +#include "BooksTaskQueue.h" +#include "BooksBook.h" + +#include "ZLImage.h" +#include "image/ZLQtImageManager.h" + +#include +#include + +class BooksCoverWidget: public QQuickPaintedItem +{ + Q_OBJECT + Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged) + Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) + Q_PROPERTY(bool stretch READ stretch WRITE setStretch NOTIFY stretchChanged) + Q_PROPERTY(bool synchronous READ synchronous WRITE setSynchronous NOTIFY synchronousChanged) + Q_PROPERTY(qreal borderWidth READ borderWidth WRITE setBorderWidth NOTIFY borderWidthChanged) + Q_PROPERTY(qreal borderRadius READ borderRadius WRITE setBorderRadius NOTIFY borderRadiusChanged) + Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor NOTIFY borderColorChanged) + Q_PROPERTY(QUrl defaultCover READ defaultCover WRITE setDefaultCover NOTIFY defaultCoverChanged) + Q_PROPERTY(BooksBook* book READ book WRITE setBook NOTIFY bookChanged) + +public: + BooksCoverWidget(QQuickItem* aParent = NULL); + ~BooksCoverWidget(); + + bool empty() const; + bool loading() const; + + qreal borderWidth() const { return iBorderRadius; } + void setBorderWidth(qreal aWidth); + + qreal borderRadius() const { return iBorderRadius; } + void setBorderRadius(qreal aRadius); + + QColor borderColor() const { return iBorderColor; } + void setBorderColor(QColor aColor); + + QUrl defaultCover() const { return iDefaultCover; } + void setDefaultCover(QUrl aUrl); + + BooksBook* book() const { return iBook; } + void setBook(BooksBook* aBook); + + bool stretch() const { return iStretch; } + void setStretch(bool aValue); + + bool synchronous() const { return iSynchronous; } + void setSynchronous(bool aValue); + +Q_SIGNALS: + void bookChanged(); + void emptyChanged(); + void loadingChanged(); + void stretchChanged(); + void synchronousChanged(); + void borderWidthChanged(); + void borderRadiusChanged(); + void borderColorChanged(); + void defaultCoverChanged(); + +private Q_SLOTS: + void onCoverImageChanged(); + void onSizeChanged(); + void onScaleTaskDone(); + +private: + void paint(QPainter *painter); + void scaleImage(bool aWasEmpty); + void scaleImage() { scaleImage(empty()); } + +private: + class ScaleTask; + class DefaultImage; + shared_ptr iTaskQueue; + ScaleTask* iScaleTask; + QImage iScaledImage; + QImage iCoverImage; + shared_ptr iBookRef; + BooksBook* iBook; + QImage* iDefaultImage; + qreal iBorderWidth; + qreal iBorderRadius; + QColor iBorderColor; + QUrl iDefaultCover; + QString iTitle; + bool iStretch; + bool iSynchronous; +}; + +#endif // BOOKS_COVER_WIDGET_H diff --git a/app/src/BooksDefs.h b/app/src/BooksDefs.h new file mode 100644 index 0000000..cca95c6 --- /dev/null +++ b/app/src/BooksDefs.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_DEFS_H +#define BOOKS_DEFS_H + +#include + +#define BOOKS_APP_NAME "harbour-books" +#define BOOKS_DATA_ROOT "usr/share/" BOOKS_APP_NAME +#define BOOKS_QML_DIR BOOKS_DATA_ROOT "/qml" +#define BOOKS_ICONS_DIR BOOKS_DATA_ROOT "/icons" +#define BOOKS_DATA_DIR BOOKS_DATA_ROOT "/data" +#define BOOKS_QML_FILE BOOKS_QML_DIR "/BooksMain.qml" + +#define BOOKS_ROOT_SHELF_DIR "Books" + +#define BOOKS_QML_PLUGIN "harbour.books" +#define BOOKS_QML_PLUGIN_V1 1 +#define BOOKS_QML_PLUGIN_V2 0 +#define BOOKS_QML_REGISTER(klass,name) \ + qmlRegisterType(BOOKS_QML_PLUGIN, BOOKS_QML_PLUGIN_V1, \ + BOOKS_QML_PLUGIN_V2, name) + +#define BOOKS_STATE_FILE_SUFFIX ".state" + +#if defined(__i386__) +# define BOOKS_PPI (330) // Tablet 1536x2048 +#elif defined(__arm__) +# define BOOKS_PPI (245) // Jolla1 540x960 +#else +# error Unexpected architechture +#endif + +#endif // BOOKS_DEFS_H diff --git a/app/src/BooksDialogManager.cpp b/app/src/BooksDialogManager.cpp new file mode 100644 index 0000000..67a7d4b --- /dev/null +++ b/app/src/BooksDialogManager.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksDialogManager.h" + +#include "HarbourDebug.h" + +#include "ZLDialog.h" +#include "ZLOptionsDialog.h" +#include "ZLProgressDialog.h" +#include "image/ZLQtImageManager.h" + +#include +#include + +void BooksDialogManager::createApplicationWindow(ZLApplication* aApp) const +{ + HDEBUG("THIS IS NOT SUPPOSED TO HAPPEN!"); +} + +shared_ptr +BooksDialogManager::createDialog( + const ZLResourceKey& aKey) const +{ + HDEBUG(aKey.Name.c_str()); + return NULL; +} + +shared_ptr +BooksDialogManager::createOptionsDialog( + const ZLResourceKey& aKey, + shared_ptr aApplyAction, + bool aApplyButton) const +{ + HDEBUG(aKey.Name.c_str()); + return NULL; +} + +shared_ptr +BooksDialogManager::createOpenFileDialog( + const ZLResourceKey& aKey, + const std::string& aDirPath, + const std::string& aFilePath, + const ZLOpenFileDialog::Filter& aFilter) const +{ + HDEBUG(aKey.Name.c_str()); + return NULL; +} + +void +BooksDialogManager::informationBox( + const std::string& title, + const std::string& message) const +{ + HDEBUG(QString::fromStdString(title) << message.c_str()); +} + +void +BooksDialogManager::errorBox( + const ZLResourceKey& key, + const std::string& message) const +{ + HDEBUG(QString::fromStdString(key.Name) << message.c_str()); +} + +int +BooksDialogManager::questionBox( + const ZLResourceKey& key, + const std::string& message, + const ZLResourceKey& button0, + const ZLResourceKey& button1, + const ZLResourceKey& button2) const +{ + HDEBUG(QString::fromStdString(key.Name) << message.c_str()); + return -1; +} + +shared_ptr +BooksDialogManager::createProgressDialog( + const ZLResourceKey& aKey) const +{ + HDEBUG(aKey.Name.c_str()); + return NULL; +} + +bool +BooksDialogManager::isClipboardSupported( + ClipboardType aType) const +{ + return true; +} + +void +BooksDialogManager::setClipboardText( + const std::string& text, + ClipboardType type) const +{ + if (!text.empty()) { + QGuiApplication::clipboard()->setText(QString::fromStdString(text), + (type == CLIPBOARD_MAIN) ? QClipboard::Clipboard : QClipboard::Selection); + } +} + +void +BooksDialogManager::setClipboardImage( + const ZLImageData &imageData, + ClipboardType type) const +{ + QGuiApplication::clipboard()->setImage( + *static_cast(imageData).image(), + (type == CLIPBOARD_MAIN) ? QClipboard::Clipboard : QClipboard::Selection); +} diff --git a/app/src/BooksDialogManager.h b/app/src/BooksDialogManager.h new file mode 100644 index 0000000..65f6137 --- /dev/null +++ b/app/src/BooksDialogManager.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_DIALOG_MANAGER_H +#define BOOKS_DIALOG_MANAGER_H + +#include + +class BooksDialogManager: public ZLDialogManager { + +public: + static void createInstance() { ourInstance = new BooksDialogManager(); } + +private: + BooksDialogManager() {} + +public: + static const ZLResource& resource(const ZLResourceKey& aKey) + { return ZLDialogManager::resource()[aKey]; } + + void createApplicationWindow(ZLApplication *application) const; + + shared_ptr createDialog(const ZLResourceKey &key) const; + shared_ptr createOptionsDialog(const ZLResourceKey &key, shared_ptr applyAction, bool showApplyButton) const; + shared_ptr createOpenFileDialog(const ZLResourceKey &key, const std::string &directoryPath, const std::string &filePath, const ZLOpenFileDialog::Filter &filter) const; + void informationBox(const std::string &title, const std::string &message) const; + void errorBox(const ZLResourceKey &key, const std::string &message) const; + int questionBox(const ZLResourceKey &key, const std::string &message, const ZLResourceKey &button0, const ZLResourceKey &button1, const ZLResourceKey &button2) const; + shared_ptr createProgressDialog(const ZLResourceKey &key) const; + + bool isClipboardSupported(ClipboardType type) const; + void setClipboardText(const std::string &text, ClipboardType type) const; + void setClipboardImage(const ZLImageData &imageData, ClipboardType type) const; +}; + +#endif /* BOOKS_DIALOG_MANAGER_H */ diff --git a/app/src/BooksImportModel.cpp b/app/src/BooksImportModel.cpp new file mode 100644 index 0000000..01b5dc7 --- /dev/null +++ b/app/src/BooksImportModel.cpp @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksImportModel.h" +#include "BooksStorage.h" +#include "BooksTask.h" + +#include "HarbourDebug.h" + +#include +#include + +enum BooksImportRole { + BooksImportRoleTitle = Qt::UserRole, + BooksImportRoleBook, + BooksImportRolePath, + BooksImportRoleFileName, + BooksImportRoleSelected +}; + +// ========================================================================== +// BooksImportModel::Data +// ========================================================================== + +class BooksImportModel::Data { +public: + Data(BooksBook* iBook); + ~Data(); + + QString title() { return iBook->title(); } + QString path() { return iBook->path(); } + QString fileName() { return iBook->fileName(); } + +public: + BooksBook* iBook; + bool iSelected; +}; + +BooksImportModel::Data::Data(BooksBook* aBook) : + iBook(aBook), + iSelected(false) +{ + iBook->retain(); +} + +BooksImportModel::Data::~Data() +{ + iBook->release(); +} + +// ========================================================================== +// BooksImportModel::Task +// ========================================================================== + +class BooksImportModel::Task : public BooksTask +{ + Q_OBJECT + +public: + Task(QString aDest) : iDestDir(aDest), iBufSize(0x1000), iBuf(NULL) {} + ~Task(); + + void performTask(); + void scanDir(QDir aDir); + bool isDuplicate(QString aPath, QFileInfoList aFileList); + QByteArray calculateFileHash(QString aPath); + QByteArray getFileHash(QString aPath); + +Q_SIGNALS: + void bookFound(BooksBook* aBook) const; + +public: + QList iBooks; + QHash iFileHash; + QHash iHashFile; + QFileInfoList iDestFiles; + QFileInfoList iSrcFiles; + QString iDestDir; + qint64 iBufSize; + char* iBuf; +}; + +BooksImportModel::Task::~Task() +{ + const int n = iBooks.count(); + for (int i=0; irelease(); + delete [] iBuf; +} + +QByteArray BooksImportModel::Task::calculateFileHash(QString aPath) +{ + QByteArray result; + QFile file(aPath); + if (file.open(QIODevice::ReadOnly)) { + qint64 len; + QCryptographicHash hash(QCryptographicHash::Md5); + hash.reset(); + if (!iBuf) iBuf = new char[iBufSize]; + while (!isCanceled() && (len = file.read(iBuf, iBufSize)) > 0) { + hash.addData(iBuf, len); + } + if (len == 0) { + if (!isCanceled()) { + result = hash.result(); + HDEBUG(qPrintable(aPath) << QString(result.toHex())); + } + } else { + HWARN("error reading" << qPrintable(aPath)); + } + file.close(); + } + return result; +} + +QByteArray BooksImportModel::Task::getFileHash(QString aPath) +{ + if (iFileHash.contains(aPath)) { + return iFileHash.value(aPath); + } else { + QByteArray hash = calculateFileHash(aPath); + iFileHash.insert(aPath, hash); + iHashFile.insert(hash, aPath); + return hash; + } +} + +bool BooksImportModel::Task::isDuplicate(QString aPath, QFileInfoList aList) +{ + const int n = aList.count(); + if (n > 0) { + QFileInfo file(aPath); + QByteArray fileHash; + for (int i=0; i book = Book::loadFromFile(file); + if (!book.isNull()) { + if (!isDuplicate(filePath, iDestFiles) && + !isDuplicate(filePath, iSrcFiles)) { + BooksBook* newBook = new BooksBook(BooksStorage(), book); + newBook->moveToThread(thread()); + iBooks.append(newBook); + iSrcFiles.append(fileInfo); + HDEBUG("found" << path.c_str() << newBook->title()); + Q_EMIT bookFound(newBook); + } + } else { + HDEBUG("not a book:" << path.c_str()); + } + } + } + + // Then directories + if (!isCanceled()) { + QFileInfoList dirList = aDir.entryInfoList(QDir::Dirs | + QDir::NoDotAndDotDot | QDir::Readable, QDir::Time); + const int n = dirList.count(); + for (int i=0; irelease(this); +} + +void BooksImportModel::setDestination(QString aDestination) +{ + if (iDestination != aDestination) { + iDestination = aDestination; + HDEBUG(aDestination); + Q_EMIT destinationChanged(); + if (iAutoRefresh) { + if (iTask) { + iTask->release(this); + iTask = NULL; + } + refresh(); + } + } +} + +void BooksImportModel::refresh() +{ + iAutoRefresh = true; + if (!iTask) { + HDEBUG("refreshing the model"); + + if (!iList.isEmpty()) { + beginResetModel(); + qDeleteAll(iList); + iList.clear(); + endInsertRows(); + Q_EMIT countChanged(); + } + + iTask = new Task(iDestination); + connect(iTask, SIGNAL(bookFound(BooksBook*)), + SLOT(onBookFound(BooksBook*)), Qt::QueuedConnection); + connect(iTask, SIGNAL(done()), SLOT(onTaskDone())); + iTaskQueue->submit(iTask); + Q_EMIT busyChanged(); + } +} + +void BooksImportModel::setSelected(int aIndex, bool aSelected) +{ + if (validIndex(aIndex)) { + Data* data = iList.at(aIndex); + if (data->iSelected != aSelected) { + HDEBUG(data->path() << aSelected); + if (data->iSelected) iSelectedCount--; + if (aSelected) iSelectedCount++; + data->iSelected = aSelected; + + QModelIndex index(createIndex(aIndex, 0)); + Q_EMIT dataChanged(index, index, iSelectedRole); + Q_EMIT selectedCountChanged(); + } + } +} + +QObject* BooksImportModel::selectedBook(int aIndex) +{ + const int n = iList.count(); + for (int i=0, k=0; iiSelected) { + if (k == aIndex) { + return data->iBook; + } + k++; + } + } + return NULL; +} + +void BooksImportModel::onBookFound(BooksBook* aBook) +{ + if (iTask) { + // When we find the first book, we add two items. The second item + // is the "virtual" that will stay at the end of the list and will + // be removed by onTaskDone() after scanning is finished. The idea + // is to show the busy indicator at the end of the list (that's how + // QML represents the dummy item) while we keep on scanning. + const int n1 = iList.count(); + beginInsertRows(QModelIndex(), n1, n1 ? n1 : 1); + iList.append(new Data(aBook)); + endInsertRows(); + Q_EMIT countChanged(); + } +} + +void BooksImportModel::onTaskDone() +{ + HASSERT(iTask); + HASSERT(iTask == sender()); + iTask->release(this); + iTask = NULL; + if (iList.count() > 0) { + // Remove the "virtual" item at the end of the list + beginRemoveRows(QModelIndex(),iList.count(), iList.count()); + endRemoveRows(); + } + Q_EMIT busyChanged(); +} + +QHash BooksImportModel::roleNames() const +{ + QHash roles; + roles.insert(BooksImportRoleTitle, "title"); + roles.insert(BooksImportRoleBook, "book"); + roles.insert(BooksImportRolePath, "path"); + roles.insert(BooksImportRoleFileName, "fileName"); + roles.insert(BooksImportRoleSelected, "selected"); + return roles; +} + +int BooksImportModel::rowCount(const QModelIndex&) const +{ + return iTask ? (iList.count() + 1) : iList.count(); +} + +QVariant BooksImportModel::data(const QModelIndex& aIndex, int aRole) const +{ + const int i = aIndex.row(); + if (validIndex(i)) { + Data* data = iList.at(i); + switch (aRole) { + case BooksImportRoleTitle: return data->title(); + case BooksImportRoleBook: return QVariant::fromValue(data->iBook); + case BooksImportRolePath: return data->path(); + case BooksImportRoleFileName: return data->fileName(); + case BooksImportRoleSelected: return data->iSelected; + } + } else if (i == iList.count()) { + switch (aRole) { + case BooksImportRoleTitle: + case BooksImportRolePath: + case BooksImportRoleFileName: return QString(); + case BooksImportRoleBook: return QVariant::fromValue((QObject*)NULL); + case BooksImportRoleSelected: return false; + } + } + return QVariant(); +} + +#include "BooksImportModel.moc" diff --git a/app/src/BooksImportModel.h b/app/src/BooksImportModel.h new file mode 100644 index 0000000..95f2d2e --- /dev/null +++ b/app/src/BooksImportModel.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_IMPORT_MODEL_H +#define BOOKS_IMPORT_MODEL_H + +#include "BooksBook.h" +#include "BooksTaskQueue.h" + +#include +#include +#include +#include +#include + +class BooksImportModel: public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(int selectedCount READ selectedCount NOTIFY selectedCountChanged) + Q_PROPERTY(QString destination READ destination WRITE setDestination NOTIFY destinationChanged) + +public: + explicit BooksImportModel(QObject* aParent = NULL); + ~BooksImportModel(); + + bool busy() const { return iTask != NULL; } + int count() const { return iList.count(); } + int selectedCount() const { return iSelectedCount; } + QString destination() const { return iDestination; } + void setDestination(QString aDestination); + + Q_INVOKABLE void refresh(); + Q_INVOKABLE void setSelected(int aIndex, bool aSelected); + Q_INVOKABLE QObject* selectedBook(int aIndex); + + // QAbstractListModel + virtual QHash roleNames() const; + virtual int rowCount(const QModelIndex& aParent) const; + virtual QVariant data(const QModelIndex& aIndex, int aRole) const; + +Q_SIGNALS: + void countChanged(); + void busyChanged(); + void selectedCountChanged(); + void destinationChanged(); + +private Q_SLOTS: + void onBookFound(BooksBook* aBook); + void onTaskDone(); + +private: + bool validIndex(int aIndex) const; + +private: + class Task; + class Data; + QString iDestination; + QList iList; + QVector iSelectedRole; + int iSelectedCount; + bool iAutoRefresh; + shared_ptr iTaskQueue; + Task* iTask; +}; + +QML_DECLARE_TYPE(BooksImportModel) + +inline bool BooksImportModel::validIndex(int aIndex) const + { return aIndex >= 0 && aIndex < iList.count(); } + +#endif // BOOKS_IMPORT_MODEL_H diff --git a/app/src/BooksItem.h b/app/src/BooksItem.h new file mode 100644 index 0000000..9b4ef89 --- /dev/null +++ b/app/src/BooksItem.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_ITEM_H +#define BOOKS_ITEM_H + +#include "BooksTypes.h" + +#include +#include + +class BooksItem +{ +public: + virtual ~BooksItem() {} + + virtual BooksItem* retain() = 0; + virtual void release() = 0; + + virtual QObject* object() = 0; + virtual BooksShelf* shelf() = 0; + virtual BooksBook* book() = 0; + virtual QString name() const = 0; + virtual QString fileName() const = 0; +}; + +#endif // BOOKS_ITEM_H diff --git a/app/src/BooksListWatcher.cpp b/app/src/BooksListWatcher.cpp new file mode 100644 index 0000000..90df81e --- /dev/null +++ b/app/src/BooksListWatcher.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksListWatcher.h" + +#include "HarbourDebug.h" + +#define LISTVIEW_CONTENT_X "contentX" +#define LISTVIEW_CONTENT_Y "contentY" +#define LISTVIEW_INDEX_AT "indexAt" + +BooksListWatcher::BooksListWatcher(QObject* aParent) : + QObject(aParent), + iCurrentIndex(0), + iContentX(0), + iContentY(0), + iListView(NULL), + iResizeTimer(new QTimer(this)) +{ + iResizeTimer->setSingleShot(true); + iResizeTimer->setInterval(0); + connect(iResizeTimer, SIGNAL(timeout()), SLOT(onResizeTimeout())); +} + +void BooksListWatcher::setListView(QQuickItem* aView) +{ + if (iListView != aView) { + const QSize oldSize(iSize); + if (iListView) iListView->disconnect(this); + iListView = aView; + if (iListView) { + connect(iListView, + SIGNAL(widthChanged()), + SLOT(onWidthChanged())); + connect(iListView, + SIGNAL(heightChanged()), + SLOT(onHeightChanged())); + connect(iListView, + SIGNAL(contentXChanged()), + SLOT(onContentXChanged())); + connect(iListView, + SIGNAL(contentYChanged()), + SLOT(onContentYChanged())); + connect(iListView, + SIGNAL(contentWidthChanged()), + SLOT(onContentSizeChanged())); + connect(iListView, + SIGNAL(contentHeightChanged()), + SLOT(onContentSizeChanged())); + iContentX = contentX(); + iContentY = contentY(); + updateCurrentIndex(); + } else { + iContentX = iContentY = 0; + iSize = QSize(0,0); + } + Q_EMIT listViewChanged(); + if (oldSize != iSize) { + Q_EMIT sizeChanged(); + } + if (oldSize.width() != iSize.width()) { + Q_EMIT widthChanged(); + } + if (oldSize.height() != iSize.height()) { + Q_EMIT heightChanged(); + } + } +} + +qreal BooksListWatcher::getRealProperty(const char *name, qreal defaultValue) +{ + QVariant value = iListView->property(name); + bool ok = false; + if (value.isValid()) { + ok = false; + qreal r = value.toReal(&ok); + if (ok) return r; + } + return defaultValue; +} + +qreal BooksListWatcher::contentX() +{ + return getRealProperty(LISTVIEW_CONTENT_X); +} + +qreal BooksListWatcher::contentY() +{ + return getRealProperty(LISTVIEW_CONTENT_Y); +} + +void BooksListWatcher::updateCurrentIndex() +{ + int index = -1; + if (QMetaObject::invokeMethod(iListView, LISTVIEW_INDEX_AT, + Q_RETURN_ARG(int,index), Q_ARG(qreal,iContentX+width()/2), + Q_ARG(qreal,iContentY+height()/2))) { + if (iCurrentIndex != index) { + HDEBUG(index); + iCurrentIndex = index; + Q_EMIT currentIndexChanged(); + } + } +} + +void BooksListWatcher::updateSize() +{ + const QSize size(iListView->width(), iListView->height()); + HDEBUG(size); + if (iSize != size) { + const QSize oldSize(iSize); + iSize = size; + Q_EMIT sizeChanged(); + if (oldSize.width() != iSize.width()) { + Q_EMIT widthChanged(); + } + if (oldSize.height() != iSize.height()) { + Q_EMIT heightChanged(); + } + } +} + +void BooksListWatcher::onWidthChanged() +{ + HASSERT(sender() == iListView); + HDEBUG(iListView->width()); + // Width change will probably be followed by height change + iResizeTimer->start(); +} + +void BooksListWatcher::onHeightChanged() +{ + HASSERT(sender() == iListView); + HDEBUG(iListView->height()); + if (iResizeTimer->isActive()) { + // Height is usually changed after width + iResizeTimer->stop(); + updateSize(); + } else { + iResizeTimer->start(); + } +} + +void BooksListWatcher::onResizeTimeout() +{ + // This can only happen if only width or height has changed. Normally, + // width change is followed by height change and view is reset from the + // setHeight() method + updateSize(); +} + +void BooksListWatcher::onContentXChanged() +{ + HASSERT(sender() == iListView); + iContentX = contentX(); + updateCurrentIndex(); +} + +void BooksListWatcher::onContentYChanged() +{ + HASSERT(sender() == iListView); + iContentY = contentY(); + updateCurrentIndex(); +} + +void BooksListWatcher::onContentSizeChanged() +{ + HASSERT(sender() == iListView); + updateCurrentIndex(); +} diff --git a/app/src/BooksListWatcher.h b/app/src/BooksListWatcher.h new file mode 100644 index 0000000..560d257 --- /dev/null +++ b/app/src/BooksListWatcher.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_LIST_WATCHER_H +#define BOOKS_LIST_WATCHER_H + +#include "BooksTypes.h" + +#include +#include + +class BooksListWatcher: public QObject +{ + Q_OBJECT + Q_PROPERTY(int currentIndex READ currentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(QQuickItem* listView READ listView WRITE setListView NOTIFY listViewChanged) + Q_PROPERTY(qreal width READ width NOTIFY widthChanged) + Q_PROPERTY(qreal height READ height NOTIFY heightChanged) + Q_PROPERTY(QSize size READ size NOTIFY sizeChanged) + +public: + explicit BooksListWatcher(QObject* aParent = NULL); + + int currentIndex() const { return iCurrentIndex; } + QSize size() const { return iSize; } + qreal width() const { return iSize.width(); } + qreal height() const { return iSize.height(); } + + QQuickItem* listView() const { return iListView; } + void setListView(QQuickItem* aView); + +private: + qreal contentX(); + qreal contentY(); + qreal getRealProperty(const char *name, qreal defaultValue = 0.0); + void updateCurrentIndex(); + void updateSize(); + +private Q_SLOTS: + void onResizeTimeout(); + void onWidthChanged(); + void onHeightChanged(); + void onContentXChanged(); + void onContentYChanged(); + void onContentSizeChanged(); + +Q_SIGNALS: + void listViewChanged(); + void sizeChanged(); + void widthChanged(); + void heightChanged(); + void currentIndexChanged(); + +private: + QSize iSize; + int iCurrentIndex; + qreal iContentX; + qreal iContentY; + QQuickItem* iListView; + QTimer* iResizeTimer; +}; + +QML_DECLARE_TYPE(BooksListWatcher) + +#endif // BOOKS_LIST_WATCHER_H diff --git a/app/src/BooksLoadingProperty.cpp b/app/src/BooksLoadingProperty.cpp new file mode 100644 index 0000000..bd48e1d --- /dev/null +++ b/app/src/BooksLoadingProperty.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksLoadingProperty.h" + +#include "HarbourDebug.h" + +void BooksLoadingProperty::suspendLoadingStateChanges() +{ + if (!(iChangesBlocked++)) { + iSavedState = loading(); + } +} + +void BooksLoadingProperty::resumeLoadingStateChanges() +{ + HASSERT(iChangesBlocked > 0); + if (!(--iChangesBlocked)) { + if (iSavedState != loading()) { + Q_EMIT loadingChanged(); + } + } +} diff --git a/app/src/BooksLoadingProperty.h b/app/src/BooksLoadingProperty.h new file mode 100644 index 0000000..bb90905 --- /dev/null +++ b/app/src/BooksLoadingProperty.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_LOADING_PROPERTY_H +#define BOOKS_LOADING_PROPERTY_H + +#include "BooksTypes.h" + +class BooksLoadingSignalBlocker; + +class BooksLoadingProperty +{ + friend class BooksLoadingSignalBlocker; + +public: + BooksLoadingProperty() : iChangesBlocked(0), iSavedState(false) {} + + virtual bool loading() const = 0; + virtual void loadingChanged() = 0; + +private: + void suspendLoadingStateChanges(); + void resumeLoadingStateChanges(); + + int iChangesBlocked; + bool iSavedState; +}; + +class BooksLoadingSignalBlocker { +public: + BooksLoadingSignalBlocker(BooksLoadingProperty* aProperty) : iProperty(aProperty) + { iProperty->suspendLoadingStateChanges(); } + ~BooksLoadingSignalBlocker() + { iProperty->resumeLoadingStateChanges(); } +private: + BooksLoadingProperty* iProperty; +}; + +#endif // BOOKS_LOADING_PROPERTY_H diff --git a/app/src/BooksPageWidget.cpp b/app/src/BooksPageWidget.cpp new file mode 100644 index 0000000..c1ff4e8 --- /dev/null +++ b/app/src/BooksPageWidget.cpp @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksPageWidget.h" +#include "BooksTextStyle.h" +#include "BooksDefs.h" + +#include "HarbourDebug.h" + +#include + +// ========================================================================== +// BooksPageWidget::Data +// ========================================================================== + +class BooksPageWidget::Data { +public: + Data(shared_ptr aModel, int aWidth, int aHeight) : + iModel(aModel), iPaintContext(aWidth, aHeight) {} + + bool paint(QPainter* aPainter); + +public: + shared_ptr iView; + shared_ptr iModel; + BooksPaintContext iPaintContext; +}; + +bool BooksPageWidget::Data::paint(QPainter* aPainter) +{ + if (!iView.isNull()) { + iPaintContext.beginPaint(aPainter); + iView->paint(); + iPaintContext.endPaint(); + return true; + } + return false; +} + +// ========================================================================== +// BooksPageWidget::ResetTask +// ========================================================================== + +class BooksPageWidget::ResetTask : public BooksTask +{ +public: + ResetTask(shared_ptr aModel, + shared_ptr aTextStyle, int aWidth, int aHeight, + const BooksMargins& aMargins, const BooksPos& aPosition); + ~ResetTask(); + + void performTask(); + +public: + BooksPageWidget::Data* iData; + shared_ptr iTextStyle; + BooksMargins iMargins; + BooksPos iPosition; +}; + +BooksPageWidget::ResetTask::ResetTask(shared_ptr aModel, + shared_ptr aTextStyle, int aWidth, int aHeight, + const BooksMargins& aMargins, const BooksPos& aPosition) : + iData(new BooksPageWidget::Data(aModel, aWidth, aHeight)), + iTextStyle(aTextStyle), + iMargins(aMargins), + iPosition(aPosition) +{ +} + +BooksPageWidget::ResetTask::~ResetTask() +{ + delete iData; +} + +void BooksPageWidget::ResetTask::performTask() +{ + if (!isCanceled()) { + BooksTextView* view = new BooksTextView(iData->iPaintContext, + iTextStyle, iMargins); + if (!isCanceled()) { + view->setModel(iData->iModel); + if (!isCanceled()) { + view->gotoPosition(iPosition); + if (!isCanceled()) { + iData->iView = view; + } else { + delete view; + } + } + } + } +} + +// ========================================================================== +// BooksPageWidget::RenderTask +// ========================================================================== + +class BooksPageWidget::RenderTask : public BooksTask { +public: + RenderTask(shared_ptr aData, int aWidth, int aHeight) : + iData(aData), iWidth(aWidth), iHeight(aHeight), iImage(NULL) {} + + void performTask(); + +public: + shared_ptr iData; + int iWidth; + int iHeight; + shared_ptr iImage; +}; + +void BooksPageWidget::RenderTask::performTask() +{ + if (!isCanceled() && !iData.isNull() && !iData->iView.isNull() && + iWidth > 0 && iHeight > 0) { + iImage = new QImage(iWidth, iHeight, QImage::Format_RGB32); + if (!isCanceled()) { + QPainter painter(&*iImage); + iData->paint(&painter); + } + } +} + +// ========================================================================== +// BooksPageWidget +// ========================================================================== + +BooksPageWidget::BooksPageWidget(QQuickItem* aParent) : + QQuickPaintedItem(aParent), + iTaskQueue(BooksTaskQueue::instance()), + iTextStyle(BooksTextStyle::defaults()), + iResizeTimer(new QTimer(this)), + iModel(NULL), + iSettings(NULL), + iResetTask(NULL), + iRenderTask(NULL), + iPaintRequest(false), + iEmpty(false), + iPage(-1) +{ + setFlag(ItemHasContents, true); + setFillColor(Qt::white); + iResizeTimer->setSingleShot(true); + iResizeTimer->setInterval(0); + connect(iResizeTimer, SIGNAL(timeout()), SLOT(onResizeTimeout())); + connect(this, SIGNAL(widthChanged()), SLOT(onWidthChanged())); + connect(this, SIGNAL(heightChanged()), SLOT(onHeightChanged())); +} + +BooksPageWidget::~BooksPageWidget() +{ + HDEBUG("page" << iPage); + if (iResetTask) iResetTask->release(this); + if (iRenderTask) iRenderTask->release(this); +} + +void BooksPageWidget::setModel(BooksBookModel* aModel) +{ + if (iModel != aModel) { + if (iModel) { + iModel->disconnect(this); + iModel = NULL; + } + iModel = aModel; + if (iModel) { +#if HARBOUR_DEBUG + if (iPage >= 0) { + HDEBUG(iModel->title() << iPage); + } else { + HDEBUG(iModel->title()); + } +#endif // HARBOUR_DEBUG + iPageMark = iModel->pageMark(iPage); + connect(iModel, SIGNAL(bookModelChanged()), + SLOT(onBookModelChanged())); + connect(iModel, SIGNAL(destroyed()), + SLOT(onBookModelDestroyed())); + connect(iModel, SIGNAL(pageMarksChanged()), + SLOT(onBookModelPageMarksChanged())); + connect(iModel, SIGNAL(loadingChanged()), + SLOT(onBookModelLoadingChanged())); + } else { + iPageMark.invalidate(); + } + resetView(); + Q_EMIT modelChanged(); + } +} + +void BooksPageWidget::setSettings(BooksSettings* aSettings) +{ + if (iSettings != aSettings) { + shared_ptr oldTextStyle(iTextStyle); + if (iSettings) iSettings->disconnect(this); + iSettings = aSettings; + if (iSettings) { + iTextStyle = iSettings->textStyle(); + connect(iSettings, SIGNAL(textStyleChanged()), SLOT(onTextStyleChanged())); + } else { + iTextStyle = BooksTextStyle::defaults(); + } + if (!BooksTextStyle::equalLayout(oldTextStyle, iTextStyle)) { + resetView(); + } + Q_EMIT settingsChanged(); + } +} + +void BooksPageWidget::onTextStyleChanged() +{ + HDEBUG(iPage); + iTextStyle = iSettings->textStyle(); + resetView(); +} + +void BooksPageWidget::onBookModelChanged() +{ + HDEBUG(iModel->title()); + BooksLoadingSignalBlocker block(this); + iPageMark = iModel->pageMark(iPage); + resetView(); +} + +void BooksPageWidget::onBookModelDestroyed() +{ + HDEBUG("model destroyed"); + HASSERT(iModel == sender()); + BooksLoadingSignalBlocker block(this); + iModel = NULL; + Q_EMIT modelChanged(); + resetView(); +} + +void BooksPageWidget::onBookModelPageMarksChanged() +{ + const BooksPos pos = iModel->pageMark(iPage); + if (iPageMark != pos) { + BooksLoadingSignalBlocker block(this); + iPageMark = pos; + HDEBUG("page" << iPage); + resetView(); + } +} + +void BooksPageWidget::onBookModelLoadingChanged() +{ + BooksLoadingSignalBlocker block(this); + if (!iModel->loading()) { + HDEBUG("page" << iPage); + const BooksPos pos = iModel->pageMark(iPage); + if (iPageMark != pos) { + iPageMark = pos; + resetView(); + } + } +} + +void BooksPageWidget::setPage(int aPage) +{ + if (iPage != aPage) { + BooksLoadingSignalBlocker block(this); + iPage = aPage; + HDEBUG(iPage); + const BooksPos pos = iModel->pageMark(iPage); + if (iPageMark != pos) { + iPageMark = pos; + resetView(); + } + resetView(); + Q_EMIT pageChanged(); + } +} + +void BooksPageWidget::setLeftMargin(int aMargin) +{ + if (iMargins.iLeft != aMargin) { + iMargins.iLeft = aMargin; + HDEBUG(aMargin); + resetView(); + Q_EMIT leftMarginChanged(); + } +} + +void BooksPageWidget::setRightMargin(int aMargin) +{ + if (iMargins.iRight != aMargin) { + iMargins.iRight = aMargin; + HDEBUG(aMargin); + resetView(); + Q_EMIT rightMarginChanged(); + } +} + +void BooksPageWidget::setTopMargin(int aMargin) +{ + if (iMargins.iTop != aMargin) { + iMargins.iTop = aMargin; + HDEBUG(aMargin); + resetView(); + Q_EMIT topMarginChanged(); + } +} + +void BooksPageWidget::setBottomMargin(int aMargin) +{ + if (iMargins.iBottom != aMargin) { + iMargins.iBottom = aMargin; + HDEBUG(aMargin); + resetView(); + Q_EMIT bottomMarginChanged(); + } +} + +void BooksPageWidget::paint(QPainter* aPainter) +{ + if (!iImage.isNull()) { + HDEBUG("page" << iPage); + aPainter->drawImage(0, 0, *iImage); + iEmpty = false; + } else if (iPage >= 0 && iPageMark.valid()) { + if (!iPaintRequest) { + iPaintRequest = true; + HDEBUG("page" << iPage << "(scheduled)"); + scheduleRepaint(); + } else { + HDEBUG("page" << iPage << "(not yet ready)"); + } + iEmpty = true; + } else { + HDEBUG("page" << iPage << "(empty)"); + iEmpty = true; + } +} + +bool BooksPageWidget::loading() const +{ + return iPage >= 0 && iResetTask && iRenderTask; +} + +void BooksPageWidget::resetView() +{ + BooksLoadingSignalBlocker block(this); + if (iResetTask) { + iResetTask->release(this); + iResetTask = NULL; + } + iData.reset(); + if (iPage >= 0 && iPageMark.valid() && + width() > 0 && height() > 0 && iModel) { + shared_ptr textModel = iModel->bookTextModel(); + if (!textModel.isNull()) { + iResetTask = new ResetTask(textModel, iTextStyle, + width(), height(), iMargins, iPageMark); + iTaskQueue->submit(iResetTask, this, SLOT(onResetTaskDone())); + cancelRepaint(); + } + } + if (!iResetTask && !iEmpty) { + update(); + } +} + +void BooksPageWidget::cancelRepaint() +{ + BooksLoadingSignalBlocker block(this); + if (iRenderTask) { + iRenderTask->release(this); + iRenderTask = NULL; + } +} + +void BooksPageWidget::scheduleRepaint() +{ + BooksLoadingSignalBlocker block(this); + cancelRepaint(); + if (width() > 0 && height() > 0 && !iData.isNull()) { + iRenderTask = new RenderTask(iData, width(), height()); + iTaskQueue->submit(iRenderTask, this, SLOT(onRenderTaskDone())); + } else { + update(); + } +} + +void BooksPageWidget::onResetTaskDone() +{ + BooksLoadingSignalBlocker block(this); + HASSERT(sender() == iResetTask); + iData = iResetTask->iData; + iResetTask->iData = NULL; + iResetTask->release(this); + iResetTask = NULL; + if (iPaintRequest) { + scheduleRepaint(); + } else { + update(); + } +} + +void BooksPageWidget::onRenderTaskDone() +{ + BooksLoadingSignalBlocker block(this); + HASSERT(sender() == iRenderTask); + iImage = iRenderTask->iImage; + iRenderTask->release(this); + iRenderTask = NULL; + iPaintRequest = false; + update(); +} + +void BooksPageWidget::updateSize() +{ + HDEBUG(QSize(width(), height())); + resetView(); +} + +void BooksPageWidget::onWidthChanged() +{ + const int w = width(); + HDEBUG(w); + // Width change will probably be followed by height change + iResizeTimer->start(); +} + +void BooksPageWidget::onHeightChanged() +{ + const int h = height(); + HDEBUG(h); + if (iResizeTimer->isActive()) { + // Height is usually changed after width, repaint right away + iResizeTimer->stop(); + updateSize(); + } else { + iResizeTimer->start(); + } +} + +void BooksPageWidget::onResizeTimeout() +{ + // This can only happen if only width or height has changed. Normally, + // width change is followed by height change and view is reset from the + // setHeight() method + updateSize(); +} diff --git a/app/src/BooksPageWidget.h b/app/src/BooksPageWidget.h new file mode 100644 index 0000000..c9f34f4 --- /dev/null +++ b/app/src/BooksPageWidget.h @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_PAGE_WIDGET_H +#define BOOKS_PAGE_WIDGET_H + +#include "BooksTypes.h" +#include "BooksTask.h" +#include "BooksTaskQueue.h" +#include "BooksSettings.h" +#include "BooksBookModel.h" +#include "BooksPos.h" +#include "BooksPaintContext.h" +#include "BooksLoadingProperty.h" + +#include "ZLTextStyle.h" + +#include +#include + +class BooksPageWidget: public QQuickPaintedItem, private BooksLoadingProperty +{ + Q_OBJECT + Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) + Q_PROPERTY(int page READ page WRITE setPage NOTIFY pageChanged) + Q_PROPERTY(int leftMargin READ leftMargin WRITE setLeftMargin NOTIFY leftMarginChanged) + Q_PROPERTY(int rightMargin READ rightMargin WRITE setRightMargin NOTIFY rightMarginChanged) + Q_PROPERTY(int topMargin READ topMargin WRITE setTopMargin NOTIFY topMarginChanged) + Q_PROPERTY(int bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY bottomMarginChanged) + Q_PROPERTY(BooksBookModel* model READ model WRITE setModel NOTIFY modelChanged) + Q_PROPERTY(BooksSettings* settings READ settings WRITE setSettings NOTIFY settingsChanged) + +public: + class Data; + + BooksPageWidget(QQuickItem* aParent = NULL); + ~BooksPageWidget(); + + bool loading() const; + + int page() const { return iPage; } + void setPage(int aPage); + + BooksBookModel* model() const { return iModel; } + void setModel(BooksBookModel* aModel); + + BooksSettings* settings() const { return iSettings; } + void setSettings(BooksSettings* aSettings); + + int leftMargin() const { return iMargins.iLeft; } + int rightMargin() const { return iMargins.iRight; } + int topMargin() const { return iMargins.iTop; } + int bottomMargin() const { return iMargins.iBottom; } + + void setLeftMargin(int aMargin); + void setRightMargin(int aMargin); + void setTopMargin(int aMargin); + void setBottomMargin(int aMargin); + + BooksMargins margins() const { return iMargins; } + shared_ptr textStyle() const { return iTextStyle; } + +Q_SIGNALS: + void loadingChanged(); + void pageChanged(); + void modelChanged(); + void settingsChanged(); + void leftMarginChanged(); + void rightMarginChanged(); + void topMarginChanged(); + void bottomMarginChanged(); + +private Q_SLOTS: + void onWidthChanged(); + void onHeightChanged(); + void onResizeTimeout(); + void onBookModelChanged(); + void onBookModelDestroyed(); + void onBookModelPageMarksChanged(); + void onBookModelLoadingChanged(); + void onTextStyleChanged(); + void onResetTaskDone(); + void onRenderTaskDone(); + +private: + void paint(QPainter *painter); + void updateSize(); + void resetView(); + void scheduleRepaint(); + void cancelRepaint(); + +private: + class ResetTask; + class RenderTask; + + shared_ptr iTaskQueue; + shared_ptr iTextStyle; + BooksPos iPageMark; + QTimer* iResizeTimer; + BooksBookModel* iModel; + BooksSettings* iSettings; + BooksMargins iMargins; + shared_ptr iData; + shared_ptr iImage; + ResetTask* iResetTask; + RenderTask* iRenderTask; + bool iPaintRequest; + bool iEmpty; + int iPage; +}; + +#endif // BOOKS_PAGE_WIDGET_H diff --git a/app/src/BooksPaintContext.cpp b/app/src/BooksPaintContext.cpp new file mode 100644 index 0000000..c2a3f11 --- /dev/null +++ b/app/src/BooksPaintContext.cpp @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksPaintContext.h" + +#include "HarbourDebug.h" + +#include "ZLImage.h" +#include "image/ZLQtImageManager.h" + +#include +#include +#include +#include + +static const std::string HELVETICA = "Helvetica"; + +BooksPaintContext::BooksPaintContext() : + iPainter(NULL), iWidth(0), iHeight(0), + iSpaceWidth(0), iDescent(0) +{ +} + +BooksPaintContext::BooksPaintContext(int aWidth, int aHeight) : + iPainter(NULL), iWidth(aWidth), iHeight(aHeight), + iSpaceWidth(0), iDescent(0) +{ +} + +BooksPaintContext::~BooksPaintContext() +{ +} + +void BooksPaintContext::beginPaint(QPainter *aPainter) +{ + iPainter = aPainter; + iPainter->setFont(iFont); +} + +void BooksPaintContext::endPaint() +{ + iPainter = NULL; +} + +void BooksPaintContext::fillFamiliesList(std::vector &families) const +{ + QFontDatabase db; + const QStringList qFamilies = db.families(); + bool helveticaFlag = false; + for (QStringList::ConstIterator it = qFamilies.begin(); it != qFamilies.end(); ++it) { + std::string family = it->toUtf8().constData(); + helveticaFlag |= (family == HELVETICA); + families.push_back(family); + } + if (!helveticaFlag) { + families.push_back(HELVETICA); + } +} + +const std::string BooksPaintContext::realFontFamilyName(std::string &fontFamily) const +{ + QString fullName = QFontInfo(QFont(QString::fromUtf8(fontFamily.c_str()))).family(); + if (fullName.isNull() || fullName.isEmpty()) { + return HELVETICA; + } + return fullName.toStdString(); +} + +void BooksPaintContext::setFont(const std::string& family, int size, bool bold, bool italic) +{ + bool fontChanged = false; + + if (!family.empty()) { + QString qtFamily(QString::fromStdString(family)); + if (iFont.family() != qtFamily) { + iFont.setFamily(qtFamily); + fontChanged = true; + } + } + + if (iFont.pointSize() != size) { + iFont.setPointSize(size); + fontChanged = true; + } + + if ((iFont.weight() != (bold ? QFont::Bold : QFont::Normal))) { + iFont.setWeight(bold ? QFont::Bold : QFont::Normal); + fontChanged = true; + } + + if (iFont.italic() != italic) { + iFont.setItalic(italic); + fontChanged = true; + } + + if (fontChanged) { + QFontMetrics fontMetrics(iFont); + iSpaceWidth = fontMetrics.width(QString(" ")); + iDescent = fontMetrics.descent(); + if (iPainter) { + iPainter->setFont(iFont); + } + } +} + +void BooksPaintContext::setColor(ZLColor color, LineStyle style) +{ + if (iPainter) { + iPainter->setPen(QPen(qtColor(color), 1, (style == SOLID_LINE) ? + Qt::SolidLine : Qt::DashLine)); + } +} + +void BooksPaintContext::setFillColor(ZLColor color, FillStyle style) +{ + if (iPainter) { + iPainter->setBrush(QBrush(qtColor(color), (style == SOLID_FILL) ? + Qt::SolidPattern : Qt::Dense4Pattern)); + } +} + +int BooksPaintContext::stringWidth(const char *str, int len, bool) const +{ + QFontMetrics fontMetrics(iFont); + return fontMetrics.width(QString::fromUtf8(str, len)); +} + +int BooksPaintContext::spaceWidth() const +{ + return iSpaceWidth; +} + +int BooksPaintContext::descent() const +{ + return iDescent; +} + +int BooksPaintContext::stringHeight() const +{ + return iFont.pointSize() + 2; +} + +void BooksPaintContext::drawString(int x, int y, const char* str, int len, bool rtl) +{ + if (iPainter) { + QString qStr = QString::fromUtf8(str, len); + iPainter->setLayoutDirection(rtl ? Qt::RightToLeft : Qt::LeftToRight); + iPainter->drawText(x, y, qStr); + } +} + +void BooksPaintContext::drawImage(int x, int y, const ZLImageData& image) +{ + if (iPainter) { + const QImage* qImage = ((ZLQtImageData&)image).image(); + if (qImage) { + iPainter->drawImage(x, y - image.height(), *qImage); + } + } +} + +void BooksPaintContext::drawImage(int x, int y, const ZLImageData& image, + int width, int height, ScalingType type) +{ + if (iPainter) { + const QImage* qImage = ((ZLQtImageData&)image).image(); + if (qImage) { + const QImage scaled = qImage->scaled( + QSize(imageWidth(image, width, height, type), + imageHeight(image, width, height, type)), + Qt::KeepAspectRatio, + Qt::SmoothTransformation + ); + iPainter->drawImage(x, y - scaled.height(), scaled); + } + } +} + +void BooksPaintContext::drawLine(int x0, int y0, int x1, int y1) +{ + if (iPainter) { + iPainter->drawPoint(x0, y0); + iPainter->drawLine(x0, y0, x1, y1); + iPainter->drawPoint(x1, y1); + } +} + +void BooksPaintContext::fillRectangle(int x0, int y0, int x1, int y1) +{ + if (iPainter) { + if (x1 < x0) { + int tmp = x1; + x1 = x0; + x0 = tmp; + } + if (y1 < y0) { + int tmp = y1; + y1 = y0; + y0 = tmp; + } + iPainter->fillRect(x0, y0, x1-x0+1, y1-y0+1, iPainter->brush()); + } +} + +void BooksPaintContext::drawFilledCircle(int x, int y, int r) +{ + if (iPainter) { + iPainter->drawEllipse(x - r, y - r, 2 * r + 1, 2 * r + 1); + } +} + +void BooksPaintContext::clear(ZLColor aColor) +{ + if (iPainter) { + iPainter->fillRect(0, 0, iWidth, iHeight, qtColor(aColor)); + } +} + +int BooksPaintContext::width() const +{ + return iWidth; +} + +int BooksPaintContext::height() const +{ + return iHeight; +} diff --git a/app/src/BooksPaintContext.h b/app/src/BooksPaintContext.h new file mode 100644 index 0000000..7db4de7 --- /dev/null +++ b/app/src/BooksPaintContext.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_PAINT_CONTEXT_H +#define BOOKS_PAINT_CONTEXT_H + +#include "BooksTypes.h" + +#include "ZLPaintContext.h" + +#include +#include +#include + +class QPainter; + +class BooksPaintContext : public ZLPaintContext { + +public: + BooksPaintContext(int aWidth, int aHeight); + BooksPaintContext(); + ~BooksPaintContext(); + + void setWidth(int aWidth); + void setHeight(int aHeight); + void setSize(int aWidth, int aHeight); + bool isEmpty() const; + QSize size() const; + + // ZLPaintContext + void beginPaint(QPainter* painter); + void endPaint(); + + int width() const; + int height() const; + + void clear(ZLColor color); + + void fillFamiliesList(std::vector& families) const; + const std::string realFontFamilyName(std::string& fontFamily) const; + + void setFont(const std::string& family, int size, bool bold, bool italic); + void setColor(ZLColor color, LineStyle style); + void setFillColor(ZLColor color, FillStyle style); + + int stringWidth(const char* str, int len, bool rtl) const; + int spaceWidth() const; + int stringHeight() const; + int descent() const; + void drawString(int x, int y, const char* str, int len, bool rtl); + + void drawImage(int x, int y, const ZLImageData& image); + void drawImage(int x, int y, const ZLImageData& image, int width, int height, ScalingType type); + + void drawLine(int x0, int y0, int x1, int y1); + void fillRectangle(int x0, int y0, int x1, int y1); + void drawFilledCircle(int x, int y, int r); + +private: + QPainter* iPainter; + int iWidth; + int iHeight; + mutable int iSpaceWidth; + int iDescent; + QFont iFont; +}; + +inline void BooksPaintContext::setWidth(int aWidth) + { iWidth = aWidth; } +inline void BooksPaintContext::setHeight(int aHeight) + { iHeight = aHeight; } +inline void BooksPaintContext::setSize(int aWidth, int aHeight) + { iWidth = aWidth; iHeight = aHeight; } +inline bool BooksPaintContext::isEmpty() const + { return (iWidth <= 0 || iHeight <= 0); } +inline QSize BooksPaintContext::size() const + { return QSize(iWidth, iHeight); } + +inline QColor qtColor(const ZLColor& aColor) + { return QColor(aColor.Red, aColor.Green, aColor.Blue); } + +#endif /* BOOKS_PAINT_CONTEXT_H */ diff --git a/app/src/BooksPos.h b/app/src/BooksPos.h new file mode 100644 index 0000000..2412acd --- /dev/null +++ b/app/src/BooksPos.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_POSITION_H +#define BOOKS_POSITION_H + +#include +#include +#include + + +#include "ZLTextParagraphCursor.h" + +struct BooksPos { + int iParagraphIndex, iElementIndex, iCharIndex; + + typedef QList List; + typedef QList::iterator Iterator; + typedef QList::const_iterator ConstIterator; + + BooksPos() : + iParagraphIndex(-1), + iElementIndex(-1), + iCharIndex(-1) {} + + BooksPos(const ZLTextWordCursor& aCursor) : + iElementIndex(aCursor.elementIndex()), + iCharIndex(aCursor.charIndex()) + { + ZLTextParagraphCursorPtr ptr(aCursor.paragraphCursorPtr()); + iParagraphIndex = ptr.isNull() ? 0 : ptr->index(); + } + + BooksPos(int aParagraphIndex, int aElementIndex, int aCharIndex) : + iParagraphIndex(aParagraphIndex), + iElementIndex(aElementIndex), + iCharIndex(aCharIndex) {} + + BooksPos(const BooksPos& aPos) : + iParagraphIndex(aPos.iParagraphIndex), + iElementIndex(aPos.iElementIndex), + iCharIndex(aPos.iCharIndex) {} + + void invalidate() + { + iParagraphIndex = iElementIndex = iCharIndex = -1; + } + + bool valid() const + { + return iParagraphIndex >= 0 && iElementIndex >= 0 && iCharIndex >= 0; + } + + QVariant toVariant() const + { + QVariantList list; + list.append(iParagraphIndex); + list.append(iElementIndex); + list.append(iCharIndex); + return QVariant::fromValue(list); + } + + static BooksPos fromVariant(QVariant aVariant) + { + if (aVariant.isValid()) { + QVariantList list = aVariant.toList(); + if (list.count() == 3) { + bool ok = false; + int paraIndex = list.at(0).toInt(&ok); + if (ok) { + int elemIndex = list.at(1).toInt(&ok); + if (ok) { + int charIndex = list.at(2).toInt(&ok); + if (ok) { + return BooksPos(paraIndex, elemIndex, charIndex); + } + } + } + } + } + return BooksPos(); + } + + const BooksPos& operator = (const BooksPos& aPos) + { + iParagraphIndex = aPos.iParagraphIndex; + iElementIndex = aPos.iElementIndex; + iCharIndex = aPos.iCharIndex; + return *this; + } + + bool operator < (const BooksPos& aPos) const + { + return (iParagraphIndex < aPos.iParagraphIndex) ? true : + (iParagraphIndex > aPos.iParagraphIndex) ? false : + (iElementIndex < aPos.iElementIndex) ? true : + (iElementIndex > aPos.iElementIndex) ? false : + (iCharIndex < aPos.iCharIndex) ? true : + (iCharIndex > aPos.iCharIndex) ? false : true; + } + + bool operator > (const BooksPos& aPos) const + { + return (aPos < *this); + } + + bool operator <= (const BooksPos& aPos) const + { + return !(*this > aPos); + } + + bool operator >= (const BooksPos& aPos) const + { + return !(*this < aPos); + } + + bool operator == (const BooksPos& aPos) const + { + return iParagraphIndex == aPos.iParagraphIndex && + iElementIndex == aPos.iElementIndex && + iCharIndex == aPos.iCharIndex; + } + + bool operator != (const BooksPos& aPos) const + { + return iParagraphIndex != aPos.iParagraphIndex || + iElementIndex != aPos.iElementIndex || + iCharIndex != aPos.iCharIndex; + } +}; + +inline QDebug& operator<<(QDebug& aDebug, const BooksPos& aPosition) + { aDebug << qPrintable(QString("BooksPos(%1,%2,%3)"). + arg(aPosition.iParagraphIndex).arg(aPosition.iElementIndex). + arg(aPosition.iCharIndex)); return aDebug.maybeSpace(); } + +#endif /* BOOKS_POSITION_H */ diff --git a/app/src/BooksSaveTimer.cpp b/app/src/BooksSaveTimer.cpp new file mode 100644 index 0000000..17f6fb9 --- /dev/null +++ b/app/src/BooksSaveTimer.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksSaveTimer.h" + +#include +#include + +#define BOOKS_DEFAULT_INACTIVITY_TIMEOUT (1000) +#define BOOKS_DEFAULT_MANDATORY_SAVE_TIMEOUT (30000) + +BooksSaveTimer::BooksSaveTimer(QObject* aParent) : + QObject(aParent), + iInactivityTimer(new QTimer(this)), + iMandatarySaveTimer(new QTimer(this)), + iSaveRequested(false) +{ + connect(qApp, SIGNAL(aboutToQuit()), SLOT(onAboutToQuit())); + + iInactivityTimer->setInterval(BOOKS_DEFAULT_INACTIVITY_TIMEOUT); + iInactivityTimer->setSingleShot(true); + connect(iInactivityTimer, SIGNAL(timeout()), SLOT(onTimeout())); + + iMandatarySaveTimer->setInterval(BOOKS_DEFAULT_MANDATORY_SAVE_TIMEOUT); + iMandatarySaveTimer->setSingleShot(true); + connect(iMandatarySaveTimer, SIGNAL(timeout()), SLOT(onTimeout())); +} + +void BooksSaveTimer::requestSave() +{ + if (!iMandatarySaveTimer->isActive()) { + iMandatarySaveTimer->start(); + } + iInactivityTimer->start(); +} + +void BooksSaveTimer::cancelSave() +{ + iMandatarySaveTimer->stop(); + iInactivityTimer->stop(); +} + +bool BooksSaveTimer::saveRequested() const +{ + return iMandatarySaveTimer->isActive(); +} + +void BooksSaveTimer::onTimeout() +{ + iMandatarySaveTimer->stop(); + iInactivityTimer->stop(); + Q_EMIT save(); +} + +void BooksSaveTimer::onAboutToQuit() +{ + if (saveRequested()) { + onTimeout(); + } +} diff --git a/app/src/BooksSaveTimer.h b/app/src/BooksSaveTimer.h new file mode 100644 index 0000000..8cfc859 --- /dev/null +++ b/app/src/BooksSaveTimer.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_SAVE_TIMER_H +#define BOOKS_SAVE_TIMER_H + +#include + +class QTimer; + +class BooksSaveTimer : public QObject +{ + Q_OBJECT + +public: + BooksSaveTimer(QObject* aParent = NULL); + + void requestSave(); + void cancelSave(); + bool saveRequested() const; + +Q_SIGNALS: + void save(); + +private Q_SLOTS: + void onAboutToQuit(); + void onTimeout(); + +private: + QTimer* iInactivityTimer; + QTimer* iMandatarySaveTimer; + bool iSaveRequested; +}; + +#endif // BOOKS_SAVE_TIMER_H diff --git a/app/src/BooksSettings.cpp b/app/src/BooksSettings.cpp new file mode 100644 index 0000000..9cc7c3d --- /dev/null +++ b/app/src/BooksSettings.cpp @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksSettings.h" +#include "BooksTextStyle.h" +#include "BooksBook.h" +#include "BooksDefs.h" +#include "HarbourDebug.h" + +#include + +#define DCONF_PATH "/apps/" BOOKS_APP_NAME "/" +#define KEY_FONT_SIZE "fontSize" +#define KEY_PAGE_DETAILS "pageDetails" +#define KEY_CURRENT_BOOK "currentBook" +#define DEFAULT_FONT_SIZE 0 +#define DEFAULT_PAGE_DETAILS 0 +#define DEFAULT_CURRENT_BOOK QString() + +// ========================================================================== +// BooksSettings::TextStyle +// ========================================================================== + +class BooksSettings::TextStyle : public ZLTextStyle +{ +public: + TextStyle(int aFontSizeModifier) : + iDefaultStyle(BooksTextStyle::defaults()), + iFontSize(iDefaultStyle->fontSize() + 2*aFontSizeModifier) + { HDEBUG(iFontSize); } + + bool isDecorated() const; + + const std::string &fontFamily() const; + + int fontSize() const; + bool bold() const; + bool italic() const; + + const std::string &colorStyle() const; + + short spaceBefore(const ZLTextStyleEntry::Metrics& aMetrics) const; + short spaceAfter(const ZLTextStyleEntry::Metrics& aMetrics) const; + short lineStartIndent(const ZLTextStyleEntry::Metrics& aMetrics, bool aRtl) const; + short lineEndIndent(const ZLTextStyleEntry::Metrics& aMetrics, bool aRtl) const; + short firstLineIndentDelta(const ZLTextStyleEntry::Metrics& aMetrics) const; + int verticalShift() const; + + ZLTextAlignmentType alignment() const; + + double lineSpace() const; + bool allowHyphenations() const; + +private: + shared_ptr iDefaultStyle; + int iFontSize; +}; + +const std::string& +BooksSettings::TextStyle::colorStyle() const +{ + return iDefaultStyle->colorStyle(); +} + +bool +BooksSettings::TextStyle::isDecorated() const +{ + return iDefaultStyle->isDecorated(); +} + +const std::string& +BooksSettings::TextStyle::fontFamily() const +{ + return iDefaultStyle->fontFamily(); +} + +int +BooksSettings::TextStyle::fontSize() const +{ + return iFontSize; +} + +bool +BooksSettings::TextStyle::bold() const +{ + return iDefaultStyle->bold(); +} + +bool +BooksSettings::TextStyle::italic() const +{ + return iDefaultStyle->italic(); +} + +short +BooksSettings::TextStyle::spaceBefore( + const ZLTextStyleEntry::Metrics& aMetrics) const +{ + return iDefaultStyle->spaceBefore(aMetrics); +} + +short +BooksSettings::TextStyle::spaceAfter( + const ZLTextStyleEntry::Metrics& aMetrics) const +{ + return iDefaultStyle->spaceAfter(aMetrics); +} + +short +BooksSettings::TextStyle::lineStartIndent( + const ZLTextStyleEntry::Metrics& aMetrics, + bool aRtl) const +{ + return iDefaultStyle->lineStartIndent(aMetrics, aRtl); +} + +short +BooksSettings::TextStyle::lineEndIndent( + const ZLTextStyleEntry::Metrics& aMetrics, + bool aRtl) const +{ + return iDefaultStyle->lineEndIndent(aMetrics, aRtl); +} + +short +BooksSettings::TextStyle::firstLineIndentDelta( + const ZLTextStyleEntry::Metrics& aMetrics) const +{ + return iDefaultStyle->firstLineIndentDelta(aMetrics); +} + +int +BooksSettings::TextStyle::verticalShift() const +{ + return iDefaultStyle->verticalShift(); +} + +ZLTextAlignmentType +BooksSettings::TextStyle::alignment() const +{ + return iDefaultStyle->alignment(); +} + +double +BooksSettings::TextStyle::lineSpace() const +{ + return iDefaultStyle->lineSpace(); +} + +bool +BooksSettings::TextStyle::allowHyphenations() const +{ + return iDefaultStyle->allowHyphenations(); +} + +// ========================================================================== +// BooksSettings +// ========================================================================== + +BooksSettings::BooksSettings(QObject* aParent) : + QObject(aParent), + iFontSize(new MGConfItem(DCONF_PATH KEY_FONT_SIZE, this)), + iPageDetails(new MGConfItem(DCONF_PATH KEY_PAGE_DETAILS, this)), + iCurrentBookPath(new MGConfItem(DCONF_PATH KEY_CURRENT_BOOK, this)), + iCurrentBook(NULL) +{ + iTextStyle = new TextStyle(fontSize()); + updateCurrentBook(); + connect(iFontSize, SIGNAL(valueChanged()), SLOT(onFontSizeValueChanged())); + connect(iPageDetails, SIGNAL(valueChanged()), SIGNAL(pageDetailsChanged())); + connect(iCurrentBookPath, SIGNAL(valueChanged()), SLOT(onCurrentBookPathChanged())); +} + +int +BooksSettings::fontSize() const +{ + return iFontSize->value(DEFAULT_FONT_SIZE).toInt(); +} + +void +BooksSettings::setFontSize( + int aValue) +{ + HDEBUG(aValue); + iFontSize->set(aValue); +} + +void +BooksSettings::onFontSizeValueChanged() +{ + const int newSize = fontSize(); + HDEBUG(newSize); + iTextStyle = new TextStyle(newSize); + Q_EMIT fontSizeChanged(); + Q_EMIT textStyleChanged(); +} + +int +BooksSettings::pageDetails() const +{ + return iPageDetails->value(DEFAULT_PAGE_DETAILS).toInt(); +} + +void +BooksSettings::setPageDetails( + int aValue) +{ + HDEBUG(aValue); + iPageDetails->set(aValue); +} + +QObject* +BooksSettings::currentBook() const +{ + return iCurrentBook; +} + +void +BooksSettings::setCurrentBook(QObject* aBook) +{ + BooksBook* book = qobject_cast(aBook); + if (iCurrentBook != book) { + if (iCurrentBook) iCurrentBook->release(); + if (book) { + HDEBUG(book->path()); + (iCurrentBook = book)->retain(); + iCurrentBookPath->set(book->path()); + } else { + iCurrentBook = NULL; + iCurrentBookPath->set(QString()); + } + Q_EMIT currentBookChanged(); + } +} + +bool +BooksSettings::updateCurrentBook() +{ + QString path = iCurrentBookPath->value(DEFAULT_CURRENT_BOOK).toString(); + if (path.isEmpty()) { + if (iCurrentBook) { + iCurrentBook->release(); + iCurrentBook = NULL; + return true; + } + } else if (!iCurrentBook || iCurrentBook->path() != path) { + ZLFile file(path.toStdString()); + shared_ptr book = Book::loadFromFile(file); + if (!book.isNull()) { + QFileInfo info(path); + BooksStorageManager* mgr = BooksStorageManager::instance(); + BooksStorage storage = mgr->storageForPath(info.path()); + if (storage.isValid()) { + if (iCurrentBook) iCurrentBook->release(); + iCurrentBook = new BooksBook(storage, book); + iCurrentBook->requestCoverImage(); + return true; + } + } + if (iCurrentBook) { + iCurrentBook->release(); + iCurrentBook = NULL; + return true; + } + } + return false; +} + +void +BooksSettings::onCurrentBookPathChanged() +{ + if (updateCurrentBook()) { + Q_EMIT currentBookChanged(); + } +} diff --git a/app/src/BooksSettings.h b/app/src/BooksSettings.h new file mode 100644 index 0000000..586cd63 --- /dev/null +++ b/app/src/BooksSettings.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_SETTINGS_H +#define BOOKS_SETTINGS_H + +#include "BooksTypes.h" +#include "ZLTextStyle.h" +#include + +class MGConfItem; + +class BooksSettings : public QObject +{ + Q_OBJECT + Q_ENUMS(FontSize) + Q_PROPERTY(int fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) + Q_PROPERTY(int pageDetails READ pageDetails WRITE setPageDetails NOTIFY pageDetailsChanged) + Q_PROPERTY(QObject* currentBook READ currentBook WRITE setCurrentBook NOTIFY currentBookChanged) + class TextStyle; + +public: + enum FontSize { + MinFontSize = -5, + DefaultFontSize = 0, + MaxFontSize = 5 + }; + + explicit BooksSettings(QObject* aParent = NULL); + + int fontSize() const; + void setFontSize(int aValue); + + int pageDetails() const; + void setPageDetails(int aValue); + + shared_ptr textStyle() const { return iTextStyle; } + + QObject* currentBook() const; + void setCurrentBook(QObject* aBook); + +signals: + void fontSizeChanged(); + void textStyleChanged(); + void pageDetailsChanged(); + void currentBookChanged(); + +private Q_SLOTS: + void onFontSizeValueChanged(); + void onCurrentBookPathChanged(); + +private: + void updateRenderType(); + bool updateCurrentBook(); + +private: + MGConfItem* iFontSize; + MGConfItem* iPageDetails; + MGConfItem* iCurrentBookPath; + shared_ptr iTextStyle; + BooksBook* iCurrentBook; +}; + +QML_DECLARE_TYPE(BooksSettings) + +#endif // BOOKS_SETTINGS_H diff --git a/app/src/BooksShelf.cpp b/app/src/BooksShelf.cpp new file mode 100644 index 0000000..d68b4da --- /dev/null +++ b/app/src/BooksShelf.cpp @@ -0,0 +1,1071 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksShelf.h" +#include "BooksDefs.h" +#include "BooksBook.h" + +#include "HarbourJson.h" +#include "HarbourDebug.h" + +#include +#include + +enum BooksItemRole { + BooksItemName = Qt::UserRole, + BooksItemBook, + BooksItemShelf, + BooksItemAccessible, + BooksItemCopyingOut, + BooksItemCopyingIn, + BooksItemCopyPercent, + BooksItemDummy, + BooksItemDeleteRequested +}; + +#define MIN_SIGNAL_DELAY (100) /* ms */ +#define FILE_PERMISSIONS \ + (QFile::ReadOwner | QFile::WriteOwner | \ + QFile::ReadGroup | QFile::ReadOther) + +#define SHELF_STATE_FILE BOOKS_STATE_FILE_SUFFIX +#define SHELF_STATE_ORDER "order" + +class BooksShelf::CopyTask : public BooksTask +{ + Q_OBJECT + +public: + CopyTask(BooksShelf::Data* aData, BooksBook* aBook); + ~CopyTask(); + + void performTask(); + + bool linkFiles(); + +Q_SIGNALS: + void copyPercentChanged(); + +public: + BooksShelf::Data* iData; + int iCopyPercent; + bool iSuccess; + QString iSource; + QString iDest; +}; + +// ========================================================================== +// BooksShelf::LoadTask +// ========================================================================== + +class BooksShelf::LoadTask : public BooksTask +{ + Q_OBJECT + +public: + LoadTask(BooksStorage aStorage, QString aPath, QString aStateFilePath) : + iStorage(aStorage), iPath(aPath), iStateFilePath(aStateFilePath) {} + ~LoadTask(); + + void performTask(); + + int findBook(QString aFileName) const; + static int find(QFileInfoList aList, QString aFileName, int aStart); + +Q_SIGNALS: + void bookFound(BooksBook* aBook); + +public: + BooksStorage iStorage; + QString iPath; + QString iStateFilePath; + QList iBooks; +}; + +BooksShelf::LoadTask::~LoadTask() +{ + const int n = iBooks.count(); + for (int i=0; irelease(); +} + +int BooksShelf::LoadTask::find(QFileInfoList aList, QString aName, int aStart) +{ + if (!aName.isEmpty()) { + const int n = aList.count(); + for (int i=aStart; ifileName() == aFileName) { + return i; + } + } + } + return -1; +} + +void BooksShelf::LoadTask::performTask() +{ + if (!isCanceled()) { + QDir dir(iPath); + HDEBUG("checking" << iPath); + QFileInfoList list = dir.entryInfoList(QDir::Files, QDir::Time); + + // Restore the order + QVariantMap state; + if (HarbourJson::load(iStateFilePath, state)) { + QVariantList order = state.value(SHELF_STATE_ORDER).toList(); + const int n = order.count(); + for (int i=0, dest=0; i= 0) { + if (index != dest) { + HDEBUG(order.at(i).toString() << index << "->" << dest); + list.move(index, dest); + } else { + HDEBUG(order.at(i).toString() << index); + } + dest++; + } else { + HDEBUG(order.at(i).toString()); + } + } + } + + const int n = list.count(); + for (int i=0; i book = Book::loadFromFile(file); + if (!book.isNull()) { + BooksBook* newBook = new BooksBook(iStorage, book); + newBook->moveToThread(thread()); + iBooks.append(newBook); + HDEBUG("[" << iBooks.size() << "]" << + qPrintable(newBook->fileName()) << + newBook->title()); + Q_EMIT bookFound(newBook); + } else { + HDEBUG("not a book:" << path.c_str()); + } + } + } + + // Cleanup the state files + if (!isCanceled()) { + QStringList deleteMe; + const QString suffix(BOOKS_STATE_FILE_SUFFIX); + QDirIterator configIt(iStorage.configDir()); + while (configIt.hasNext() && !isCanceled()) { + QString path(configIt.next()); + if (path.endsWith(suffix)) { + QString fileName(configIt.fileName()); + QString name(fileName.left(fileName.length() - suffix.length())); + if (!name.isEmpty() && findBook(name) < 0) { + deleteMe.append(path); + } + } + } + while (!deleteMe.isEmpty() && !isCanceled()) { + QString path(deleteMe.takeLast()); + if (QFile::remove(path)) { + HDEBUG("removed" << qPrintable(path)); + } else { + HWARN("failed to remove" << qPrintable(path)); + } + } + } +} + +// ========================================================================== +// BooksShelf::Data +// ========================================================================== + +class BooksShelf::Data { +public: + Data(BooksShelf* aShelf, BooksItem* aItem, bool aExternal); + ~Data(); + + QString name() { return iItem ? iItem->name() : QString(); } + QString fileName() { return iItem ? iItem->fileName() : QString(); } + QObject* object() { return iItem ? iItem->object() : NULL; } + BooksBook* book() { return iItem ? iItem->book() : NULL; } + BooksShelf* shelf() { return iItem ? iItem->shelf() : NULL; } + bool accessible(); + bool copyingOut(); + bool copyingIn() { return iCopyTask != NULL; } + int copyPercent() { return iCopyTask ? iCopyTask->iCopyPercent : 0; } + + void setBook(BooksBook* aBook, bool aExternal); + void connectSignals(BooksBook* aBook); + +public: + BooksShelf* iShelf; + BooksItem* iItem; + BooksShelf::CopyTask* iCopyTask; + bool iDeleteRequested; +}; + +BooksShelf::Data::Data(BooksShelf* aShelf, BooksItem* aItem, bool aExternal) : + iShelf(aShelf), + iItem(aItem), + iCopyTask(NULL), + iDeleteRequested(false) +{ + if (iItem && !aExternal) { + connectSignals(iItem->book()); + } +} + +BooksShelf::Data::~Data() +{ + if (iItem) { + iItem->object()->disconnect(iShelf); + iItem->release(); + } + if (iCopyTask) { + iCopyTask->release(iShelf); + } +} + +void BooksShelf::Data::connectSignals(BooksBook* aBook) +{ + if (aBook) { + iShelf->connect(aBook, + SIGNAL(accessibleChanged()), + SLOT(onBookAccessibleChanged())); + iShelf->connect(aBook, + SIGNAL(copyingOutChanged()), + SLOT(onBookCopyingOutChanged())); + iShelf->connect(aBook, + SIGNAL(movedAway()), + SLOT(onBookMovedAway())); + } +} + +void BooksShelf::Data::setBook(BooksBook* aBook, bool aExternal) +{ + if (iItem != aBook) { + if (iItem) { + iItem->object()->disconnect(iShelf); + iItem->release(); + } + iItem = aBook; + if (aBook) { + aBook->retain(); + if (!aExternal) connectSignals(aBook); + } + } +} + +inline bool BooksShelf::Data::accessible() +{ + if (iCopyTask) { + return false; + } else { + BooksBook* bookItem = book(); + return bookItem && bookItem->accessible(); + } +} + +inline bool BooksShelf::Data::copyingOut() +{ + BooksBook* bookItem = book(); + return bookItem && bookItem->copyingOut(); +} + +// ========================================================================== +// BooksShelf::CopyTask +// ========================================================================== + +BooksShelf::CopyTask::CopyTask(BooksShelf::Data* aData, BooksBook* aBook) : + iData(aData), + iCopyPercent(0), + iSuccess(false), + iSource(aBook->path()), + iDest(QFileInfo(aData->iShelf->path(), + QFileInfo(iSource).fileName()).absoluteFilePath()) +{ + HDEBUG(qPrintable(iSource) << "->" << qPrintable(iDest)); + if (iData->iCopyTask) { + iData->iCopyTask->release(iData->iShelf); + } + iData->iCopyTask = this; + iData->iShelf->connect(this, SIGNAL(done()), SLOT(onCopyTaskDone())); + iData->iShelf->connect(this, SIGNAL(copyPercentChanged()), + SLOT(onCopyTaskPercentChanged()), Qt::QueuedConnection); +} + +BooksShelf::CopyTask::~CopyTask() +{ + HASSERT(!iData); +} + +bool BooksShelf::CopyTask::linkFiles() +{ + QByteArray oldp(iSource.toLocal8Bit()); + QByteArray newp(iDest.toLocal8Bit()); + if (!oldp.isEmpty()) { + if (!newp.isEmpty()) { + int err = link(oldp.data(), newp.data()); + if (!err) { + HDEBUG("linked" << newp << "->" << oldp); + iSuccess = true; + } else { + HDEBUG(newp << "->" << oldp << "error:" << strerror(errno)); + } + } else { + HDEBUG("failed to convert" << newp << "to locale encoding"); + } + } else { + HDEBUG("failed to convert" << oldp << "to locale encoding"); + } + return iSuccess; +} + +void BooksShelf::CopyTask::performTask() +{ + if (!isCanceled() && !linkFiles()) { + QFile src(iSource); + const qint64 total = src.size(); + qint64 copied = 0; + if (src.open(QIODevice::ReadOnly)) { + QFile dest(iDest); + QDir dir(QFileInfo(dest).dir()); + dir.mkpath(dir.path()); + if (dest.open(QIODevice::WriteOnly)) { + QDateTime lastSignal; + const qint64 bufsiz = 0x1000; + char* buf = new char[bufsiz]; + qint64 len; + while (!isCanceled() && (len = src.read(buf, bufsiz)) > 0 && + !isCanceled() && dest.write(buf, len) == len) { + copied += len; + int percent = (int)(copied*100/total); + if (iCopyPercent != percent) { + // Don't fire signals too often + QDateTime now(QDateTime::currentDateTimeUtc()); + if (!lastSignal.isValid() || + lastSignal.msecsTo(now) >= MIN_SIGNAL_DELAY) { + lastSignal = now; + iCopyPercent = percent; + Q_EMIT copyPercentChanged(); + } + } + } + delete [] buf; + dest.close(); + if (copied == total) { + dest.setPermissions(FILE_PERMISSIONS); + iSuccess = true; + HDEBUG(total << "bytes copied from"<< qPrintable(iSource) << + "to" << qPrintable(iDest)); + } else { + if (isCanceled()) { + HDEBUG("copy" << qPrintable(iSource) << "to" << + qPrintable(iDest) << "cancelled"); + } else { + HWARN(copied << "out of" << total << + "bytes copied from" << qPrintable(iSource) << + "to" << qPrintable(iDest)); + } + dest.remove(); + } + } else { + HWARN("failed to open" << qPrintable(iDest)); + } + src.close(); + } else { + HWARN("failed to open" << qPrintable(iSource)); + } + } +} + +// ========================================================================== +// BooksShelf::DeleteTask +// ========================================================================== + +class BooksShelf::DeleteTask : public BooksTask +{ +public: + DeleteTask(BooksBook* aBook); + ~DeleteTask(); + void performTask(); + +public: + BooksBook* iBook; +}; + +BooksShelf::DeleteTask::DeleteTask(BooksBook* aBook) : + iBook(aBook) +{ + iBook->retain(); + iBook->cancelCoverRequest(); +} + +BooksShelf::DeleteTask::~DeleteTask() +{ + iBook->release(); +} + +void BooksShelf::DeleteTask::performTask() +{ + if (isCanceled()) { + HDEBUG("cancelled" << iBook->title()); + } else { + HDEBUG(iBook->title()); + iBook->deleteFiles(); + } +} + +// ========================================================================== +// BooksShelf +// ========================================================================== + +BooksShelf::BooksShelf(QObject* aParent) : + QAbstractListModel(aParent), + iLoadTask(NULL), + iDummyItemIndex(-1), + iEditMode(false), + iRef(-1), + iSaveTimer(new BooksSaveTimer(this)), + iTaskQueue(BooksTaskQueue::instance()) +{ +#if QT_VERSION < 0x050000 + setRoleNames(roleNames()); +#endif + QQmlEngine::setObjectOwnership(&iStorage, QQmlEngine::CppOwnership); + connect(iSaveTimer, SIGNAL(save()), SLOT(saveState())); +} + +BooksShelf::~BooksShelf() +{ + const int n = iDeleteTasks.count(); + for (int i=0; irelease(this); + if (iLoadTask) iLoadTask->release(this); + if (iSaveTimer->saveRequested()) saveState(); + removeAllBooks(); + HDEBUG("destroyed"); +} + +void BooksShelf::removeAllBooks() +{ + while (!iList.isEmpty()) { + Data* data = iList.takeLast(); + BooksBook* book = data->book(); + if (book) { + Q_EMIT bookRemoved(book); + } + delete data; + } +} + +void BooksShelf::setRelativePath(QString aPath) +{ + if (iRelativePath != aPath) { + iRelativePath = aPath; + updatePath(); + Q_EMIT relativePathChanged(); + } +} + +void BooksShelf::setDevice(QString aDevice) +{ + if (device() != aDevice) { + iStorage = BooksStorageManager::instance()->storageForDevice(aDevice); + updatePath(); + Q_EMIT deviceChanged(); + } +} + +void BooksShelf::updatePath() +{ + const QString oldPath = iPath; + iPath.clear(); + if (iStorage.isValid()) { + QString newPath(iStorage.root()); + if (!iRelativePath.isEmpty()) { + if (!newPath.endsWith('/')) newPath += '/'; + newPath += iRelativePath; + } + iPath = QDir::cleanPath(newPath); + } + if (oldPath != iPath) { + const int oldCount = iList.count(); + const int oldDummyItemIndex = iDummyItemIndex; + beginResetModel(); + HDEBUG(iPath); + removeAllBooks(); + iDummyItemIndex = -1; + if (!iPath.isEmpty()) loadBookList(); + endResetModel(); + Q_EMIT pathChanged(); + if (oldDummyItemIndex != iDummyItemIndex) { + Q_EMIT dummyItemIndexChanged(); + if (oldDummyItemIndex <0 || iDummyItemIndex < 0) { + Q_EMIT hasDummyItemChanged(); + } + } + if (oldCount != iList.count()) { + Q_EMIT countChanged(); + } + } +} + +void BooksShelf::onLoadTaskDone() +{ + HASSERT(iLoadTask); + HASSERT(iLoadTask == sender()); + 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(), true)); + endInsertRows(); + Q_EMIT bookAdded(aBook); + Q_EMIT countChanged(); + } +} + +void BooksShelf::loadBookList() +{ + if (!iList.isEmpty()) { + beginResetModel(); + removeAllBooks(); + endResetModel(); + } + + const bool wasLoading = loading(); + if (iLoadTask) iLoadTask->release(this); + if (iPath.isEmpty()) { + iLoadTask = NULL; + } else { + HDEBUG(iPath); + iLoadTask = new LoadTask(iStorage, iPath, stateFileName()); + connect(iLoadTask, SIGNAL(bookFound(BooksBook*)), + SLOT(onBookFound(BooksBook*)), Qt::QueuedConnection); + connect(iLoadTask, SIGNAL(done()), SLOT(onLoadTaskDone())); + iTaskQueue->submit(iLoadTask); + } + if (wasLoading != loading()) { + Q_EMIT loadingChanged(); + } +} + +void BooksShelf::saveState() +{ + QStringList order; + const int n = iList.count(); + for (int i=0; ifileName()); + } + QVariantMap state; + state.insert(SHELF_STATE_ORDER, order); + if (HarbourJson::save(stateFileName(), state)) { + HDEBUG("wrote" << stateFileName()); + } +} + +void BooksShelf::queueStateSave() +{ + if (iEditMode) { + iSaveTimer->requestSave(); + } +} + +QString BooksShelf::stateFileName() const +{ + return iStorage.isValid() ? + iStorage.configDir().path() + ("/" SHELF_STATE_FILE) : + QString(); +} + +int BooksShelf::bookIndex(BooksBook* aBook) const +{ + if (aBook) { + const int n = iList.count(); + for (int i=0; ibook() == aBook) { + return i; + } + } + } + return -1; +} + +int BooksShelf::itemIndex(QString aFileName, int aStartIndex) const +{ + if (!aFileName.isEmpty()) { + const int n = iList.count(); + for (int i=aStartIndex; ifileName() == aFileName) { + return i; + } + } + } + return -1; +} + +void BooksShelf::setHasDummyItem(bool aHasDummyItem) +{ + if (aHasDummyItem && !hasDummyItem()) { + iDummyItemIndex = iList.count(); + beginInsertRows(QModelIndex(), iDummyItemIndex, iDummyItemIndex); + iList.append(new Data(this, NULL, false)); + endInsertRows(); + Q_EMIT countChanged(); + Q_EMIT hasDummyItemChanged(); + Q_EMIT dummyItemIndexChanged(); + } else if (!aHasDummyItem && hasDummyItem()) { + remove(iDummyItemIndex); + } +} + +void BooksShelf::setEditMode(bool aEditMode) +{ + if (iEditMode != aEditMode) { + iEditMode = aEditMode; + HDEBUG(iEditMode); + if (iSaveTimer->saveRequested()) { + iSaveTimer->cancelSave(); + saveState(); + } + setHasDummyItem(false); + Q_EMIT editModeChanged(); + } +} + +void BooksShelf::setDummyItemIndex(int aIndex) +{ + if (validIndex(aIndex) && hasDummyItem() && iDummyItemIndex != aIndex) { + const int oldDummyItemIndex = iDummyItemIndex; + iDummyItemIndex = aIndex; + move(oldDummyItemIndex, aIndex); + Q_EMIT dummyItemIndexChanged(); + } +} + +BooksItem* BooksShelf::retain() +{ + if (iRef.load() >= 0) { + iRef.ref(); + } + return this; +} + +void BooksShelf::release() +{ + if (iRef.load() >= 0 && !iRef.deref()) { + delete this; + } +} + +QObject* BooksShelf::object() +{ + return this; +} + +BooksShelf* BooksShelf::shelf() +{ + return this; +} + +BooksBook* BooksShelf::book() +{ + return NULL; +} + +QString BooksShelf::name() const +{ + return iName; +} + +QString BooksShelf::fileName() const +{ + return iFileName; +} + +int BooksShelf::count() const +{ + return iList.count(); +} + +QObject* BooksShelf::get(int aIndex) const +{ + if (validIndex(aIndex)) { + return iList.at(aIndex)->object(); + } + HWARN("invalid index" << aIndex); + return NULL; +} + +BooksBook* BooksShelf::bookAt(int aIndex) const +{ + if (validIndex(aIndex)) { + return iList.at(aIndex)->book(); + } + HWARN("invalid index" << aIndex); + return NULL; +} + +bool BooksShelf::drop(QObject* aItem) +{ + if (iDummyItemIndex >= 0) { + BooksBook* book = qobject_cast(aItem); + if (!book) { + HWARN("unexpected drop object"); + } else if (itemIndex(book->fileName()) >= 0) { + HWARN("duplicate file name"); + setHasDummyItem(false); + } else { + HDEBUG("copying" << book->name() << "to" << qPrintable(path())); + book->setCopyingOut(true); + // Dropped object replaces the dummy placeholder object + QModelIndex index(createIndex(iDummyItemIndex, 0)); + Data* data = iList.at(iDummyItemIndex); + HASSERT(!data->iItem); + iDummyItemIndex = -1; + // Don't connect signals since it's not our item + data->setBook(book, true); + // Start copying the data + iTaskQueue->submit(new CopyTask(data, book)); + Q_EMIT hasDummyItemChanged(); + Q_EMIT dummyItemIndexChanged(); + Q_EMIT dataChanged(index, index); + return true; + } + } else { + HWARN("unexpected drop"); + } + return false; +} + +void BooksShelf::move(int aFrom, int aTo) +{ + if (aFrom != aTo) { + if (validIndex(aFrom) && validIndex(aTo)) { + HDEBUG(iList.at(aFrom)->name() << "from" << aFrom << "to" << aTo); + int dest = (aTo < aFrom) ? aTo : (aTo+1); + beginMoveRows(QModelIndex(), aFrom, aFrom, QModelIndex(), dest); + iList.move(aFrom, aTo); + queueStateSave(); + endMoveRows(); + } else { + HWARN("invalid move" << aFrom << "->" << aTo); + } + } +} + +void BooksShelf::remove(int aIndex) +{ + BooksBook* book = removeBook(aIndex); + if (book) { + Q_EMIT bookRemoved(book); + } +} + +BooksBook* BooksShelf::removeBook(int aIndex) +{ + if (validIndex(aIndex)) { + HDEBUG(iList.at(aIndex)->name()); + beginRemoveRows(QModelIndex(), aIndex, aIndex); + BooksBook* book = iList.at(aIndex)->book(); + if (book) { + DeleteTask* task = new DeleteTask(book); + iDeleteTasks.append(task); + iTaskQueue->submit(task); + } + if (iDummyItemIndex == aIndex) { + iDummyItemIndex = -1; + Q_EMIT hasDummyItemChanged(); + Q_EMIT dummyItemIndexChanged(); + } + delete iList.takeAt(aIndex); + queueStateSave(); + Q_EMIT countChanged(); + endRemoveRows(); + return book; + } + return NULL; +} + +void BooksShelf::removeAll() +{ + if (!iList.isEmpty()) { + beginRemoveRows(QModelIndex(), 0, iList.count()-1); + const int n = iList.count(); + for (int i=0; ibook(); + if (book) { + DeleteTask* task = new DeleteTask(book); + iDeleteTasks.append(task); + iTaskQueue->submit(task); + Q_EMIT bookRemoved(book); + } + } + if (iDummyItemIndex >= 0) { + iDummyItemIndex = -1; + Q_EMIT hasDummyItemChanged(); + Q_EMIT dummyItemIndexChanged(); + } + qDeleteAll(iList); + iList.clear(); + queueStateSave(); + Q_EMIT countChanged(); + endRemoveRows(); + } +} + +bool BooksShelf::deleteRequested(int aIndex) const +{ + if (validIndex(aIndex)) { + return iList.at(aIndex)->iDeleteRequested; + } else { + return false; + } +} + +void BooksShelf::setDeleteRequested(int aIndex, bool aValue) +{ + if (validIndex(aIndex)) { + Data* data = iList.at(aIndex); + if (data->iDeleteRequested != aValue) { + if (aValue) { + if (!data->copyingIn() && !data->copyingOut()) { + data->iDeleteRequested = true; + HDEBUG(aValue << data->name()); + emitDataChangedSignal(aIndex, BooksItemDeleteRequested); + } + } else { + data->iDeleteRequested = false; + HDEBUG(aValue << data->name()); + emitDataChangedSignal(aIndex, BooksItemDeleteRequested); + } + } + } +} + +void BooksShelf::cancelAllDeleteRequests() +{ + for (int i=iList.count()-1; i>=0; i--) { + setDeleteRequested(i, false); + } +} + +void BooksShelf::importBook(QObject* aBook) +{ + BooksBook* book = qobject_cast(aBook); + if (!book) { + HWARN("unexpected import object"); + } else if (itemIndex(book->fileName()) >= 0) { + HWARN("duplicate file name" << book->fileName()); + } else { + HDEBUG(qPrintable(book->path()) << "->" << qPrintable(iPath)); + beginInsertRows(QModelIndex(), 0, 0); + Data* data = new Data(this, book->retain(), true); + iList.insert(0, data); + iTaskQueue->submit(new CopyTask(data, book)); + endInsertRows(); + Q_EMIT countChanged(); + saveState(); + } +} + +void BooksShelf::emitDataChangedSignal(int aRow, int aRole) +{ + if (aRow >= 0) { + QModelIndex index(createIndex(aRow, 0)); + QVector roles; + roles.append(aRole); + Q_EMIT dataChanged(index, index, roles); + } +} + +void BooksShelf::onBookAccessibleChanged() +{ + int row = bookIndex(qobject_cast(sender())); + if (row >= 0) { + HDEBUG(iList.at(row)->name() << iList.at(row)->accessible()); + emitDataChangedSignal(row, BooksItemAccessible); + } +} + +void BooksShelf::onBookCopyingOutChanged() +{ + int row = bookIndex(qobject_cast(sender())); + if (row >= 0) { + HDEBUG(iList.at(row)->name() << iList.at(row)->copyingOut()); + emitDataChangedSignal(row, BooksItemCopyingOut); + } +} + +void BooksShelf::onBookMovedAway() +{ + int row = bookIndex(qobject_cast(sender())); + if (row >= 0) { + HDEBUG(iList.at(row)->name()); + remove(row); + } +} + +void BooksShelf::onCopyTaskPercentChanged() +{ + CopyTask* task = qobject_cast(sender()); + HASSERT(task); + if (task) { + HDEBUG(task->iDest << task->iCopyPercent); + const int row = iList.indexOf(task->iData); + emitDataChangedSignal(row, BooksItemCopyPercent); + } +} + +void BooksShelf::onCopyTaskDone() +{ + CopyTask* task = qobject_cast(sender()); + HASSERT(task); + if (task) { + HDEBUG(qPrintable(task->iSource) << "->" << qPrintable(task->iDest) << + "copy" << (task->iSuccess ? "done" : "FAILED")); + + Data* data = task->iData; + const int row = iList.indexOf(data); + HASSERT(row >= 0); + + BooksBook* copy = NULL; + BooksBook* src = data->book(); + HASSERT(src); + if (src) { + src->retain(); + if (task->iSuccess) { + ZLFile file(task->iDest.toStdString()); + shared_ptr book = Book::loadFromFile(file); + if (!book.isNull()) { + copy = new BooksBook(iStorage, book); + copy->setLastPos(src->lastPos()); + copy->setCoverImage(src->coverImage()); + copy->requestCoverImage(); + } else { + HWARN("can't open copied book:" << file.path().c_str()); + } + } + } + + // Disassociate book data from the copy task + data->iCopyTask = NULL; + task->iData = NULL; + task->release(this); + + // Notify the source shelf. This will actually remove the source file. + if (copy) { + Q_EMIT src->movedAway(); + } + + if (src) { + src->setCopyingOut(false); + src->release(); + } + + if (copy) { + // We own this item now, connect the signals + data->setBook(copy, false); + Q_EMIT bookAdded(copy); + + // The entire row has changed + QModelIndex index(createIndex(row, 0)); + Q_EMIT dataChanged(index, index); + } else { + removeBook(row); + } + } +} + +void BooksShelf::onDeleteTaskDone() +{ + HVERIFY(iDeleteTasks.removeOne((DeleteTask*)sender())); +} + +QHash BooksShelf::roleNames() const +{ + QHash roles; + roles.insert(BooksItemName, "name"); + roles.insert(BooksItemBook, "book"); + roles.insert(BooksItemShelf, "shelf"); + roles.insert(BooksItemAccessible, "accessible"); + roles.insert(BooksItemCopyingOut, "copyingOut"); + roles.insert(BooksItemCopyingIn, "copyingIn"); + roles.insert(BooksItemCopyPercent, "copyPercent"); + roles.insert(BooksItemDummy, "dummy"); + roles.insert(BooksItemDeleteRequested, "deleteRequested"); + return roles; +} + +int BooksShelf::rowCount(const QModelIndex&) const +{ + return iList.count(); +} + +QVariant BooksShelf::data(const QModelIndex& aIndex, int aRole) const +{ + const int i = aIndex.row(); + if (validIndex(i)) { + Data* data = iList.at(i); + switch (aRole) { + case BooksItemName: return data->name(); + case BooksItemBook: return QVariant::fromValue(data->book()); + case BooksItemShelf: return QVariant::fromValue(data->shelf()); + case BooksItemAccessible: return data->accessible(); + case BooksItemCopyingOut: return data->copyingOut(); + case BooksItemCopyingIn: return data->copyingIn(); + case BooksItemCopyPercent: return data->copyPercent(); + case BooksItemDummy: return QVariant::fromValue(!data->iItem); + case BooksItemDeleteRequested: return data->iDeleteRequested; + } + } + return QVariant(); +} + +#include "BooksShelf.moc" diff --git a/app/src/BooksShelf.h b/app/src/BooksShelf.h new file mode 100644 index 0000000..afe3d3d --- /dev/null +++ b/app/src/BooksShelf.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_SHELF_MODEL_H +#define BOOKS_SHELF_MODEL_H + +#include "BooksItem.h" +#include "BooksStorage.h" +#include "BooksSaveTimer.h" +#include "BooksTask.h" +#include "BooksTaskQueue.h" + +#include +#include +#include +#include +#include + +class BooksShelf: public QAbstractListModel, public BooksItem +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) + Q_PROPERTY(QString path READ path NOTIFY pathChanged) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString device READ device WRITE setDevice NOTIFY deviceChanged) + Q_PROPERTY(QString relativePath READ relativePath WRITE setRelativePath NOTIFY relativePathChanged) + Q_PROPERTY(bool editMode READ editMode WRITE setEditMode NOTIFY editModeChanged) + Q_PROPERTY(bool hasDummyItem READ hasDummyItem WRITE setHasDummyItem NOTIFY hasDummyItemChanged) + Q_PROPERTY(int dummyItemIndex READ dummyItemIndex WRITE setDummyItemIndex NOTIFY dummyItemIndexChanged) + Q_PROPERTY(BooksBook* book READ book CONSTANT) + +public: + explicit BooksShelf(QObject* aParent = NULL); + ~BooksShelf(); + + Q_INVOKABLE QObject* get(int aIndex) const; + Q_INVOKABLE bool drop(QObject* aItem); + Q_INVOKABLE void move(int aFrom, int aTo); + Q_INVOKABLE void remove(int aIndex); + Q_INVOKABLE void removeAll(); + Q_INVOKABLE bool deleteRequested(int aIndex) const; + Q_INVOKABLE void setDeleteRequested(int aIndex, bool aValue); + Q_INVOKABLE void cancelAllDeleteRequests(); + Q_INVOKABLE void importBook(QObject* aBook); + + bool loading() const { return iLoadTask != NULL; } + int count() const; + QString path() const { return iPath; } + QString relativePath() const { return iRelativePath; } + void setRelativePath(QString aPath); + BooksBook* bookAt(int aIndex) const; + + bool editMode() const { return iEditMode; } + void setEditMode(bool aEditMode); + + bool hasDummyItem() const { return iDummyItemIndex >= 0; } + void setHasDummyItem(bool aHasDummyItem); + + int dummyItemIndex() const { return iDummyItemIndex; } + void setDummyItemIndex(int aIndex); + + QString device() { return iStorage.device(); } + void setDevice(QString aDevice); + + // QAbstractListModel + virtual QHash roleNames() const; + virtual int rowCount(const QModelIndex& aParent) const; + virtual QVariant data(const QModelIndex& aIndex, int aRole) const; + + // BooksListItem + virtual BooksItem* retain(); + virtual void release(); + virtual QObject* object(); + virtual BooksShelf* shelf(); + virtual BooksBook* book(); + virtual QString name() const; + virtual QString fileName() const; + +Q_SIGNALS: + void loadingChanged(); + void countChanged(); + void pathChanged(); + void nameChanged(); + void deviceChanged(); + void relativePathChanged(); + void editModeChanged(); + void hasDummyItemChanged(); + void dummyItemIndexChanged(); + void bookAdded(BooksBook* aBook); + void bookRemoved(BooksBook* aBook); + +private Q_SLOTS: + void onLoadTaskDone(); + void onBookFound(BooksBook* aBook); + void onBookAccessibleChanged(); + void onBookCopyingOutChanged(); + void onBookMovedAway(); + void onCopyTaskPercentChanged(); + void onCopyTaskDone(); + void onDeleteTaskDone(); + void saveState(); + +private: + QString stateFileName() const; + int bookIndex(BooksBook* aBook) const; + int itemIndex(QString aFileName, int aStartIndex = 0) const; + bool validIndex(int aIndex) const; + void emitDataChangedSignal(int aRow, int aRole); + void queueStateSave(); + void loadBookList(); + void updatePath(); + void removeAllBooks(); + BooksBook* removeBook(int aIndex); + +private: + class Data; + class CopyTask; + class LoadTask; + class ImportTask; + class DeleteTask; + QList iList; + QList iDeleteTasks; + LoadTask* iLoadTask; + QString iName; + QString iFileName; + QString iPath; + QString iRelativePath; + BooksStorage iStorage; + int iDummyItemIndex; + bool iEditMode; + QAtomicInt iRef; + BooksSaveTimer* iSaveTimer; + shared_ptr iTaskQueue; +}; + +QML_DECLARE_TYPE(BooksShelf) + +inline bool BooksShelf::validIndex(int aIndex) const + { return aIndex >= 0 && aIndex < iList.count(); } + +#endif // BOOKS_SHELF_MODEL_H diff --git a/app/src/BooksStorage.cpp b/app/src/BooksStorage.cpp new file mode 100644 index 0000000..0910e23 --- /dev/null +++ b/app/src/BooksStorage.cpp @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksStorage.h" +#include "BooksDefs.h" + +#include "HarbourDebug.h" + +#include "ZLibrary.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define INTERNAL_STATE_DIR "internal" +#define REMOVABLE_STATE_DIR "removable" + +// ========================================================================== +// BooksStorage::Data +// ========================================================================== + +class BooksStorage::Private: public QObject +{ + Q_OBJECT + +public: + Private(QString aDevice, QDir aBooksDir, bool aInternal); + + bool isRemoved() const; + bool equal(const Private& aData) const; + + static QString mountPoint(QString aPath); + static bool isMountPoint(QString aPath); + +Q_SIGNALS: + void removed(); + +public: + QAtomicInt iRef; + QString iDevice; + QDir iBooksDir; + QDir iConfigDir; + bool iInternal; + bool iPresent; +}; + +BooksStorage::Private::Private(QString aDevice, QDir aBooksDir, bool aInternal) : + iRef(1), iDevice(aDevice), iBooksDir(aBooksDir), iInternal(aInternal), + iPresent(true) +{ + QString cfgDir; + cfgDir = QString::fromStdString(ZLibrary::ApplicationWritableDirectory()); + if (!cfgDir.endsWith('/')) cfgDir += '/'; + if (aInternal) { + cfgDir += INTERNAL_STATE_DIR; + QString subDir(aDevice); + if (subDir.startsWith("/dev")) subDir.remove(0,4); + if (!subDir.startsWith('/')) cfgDir += '/'; + cfgDir += subDir; + } else { + cfgDir += REMOVABLE_STATE_DIR "/"; + QString label = QDir(Private::mountPoint(aBooksDir.path())).dirName(); + if (label.isEmpty()) label = "sdcard"; + cfgDir += label; + } + iConfigDir.setPath(cfgDir); +} + +bool BooksStorage::Private::equal(const BooksStorage::Private& aData) const +{ + return iInternal == iInternal && + iPresent == iPresent && + iDevice == aData.iDevice && + iBooksDir == aData.iBooksDir; +} + +bool BooksStorage::Private::isMountPoint(QString aPath) +{ + std::string path = aPath.toStdString(); + std::string parent = path + "/.."; + struct stat stPath, stParent; + return stat(path.c_str(), &stPath) == 0 && + stat(parent.c_str(), &stParent) == 0 && + stPath.st_dev != stParent.st_dev; +} + +QString BooksStorage::Private::mountPoint(QString aPath) +{ + QFileInfo info(aPath); + QDir dir = info.isDir() ? QDir(aPath) : info.dir(); + dir.makeAbsolute(); + while (!isMountPoint(dir.path()) && !dir.isRoot()) { + info.setFile(dir.path()); + dir = info.dir(); + } + return dir.path(); +} + +// ========================================================================== +// BooksStorage +// ========================================================================== + +BooksStorage::BooksStorage() : + QObject(NULL), + iPrivate(NULL), + iPassThrough(false) +{ +} + +BooksStorage::BooksStorage(const BooksStorage& aStorage) : + QObject(NULL), + iPrivate(aStorage.iPrivate), + iPassThrough(false) +{ + if (iPrivate) iPrivate->iRef.ref(); +} + +BooksStorage::BooksStorage(QString aDevice, QDir aBooksDir, bool aInternal) : + QObject(NULL), + iPrivate(new Private(aDevice, aBooksDir, aInternal)), + iPassThrough(false) +{ + HDEBUG("config dir" << qPrintable(configDir().path())); +} + +BooksStorage::~BooksStorage() +{ + if (iPrivate && !iPrivate->iRef.deref()) delete iPrivate; +} + +void BooksStorage::connectNotify(const QMetaMethod& aSignal) +{ + if (iPrivate && !iPassThrough) { + iPassThrough = true; + connect(iPrivate, SIGNAL(removed()), SIGNAL(removed())); + } + + // Prototype of this method has changed in Qt5. Make sure that + // compilation breaks on Qt with older version of Qt: + QObject::connectNotify(aSignal); +} + +QString BooksStorage::device() const +{ + return iPrivate ? iPrivate->iDevice : QString(); +} + +QDir BooksStorage::booksDir() const +{ + return iPrivate ? iPrivate->iBooksDir : QDir(); +} + +QDir BooksStorage::configDir() const +{ + return iPrivate ? iPrivate->iConfigDir : QDir(); +} + +bool BooksStorage::isInternal() const +{ + return iPrivate && iPrivate->iInternal; +} + +bool BooksStorage::isPresent() const +{ + return iPrivate && iPrivate->iPresent; +} + +bool BooksStorage::equal(const BooksStorage& aStorage) const +{ + if (iPrivate == aStorage.iPrivate) { + return true; + } else if (!iPrivate || !aStorage.iPrivate) { + return false; + } else { + return iPrivate->equal(*aStorage.iPrivate); + } +} + +BooksStorage& BooksStorage::operator = (const BooksStorage& aStorage) +{ + if (iPrivate != aStorage.iPrivate) { + if (iPrivate && !iPrivate->iRef.deref()) delete iPrivate; + iPrivate = aStorage.iPrivate; + if (iPrivate) iPrivate->iRef.ref(); + } + return *this; +} + + +// ========================================================================== +// BooksStorageManager::Private +// ========================================================================== + +#define STORAGE_MOUNTS_FILE "/proc/mounts" + +#define STORAGE_SUBSYSTEM "block" +#define STORAGE_DISK "disk" +#define STORAGE_PARTITION "partition" +#define STORAGE_MOUNT_PREFIX "/media/" + +#define STORAGE_ACTION_ADD "add" +#define STORAGE_ACTION_REMOVE "remove" + +#define STORAGE_SCAN_INTERVAL 100 +#define STORAGE_SCAN_TIMEOUT 5000 + +class BooksStorageManager::Private { +public: + static BooksStorageManager* gInstance; + + Private(); + ~Private(); + + int findDevice(QString aDevice) const; + int findPath(QString aPath) const; + +public: + QList iStorageList; + struct udev* iUdev; + struct udev_monitor* iMonitor; + int iDescriptor; + QSocketNotifier* iNotifier; + QTimer* iScanMountsTimer; + QDateTime iScanDeadline; +}; + +BooksStorageManager* BooksStorageManager::Private::gInstance = NULL; + +BooksStorageManager::Private::Private() : + iUdev(udev_new()), + iMonitor(NULL), + iDescriptor(-1), + iNotifier(NULL), + iScanMountsTimer(NULL) +{ + QString homeDevice; + QString homeBooks = QDir::homePath(); + QString homeMount(BooksStorage::Private::mountPoint(homeBooks)); + if (!homeBooks.endsWith('/')) homeBooks += '/'; + homeBooks += QLatin1String(BOOKS_ROOT_SHELF_DIR); + HDEBUG("home mount" << qPrintable(homeMount)); + HDEBUG("home books path" << qPrintable(homeBooks)); + + QFile mounts(STORAGE_MOUNTS_FILE); + if (mounts.open(QIODevice::ReadOnly | QIODevice::Text)) { + // For some reason QTextStream can't read /proc/mounts line by line + QByteArray contents = mounts.readAll(); + QTextStream in(&contents); + QString mediaPrefix(STORAGE_MOUNT_PREFIX); + while (!in.atEnd()) { + QString line = in.readLine(); + QStringList entries = line.split(' ', QString::SkipEmptyParts); + if (entries.count() > 2) { + QString mount(entries.at(1)); + if (mount == homeMount) { + homeDevice = entries.at(0); + HDEBUG("home device" << homeDevice); + } else if (mount.startsWith(mediaPrefix)) { + QString dev = entries.at(0); + QString path = mount; + if (!path.endsWith('/')) path += '/'; + path += QLatin1String(BOOKS_ROOT_SHELF_DIR); + HDEBUG("removable device" << dev << path); + iStorageList.append(BooksStorage(dev, path, false)); + } + } + } + mounts.close(); + } + + iStorageList.insert(0, BooksStorage(homeDevice, homeBooks, true)); + + if (iUdev) { + iMonitor = udev_monitor_new_from_netlink(iUdev, "udev"); + if (iMonitor) { + udev_monitor_filter_add_match_subsystem_devtype(iMonitor, + STORAGE_SUBSYSTEM, NULL); + udev_monitor_enable_receiving(iMonitor); + iDescriptor = udev_monitor_get_fd(iMonitor); + if (iDescriptor >= 0) { + iNotifier = new QSocketNotifier(iDescriptor, + QSocketNotifier::Read); + } + } + } +} + +int BooksStorageManager::Private::findDevice(QString aDevice) const +{ + const int n = iStorageList.count(); + for (int i=0; iiDevice == aDevice) { + return i; + } + } + return -1; +} + +int BooksStorageManager::Private::findPath(QString aPath) const +{ + if (!aPath.isEmpty()) { + const int n = iStorageList.count(); + for (int i=0; iiBooksDir.path())) { + return i; + } + } + } + return -1; +} + +BooksStorageManager::Private::~Private() +{ + if (iUdev) { + if (iMonitor) { + if (iDescriptor >= 0) { + delete iNotifier; + close(iDescriptor); + } + udev_monitor_unref(iMonitor); + } + udev_unref(iUdev); + } +} + +// ========================================================================== +// BooksStorageManager +// ========================================================================== + +BooksStorageManager* BooksStorageManager::instance() +{ + if (!Private::gInstance) { + Private::gInstance = new BooksStorageManager; + } + return Private::gInstance; +} + +void BooksStorageManager::deleteInstance() +{ + delete Private::gInstance; + HASSERT(!Private::gInstance); +} + +BooksStorageManager::BooksStorageManager() : + iPrivate(new Private) +{ + if (iPrivate->iNotifier) { + connect(iPrivate->iNotifier, SIGNAL(activated(int)), + SLOT(onDeviceEvent(int))); + } +} + +BooksStorageManager::~BooksStorageManager() +{ + if (Private::gInstance == this) { + Private::gInstance = NULL; + } + delete iPrivate; +} + +int BooksStorageManager::count() const +{ + return iPrivate->iStorageList.count(); +} + +QList BooksStorageManager::storageList() const +{ + return iPrivate->iStorageList; +} + +BooksStorage BooksStorageManager::storageForDevice(QString aDevice) const +{ + int index = iPrivate->findDevice(aDevice); + return (index >= 0) ? iPrivate->iStorageList.at(index) : BooksStorage(); +} + +BooksStorage BooksStorageManager::storageForPath(QString aPath) const +{ + int index = iPrivate->findPath(aPath); + return (index >= 0) ? iPrivate->iStorageList.at(index) : BooksStorage(); +} + +void BooksStorageManager::onDeviceEvent(int) +{ + struct udev_device* dev = udev_monitor_receive_device(iPrivate->iMonitor); + if (dev) { + const char* devnode = udev_device_get_devnode(dev); + const char* action = udev_device_get_action(dev); + HDEBUG("got device"); + HDEBUG(" node:" << devnode); + HDEBUG(" subsystem:" << udev_device_get_subsystem(dev)); + HDEBUG(" devtype:" << udev_device_get_devtype(dev)); + HDEBUG(" action:" << action); + if (devnode && action) { + if (!(strcmp(action, STORAGE_ACTION_ADD))) { + // Mount list isn't updated yet when we receive this + // notification. It takes hundreds of milliseconds until + // it gets mounted and becomes accessible. + if (!scanMounts()) { + HDEBUG("no new mounts found"); + if (!iPrivate->iScanMountsTimer) { + QTimer* timer = new QTimer(this); + timer->setSingleShot(false); + timer->setInterval(STORAGE_SCAN_INTERVAL); + connect(timer, SIGNAL(timeout()), SLOT(onScanMounts())); + iPrivate->iScanMountsTimer = timer; + } + iPrivate->iScanMountsTimer->start(); + iPrivate->iScanDeadline = QDateTime::currentDateTime(). + addMSecs(STORAGE_SCAN_TIMEOUT); + } + } else if (!(strcmp(action, STORAGE_ACTION_REMOVE))) { + int pos = iPrivate->findDevice(devnode); + if (pos >= 0) { + HDEBUG("removable device is gone"); + BooksStorage storage = iPrivate->iStorageList.takeAt(pos); + storage.iPrivate->iPresent = false; + Q_EMIT storage.iPrivate->removed(); + Q_EMIT storageRemoved(storage); + } + } + } + udev_device_unref(dev); + } else { + HWARN("no device!"); + } +} + +bool BooksStorageManager::scanMounts() +{ + bool newStorageFound = false; + QFile mounts(STORAGE_MOUNTS_FILE); + if (mounts.open(QIODevice::ReadOnly | QIODevice::Text)) { + // For some reason QTextStream can't read /proc/mounts line by line + QByteArray contents = mounts.readAll(); + QTextStream in(&contents); + QString mediaPrefix(STORAGE_MOUNT_PREFIX); + while (!in.atEnd()) { + QString line = in.readLine(); + QStringList entries = line.split(' ', QString::SkipEmptyParts); + if (entries.count() > 2) { + QString mount(entries.at(1)); + if (mount.startsWith(mediaPrefix)) { + QString dev = entries.at(0); + int index = iPrivate->findDevice(dev); + if (index < 0) { + QString path = mount; + if (!path.endsWith('/')) path += '/'; + path += QLatin1String(BOOKS_ROOT_SHELF_DIR); + HDEBUG("new removable device" << dev << path); + BooksStorage storage(dev, path, false); + iPrivate->iStorageList.append(storage); + Q_EMIT storageAdded(storage); + newStorageFound = true; + } + } + } + } + mounts.close(); + } + return newStorageFound; +} + +void BooksStorageManager::onScanMounts() +{ + if (scanMounts()) { + iPrivate->iScanMountsTimer->stop(); + } else { + QDateTime now = QDateTime::currentDateTime(); + if (now > iPrivate->iScanDeadline) { + HDEBUG("timeout waiting for new mount to appear"); + iPrivate->iScanMountsTimer->stop(); + } else { + HDEBUG("no new mounts found"); + } + } +} + +#include "BooksStorage.moc" diff --git a/app/src/BooksStorage.h b/app/src/BooksStorage.h new file mode 100644 index 0000000..d739891 --- /dev/null +++ b/app/src/BooksStorage.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_STORAGE_H +#define BOOKS_STORAGE_H + +#include +#include +#include + +class BooksStorageManager; + +class BooksStorage: public QObject +{ + Q_OBJECT + Q_PROPERTY(QString root READ root CONSTANT) + +public: + BooksStorage(); + BooksStorage(const BooksStorage& aStorage); + ~BooksStorage(); + + QString device() const; + QDir booksDir() const; + QDir configDir() const; + QString label() const { return booksDir().dirName(); } + QString root() const { return booksDir().path(); } + + bool isValid() const { return iPrivate != NULL; } + bool isInternal() const; + bool isPresent() const; + bool equal(const BooksStorage& aStorage) const; + + BooksStorage& operator = (const BooksStorage& aStorage); + bool operator == (const BooksStorage& aStorage) const + { return equal(aStorage); } + +Q_SIGNALS: + void removed(); + +private: + friend class BooksStorageManager; + BooksStorage(QString, QDir, bool); + void connectNotify(const QMetaMethod& aSignal); + +private: + class Private; + Private* iPrivate; + bool iPassThrough; +}; + +class BooksStorageManager: public QObject +{ + Q_OBJECT + +public: + static BooksStorageManager* instance(); + static void deleteInstance(); + ~BooksStorageManager(); + + int count() const; + QList storageList() const; + BooksStorage storageForDevice(QString aDevice) const; + BooksStorage storageForPath(QString aPath) const; + +Q_SIGNALS: + void storageAdded(BooksStorage aStorage); + void storageRemoved(BooksStorage aStorage); + +private: + BooksStorageManager(); + bool scanMounts(); + +private Q_SLOTS: + void onDeviceEvent(int); + void onScanMounts(); + +private: + class Private; + Private* iPrivate; +}; + +Q_DECLARE_METATYPE(BooksStorage) + +#endif // BOOKS_STORAGE_H diff --git a/app/src/BooksStorageModel.cpp b/app/src/BooksStorageModel.cpp new file mode 100644 index 0000000..76ecfea --- /dev/null +++ b/app/src/BooksStorageModel.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksStorageModel.h" + +#include "HarbourDebug.h" + +enum BooksStorageRole { + BooksStorageRoot = Qt::UserRole, + BooksStorageDevice, + BooksStorageRemovable, + BooksStorageDeleteAllRequest +}; + +// ========================================================================== +// BooksStorageModel::Data +// ========================================================================== + +class BooksStorageModel::Data { +public: + Data(const BooksStorage& aStorage); + + QString device() const { return iStorage.device(); } + QString root() const { return iStorage.root(); } + bool isRemovable() const { return !iStorage.isInternal(); } + +public: + BooksStorage iStorage; + bool iDeleteAllRequest; +}; + +BooksStorageModel::Data::Data(const BooksStorage& aStorage) : + iStorage(aStorage), + iDeleteAllRequest(false) +{ + // Pointers to these objects can be returned to QML + QQmlEngine::setObjectOwnership(&iStorage, QQmlEngine::CppOwnership); +} + +// ========================================================================== +// BooksStorageModel +// ========================================================================== + +BooksStorageModel::BooksStorageModel(QObject* aParent) : + QAbstractListModel(aParent) +{ +#if QT_VERSION < 0x050000 + setRoleNames(roleNames()); +#endif + + BooksStorageManager* mgr = BooksStorageManager::instance(); + QList list = mgr->storageList(); + const int n = list.count(); + for (int i=0; iiDeleteAllRequest != aValue) { + data->iDeleteAllRequest = aValue; + QModelIndex index(createIndex(aIndex, 0)); + QVector roles; + roles.append(BooksStorageDeleteAllRequest); + HDEBUG(aValue << data->root()); + Q_EMIT dataChanged(index, index, roles); + } + } +} + +void BooksStorageModel::cancelDeleteAllRequests() +{ + for (int i=iList.count()-1; i>=0; i--) { + setDeleteAllRequest(i, false); + } +} + +QObject* BooksStorageModel::get(int aIndex) const +{ + if (validIndex(aIndex)) { + return &(iList.at(aIndex)->iStorage); + } + return NULL; +} + +QHash BooksStorageModel::roleNames() const +{ + QHash roles; + roles.insert(BooksStorageRemovable, "removable"); + roles.insert(BooksStorageRoot, "root"); + roles.insert(BooksStorageDevice, "device"); + roles.insert(BooksStorageDeleteAllRequest, "deleteAllRequest"); + return roles; +} + +int BooksStorageModel::rowCount(const QModelIndex&) const +{ + return iList.count(); +} + +QVariant BooksStorageModel::data(const QModelIndex& aIndex, int aRole) const +{ + const int i = aIndex.row(); + if (validIndex(i)) { + Data* data = iList.at(i); + switch (aRole) { + case BooksStorageRoot: return data->root(); + case BooksStorageDevice: return data->device(); + case BooksStorageRemovable: return data->isRemovable(); + case BooksStorageDeleteAllRequest: return data->iDeleteAllRequest; + } + } + return QVariant(); +} + +int BooksStorageModel::find(const BooksStorage& aStorage) const +{ + const int n = iList.count(); + for (int i=0; iiStorage.equal(aStorage)) { + return i; + } + } + return -1; +} + +void BooksStorageModel::onStorageAdded(BooksStorage aStorage) +{ + HDEBUG(aStorage.device() << "found"); + const int index = iList.count(); + beginInsertRows(QModelIndex(), index, index); + iList.append(new Data(aStorage)); + endInsertRows(); + Q_EMIT countChanged(); + Q_EMIT newStorage(index); +} + +void BooksStorageModel::onStorageRemoved(BooksStorage aStorage) +{ + int index = find(aStorage); + if (index >=0) { + beginRemoveRows(QModelIndex(), index, index); + delete iList.takeAt(index); + endRemoveRows(); + Q_EMIT countChanged(); + } else { + HWARN("device not dfound on the list"); + } +} diff --git a/app/src/BooksStorageModel.h b/app/src/BooksStorageModel.h new file mode 100644 index 0000000..486b7dc --- /dev/null +++ b/app/src/BooksStorageModel.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_STORAGE_MODEL_H +#define BOOKS_STORAGE_MODEL_H + +#include "BooksStorage.h" + +#include +#include +#include +#include +#include + +class BooksStorageModel: public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QObject* internalStorage READ internalStorage CONSTANT) + +public: + explicit BooksStorageModel(QObject* aParent = NULL); + ~BooksStorageModel(); + + Q_INVOKABLE int count() const; + Q_INVOKABLE void setDeleteAllRequest(int aIndex, bool aValue); + Q_INVOKABLE void cancelDeleteAllRequests(); + Q_INVOKABLE QObject* get(int aIndex) const; + QObject* internalStorage() { return &iInternalStorage; } + + // QAbstractListModel + virtual QHash roleNames() const; + virtual int rowCount(const QModelIndex& aParent) const; + virtual QVariant data(const QModelIndex& aIndex, int aRole) const; + +Q_SIGNALS: + void countChanged(); + void newStorage(int index); + +private Q_SLOTS: + void onStorageAdded(BooksStorage aStorage); + void onStorageRemoved(BooksStorage aStorage); + +private: + bool validIndex(int aIndex) const; + int find(const BooksStorage& aStorage) const; + +private: + class Data; + QList iList; + BooksStorage iInternalStorage; +}; + +QML_DECLARE_TYPE(BooksStorageModel) + +inline bool BooksStorageModel::validIndex(int aIndex) const + { return aIndex >= 0 && aIndex < iList.count(); } + +#endif // BOOKS_STORAGE_MODEL_H diff --git a/app/src/BooksTask.cpp b/app/src/BooksTask.cpp new file mode 100644 index 0000000..fc99edf --- /dev/null +++ b/app/src/BooksTask.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksTask.h" +#include "BooksTaskQueue.h" + +#include "HarbourDebug.h" + +#include + +BooksTask::BooksTask() : + iAboutToQuit(false), + iReleased(false), + iStarted(false), + iDone(false) +{ + setAutoDelete(false); + connect(qApp, SIGNAL(aboutToQuit()), SLOT(onAboutToQuit())); + connect(this, SIGNAL(runFinished()), SLOT(onRunFinished()), + Qt::QueuedConnection); +} + +BooksTask::~BooksTask() +{ + HASSERT(iReleased); + if (iStarted) wait(); +} + +void BooksTask::release(QObject* aHandler) +{ + disconnect(aHandler); + iReleased = true; + if (!iStarted || iDone) { + delete this; + } +} + +void BooksTask::run() +{ + performTask(); + Q_EMIT runFinished(); +} + +void BooksTask::onRunFinished() +{ + HASSERT(!iDone); + if (!iReleased) { + Q_EMIT done(); + } + iDone = true; + if (iReleased) { + delete this; + } +} + +void BooksTask::onAboutToQuit() +{ + HDEBUG("OK"); + iAboutToQuit = true; +} diff --git a/app/src/BooksTask.h b/app/src/BooksTask.h new file mode 100644 index 0000000..ab11621 --- /dev/null +++ b/app/src/BooksTask.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_TASK_H +#define BOOKS_TASK_H + +#include +#include + +class BooksTaskQueue; + +class BooksTask : public QObject, public QRunnable +{ + Q_OBJECT + friend class BooksTaskQueue; + +protected: + BooksTask(); + +public: + virtual ~BooksTask(); + + void release(QObject* aHandler); + +protected: + bool isCanceled() const { return iReleased || iAboutToQuit; } + + virtual void run(); + virtual void performTask() = 0; + +Q_SIGNALS: + void runFinished(); + void done(); + +private Q_SLOTS: + void onAboutToQuit(); + void onRunFinished(); + +private: + bool iAboutToQuit; + bool iReleased; + bool iStarted; + bool iDone; +}; + +#endif // BOOKS_TASK_H diff --git a/app/src/BooksTaskQueue.cpp b/app/src/BooksTaskQueue.cpp new file mode 100644 index 0000000..0c9b277 --- /dev/null +++ b/app/src/BooksTaskQueue.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksTaskQueue.h" +#include "BooksTask.h" + +#include "HarbourDebug.h" + +static weak_ptr booksTaskQueueInstance; + +shared_ptr BooksTaskQueue::instance() +{ + shared_ptr worker; + if (booksTaskQueueInstance.isNull()) { + booksTaskQueueInstance = (worker = new BooksTaskQueue()); + } else { + worker = booksTaskQueueInstance; + } + return worker; +} + +BooksTaskQueue::BooksTaskQueue() : + iPool(new QThreadPool) +{ + HDEBUG("created"); + iPool->setMaxThreadCount(1); +} + +void BooksTaskQueue::waitForDone(int aMsecs) +{ + shared_ptr worker = booksTaskQueueInstance; + if (!worker.isNull()) { + worker->iPool->waitForDone(aMsecs); + } +} + +BooksTaskQueue::~BooksTaskQueue() +{ + HDEBUG("deleting"); + iPool->waitForDone(); + delete iPool; + HDEBUG("deleted"); +} + +void BooksTaskQueue::submit(BooksTask* aTask) +{ + HASSERT(!aTask->iStarted); + aTask->iStarted = true; + iPool->start(aTask); +} + +void BooksTaskQueue::submit(BooksTask* aTask, QObject* aTarget, + const char* aSlot) +{ + QObject::connect(aTask, SIGNAL(done()), aTarget, aSlot); + submit(aTask); +} diff --git a/app/src/BooksTaskQueue.h b/app/src/BooksTaskQueue.h new file mode 100644 index 0000000..9160f89 --- /dev/null +++ b/app/src/BooksTaskQueue.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_TASK_QUEUE_H +#define BOOKS_TASK_QUEUE_H + +#include "shared_ptr.h" + +#include + +class BooksTask; + +class BooksTaskQueue +{ + friend class shared_ptr_storage; + +public: + static shared_ptr instance(); + static void waitForDone(int aMsecs = -1); + + void submit(BooksTask* aTask); + void submit(BooksTask* aTask, QObject* aTarget, const char* aSlot); + +private: + BooksTaskQueue(); + ~BooksTaskQueue(); + +private: + QThreadPool* iPool; +}; + +#endif // BOOKS_TASK_QUEUE_H diff --git a/app/src/BooksTextStyle.cpp b/app/src/BooksTextStyle.cpp new file mode 100644 index 0000000..cb5612f --- /dev/null +++ b/app/src/BooksTextStyle.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksTextStyle.h" +#include "BooksDefs.h" + +class BooksTextStyle::Default +{ +public: + static weak_ptr instance; + static const std::string FONT_FAMILY; +}; + +weak_ptr BooksTextStyle::Default::instance; +const std::string BooksTextStyle::Default::FONT_FAMILY("Times"); + +shared_ptr BooksTextStyle::defaults() +{ + shared_ptr style = Default::instance; + if (style.isNull()) { + style = new BooksTextStyle; + Default::instance = style; + } + return style; +} + +bool +BooksTextStyle::equalLayout( + shared_ptr aStyle1, + shared_ptr aStyle2) +{ + if (aStyle1.isNull()) aStyle1 = defaults(); + if (aStyle2.isNull()) aStyle2 = defaults(); + return aStyle1->fontSize() == aStyle2->fontSize(); +} + +const std::string& BooksTextStyle::colorStyle() const +{ + return REGULAR_TEXT; +} + +bool BooksTextStyle::isDecorated() const +{ + return false; +} + +const std::string& BooksTextStyle::fontFamily() const +{ + return Default::FONT_FAMILY; +} + +int BooksTextStyle::fontSize() const +{ + return (26 * BOOKS_PPI / 330) & (~1); // Make sure it's divisible by 2 +} + +bool BooksTextStyle::bold() const +{ + return false; +} + +bool BooksTextStyle::italic() const +{ + return false; +} + +short BooksTextStyle::spaceBefore(const ZLTextStyleEntry::Metrics&) const +{ + return 0; +} + +short BooksTextStyle::spaceAfter(const ZLTextStyleEntry::Metrics&) const +{ + return 0; +} + +short BooksTextStyle::lineStartIndent(const ZLTextStyleEntry::Metrics&, bool) const +{ + return 0; +} + +short BooksTextStyle::lineEndIndent(const ZLTextStyleEntry::Metrics&, bool) const +{ + return 0; +} + +short BooksTextStyle::firstLineIndentDelta(const ZLTextStyleEntry::Metrics&) const +{ + return 0; +} + +int BooksTextStyle::verticalShift() const +{ + return 0; +} + +ZLTextAlignmentType BooksTextStyle::alignment() const +{ + return ALIGN_JUSTIFY; +} + +double BooksTextStyle::lineSpace() const +{ + return 1.4; +} + +bool BooksTextStyle::allowHyphenations() const +{ + return true; +} diff --git a/app/src/BooksTextStyle.h b/app/src/BooksTextStyle.h new file mode 100644 index 0000000..3996a84 --- /dev/null +++ b/app/src/BooksTextStyle.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_TEXT_STYLE_H +#define BOOKS_TEXT_STYLE_H + +#include "BooksTypes.h" + +#include + +class BooksTextStyle : public ZLTextStyle +{ + class Default; +public: + static shared_ptr defaults(); + static bool equalLayout(shared_ptr aStyle1, shared_ptr aStyle2); + +private: + static weak_ptr gInstance; + +private: + BooksTextStyle() {} + +public: + bool isDecorated() const; + + const std::string &fontFamily() const; + + int fontSize() const; + bool bold() const; + bool italic() const; + + const std::string &colorStyle() const; + + short spaceBefore(const ZLTextStyleEntry::Metrics& aMetrics) const; + short spaceAfter(const ZLTextStyleEntry::Metrics& aMetrics) const; + short lineStartIndent(const ZLTextStyleEntry::Metrics& aMetrics, bool aRtl) const; + short lineEndIndent(const ZLTextStyleEntry::Metrics& aMetrics, bool aRtl) const; + short firstLineIndentDelta(const ZLTextStyleEntry::Metrics& aMetrics) const; + int verticalShift() const; + + ZLTextAlignmentType alignment() const; + + double lineSpace() const; + bool allowHyphenations() const; +}; + +#endif // BOOKS_TEXT_STYLE_H diff --git a/app/src/BooksTextView.cpp b/app/src/BooksTextView.cpp new file mode 100644 index 0000000..2d9fddf --- /dev/null +++ b/app/src/BooksTextView.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksTextView.h" +#include "BooksTextStyle.h" + +#include "options/FBOptions.h" + +#define SUPER ZLTextView + +BooksTextView::BooksTextView( + ZLPaintContext& aContext, + shared_ptr aTextStyle, + BooksMargins aMargins) : + SUPER(aContext), + iMargins(aMargins), + iBackgroundColor(255, 255, 255), + iTextStyle(aTextStyle) +{ +} + +void BooksTextView::paint() +{ + SUPER::paint(); +} + +const std::string& BooksTextView::caption() const +{ + return iCaption; +} + +int BooksTextView::leftMargin() const +{ + return iMargins.iLeft + iTextStyle->fontSize(); +} + +int BooksTextView::rightMargin() const +{ + return iMargins.iRight + iTextStyle->fontSize(); +} + +int BooksTextView::topMargin() const +{ + return iMargins.iTop; +} + +int BooksTextView::bottomMargin() const +{ + return iMargins.iBottom; +} + +ZLColor BooksTextView::backgroundColor() const +{ + return iBackgroundColor; +} + +ZLColor BooksTextView::color(const std::string &colorStyle) const +{ + return FBOptions::Instance().colorOption(colorStyle).value(); +} + +shared_ptr BooksTextView::baseStyle() const +{ + return iTextStyle; +} + +bool BooksTextView::isSelectionEnabled() const +{ + return false; +} + +int BooksTextView::doubleClickDelay() const +{ + return isSelectionEnabled() ? 200 : 0; +} + +shared_ptr BooksTextView::indicatorInfo() const +{ + return NULL; +} + +const BooksPos BooksTextView::rewind() +{ + SUPER::gotoPosition(0, 0, 0); + preparePaintInfo(); + if (!textArea().isVisible()) nextPage(); + return position(); +} + +bool BooksTextView::nextPage() +{ + BooksPos saved(position()); + BooksPos current(saved); + do { + scrollPage(true, ZLTextAreaController::NO_OVERLAPPING, 1); + preparePaintInfo(); + const BooksPos pos = position(); + if (pos == current) { + gotoPosition(saved); + return false; + } + current = pos; + } while (!textArea().isVisible()); + return true; +} + +void BooksTextView::gotoPosition(const BooksPos& aPos) +{ + SUPER::gotoPosition(aPos.iParagraphIndex, aPos.iElementIndex, + aPos.iCharIndex); +} diff --git a/app/src/BooksTextView.h b/app/src/BooksTextView.h new file mode 100644 index 0000000..8a35be1 --- /dev/null +++ b/app/src/BooksTextView.h @@ -0,0 +1,89 @@ +/* + Copyright (C) 2015 Jolla Ltd. + Contact: Slava Monich + + 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_TEXT_VIEW_H +#define BOOKS_TEXT_VIEW_H + +#include "BooksTypes.h" +#include "BooksPos.h" + +#include "ZLColor.h" +#include "ZLTextView.h" +#include "ZLTextModel.h" +#include "ZLTextStyle.h" +#include "ZLPaintContext.h" + +class BooksTextView: public ZLTextView +{ +public: + BooksTextView(ZLPaintContext& aContext, + shared_ptr aTextStyle, + BooksMargins aMargin); + +public: + BooksPos position() const; + const BooksPos rewind(); + void gotoPosition(const BooksPos& aPos); + bool nextPage(); + void paint(); + + // ZLView + virtual const std::string &caption() const; + virtual ZLColor backgroundColor() const; + + // ZLTextView + virtual shared_ptr indicatorInfo() const; + virtual int doubleClickDelay() const; + virtual int leftMargin() const; + virtual int rightMargin() const; + virtual int topMargin() const; + virtual int bottomMargin() const; + + // ZLTextArea::Properties + virtual shared_ptr baseStyle() const; + virtual ZLColor color(const std::string &style = std::string()) const; + virtual bool isSelectionEnabled() const; + +public: + BooksMargins iMargins; + ZLColor iBackgroundColor; + +private: + std::string iCaption; + shared_ptr iTextStyle; +}; + +inline BooksPos BooksTextView::position() const + { return BooksPos(textArea().startCursor()); } + +#endif // BOOKS_TEXT_VIEW_H diff --git a/app/src/BooksTypes.h b/app/src/BooksTypes.h new file mode 100644 index 0000000..ccf203c --- /dev/null +++ b/app/src/BooksTypes.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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_TYPES_H +#define BOOKS_TYPES_H + +#include "shared_ptr.h" + +class BooksBook; +class BooksShelf; + +struct BooksMargins { + int iLeft; + int iRight; + int iTop; + int iBottom; + BooksMargins() : iLeft(0), iRight(0), iTop(0), iBottom(0) {} +}; + +#endif // BOOKS_TYPES_H diff --git a/app/src/ZLApplication.cpp b/app/src/ZLApplication.cpp new file mode 100644 index 0000000..fb891c4 --- /dev/null +++ b/app/src/ZLApplication.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksDefs.h" + +#include "HarbourDebug.h" + +#include "ZLApplication.h" +#include "ZLApplicationWindow.h" +#include "ZLPaintContext.h" +#include "ZLViewWidget.h" +#include "ZLKeyBindings.h" +#include "ZLPopupData.h" +#include "ZLToolbar.h" +#include "ZLMenu.h" + +// We are not currently using ZLApplication and ZLApplicationWindow but +// there are some references to it from the fbreader code which we must +// resolve. + +ZLApplication *ZLApplication::ourInstance = NULL; + +static const std::string ROTATION = "Rotation"; +static const std::string ANGLE = "Angle"; +static const std::string STATE = "State"; +static const std::string KEYBOARD = "Keyboard"; +static const std::string FULL_CONTROL = "FullControl"; +static const std::string CONFIG = "Config"; +static const std::string AUTO_SAVE = "AutoSave"; +static const std::string TIMEOUT = "Timeout"; + +ZLApplication &ZLApplication::Instance() +{ + if (!ourInstance) new ZLApplication(BOOKS_APP_NAME); + return *ourInstance; +} + +void ZLApplication::deleteInstance() +{ + if (ourInstance) { + delete ourInstance; + ourInstance = NULL; + } +} + +ZLApplication::ZLApplication(const std::string &name) : + ZLApplicationBase(name), + RotationAngleOption(ZLCategoryKey::CONFIG, ROTATION, ANGLE, ZLView::DEGREES90), + AngleStateOption(ZLCategoryKey::CONFIG, STATE, ANGLE, ZLView::DEGREES0), + KeyboardControlOption(ZLCategoryKey::CONFIG, KEYBOARD, FULL_CONTROL, false), + ConfigAutoSavingOption(ZLCategoryKey::CONFIG, CONFIG, AUTO_SAVE, true), + ConfigAutoSaveTimeoutOption(ZLCategoryKey::CONFIG, CONFIG, TIMEOUT, 1, 6000, 30), + KeyDelayOption(ZLCategoryKey::CONFIG, "Options", "KeyDelay", 0, 5000, 250) +{ + ourInstance = this; +} + +ZLApplication::~ZLApplication() +{ +} + +void ZLApplication::initWindow() +{ +} + +shared_ptr ZLApplication::keyBindings() +{ + return NULL; +} + +bool ZLApplication::closeView() +{ + HDEBUG("NO"); + return false; +} + +void ZLApplication::openFile(ZLFile const&) +{ +} + +bool ZLApplication::canDragFiles(std::vector > const&) const +{ + HDEBUG("NO"); + return false; +} + +void ZLApplication::dragFiles(std::vector > const&) +{ + HDEBUG("WHAT?"); +} + +bool ZLApplication::isViewFinal() const +{ + HDEBUG("YES"); + return true; +} + +void ZLApplication::refreshWindow() +{ + HDEBUG("OK"); +} + +void ZLApplication::setHyperlinkCursor(bool aHyperLink) +{ + HDEBUG(aHyperLink); +} + +ZLApplicationWindow &ZLApplicationWindow::Instance() +{ + HDEBUG("THIS IS NOT SUPPOSED TO HAPPEN!"); + return *((ZLApplicationWindow*)NULL); +} + +void ZLApplicationWindow::init() +{ +} + +void ZLApplicationWindow::refresh() +{ +} diff --git a/app/src/ZLibrary.cpp b/app/src/ZLibrary.cpp new file mode 100644 index 0000000..d5007f5 --- /dev/null +++ b/app/src/ZLibrary.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 "BooksDefs.h" +#include "BooksStorage.h" +#include "BooksPaintContext.h" +#include "BooksDialogManager.h" + +#include "HarbourDebug.h" + +#include "ZLibrary.h" +#include "ZLApplication.h" +#include "ZLLanguageUtil.h" + +#include "filesystem/ZLQtFSManager.h" +#include "time/ZLQtTime.h" +#include "image/ZLQtImageManager.h" +#include "iconv/IConvEncodingConverter.h" + +#include + +#include +#include +#include +#include + +#include +#include + +bool ZLibrary::ourLocaleIsInitialized = false; +std::string ZLibrary::ourLanguage; +std::string ZLibrary::ourCountry; +std::string ZLibrary::ourZLibraryDirectory; + +std::string ZLibrary::ourApplicationName; +std::string ZLibrary::ourImageDirectory; +std::string ZLibrary::ourApplicationImageDirectory; +std::string ZLibrary::ourApplicationDirectory; +std::string ZLibrary::ourApplicationWritableDirectory; +std::string ZLibrary::ourDefaultFilesPathPrefix; + +const std::string ZLibrary::FileNameDelimiter("/"); +const std::string ZLibrary::PathDelimiter(":"); +const std::string ZLibrary::EndOfLine("\n"); +const std::string ZLibrary::BaseDirectory; + +void ZLibrary::initLocale() +{ + const char* locale = setlocale(LC_MESSAGES, ""); + HDEBUG(locale); + if (locale) { + std::string sLocale = locale; + const int dotIndex = sLocale.find('.'); + if (dotIndex != -1) sLocale = sLocale.substr(0, dotIndex); + const int dashIndex = std::min(sLocale.find('_'), sLocale.find('-')); + if (dashIndex == -1) { + ourLanguage = sLocale; + } else { + ourLanguage = sLocale.substr(0, dashIndex); + ourCountry = sLocale.substr(dashIndex + 1); + } + } +} + +ZLPaintContext* ZLibrary::createContext() +{ + HDEBUG("creating context"); + return new BooksPaintContext(); +} + +bool ZLibrary::init(int& aArgc, char** &aArgv) +{ + HDEBUG("initializing"); + + ZLibrary::parseArguments(aArgc, aArgv); + + std::string rootDir("/"); + Dl_info info; + void* addr = NULL; + if (backtrace(&addr, 1) && dladdr(addr, &info)) { + // Step two levels up. For an application deployed from QtCreator + // it's going to be /opt/sdk/ directory, for a normally + // installed app - the root directory. + HDEBUG("app path" << info.dli_fname); + char* slash = (char*)strrchr(info.dli_fname, '/'); + if (slash) { + slash[0] = 0; + HDEBUG("app dir" << info.dli_fname); + ourApplicationDirectory = info.dli_fname; + slash = (char*)strrchr(info.dli_fname, '/'); + if (slash) { + slash[0] = 0; + slash = (char*)strrchr(info.dli_fname, '/'); + if (slash) { + slash[1] = 0; + HDEBUG("root dir" << info.dli_fname); + rootDir = info.dli_fname; + } + } + } + } + + ((std::string&)BaseDirectory) = rootDir; + ourApplicationName = BOOKS_APP_NAME; + ourZLibraryDirectory = BaseDirectory + BOOKS_DATA_DIR; + ourApplicationDirectory = BaseDirectory + BOOKS_DATA_ROOT; + ourImageDirectory = BaseDirectory + BOOKS_ICONS_DIR; + ourDefaultFilesPathPrefix = ourZLibraryDirectory + "/"; + ourApplicationImageDirectory = ourImageDirectory; + ourApplicationWritableDirectory = + (QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + + QLatin1String("/" BOOKS_APP_NAME)).toStdString(); + + HDEBUG("zlibrary dir" << ourZLibraryDirectory.c_str()); + HDEBUG("image dir" << ourImageDirectory.c_str()); + HDEBUG("writable dir" << ourApplicationWritableDirectory.c_str()); + + BooksStorageManager::instance(); + ZLQtTimeManager::createInstance(); + ZLQtFSManager::createInstance(); + BooksDialogManager::createInstance(); + ZLQtImageManager::createInstance(); + ZLEncodingCollection::Instance().registerProvider(new IConvEncodingConverterProvider()); + ZLApplication::Instance(); + return true; +} + +void ZLibrary::run(ZLApplication* aApp) +{ + if (ZLLanguageUtil::isRTLLanguage(ZLibrary::Language())) { + qApp->setLayoutDirection(Qt::RightToLeft); + } + + QString qml(QString::fromStdString(BaseDirectory + BOOKS_QML_FILE)); + HDEBUG("qml file" << qPrintable(qml)); + + QQuickView* view = SailfishApp::createView(); + view->setSource(QUrl::fromLocalFile(qml)); + view->rootContext()->setContextProperty("PointsPerInch", BOOKS_PPI); + + view->show(); + HDEBUG("started"); + qApp->exec(); + HDEBUG("exiting..."); +} + +void ZLibrary::parseArguments(int &argc, char **&argv) +{ +} + +void ZLibrary::shutdown() +{ + ZLApplication::deleteInstance(); + ZLImageManager::deleteInstance(); + ZLDialogManager::deleteInstance(); + ZLFSManager::deleteInstance(); + ZLTimeManager::deleteInstance(); + BooksStorageManager::deleteInstance(); +} + +void ZLibrary::initApplication(const std::string& aName) +{ + HDEBUG(aName.c_str()); +} + +std::string ZLibrary::Language() +{ + if (ourLanguage.empty()) { + if (!ourLocaleIsInitialized) { + initLocale(); + ourLocaleIsInitialized = true; + } + } + if (ourLanguage.empty()) { + ourLanguage = "en"; + } + return ourLanguage; +} + +std::string ZLibrary::Country() +{ + if (ourCountry.empty() && !ourLocaleIsInitialized) { + initLocale(); + ourLocaleIsInitialized = true; + } + return ourCountry; +} diff --git a/app/src/libudev.c b/app/src/libudev.c new file mode 100644 index 0000000..87f20cb --- /dev/null +++ b/app/src/libudev.c @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2015 Jolla Ltd. + * Contact: Slava Monich + * + * 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 +#include +#include + +#define LIBUDEV_FUNCTIONS(f) \ + f(struct udev*, udev_unref,\ + (struct udev* udev), \ + (udev)) \ + f(struct udev_monitor*, udev_monitor_new_from_netlink, \ + (struct udev* udev, const char* name), \ + (udev, name)) \ + f(struct udev_device*, udev_monitor_receive_device, \ + (struct udev_monitor* mon), \ + (mon)) \ + f(int, udev_monitor_filter_add_match_subsystem_devtype, \ + (struct udev_monitor* mon, const char* sub, const char* dev), \ + (mon, sub, dev)) \ + f(int, udev_monitor_enable_receiving, \ + (struct udev_monitor* mon), \ + (mon)) \ + f(int, udev_monitor_get_fd, \ + (struct udev_monitor* mon), \ + (mon)) \ + f(struct udev_monitor*, udev_monitor_unref, \ + (struct udev_monitor* mon), \ + (mon)) \ + f(const char*, udev_device_get_devnode, \ + (struct udev_device* dev), \ + (dev)) \ + f(const char*, udev_device_get_devpath, \ + (struct udev_device* dev), \ + (dev)) \ + f(const char*, udev_device_get_subsystem, \ + (struct udev_device* dev), \ + (dev)) \ + f(const char*, udev_device_get_devtype, \ + (struct udev_device* dev), \ + (dev)) \ + f(const char*, udev_device_get_action, \ + (struct udev_device* dev), \ + (dev)) \ + f(struct udev_device*, udev_device_unref, \ + (struct udev_device* dev), \ + (dev)) + +static struct { + void* handle; + union { + struct libudev_proc { + struct udev* (*udev_new)(void); + #define FN_POINTER(ret,name,params,args) ret (* name) params; + LIBUDEV_FUNCTIONS(FN_POINTER) + } udev; + void* entry[1]; + } fn; +} libudev; + +static const char* libudev_names[] = { + "udev_new", + #define FN_NAME(ret,name,params,args) #name, + LIBUDEV_FUNCTIONS(FN_NAME) +}; + +#define LIBUDEV_NUM_FUNCTIONS (sizeof(libudev_names)/sizeof(libudev_names[0])) +#define LIBUDEV_NO_HANDLE ((void*)-1) +#define LIBUDEV_SO "/usr/lib/libudev.so.1" + +/* udev_new() is the special function where we load the library */ +struct udev* +udev_new() +{ + if (!libudev.handle) { + libudev.handle = dlopen(LIBUDEV_SO, RTLD_LAZY); + if (libudev.handle) { + unsigned int i; + for (i=0; i + * + * 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 "BooksDefs.h" +#include "BooksShelf.h" +#include "BooksBook.h" +#include "BooksBookModel.h" +#include "BooksCoverModel.h" +#include "BooksConfig.h" +#include "BooksSettings.h" +#include "BooksImportModel.h" +#include "BooksStorageModel.h" +#include "BooksPageWidget.h" +#include "BooksListWatcher.h" +#include "BooksCoverWidget.h" +#include "BooksTaskQueue.h" + +#include "HarbourDebug.h" +#include "HarbourLib.h" + +#include "ZLibrary.h" + +#include + +#include + +int main(int argc, char **argv) +{ + QGuiApplication* app = SailfishApp::application(argc, argv); + BOOKS_QML_REGISTER(BooksShelf, "Shelf"); + BOOKS_QML_REGISTER(BooksBook, "Book"); + BOOKS_QML_REGISTER(BooksBookModel, "BookModel"); + BOOKS_QML_REGISTER(BooksCoverModel, "CoverModel"); + BOOKS_QML_REGISTER(BooksImportModel, "BooksImportModel"); + BOOKS_QML_REGISTER(BooksStorageModel, "BookStorage"); + BOOKS_QML_REGISTER(BooksPageWidget, "PageWidget"); + BOOKS_QML_REGISTER(BooksListWatcher, "ListWatcher"); + BOOKS_QML_REGISTER(BooksCoverWidget, "BookCover"); + BOOKS_QML_REGISTER(BooksSettings, "Settings"); + HarbourLib::registerTypes(BOOKS_QML_PLUGIN, + BOOKS_QML_PLUGIN_V1, BOOKS_QML_PLUGIN_V2); + + QLocale locale; + QTranslator* translator = new QTranslator(app); + QString transDir = SailfishApp::pathTo("translations").toLocalFile(); + QString transFile(BOOKS_APP_NAME); + if (translator->load(locale, transFile, "-", transDir) || + translator->load(transFile, transDir)) { + app->installTranslator(translator); + } else { + HDEBUG("Failed to load translator for" << locale); + delete translator; + } + + BooksConfigManager configManager; + if (ZLibrary::init(argc, argv)) { + ZLibrary::run(NULL); + BooksTaskQueue::waitForDone(); + ZLibrary::shutdown(); + } + + delete app; + return 0; +} diff --git a/app/translations/harbour-books-ru.ts b/app/translations/harbour-books-ru.ts new file mode 100644 index 0000000..96b6ef8 --- /dev/null +++ b/app/translations/harbour-books-ru.ts @@ -0,0 +1,75 @@ + + + + + + + Use default fonts + Шрифты по умолчанию + + + Use smaller fonts + Шрифты поменьше + + + Use larger fonts + Шрифты побольше + + + Back to library + Вернуться в библиотеку + + + Loading... + Загрузка... + + + Deleting all books + Удаляем все книги + + + No books + Здесь пусто, нет ничего вообще + + + Memory card + Карта памяти + + + Internal storage + Встроенная память + + + %0 book(s) + + %0 книга + %0 книги + %0 книг + + + + Scan downloads + Проверить загрузки + + + Delete all books + Удалить все книги + + + Import %0 book(s) + + Взять %0 книгу + Взять %0 книги + Взять %0 книг + + + + Select books + Выбирайте + + + No new books found + Новых книг не найдено, вообще ни одной + + + diff --git a/app/translations/harbour-books.ts b/app/translations/harbour-books.ts new file mode 100644 index 0000000..ccb1ae6 --- /dev/null +++ b/app/translations/harbour-books.ts @@ -0,0 +1,73 @@ + + + + + + + Use default fonts + Use default fonts + + + Use smaller fonts + Use smaller fonts + + + Use larger fonts + Use larger fonts + + + Back to library + Back to library + + + Loading... + Loading... + + + Deleting all books + Deleting all books + + + No books + No books + + + Memory card + Memory card + + + Internal storage + Internal storage + + + %0 book(s) + + %0 book + %0 books + + + + Scan downloads + Check downloads + + + Delete all books + Delete all books + + + Import %0 book(s) + + Import %0 book + Import %0 books + + + + Select books + Select books + + + No new books found + No new books found + + + diff --git a/fbreader/fbreader.pro b/fbreader/fbreader.pro new file mode 100644 index 0000000..2ee2cd9 --- /dev/null +++ b/fbreader/fbreader.pro @@ -0,0 +1,484 @@ +TEMPLATE = lib +TARGET = fbreader +CONFIG += staticlib object_parallel_to_source link_pkgconfig +PKGCONFIG += expat glib-2.0 + +!include(../common.pri) + +# Directories +FRIBIDI_DIR = $$_PRO_FILE_PWD_/../fribidi +LINEBREAK_DIR = $$_PRO_FILE_PWD_/../linebreak +FBREADER_DIR = fbreader + +DEFINES += \ + VERSION="\\\"0.99\\\"" \ + FBREADER_DISABLE_ZLFILE_PLAIN_STREAM_CACHE=1 \ + FBREADER_DISABLE_BOOKS_DB=1 + +# Core +CORE = $$FBREADER_DIR/zlibrary/core +CORE_SRC = $$CORE/src + +INCLUDEPATH += $$CORE/include + +SOURCES += \ + $$CORE_SRC/encoding/MyEncodingConverter.cpp \ + $$CORE_SRC/encoding/EncodingCollectionReader.cpp \ + $$CORE_SRC/encoding/DummyEncodingConverter.cpp \ + $$CORE_SRC/encoding/ZLEncodingSet.cpp \ + $$CORE_SRC/encoding/ZLEncodingConverter.cpp \ + $$CORE_SRC/encoding/ZLEncodingCollection.cpp \ + $$CORE_SRC/typeId/ZLTypeId.cpp \ + $$CORE_SRC/language/ZLLanguageDetector.cpp \ + $$CORE_SRC/language/ZLStatistics.cpp \ + $$CORE_SRC/language/ZLStatisticsGenerator.cpp \ + $$CORE_SRC/language/ZLStatisticsXMLReader.cpp \ + $$CORE_SRC/language/ZLStatisticsXMLWriter.cpp \ + $$CORE_SRC/language/ZLLanguageList.cpp \ + $$CORE_SRC/language/ZLCharSequence.cpp \ + $$CORE_SRC/language/ZLStatisticsItem.cpp \ + $$CORE_SRC/language/ZLLanguageMatcher.cpp \ + $$CORE_SRC/options/ZLConfig.cpp \ + $$CORE_SRC/options/ZLOptions.cpp \ + $$CORE_SRC/options/ZLCategoryKey.cpp \ + $$CORE_SRC/runnable/ZLRunnable.cpp \ + $$CORE_SRC/runnable/ZLExecutionData.cpp \ + $$CORE_SRC/application/ZLApplicationBase.cpp \ + $$CORE_SRC/application/ZLKeyBindings.cpp \ + $$CORE_SRC/util/ZLUnicodeUtil.cpp \ + $$CORE_SRC/util/ZLKeyUtil.cpp \ + $$CORE_SRC/util/ZLUserData.cpp \ + $$CORE_SRC/util/ZLFileUtil.cpp \ + $$CORE_SRC/util/ZLSearchUtil.cpp \ + $$CORE_SRC/util/ZLStringUtil.cpp \ + $$CORE_SRC/util/ZLLanguageUtil.cpp \ + $$CORE_SRC/dialogs/ZLDialogManager.cpp \ + $$CORE_SRC/dialogs/ZLDialog.cpp \ + $$CORE_SRC/dialogs/ZLOptionEntry.cpp \ + $$CORE_SRC/dialogs/ZLOptionsDialog.cpp \ + $$CORE_SRC/dialogs/ZLProgressDialog.cpp \ + $$CORE_SRC/dialogs/ZLOptionView.cpp \ + $$CORE_SRC/dialogs/ZLDialogContent.cpp \ + $$CORE_SRC/dialogs/ZLDialogContentBuilder.cpp \ + $$CORE_SRC/dialogs/ZLOpenFileDialog.cpp \ + $$CORE_SRC/time/ZLTimeManager.cpp \ + $$CORE_SRC/time/ZLTime.cpp \ + $$CORE_SRC/logger/ZLLogger.cpp \ + $$CORE_SRC/optionEntries/ZLSimpleOptionEntry.cpp \ + $$CORE_SRC/xml/ZLXMLWriter.cpp \ + $$CORE_SRC/xml/ZLXMLReader.cpp \ + $$CORE_SRC/xml/expat/ZLXMLReaderInternal.cpp \ + $$CORE_SRC/view/ZLPaintContext.cpp \ + $$CORE_SRC/view/ZLView.cpp \ + $$CORE_SRC/view/ZLMirroredPaintContext.cpp \ + $$CORE_SRC/filesystem/tar/ZLTar.cpp \ + $$CORE_SRC/filesystem/ZLInputStreamDecorator.cpp \ + $$CORE_SRC/filesystem/ZLFile.cpp \ + $$CORE_SRC/filesystem/ZLDir.cpp \ + $$CORE_SRC/filesystem/ZLFSManager.cpp \ + $$CORE_SRC/filesystem/zip/ZLZipInputStream.cpp \ + $$CORE_SRC/filesystem/zip/ZLZipDir.cpp \ + $$CORE_SRC/filesystem/zip/ZLZipEntryCache.cpp \ + $$CORE_SRC/filesystem/zip/ZLZipHeader.cpp \ + $$CORE_SRC/filesystem/zip/ZLZDecompressor.cpp \ + $$CORE_SRC/filesystem/zip/ZLGzipInputStream.cpp \ + $$CORE_SRC/filesystem/bzip2/ZLBzip2InputStream.cpp \ + $$CORE_SRC/resources/ZLResource.cpp \ + $$CORE_SRC/image/ZLBase64EncodedImage.cpp \ + $$CORE_SRC/image/ZLNetworkImage.cpp \ + $$CORE_SRC/image/ZLImage.cpp \ + $$CORE_SRC/image/ZLFileImage.cpp \ + $$CORE_SRC/image/ZLStreamImage.cpp \ + $$CORE_SRC/image/ZLImageManager.cpp \ + $$CORE_SRC/unix/iconv/IConvEncodingConverter.cpp \ + $$CORE_SRC/unix/time/ZLUnixTime.cpp \ + $$CORE_SRC/unix/filesystem/ZLUnixFSManager.cpp \ + $$CORE_SRC/unix/filesystem/ZLUnixFileInputStream.cpp \ + $$CORE_SRC/unix/filesystem/ZLUnixFSDir.cpp \ + $$CORE_SRC/unix/filesystem/ZLUnixFileOutputStream.cpp \ + $$CORE_SRC/network/ZLAsynchronousInputStream.cpp \ + $$CORE_SRC/network/ZLPlainAsynchronousInputStream.cpp \ + $$CORE_SRC/constants/ZLXMLNamespace.cpp \ + $$CORE_SRC/constants/ZLMimeType.cpp + +HEADERS += \ + $$CORE_SRC/encoding/EncodingCollectionReader.h \ + $$CORE_SRC/encoding/DummyEncodingConverter.h \ + $$CORE_SRC/encoding/ZLEncodingConverterProvider.h \ + $$CORE_SRC/encoding/MyEncodingConverter.h \ + $$CORE_SRC/encoding/ZLEncodingConverter.h \ + $$CORE_SRC/typeId/ZLTypeId.h \ + $$CORE_SRC/language/ZLLanguageList.h \ + $$CORE_SRC/language/ZLStatistics.h \ + $$CORE_SRC/language/ZLStatisticsGenerator.h \ + $$CORE_SRC/language/ZLLanguageMatcher.h \ + $$CORE_SRC/language/ZLLanguageDetector.h \ + $$CORE_SRC/language/ZLStatisticsXMLWriter.h \ + $$CORE_SRC/language/ZLStatisticsItem.h \ + $$CORE_SRC/language/ZLCharSequence.h \ + $$CORE_SRC/language/ZLStatisticsXMLReader.h \ + $$CORE_SRC/options/ZLOptions.h \ + $$CORE_SRC/options/ZLConfig.h \ + $$CORE_SRC/runnable/ZLExecutionData.h \ + $$CORE_SRC/runnable/ZLRunnable.h \ + $$CORE_SRC/application/ZLApplication.h \ + $$CORE_SRC/application/ZLKeyBindings.h \ + $$CORE_SRC/application/ZLApplicationWindow.h \ + $$CORE_SRC/util/ZLStringUtil.h \ + $$CORE_SRC/util/ZLUserData.h \ + $$CORE_SRC/util/ZLBoolean3.h \ + $$CORE_SRC/util/shared_ptr.h \ + $$CORE_SRC/util/ZLFileUtil.h \ + $$CORE_SRC/util/ZLColor.h \ + $$CORE_SRC/util/ZLUnicodeUtil.h \ + $$CORE_SRC/util/ZLSearchUtil.h \ + $$CORE_SRC/util/allocator.h \ + $$CORE_SRC/util/ZLKeyUtil.h \ + $$CORE_SRC/util/ZLLanguageUtil.h \ + $$CORE_SRC/dialogs/ZLOptionsDialog.h \ + $$CORE_SRC/dialogs/ZLDialogManager.h \ + $$CORE_SRC/dialogs/ZLOptionView.h \ + $$CORE_SRC/dialogs/ZLDialog.h \ + $$CORE_SRC/dialogs/ZLProgressDialog.h \ + $$CORE_SRC/dialogs/ZLOptionEntry.h \ + $$CORE_SRC/dialogs/ZLDialogContent.h \ + $$CORE_SRC/dialogs/ZLDialogContentBuilder.h \ + $$CORE_SRC/dialogs/ZLOpenFileDialog.h \ + $$CORE_SRC/library/ZLibrary.h \ + $$CORE_SRC/time/ZLTime.h \ + $$CORE_SRC/time/ZLTimeManager.h \ + $$CORE_SRC/logger/ZLLogger.h \ + $$CORE_SRC/optionEntries/ZLSimpleOptionEntry.h \ + $$CORE_SRC/xml/ZLXMLWriter.h \ + $$CORE_SRC/xml/ZLXMLReader.h \ + $$CORE_SRC/xml/expat/ZLXMLReaderInternal.h \ + $$CORE_SRC/view/ZLPaintContext.h \ + $$CORE_SRC/view/ZLMirroredPaintContext.h \ + $$CORE_SRC/view/ZLViewWidget.h \ + $$CORE_SRC/view/ZLView.h \ + $$CORE_SRC/filesystem/tar/ZLTar.h \ + $$CORE_SRC/filesystem/ZLInputStream.h \ + $$CORE_SRC/filesystem/ZLFSManager.h \ + $$CORE_SRC/filesystem/ZLDir.h \ + $$CORE_SRC/filesystem/ZLFileInfo.h \ + $$CORE_SRC/filesystem/zip/ZLZDecompressor.h \ + $$CORE_SRC/filesystem/zip/ZLZipHeader.h \ + $$CORE_SRC/filesystem/zip/ZLZip.h \ + $$CORE_SRC/filesystem/ZLOutputStream.h \ + $$CORE_SRC/filesystem/ZLFile.h \ + $$CORE_SRC/filesystem/ZLFSDir.h \ + $$CORE_SRC/filesystem/bzip2/ZLBzip2InputStream.h \ + $$CORE_SRC/resources/ZLResource.h \ + $$CORE_SRC/image/ZLImage.h \ + $$CORE_SRC/image/ZLBase64EncodedImage.h \ + $$CORE_SRC/image/ZLFileImage.h \ + $$CORE_SRC/image/ZLStreamImage.h \ + $$CORE_SRC/image/ZLImageManager.h \ + $$CORE_SRC/image/ZLNetworkImage.h \ + $$CORE_SRC/unix/iconv/IConvEncodingConverter.h \ + $$CORE_SRC/unix/library/ZLibraryImplementation.h \ + $$CORE_SRC/unix/time/ZLUnixTime.h \ + $$CORE_SRC/unix/filesystem/ZLUnixFSManager.h \ + $$CORE_SRC/unix/filesystem/ZLUnixFileInputStream.h \ + $$CORE_SRC/unix/filesystem/ZLUnixFileOutputStream.h \ + $$CORE_SRC/unix/filesystem/ZLUnixFSDir.h \ + $$CORE_SRC/network/ZLAsynchronousInputStream.h \ + $$CORE_SRC/network/ZLPlainAsynchronousInputStream.h \ + $$CORE_SRC/constants/ZLMimeType.h \ + $$CORE_SRC/constants/ZLXMLNamespace.h + +# Text +TEXT = $$FBREADER_DIR/zlibrary/text +TEXT_SRC = $$TEXT/src + +INCLUDEPATH += \ + $$TEXT/include \ + $$LINEBREAK_DIR/include \ + $$FRIBIDI_DIR/include + +SOURCES += \ + $$TEXT_SRC/style/ZLTextStyle.cpp \ + $$TEXT_SRC/style/ZLTextDecoratedStyle.cpp \ + $$TEXT_SRC/style/ZLTextStyleCollection.cpp \ + $$TEXT_SRC/model/ZLTextModel.cpp \ + $$TEXT_SRC/model/ZLTextParagraph.cpp \ + $$TEXT_SRC/model/ZLTextRowMemoryAllocator.cpp \ + $$TEXT_SRC/hyphenation/ZLTextTeXHyphenator.cpp \ + $$TEXT_SRC/hyphenation/ZLTextHyphenator.cpp \ + $$TEXT_SRC/hyphenation/ZLTextHyphenationReader.cpp \ + $$TEXT_SRC/view/ZLTextPositionIndicator.cpp \ + $$TEXT_SRC/view/ZLTextSelectionScroller.cpp \ + $$TEXT_SRC/view/ZLTextView_paint.cpp \ + $$TEXT_SRC/view/ZLTextView.cpp \ + $$TEXT_SRC/area/ZLTextArea.cpp \ + $$TEXT_SRC/area/ZLTextArea_processTextLine.cpp \ + $$TEXT_SRC/area/ZLTextArea_drawTextLine.cpp \ + $$TEXT_SRC/area/ZLTextAreaController.cpp \ + $$TEXT_SRC/area/ZLTextParagraphBuilder.cpp \ + $$TEXT_SRC/area/ZLTextParagraphCursor.cpp \ + $$TEXT_SRC/area/ZLTextWord.cpp \ + $$TEXT_SRC/area/ZLTextElement.cpp \ + $$TEXT_SRC/area/ZLTextAreaStyle.cpp \ + $$TEXT_SRC/area/ZLTextArea_drawWord.cpp \ + $$TEXT_SRC/area/ZLTextArea_drawTreeLines.cpp \ + $$TEXT_SRC/area/ZLTextArea_prepareTextLine.cpp \ + $$TEXT_SRC/area/ZLTextSelectionModel.cpp + +HEADERS += \ + $$TEXT_SRC/style/ZLTextStyleCollection.h \ + $$TEXT_SRC/style/ZLTextStyle.h \ + $$TEXT_SRC/style/ZLTextDecoratedStyle.h \ + $$TEXT_SRC/model/ZLTextAlignmentType.h \ + $$TEXT_SRC/model/ZLTextFontModifier.h \ + $$TEXT_SRC/model/ZLTextParagraph.h \ + $$TEXT_SRC/model/ZLTextModel.h \ + $$TEXT_SRC/model/ZLTextMark.h \ + $$TEXT_SRC/model/ZLTextRowMemoryAllocator.h \ + $$TEXT_SRC/model/ZLTextKind.h \ + $$TEXT_SRC/hyphenation/ZLTextHyphenator.h \ + $$TEXT_SRC/hyphenation/ZLTextHyphenationReader.h \ + $$TEXT_SRC/hyphenation/ZLTextTeXHyphenator.h \ + $$TEXT_SRC/view/ZLTextView.h \ + $$TEXT_SRC/view/ZLTextPositionIndicatorInfo.h \ + $$TEXT_SRC/view/ZLTextPositionIndicator.h \ + $$TEXT_SRC/view/ZLTextSelectionScroller.h \ + $$TEXT_SRC/area/ZLTextWord.h \ + $$TEXT_SRC/area/ZLTextElement.h \ + $$TEXT_SRC/area/ZLTextRectangle.h \ + $$TEXT_SRC/area/ZLTextAreaStyle.h \ + $$TEXT_SRC/area/ZLTextParagraphBuilder.h \ + $$TEXT_SRC/area/ZLTextAreaController.h \ + $$TEXT_SRC/area/ZLTextLineInfo.h \ + $$TEXT_SRC/area/ZLTextSelectionModel.h \ + $$TEXT_SRC/area/ZLTextParagraphCursor.h \ + $$TEXT_SRC/area/ZLTextArea.h + +# UI + +UI_SRC = $$FBREADER_DIR/zlibrary/ui/src/qt4 + +SOURCES += \ + $$UI_SRC/time/ZLQtTime.cpp \ + $$UI_SRC/filesystem/ZLQtFSManager.cpp \ + $$UI_SRC/image/ZLQtImageManager.cpp + +HEADERS += \ + $$UI_SRC/time/ZLQtTime.h \ + $$UI_SRC/filesystem/ZLQtFSManager.h \ + $$UI_SRC/image/ZLQtImageManager.h + +# FBReader + +FBREADER_SRC = $$FBREADER_DIR/fbreader/src + +SOURCES += \ + $$FBREADER_SRC/migration/BookInfo.cpp \ + $$FBREADER_SRC/options/FBOptions.cpp \ + $$FBREADER_SRC/options/FBCategoryKey.cpp \ + $$FBREADER_SRC/libraryActions/AuthorInfoDialog.cpp \ + $$FBREADER_SRC/libraryActions/LibraryTagActions.cpp \ + $$FBREADER_SRC/libraryActions/LibraryBookActions.cpp \ + $$FBREADER_SRC/libraryActions/LibraryAuthorActions.cpp \ + $$FBREADER_SRC/libraryActions/BooksUtil.cpp \ + $$FBREADER_SRC/fbreader/PreferencesPopupData.cpp \ + $$FBREADER_SRC/fbreader/FBReaderActions.cpp \ + $$FBREADER_SRC/fbreader/TimeUpdater.cpp \ + $$FBREADER_SRC/fbreader/SearchActions.cpp \ + $$FBREADER_SRC/fbreader/ContentsView.cpp \ + $$FBREADER_SRC/fbreader/BooksOrderAction.cpp \ + $$FBREADER_SRC/fbreader/BookTextView.cpp \ + $$FBREADER_SRC/fbreader/FBReaderActionCode.cpp \ + $$FBREADER_SRC/fbreader/RecentBooksPopupData.cpp \ + $$FBREADER_SRC/fbreader/SearchOnNetworkAction.cpp \ + $$FBREADER_SRC/fbreader/ScrollingAction.cpp \ + $$FBREADER_SRC/fbreader/AddBookAction.cpp \ + $$FBREADER_SRC/library/Book.cpp \ + $$FBREADER_SRC/library/Comparators.cpp \ + $$FBREADER_SRC/library/Library.cpp \ + $$FBREADER_SRC/library/Tag.cpp \ + $$FBREADER_SRC/library/Author.cpp \ + $$FBREADER_SRC/formats/dummy/DummyBookReader.cpp \ + $$FBREADER_SRC/formats/dummy/DummyMetaInfoReader.cpp \ + $$FBREADER_SRC/formats/fb2/FB2MetaInfoReader.cpp \ + $$FBREADER_SRC/formats/fb2/FB2TagManager.cpp \ + $$FBREADER_SRC/formats/fb2/FB2Plugin.cpp \ + $$FBREADER_SRC/formats/fb2/FB2Reader.cpp \ + $$FBREADER_SRC/formats/fb2/FB2BookReader.cpp \ + $$FBREADER_SRC/formats/fb2/FB2CoverReader.cpp \ + $$FBREADER_SRC/formats/openreader/OpenReaderPlugin.cpp \ + $$FBREADER_SRC/formats/openreader/ORDescriptionReader.cpp \ + $$FBREADER_SRC/formats/openreader/ORBookReader.cpp \ + $$FBREADER_SRC/formats/util/TextFormatDetector.cpp \ + $$FBREADER_SRC/formats/util/EntityFilesCollector.cpp \ + $$FBREADER_SRC/formats/util/MiscUtil.cpp \ + $$FBREADER_SRC/formats/util/XMLTextStream.cpp \ + $$FBREADER_SRC/formats/util/MergedStream.cpp \ + $$FBREADER_SRC/formats/FormatPlugin.cpp \ + $$FBREADER_SRC/formats/EncodedTextReader.cpp \ + $$FBREADER_SRC/formats/css/StyleSheetParser.cpp \ + $$FBREADER_SRC/formats/css/StyleSheetTable.cpp \ + $$FBREADER_SRC/formats/tcr/TcrPlugin.cpp \ + $$FBREADER_SRC/formats/tcr/TcrStream.cpp \ + $$FBREADER_SRC/formats/tcr/PPLBookReader.cpp \ + $$FBREADER_SRC/formats/rtf/RtfReaderStream.cpp \ + $$FBREADER_SRC/formats/rtf/RtfBookReader.cpp \ + $$FBREADER_SRC/formats/rtf/RtfReader.cpp \ + $$FBREADER_SRC/formats/rtf/RtfDescriptionReader.cpp \ + $$FBREADER_SRC/formats/rtf/RtfPlugin.cpp \ + $$FBREADER_SRC/formats/rtf/RtfImage.cpp \ + $$FBREADER_SRC/formats/pdb/PmlBookReader.cpp \ + $$FBREADER_SRC/formats/pdb/PalmDocLikePlugin.cpp \ + $$FBREADER_SRC/formats/pdb/HuffDecompressor.cpp \ + $$FBREADER_SRC/formats/pdb/EReaderStream.cpp \ + $$FBREADER_SRC/formats/pdb/MobipocketPlugin.cpp \ + $$FBREADER_SRC/formats/pdb/PluckerBookReader.cpp \ + $$FBREADER_SRC/formats/pdb/PalmDocLikeStream.cpp \ + $$FBREADER_SRC/formats/pdb/PluckerImages.cpp \ + $$FBREADER_SRC/formats/pdb/PdbStream.cpp \ + $$FBREADER_SRC/formats/pdb/PmlReader.cpp \ + $$FBREADER_SRC/formats/pdb/BitReader.cpp \ + $$FBREADER_SRC/formats/pdb/PluckerTextStream.cpp \ + $$FBREADER_SRC/formats/pdb/MobipocketHtmlBookReader.cpp \ + $$FBREADER_SRC/formats/pdb/ZTXTStream.cpp \ + $$FBREADER_SRC/formats/pdb/ZTXTPlugin.cpp \ + $$FBREADER_SRC/formats/pdb/PdbPlugin.cpp \ + $$FBREADER_SRC/formats/pdb/HtmlMetainfoReader.cpp \ + $$FBREADER_SRC/formats/pdb/PdbReader.cpp \ + $$FBREADER_SRC/formats/pdb/PalmDocStream.cpp \ + $$FBREADER_SRC/formats/pdb/PalmDocPlugin.cpp \ + $$FBREADER_SRC/formats/pdb/DocDecompressor.cpp \ + $$FBREADER_SRC/formats/pdb/SimplePdbPlugin.cpp \ + $$FBREADER_SRC/formats/pdb/PluckerPlugin.cpp \ + $$FBREADER_SRC/formats/pdb/EReaderPlugin.cpp \ + $$FBREADER_SRC/formats/xhtml/XHTMLReader.cpp \ + $$FBREADER_SRC/formats/oeb/OEBPlugin.cpp \ + $$FBREADER_SRC/formats/oeb/OEBMetaInfoReader.cpp \ + $$FBREADER_SRC/formats/oeb/OEBCoverReader.cpp \ + $$FBREADER_SRC/formats/oeb/OEBTextStream.cpp \ + $$FBREADER_SRC/formats/oeb/OEBBookReader.cpp \ + $$FBREADER_SRC/formats/oeb/NCXReader.cpp \ + $$FBREADER_SRC/formats/PluginCollection.cpp \ + $$FBREADER_SRC/formats/html/HtmlPlugin.cpp \ + $$FBREADER_SRC/formats/html/HtmlReaderStream.cpp \ + $$FBREADER_SRC/formats/html/HtmlEntityCollection.cpp \ + $$FBREADER_SRC/formats/html/HtmlBookReader.cpp \ + $$FBREADER_SRC/formats/html/HtmlDescriptionReader.cpp \ + $$FBREADER_SRC/formats/html/HtmlReader.cpp \ + $$FBREADER_SRC/formats/chm/CHMFileImage.cpp \ + $$FBREADER_SRC/formats/chm/HHCReferenceCollector.cpp \ + $$FBREADER_SRC/formats/chm/CHMReferenceCollection.cpp \ + $$FBREADER_SRC/formats/chm/BitStream.cpp \ + $$FBREADER_SRC/formats/chm/HtmlSectionReader.cpp \ + $$FBREADER_SRC/formats/chm/LZXDecompressor.cpp \ + $$FBREADER_SRC/formats/chm/HuffmanDecoder.cpp \ + $$FBREADER_SRC/formats/chm/CHMFile.cpp \ + $$FBREADER_SRC/formats/chm/HHCReader.cpp \ + $$FBREADER_SRC/formats/chm/CHMPlugin.cpp \ + $$FBREADER_SRC/formats/chm/E8Decoder.cpp \ + $$FBREADER_SRC/formats/txt/TxtReader.cpp \ + $$FBREADER_SRC/formats/txt/PlainTextFormat.cpp \ + $$FBREADER_SRC/formats/txt/TxtBookReader.cpp \ + $$FBREADER_SRC/formats/txt/TxtPlugin.cpp \ + $$FBREADER_SRC/bookmodel/BookReader.cpp \ + $$FBREADER_SRC/bookmodel/BookModel.cpp \ + +HEADERS += \ + $$FBREADER_SRC/migration/BookInfo.h \ + $$FBREADER_SRC/options/FBOptions.h \ + $$FBREADER_SRC/libraryActions/LibraryAuthorActions.h \ + $$FBREADER_SRC/libraryActions/AuthorInfoDialog.h \ + $$FBREADER_SRC/libraryActions/LibraryBookActions.h \ + $$FBREADER_SRC/libraryActions/BooksUtil.h \ + $$FBREADER_SRC/libraryActions/LibraryTagActions.h \ + $$FBREADER_SRC/fbreader/FBView.h \ + $$FBREADER_SRC/fbreader/ReadingState.h \ + $$FBREADER_SRC/fbreader/FBReaderActions.h \ + $$FBREADER_SRC/fbreader/PreferencesPopupData.h \ + $$FBREADER_SRC/fbreader/TimeUpdater.h \ + $$FBREADER_SRC/fbreader/ContentsView.h \ + $$FBREADER_SRC/fbreader/RecentBooksPopupData.h \ + $$FBREADER_SRC/fbreader/FootnoteView.h \ + $$FBREADER_SRC/fbreader/ScrollingAction.h \ + $$FBREADER_SRC/fbreader/BookTextView.h \ + $$FBREADER_SRC/library/Author.h \ + $$FBREADER_SRC/library/Book.h \ + $$FBREADER_SRC/library/Lists.h \ + $$FBREADER_SRC/library/Library.h \ + $$FBREADER_SRC/library/Tag.h \ + $$FBREADER_SRC/formats/dummy/DummyBookReader.h \ + $$FBREADER_SRC/formats/dummy/DummyMetaInfoReader.h \ + $$FBREADER_SRC/formats/fb2/FB2Plugin.h \ + $$FBREADER_SRC/formats/fb2/FB2MetaInfoReader.h \ + $$FBREADER_SRC/formats/fb2/FB2TagManager.h \ + $$FBREADER_SRC/formats/fb2/FB2BookReader.h \ + $$FBREADER_SRC/formats/fb2/FB2Reader.h \ + $$FBREADER_SRC/formats/fb2/FB2CoverReader.h \ + $$FBREADER_SRC/formats/openreader/OpenReaderPlugin.h \ + $$FBREADER_SRC/formats/openreader/ORDescriptionReader.h \ + $$FBREADER_SRC/formats/openreader/ORBookReader.h \ + $$FBREADER_SRC/formats/EncodedTextReader.h \ + $$FBREADER_SRC/formats/util/TextFormatDetector.h \ + $$FBREADER_SRC/formats/util/MergedStream.h \ + $$FBREADER_SRC/formats/util/MiscUtil.h \ + $$FBREADER_SRC/formats/util/EntityFilesCollector.h \ + $$FBREADER_SRC/formats/util/XMLTextStream.h \ + $$FBREADER_SRC/formats/css/StyleSheetParser.h \ + $$FBREADER_SRC/formats/css/StyleSheetTable.h \ + $$FBREADER_SRC/formats/tcr/TcrStream.h \ + $$FBREADER_SRC/formats/tcr/TcrPlugin.h \ + $$FBREADER_SRC/formats/tcr/PPLBookReader.h \ + $$FBREADER_SRC/formats/rtf/RtfPlugin.h \ + $$FBREADER_SRC/formats/rtf/RtfBookReader.h \ + $$FBREADER_SRC/formats/rtf/RtfReader.h \ + $$FBREADER_SRC/formats/rtf/RtfReaderStream.h \ + $$FBREADER_SRC/formats/rtf/RtfImage.h \ + $$FBREADER_SRC/formats/rtf/RtfDescriptionReader.h \ + $$FBREADER_SRC/formats/FormatPlugin.h \ + $$FBREADER_SRC/formats/pdb/ZTXTStream.h \ + $$FBREADER_SRC/formats/pdb/PalmDocStream.h \ + $$FBREADER_SRC/formats/pdb/PdbReader.h \ + $$FBREADER_SRC/formats/pdb/PmlBookReader.h \ + $$FBREADER_SRC/formats/pdb/PdbStream.h \ + $$FBREADER_SRC/formats/pdb/PluckerImages.h \ + $$FBREADER_SRC/formats/pdb/PalmDocLikeStream.h \ + $$FBREADER_SRC/formats/pdb/HtmlMetainfoReader.h \ + $$FBREADER_SRC/formats/pdb/MobipocketHtmlBookReader.h \ + $$FBREADER_SRC/formats/pdb/BitReader.h \ + $$FBREADER_SRC/formats/pdb/HuffDecompressor.h \ + $$FBREADER_SRC/formats/pdb/DocDecompressor.h \ + $$FBREADER_SRC/formats/pdb/PmlReader.h \ + $$FBREADER_SRC/formats/pdb/PluckerBookReader.h \ + $$FBREADER_SRC/formats/pdb/PdbPlugin.h \ + $$FBREADER_SRC/formats/pdb/EReaderStream.h \ + $$FBREADER_SRC/formats/pdb/PluckerTextStream.h \ + $$FBREADER_SRC/formats/xhtml/XHTMLReader.h \ + $$FBREADER_SRC/formats/oeb/OEBBookReader.h \ + $$FBREADER_SRC/formats/oeb/OEBTextStream.h \ + $$FBREADER_SRC/formats/oeb/OEBPlugin.h \ + $$FBREADER_SRC/formats/oeb/NCXReader.h \ + $$FBREADER_SRC/formats/oeb/OEBMetaInfoReader.h \ + $$FBREADER_SRC/formats/oeb/OEBCoverReader.h \ + $$FBREADER_SRC/formats/html/HtmlPlugin.h \ + $$FBREADER_SRC/formats/html/HtmlEntityCollection.h \ + $$FBREADER_SRC/formats/html/HtmlReaderStream.h \ + $$FBREADER_SRC/formats/html/HtmlReader.h \ + $$FBREADER_SRC/formats/html/HtmlTagActions.h \ + $$FBREADER_SRC/formats/html/HtmlBookReader.h \ + $$FBREADER_SRC/formats/html/HtmlDescriptionReader.h \ + $$FBREADER_SRC/formats/chm/CHMPlugin.h \ + $$FBREADER_SRC/formats/chm/CHMFile.h \ + $$FBREADER_SRC/formats/chm/LZXDecompressor.h \ + $$FBREADER_SRC/formats/chm/CHMFileImage.h \ + $$FBREADER_SRC/formats/chm/HHCReferenceCollector.h \ + $$FBREADER_SRC/formats/chm/HtmlSectionReader.h \ + $$FBREADER_SRC/formats/chm/CHMReferenceCollection.h \ + $$FBREADER_SRC/formats/chm/HuffmanDecoder.h \ + $$FBREADER_SRC/formats/chm/BitStream.h \ + $$FBREADER_SRC/formats/chm/HHCReader.h \ + $$FBREADER_SRC/formats/txt/TxtBookReader.h \ + $$FBREADER_SRC/formats/txt/PlainTextFormat.h \ + $$FBREADER_SRC/formats/txt/TxtPlugin.h \ + $$FBREADER_SRC/formats/txt/TxtReader.h \ + $$FBREADER_SRC/bookmodel/FBTextKind.h \ + $$FBREADER_SRC/bookmodel/BookModel.h \ + $$FBREADER_SRC/bookmodel/BookReader.h diff --git a/harbour-books.pro b/harbour-books.pro new file mode 100644 index 0000000..881daf8 --- /dev/null +++ b/harbour-books.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs +CONFIG += ordered +SUBDIRS = fribidi linebreak fbreader harbour-lib app + +OTHER_FILES += \ + rpm/harbour-books.spec diff --git a/harbour-lib b/harbour-lib new file mode 160000 index 0000000..24f9fe5 --- /dev/null +++ b/harbour-lib @@ -0,0 +1 @@ +Subproject commit 24f9fe58d06dba0982aa7c3eecce461dbc3f7d6d diff --git a/rpm/harbour-books.spec b/rpm/harbour-books.spec new file mode 100644 index 0000000..8483dd7 --- /dev/null +++ b/rpm/harbour-books.spec @@ -0,0 +1,56 @@ +Name: harbour-books +Summary: E-book reader +Version: 1.0.0 +Release: 1 +License: GPL +Group: Applications/File +URL: http://github.com/monich/harbour-books +Source0: %{name}-%{version}.tar.gz + +Requires: sailfishsilica-qt5 +BuildRequires: pkgconfig(glib-2.0) +BuildRequires: pkgconfig(sailfishapp) +BuildRequires: pkgconfig(Qt5Quick) +BuildRequires: pkgconfig(Qt5Core) +BuildRequires: pkgconfig(Qt5Svg) +BuildRequires: pkgconfig(Qt5Qml) +BuildRequires: pkgconfig(mlite5) +BuildRequires: pkgconfig(expat) +BuildRequires: pkgconfig(systemd) +BuildRequires: bzip2-devel +BuildRequires: desktop-file-utils +BuildRequires: qt5-qttools-linguist + +%{!?qtc_qmake5:%define qtc_qmake5 %qmake5} +%{!?qtc_make:%define qtc_make make} +%{?qtc_builddir:%define _builddir %qtc_builddir} + +%description +FBReader-based e-book reader. + +%prep +%setup -q -n %{name}-%{version} + +%build +%qtc_qmake5 harbour-books.pro +%qtc_make %{?_smp_mflags} + +%install +rm -rf %{buildroot} +cd app +%qmake5_install + +desktop-file-install --delete-original \ + --dir %{buildroot}%{_datadir}/applications \ + %{buildroot}%{_datadir}/applications/*.desktop + +%files +%defattr(-,root,root,-) +%{_bindir}/%{name} +%{_datadir}/%{name} +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/86x86/apps/%{name}.png + +%changelog +* Thu May 14 2015 Slava Monich 1.0.0 +- Initial version