From 1a420c9679ba3f8d004e4674a3107fd9cdc5f3db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Sun, 9 Oct 2022 11:16:42 +0200 Subject: [PATCH 1/7] Add a new type of view to show the content of a folder when it doesn't contain comics This view will display subfolders only, but at some point it should evolve to show the full content in a folder. --- YACReaderLibrary/qml.qrc | 1 + YACReaderLibrary/qml/FolderContentView.qml | 457 +++++++++++++++++++ YACReaderLibrary/qml/FolderContentView6.qml | 459 ++++++++++++++++++++ YACReaderLibrary/qml6.qrc | 1 + 4 files changed, 918 insertions(+) create mode 100644 YACReaderLibrary/qml/FolderContentView.qml create mode 100644 YACReaderLibrary/qml/FolderContentView6.qml diff --git a/YACReaderLibrary/qml.qrc b/YACReaderLibrary/qml.qrc index ce33b3d5..676430e3 100644 --- a/YACReaderLibrary/qml.qrc +++ b/YACReaderLibrary/qml.qrc @@ -1,6 +1,7 @@ qml/GridComicsView.qml + qml/FolderContentView.qml qml/tick.png qml/reading.png qml/star_menu.png diff --git a/YACReaderLibrary/qml/FolderContentView.qml b/YACReaderLibrary/qml/FolderContentView.qml new file mode 100644 index 00000000..7517bfda --- /dev/null +++ b/YACReaderLibrary/qml/FolderContentView.qml @@ -0,0 +1,457 @@ +import QtQuick 2.15 + +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.12 + +import QtGraphicalEffects 1.0 + +import com.yacreader.ComicModel 1.0 + +import com.yacreader.ComicInfo 1.0 +import com.yacreader.ComicDB 1.0 + +Rectangle { + id: main + + property int continuReadingHeight: 430; + property int topContentMargin: 20; + + color: backgroundColor + anchors.margins: 0 + + Component { + id: appDelegate + Rectangle + { + id: cell + width: grid.cellWidth + height: grid.cellHeight + color: "#00000000" + + scale: mouseArea.containsMouse ? 1.025 : 1 + + Behavior on scale { + NumberAnimation { duration: 90 } + } + + Rectangle { + id: realCell + + property int position : 0 + + width: itemWidth + height: itemHeight + + color: "transparent" + anchors.horizontalCenter: parent.horizontalCenter + + MouseArea { + id: mouseArea + + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + + hoverEnabled: true + + onDoubleClicked: { + openHelper.openFolder(index); + } + + onPressed: mouse => { + var ci = grid.currentIndex; //save current index + + mouse.accepted = true; + + if(mouse.button === Qt.RightButton) // context menu is requested + { + var coordinates = main.mapFromItem(realCell,mouseX,mouseY) + contextMenuHelper.requestedFolderContextMenu(Qt.point(coordinates.x,coordinates.y), index); + mouse.accepted = false; + + } + } + + } + } + + /**/ + + Rectangle { + transform: Rotation { origin.x: coverWidth / 2; origin.y: coverHeight / 2; angle: -4} + width: coverElement.width + height: coverElement.height + radius: 10 + anchors {horizontalCenter: parent.horizontalCenter; top: realCell.top; topMargin: 0} + color: "#20000000" + border { + color: "#20FFFFFF" + width: 1 + } + } + + Rectangle { + transform: Rotation { origin.x: coverWidth / 2; origin.y: coverHeight / 2; angle: 3} + width: coverElement.width + height: coverElement.height + radius: 10 + anchors {horizontalCenter: parent.horizontalCenter; top: realCell.top; topMargin: 0} + color: "#88000000" + border { + color: "#20FFFFFF" + width: 1 + } + } + + Item { + width: coverWidth + height: coverHeight + anchors {horizontalCenter: parent.horizontalCenter; top: realCell.top; topMargin: 0} + id: coverElement + + Image { + id: coverImage + anchors.fill: parent + source: cover_path + fillMode: Image.PreserveAspectCrop + smooth: true + mipmap: true + asynchronous : true + cache: true + + layer.enabled: true + layer.effect: OpacityMask { + anchors.fill: parent + cached: true + maskSource: Rectangle { + width: coverElement.width + height: coverElement.height + radius: 10 + } + } + } + } + + //border + Rectangle { + width: coverElement.width + height: coverElement.height + radius: 10 + anchors {horizontalCenter: parent.horizontalCenter; top: realCell.top; topMargin: 0} + color: "transparent" + border { + color: "#20FFFFFF" + width: 1 + } + } + + //folder name + Text { + id : titleText + anchors { top: coverElement.bottom; left: realCell.left; leftMargin: 4; rightMargin: 4; topMargin: 10; } + width: itemWidth - 8 + maximumLineCount: 2 + wrapMode: Text.WordWrap + text: name + elide: Text.ElideRight + color: titleColor + font.letterSpacing: fontSpacing + font.pointSize: fontSize + font.family: fontFamily + } + } + } + + Rectangle { + id: scrollView + objectName: "topScrollView" + anchors.fill: parent + anchors.margins: 0 + children: grid + + color: "transparent" + + function scrollToOrigin() { + grid.contentY = grid.originY + grid.contentX = grid.originX + } + + DropArea { + anchors.fill: parent + + onEntered: { + if(drag.hasUrls) + { + if(dropManager.canDropUrls(drag.urls, drag.action)) + { + drag.accepted = true; + }else + drag.accepted = false; + } + else if (dropManager.canDropFormats(drag.formats)) { + drag.accepted = true; + } else + drag.accepted = false; + } + + onDropped: { + if(drop.hasUrls && dropManager.canDropUrls(drop.urls, drop.action)) + { + dropManager.droppedFiles(drop.urls, drop.action); + } + else{ + if (dropManager.canDropFormats(drop.formats)) + { + var destItem = grid.itemAt(drop.x,drop.y + grid.contentY); + var destLocalX = grid.mapToItem(destItem,drop.x,drop.y + grid.contentY).x + var realIndex = grid.indexAt(drop.x,drop.y + grid.contentY); + + if(realIndex === -1) + realIndex = grid.count - 1; + + var destIndex = destLocalX < (grid.cellWidth / 2) ? realIndex : realIndex + 1; + dropManager.droppedComicsForResortingAt(drop.getDataAsString(), destIndex); + } + } + } + } + + property Component continueReadingView: Component { + id: continueReadingView + Rectangle { + id: continueReadingTopView + color: "#00000000" + + height: list.count > 0 ? main.continuReadingHeight : main.topContentMargin + + Rectangle { + color: continueReadingBackgroundColor + + id: continueReadingBackground + + width: main.width + height: main.continuReadingHeight - main.topContentMargin + + visible: list.count > 0 + + Text { + id: continueReadingText + text: qsTr("Continue Reading...") + color: continueReadingColor + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: 15 + anchors.bottomMargin: 20 + anchors.leftMargin: 25 + anchors.rightMargin: 0 + font.pointSize: 18 + font.weight: Font.DemiBold + } + + ListView { + id: list + objectName: "list" + anchors { top: continueReadingText.bottom; left: parent.left; right: parent.right; bottom: parent.bottom; } + + property int previousIndex; + property int verticalPadding: 20 + + orientation: Qt.Horizontal + pixelAligned: true + + model: comicsList + + spacing: 20 + anchors.topMargin: 15 + anchors.bottomMargin: 20 + anchors.leftMargin: 25 + anchors.rightMargin: 20 + + WheelHandler { + onWheel: event => { + if (list.contentWidth <= list.width) { + return; + } + + var newValue = Math.min(list.contentWidth - list.width - anchors.leftMargin, (Math.max(list.originX , list.contentX - event.angleDelta.y))); + list.contentX = newValue + } + } + + delegate: Component { + + //cover + Rectangle { + width: Math.floor((list.height - (list.verticalPadding * 2)) * 0.65); + height: list.height - (list.verticalPadding * 2); + + color:"transparent" + + scale: mouseArea.containsMouse ? 1.025 : 1 + + Behavior on scale { + NumberAnimation { duration: 90 } + } + + Image { + id: coverElement + anchors.fill: parent + source: cover_path + fillMode: Image.PreserveAspectCrop + smooth: true + mipmap: true + asynchronous : true + cache: true + } + + //title + Text { + id : comicTitleText + anchors { top: coverElement.bottom; left: coverElement.left; right: coverElement.right; leftMargin: 4; rightMargin: 4; topMargin: 4; } + width: itemWidth - 8 + maximumLineCount: 2 + wrapMode: Text.WordWrap + text: readable_title + elide: Text.ElideRight + color: titleColor + font.letterSpacing: fontSpacing + font.pointSize: fontSize + font.family: fontFamily + } + + //border + Rectangle { + width: coverElement.width + height: coverElement.height + anchors.centerIn: coverElement + color: "transparent" + border { + color: "#30FFFFFF" + width: 1 + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + + hoverEnabled: true + + onDoubleClicked: { + list.currentIndex = index; + openHelper.openComicFromContinueReadingList(index); + } + + onReleased: mouse => { + list.currentIndex = index; + + if(mouse.button === Qt.RightButton) // context menu is requested + { + var coordinates = main.mapFromItem(coverElement,mouseX,mouseY) + contextMenuHelper.requestedContinueReadingComicContextMenu(Qt.point(coordinates.x,coordinates.y), index); + } + + mouse.accepted = true; + } + } + } + } + + focus: true + } + } + } + } + + GridView { + id:grid + objectName: "grid" + anchors.fill: parent + cellHeight: cellCustomHeight + header: continueReadingView + focus: true + model: foldersList + delegate: appDelegate + anchors.topMargin: 0 + anchors.bottomMargin: 10 + anchors.leftMargin: 0 + anchors.rightMargin: 0 + pixelAligned: true + highlightFollowsCurrentItem: true + + currentIndex: 0 + cacheBuffer: 0 + + interactive: true + + move: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } + + moveDisplaced: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } + + remove: Transition { + ParallelAnimation { + NumberAnimation { property: "opacity"; to: 0; duration: 250 } + + } + } + + removeDisplaced: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } + + displaced: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } + + function numCellsPerRow() { + return Math.floor(width / cellCustomWidth); + } + + onWidthChanged: { + calculateCellWidths(cellCustomWidth); + } + + function calculateCellWidths(cWidth) { + var wholeCells = Math.floor(width / cWidth); + var rest = width - (cWidth * wholeCells) + + grid.cellWidth = cWidth + Math.floor(rest / wholeCells); + } + + WheelHandler { + onWheel: event => { + if (grid.contentHeight <= grid.height) { + return; + } + + var newValue = Math.min((grid.contentHeight - grid.height - (true ? main.continuReadingHeight : main.topContentMargin)), (Math.max(grid.originY , grid.contentY - event.angleDelta.y))); + grid.contentY = newValue; + } + } + + ScrollBar.vertical: ScrollBar { + visible: grid.contentHeight > grid.height + + contentItem: Item { + implicitWidth: 12 + implicitHeight: 26 + Rectangle { + color: "#88424242" + anchors.fill: parent + anchors.topMargin: 6 + anchors.leftMargin: 3 + anchors.rightMargin: 2 + anchors.bottomMargin: 6 + border.color: "#AA313131" + border.width: 1 + radius: 3.5 + } + } + } + } + } +} diff --git a/YACReaderLibrary/qml/FolderContentView6.qml b/YACReaderLibrary/qml/FolderContentView6.qml new file mode 100644 index 00000000..0cf3ea16 --- /dev/null +++ b/YACReaderLibrary/qml/FolderContentView6.qml @@ -0,0 +1,459 @@ +import QtQuick 2.15 + +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.12 + +import Qt5Compat.GraphicalEffects + +import com.yacreader.ComicModel 1.0 + +import com.yacreader.ComicInfo 1.0 +import com.yacreader.ComicDB 1.0 + +import QtQuick.Controls.Basic + +Rectangle { + id: main + + property int continuReadingHeight: 430; + property int topContentMargin: 20; + + color: backgroundColor + anchors.margins: 0 + + Component { + id: appDelegate + Rectangle + { + id: cell + width: grid.cellWidth + height: grid.cellHeight + color: "#00000000" + + scale: mouseArea.containsMouse ? 1.025 : 1 + + Behavior on scale { + NumberAnimation { duration: 90 } + } + + Rectangle { + id: realCell + + property int position : 0 + + width: itemWidth + height: itemHeight + + color: "transparent" + anchors.horizontalCenter: parent.horizontalCenter + + MouseArea { + id: mouseArea + + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + + hoverEnabled: true + + onDoubleClicked: { + openHelper.openFolder(index); + } + + onPressed: mouse => { + var ci = grid.currentIndex; //save current index + + mouse.accepted = true; + + if(mouse.button === Qt.RightButton) // context menu is requested + { + var coordinates = main.mapFromItem(realCell,mouseX,mouseY) + contextMenuHelper.requestedFolderContextMenu(Qt.point(coordinates.x,coordinates.y), index); + mouse.accepted = false; + + } + } + + } + } + + /**/ + + Rectangle { + transform: Rotation { origin.x: coverWidth / 2; origin.y: coverHeight / 2; angle: -4} + width: coverElement.width + height: coverElement.height + radius: 10 + anchors {horizontalCenter: parent.horizontalCenter; top: realCell.top; topMargin: 0} + color: "#20000000" + border { + color: "#20FFFFFF" + width: 1 + } + } + + Rectangle { + transform: Rotation { origin.x: coverWidth / 2; origin.y: coverHeight / 2; angle: 3} + width: coverElement.width + height: coverElement.height + radius: 10 + anchors {horizontalCenter: parent.horizontalCenter; top: realCell.top; topMargin: 0} + color: "#88000000" + border { + color: "#20FFFFFF" + width: 1 + } + } + + Item { + width: coverWidth + height: coverHeight + anchors {horizontalCenter: parent.horizontalCenter; top: realCell.top; topMargin: 0} + id: coverElement + + Image { + id: coverImage + anchors.fill: parent + source: cover_path + fillMode: Image.PreserveAspectCrop + smooth: true + mipmap: true + asynchronous : true + cache: true + + layer.enabled: true + layer.effect: OpacityMask { + anchors.fill: parent + cached: true + maskSource: Rectangle { + width: coverElement.width + height: coverElement.height + radius: 10 + } + } + } + } + + //border + Rectangle { + width: coverElement.width + height: coverElement.height + radius: 10 + anchors {horizontalCenter: parent.horizontalCenter; top: realCell.top; topMargin: 0} + color: "transparent" + border { + color: "#20FFFFFF" + width: 1 + } + } + + //folder name + Text { + id : titleText + anchors { top: coverElement.bottom; left: realCell.left; leftMargin: 4; rightMargin: 4; topMargin: 10; } + width: itemWidth - 8 + maximumLineCount: 2 + wrapMode: Text.WordWrap + text: name + elide: Text.ElideRight + color: titleColor + font.letterSpacing: fontSpacing + font.pointSize: fontSize + font.family: fontFamily + } + } + } + + Rectangle { + id: scrollView + objectName: "topScrollView" + anchors.fill: parent + anchors.margins: 0 + children: grid + + color: "transparent" + + function scrollToOrigin() { + grid.contentY = grid.originY + grid.contentX = grid.originX + } + + DropArea { + anchors.fill: parent + + onEntered: { + if(drag.hasUrls) + { + if(dropManager.canDropUrls(drag.urls, drag.action)) + { + drag.accepted = true; + }else + drag.accepted = false; + } + else if (dropManager.canDropFormats(drag.formats)) { + drag.accepted = true; + } else + drag.accepted = false; + } + + onDropped: { + if(drop.hasUrls && dropManager.canDropUrls(drop.urls, drop.action)) + { + dropManager.droppedFiles(drop.urls, drop.action); + } + else{ + if (dropManager.canDropFormats(drop.formats)) + { + var destItem = grid.itemAt(drop.x,drop.y + grid.contentY); + var destLocalX = grid.mapToItem(destItem,drop.x,drop.y + grid.contentY).x + var realIndex = grid.indexAt(drop.x,drop.y + grid.contentY); + + if(realIndex === -1) + realIndex = grid.count - 1; + + var destIndex = destLocalX < (grid.cellWidth / 2) ? realIndex : realIndex + 1; + dropManager.droppedComicsForResortingAt(drop.getDataAsString(), destIndex); + } + } + } + } + + property Component continueReadingView: Component { + id: continueReadingView + Rectangle { + id: continueReadingTopView + color: "#00000000" + + height: list.count > 0 ? main.continuReadingHeight : main.topContentMargin + + Rectangle { + color: continueReadingBackgroundColor + + id: continueReadingBackground + + width: main.width + height: main.continuReadingHeight - main.topContentMargin + + visible: list.count > 0 + + Text { + id: continueReadingText + text: qsTr("Continue Reading...") + color: continueReadingColor + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: 15 + anchors.bottomMargin: 20 + anchors.leftMargin: 25 + anchors.rightMargin: 0 + font.pointSize: 18 + font.weight: Font.DemiBold + } + + ListView { + id: list + objectName: "list" + anchors { top: continueReadingText.bottom; left: parent.left; right: parent.right; bottom: parent.bottom; } + + property int previousIndex; + property int verticalPadding: 20 + + orientation: Qt.Horizontal + pixelAligned: true + + model: comicsList + + spacing: 20 + anchors.topMargin: 15 + anchors.bottomMargin: 20 + anchors.leftMargin: 25 + anchors.rightMargin: 20 + + WheelHandler { + onWheel: event => { + if (list.contentWidth <= list.width) { + return; + } + + var newValue = Math.min(list.contentWidth - list.width - anchors.leftMargin, (Math.max(list.originX , list.contentX - event.angleDelta.y))); + list.contentX = newValue + } + } + + delegate: Component { + + //cover + Rectangle { + width: Math.floor((list.height - (list.verticalPadding * 2)) * 0.65); + height: list.height - (list.verticalPadding * 2); + + color:"transparent" + + scale: mouseArea.containsMouse ? 1.025 : 1 + + Behavior on scale { + NumberAnimation { duration: 90 } + } + + Image { + id: coverElement + anchors.fill: parent + source: cover_path + fillMode: Image.PreserveAspectCrop + smooth: true + mipmap: true + asynchronous : true + cache: true + } + + //title + Text { + id : comicTitleText + anchors { top: coverElement.bottom; left: coverElement.left; right: coverElement.right; leftMargin: 4; rightMargin: 4; topMargin: 4; } + width: itemWidth - 8 + maximumLineCount: 2 + wrapMode: Text.WordWrap + text: readable_title + elide: Text.ElideRight + color: titleColor + font.letterSpacing: fontSpacing + font.pointSize: fontSize + font.family: fontFamily + } + + //border + Rectangle { + width: coverElement.width + height: coverElement.height + anchors.centerIn: coverElement + color: "transparent" + border { + color: "#30FFFFFF" + width: 1 + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + + hoverEnabled: true + + onDoubleClicked: { + list.currentIndex = index; + openHelper.openComicFromContinueReadingList(index); + } + + onReleased: mouse => { + list.currentIndex = index; + + if(mouse.button === Qt.RightButton) // context menu is requested + { + var coordinates = main.mapFromItem(coverElement,mouseX,mouseY) + contextMenuHelper.requestedContinueReadingComicContextMenu(Qt.point(coordinates.x,coordinates.y), index); + } + + mouse.accepted = true; + } + } + } + } + + focus: true + } + } + } + } + + GridView { + id:grid + objectName: "grid" + anchors.fill: parent + cellHeight: cellCustomHeight + header: continueReadingView + focus: true + model: foldersList + delegate: appDelegate + anchors.topMargin: 0 + anchors.bottomMargin: 10 + anchors.leftMargin: 0 + anchors.rightMargin: 0 + pixelAligned: true + highlightFollowsCurrentItem: true + + currentIndex: 0 + cacheBuffer: 0 + + interactive: true + + move: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } + + moveDisplaced: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } + + remove: Transition { + ParallelAnimation { + NumberAnimation { property: "opacity"; to: 0; duration: 250 } + + } + } + + removeDisplaced: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } + + displaced: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } + + function numCellsPerRow() { + return Math.floor(width / cellCustomWidth); + } + + onWidthChanged: { + calculateCellWidths(cellCustomWidth); + } + + function calculateCellWidths(cWidth) { + var wholeCells = Math.floor(width / cWidth); + var rest = width - (cWidth * wholeCells) + + grid.cellWidth = cWidth + Math.floor(rest / wholeCells); + } + + WheelHandler { + onWheel: event => { + if (grid.contentHeight <= grid.height) { + return; + } + + var newValue = Math.min((grid.contentHeight - grid.height - (true ? main.continuReadingHeight : main.topContentMargin)), (Math.max(grid.originY , grid.contentY - event.angleDelta.y))); + grid.contentY = newValue; + } + } + + ScrollBar.vertical: ScrollBar { + visible: grid.contentHeight > grid.height + + contentItem: Item { + implicitWidth: 12 + implicitHeight: 26 + Rectangle { + color: "#88424242" + anchors.fill: parent + anchors.topMargin: 6 + anchors.leftMargin: 3 + anchors.rightMargin: 2 + anchors.bottomMargin: 6 + border.color: "#AA313131" + border.width: 1 + radius: 3.5 + } + } + } + } + } +} diff --git a/YACReaderLibrary/qml6.qrc b/YACReaderLibrary/qml6.qrc index 2fee5923..3d995203 100644 --- a/YACReaderLibrary/qml6.qrc +++ b/YACReaderLibrary/qml6.qrc @@ -1,6 +1,7 @@ qml/GridComicsView6.qml + qml/FolderContentView6.qml qml/FlowView6.qml qml/InfoTick6.qml qml/InfoFavorites6.qml From 41767958d0916eb312c35fd84e84d5e64e86cef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Sun, 9 Oct 2022 11:20:39 +0200 Subject: [PATCH 2/7] Add full constructor --- common/folder.cpp | 14 ++++++++++++++ common/folder.h | 5 +++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/common/folder.cpp b/common/folder.cpp index 288ad66e..3dad408a 100644 --- a/common/folder.cpp +++ b/common/folder.cpp @@ -20,6 +20,20 @@ Folder::Folder(qulonglong folderId, qulonglong parentId, const QString &folderNa this->path = folderPath; } +Folder::Folder(qulonglong folderId, qulonglong parentId, const QString &folderName, const QString &folderPath, bool completed, bool finished, bool manga) + : knownParent(true), + knownId(true), + numChildren(-1) +{ + this->id = folderId; + this->parentId = parentId; + this->name = folderName; + this->path = folderPath; + this->completed = completed; + this->finished = finished; + this->manga = manga; +} + Folder::Folder(const Folder &folder) { operator=(folder); diff --git a/common/folder.h b/common/folder.h index 2c9e572e..1497e8a7 100644 --- a/common/folder.h +++ b/common/folder.h @@ -13,6 +13,7 @@ public: Folder(); Folder(qulonglong folderId, qulonglong parentId, const QString &folderName, const QString &folderPath); + Folder(qulonglong folderId, qulonglong parentId, const QString &folderName, const QString &folderPath, bool completed, bool finished, bool manga); Folder(const QString &folderName, const QString &folderPath); Folder(const Folder &folder); Folder &operator=(const Folder &other); @@ -94,8 +95,8 @@ public: } private: - bool finished; - bool completed; + bool finished; // finished means read, the user has read all the content in this folder + bool completed; // completed means the folder has all the content, e.g. a series got its final issue and the user has collected all of them bool manga; qint32 numChildren; //-1 for unknown number of children From 0a00fd3020d4000eaed0cfb5e2d8130b285838dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Sun, 9 Oct 2022 11:22:06 +0200 Subject: [PATCH 3/7] Add a new type of role to ComicModel to display a title composed with the available info --- YACReaderLibrary/db/comic_model.cpp | 10 +++++++++- YACReaderLibrary/db/comic_model.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/YACReaderLibrary/db/comic_model.cpp b/YACReaderLibrary/db/comic_model.cpp index 46af7159..0a0243e6 100644 --- a/YACReaderLibrary/db/comic_model.cpp +++ b/YACReaderLibrary/db/comic_model.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include "comic_item.h" @@ -237,6 +238,7 @@ QHash ComicModel::roleNames() const roles[HasBeenOpenedRole] = "has_been_opened"; roles[CoverPathRole] = "cover_path"; roles[PublicationDate] = "date"; + roles[ReadableTitle] = "readable_title"; return roles; } @@ -275,7 +277,13 @@ QVariant ComicModel::data(const QModelIndex &index, int role) const return item->data(Number); else if (role == TitleRole) return item->data(Title).isNull() ? item->data(FileName) : item->data(Title); - else if (role == FileNameRole) + else if (role == ReadableTitle) { + QString title; + if (!item->data(Number).isNull()) { + title = title % "#" % item->data(Number).toString() % " "; + } + return QVariant(title % (item->data(Title).isNull() ? item->data(FileName).toString() : item->data(Title).toString())); + } else if (role == FileNameRole) return item->data(FileName); else if (role == RatingRole) return item->data(Rating); diff --git a/YACReaderLibrary/db/comic_model.h b/YACReaderLibrary/db/comic_model.h index f66260e5..54548f8d 100644 --- a/YACReaderLibrary/db/comic_model.h +++ b/YACReaderLibrary/db/comic_model.h @@ -54,6 +54,7 @@ public: HasBeenOpenedRole, CoverPathRole, PublicationDateRole, + ReadableTitle, }; enum Mode { From 2e16dec51a90690c1a920c7388475f3f6f7501f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Sun, 9 Oct 2022 11:24:33 +0200 Subject: [PATCH 4/7] Update FolderModel to support new functionality --- YACReaderLibrary/db/folder_model.cpp | 171 +++++++++++++++++++++++++-- YACReaderLibrary/db/folder_model.h | 23 +++- 2 files changed, 178 insertions(+), 16 deletions(-) diff --git a/YACReaderLibrary/db/folder_model.cpp b/YACReaderLibrary/db/folder_model.cpp index 072be11b..3bf0c385 100644 --- a/YACReaderLibrary/db/folder_model.cpp +++ b/YACReaderLibrary/db/folder_model.cpp @@ -54,26 +54,26 @@ void drawMacOSXFinishedFolderIcon() #define ROOT 1 FolderModel::FolderModel(QObject *parent) - : QAbstractItemModel(parent), rootItem(0) + : QAbstractItemModel(parent), isSubfolder(false), rootItem(nullptr) { } FolderModel::FolderModel(QSqlQuery &sqlquery, QObject *parent) - : QAbstractItemModel(parent), rootItem(0) + : QAbstractItemModel(parent), isSubfolder(false), rootItem(nullptr) { // lo m�s probable es que el nodo ra�z no necesite tener informaci�n QList rootData; rootData << "root"; // id 0, padre 0, title "root" (el id, y el id del padre van a ir en la clase TreeItem) rootItem = new FolderItem(rootData); rootItem->id = ROOT; - rootItem->parentItem = 0; + rootItem->parentItem = nullptr; setupModelData(sqlquery, rootItem); // sqlquery.finish(); } FolderModel::~FolderModel() { - if (rootItem != 0) + if (rootItem != nullptr) delete rootItem; } @@ -81,8 +81,31 @@ int FolderModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return static_cast(parent.internalPointer())->columnCount(); - else + else { + if (rootItem == nullptr) { + return 0; + } return rootItem->columnCount(); + } +} + +QHash FolderModel::roleNames() const +{ + QHash roles; + + roles[FinishedRole] = "is_finished"; + roles[CompletedRole] = "is_completed"; + roles[IdRole] = "id"; + roles[MangaRole] = "is_manga"; + roles[CoverPathRole] = "cover_path"; + roles[FolderName] = "name"; + + return roles; +} + +void FolderModel::reload() +{ + setupModelData(_databasePath); } QVariant FolderModel::data(const QModelIndex &index, int role) const @@ -121,6 +144,10 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const #endif } + if (role == FolderModel::FolderName) { + return item->data(FolderModel::Name); + } + if (role == FolderModel::CompletedRole) return item->data(FolderModel::Completed); @@ -133,6 +160,9 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const if (role == FolderModel::IdRole) return item->id; + if (role == FolderModel::CoverPathRole) + return getCoverUrlPathForComicHash(item->data(FirstChildHash).toString()); + if (role != Qt::DisplayRole) return QVariant(); @@ -150,6 +180,10 @@ Qt::ItemFlags FolderModel::flags(const QModelIndex &index) const QVariant FolderModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (rootItem == nullptr) { + return QVariant(); + } + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return rootItem->data(section); @@ -196,10 +230,14 @@ int FolderModel::rowCount(const QModelIndex &parent) const if (parent.column() > 0) return 0; - if (!parent.isValid()) + if (!parent.isValid()) { + if (rootItem == nullptr) { + return 0; + } parentItem = rootItem; - else + } else { parentItem = static_cast(parent.internalPointer()); + } return parentItem->childCount(); } @@ -207,17 +245,17 @@ int FolderModel::rowCount(const QModelIndex &parent) const void FolderModel::setupModelData(QString path) { beginResetModel(); - if (rootItem != 0) + if (rootItem != nullptr) delete rootItem; // TODO comprobar que se libera bien la memoria - rootItem = 0; + rootItem = nullptr; // inicializar el nodo ra�z QList rootData; rootData << "root"; // id 0, padre 0, title "root" (el id, y el id del padre van a ir en la clase TreeItem) rootItem = new FolderItem(rootData); rootItem->id = ROOT; - rootItem->parentItem = 0; + rootItem->parentItem = nullptr; // cargar la base de datos _databasePath = path; @@ -235,6 +273,13 @@ void FolderModel::setupModelData(QString path) endResetModel(); } +void FolderModel::fullSetup(QSqlQuery &sqlquery, FolderItem *parent) +{ + rootItem = parent; + + setupModelData(sqlquery, parent); +} + void FolderModel::setupModelData(QSqlQuery &sqlquery, FolderItem *parent) { // 64 bits para la primary key, es decir la misma precisi�n que soporta sqlit 2^64 @@ -252,6 +297,7 @@ void FolderModel::setupModelData(QSqlQuery &sqlquery, FolderItem *parent) int manga = record.indexOf("manga"); int id = record.indexOf("id"); int parentId = record.indexOf("parentId"); + int firstChildHash = record.indexOf("firstChildHash"); while (sqlquery.next()) { QList data; @@ -261,6 +307,7 @@ void FolderModel::setupModelData(QSqlQuery &sqlquery, FolderItem *parent) data << sqlquery.value(finished).toBool(); data << sqlquery.value(completed).toBool(); data << sqlquery.value(manga).toBool(); + data << sqlquery.value(firstChildHash).toString(); auto item = new FolderItem(data); item->id = sqlquery.value(id).toULongLong(); @@ -286,6 +333,7 @@ void FolderModel::updateFolderModelData(QSqlQuery &sqlquery, FolderItem *parent) int manga = record.indexOf("manga"); int id = record.indexOf("id"); int parentId = record.indexOf("parentId"); + int firstChildHash = record.indexOf("firstChildHash"); while (sqlquery.next()) { QList data; @@ -295,6 +343,7 @@ void FolderModel::updateFolderModelData(QSqlQuery &sqlquery, FolderItem *parent) data << sqlquery.value(finished).toBool(); data << sqlquery.value(completed).toBool(); data << sqlquery.value(manga).toBool(); + data << sqlquery.value(firstChildHash).toString(); auto item = new FolderItem(data); item->id = sqlquery.value(id).toULongLong(); @@ -338,7 +387,7 @@ void FolderModel::updateFolderCompletedStatus(const QModelIndexList &list, bool } QSqlDatabase::removeDatabase(connectionName); - emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::Completed)); + emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::FirstChildHash)); } void FolderModel::updateFolderFinishedStatus(const QModelIndexList &list, bool status) @@ -360,7 +409,7 @@ void FolderModel::updateFolderFinishedStatus(const QModelIndexList &list, bool s } QSqlDatabase::removeDatabase(connectionName); - emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::Completed)); + emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::FirstChildHash)); } void FolderModel::updateFolderManga(const QModelIndexList &list, bool manga) @@ -390,7 +439,7 @@ void FolderModel::updateFolderManga(const QModelIndexList &list, bool manga) } QSqlDatabase::removeDatabase(connectionName); - emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::Manga)); + emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::FirstChildHash)); } QStringList FolderModel::getSubfoldersNames(const QModelIndex &mi) @@ -417,6 +466,97 @@ QStringList FolderModel::getSubfoldersNames(const QModelIndex &mi) return result; } +FolderModel *FolderModel::getSubfoldersModel(const QModelIndex &mi) +{ + qulonglong id = 1; + FolderItem *parent = nullptr; + if (mi.isValid()) { + auto item = static_cast(mi.internalPointer()); + parent = new FolderItem(item->getData(), item->parent()); + id = parent->id = item->id; + } + + if (id == 1) { + if (parent != nullptr) { + delete parent; + } + return this; + } + + auto model = new FolderModel(); + + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + + QSqlQuery selectQuery(db); // TODO check + selectQuery.prepare("SELECT * FROM folder WHERE parentId = :parentId and id <> 1"); + selectQuery.bindValue(":parentId", id); + selectQuery.exec(); + + if (parent != nullptr) { + model->fullSetup(selectQuery, parent); + } + + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + + model->_databasePath = _databasePath; + + model->isSubfolder = true; + + return model; +} + +Folder FolderModel::getFolder(const QModelIndex &mi) +{ + auto folderItem = static_cast(mi.internalPointer()); + auto name = folderItem->data(FolderModel::Name).toString(); + auto parentItem = folderItem->parent(); + auto folder = Folder(folderItem->id, + parentItem->id, + name, + folderItem->parent()->data(Columns::Path).toString() + "/" + name, + folderItem->data(Columns::Completed).toBool(), + folderItem->data(Columns::Finished).toBool(), + folderItem->data(Columns::Manga).toBool()); + + return folder; +} + +QModelIndex FolderModel::getIndexFromFolder(const Folder &folder, const QModelIndex &parent) +{ + if (rootItem == nullptr) { + return QModelIndex(); + } + + auto numRows = rowCount(parent); + for (auto i = 0; i < numRows; i++) { + auto modelIndex = index(i, 0, parent); + + if (modelIndex.isValid()) { + auto folderItem = static_cast(modelIndex.internalPointer()); + + if (folderItem->id == folder.id) { + return modelIndex; + } + + auto childModelIndex = getIndexFromFolder(folder, modelIndex); + + if (childModelIndex.isValid()) { + auto folderItem = static_cast(childModelIndex.internalPointer()); + + if (folderItem->id == folder.id) { + return childModelIndex; + } + } + } + } + + return QModelIndex(); +} + void FolderModel::fetchMoreFromDB(const QModelIndex &parent) { FolderItem *item; @@ -531,6 +671,11 @@ QModelIndex FolderModel::addFolderAtParent(const QString &folderName, const QMod return index(destRow, 0, parent); } +QUrl FolderModel::getCoverUrlPathForComicHash(const QString &hash) const +{ + return QUrl("file:" + _databasePath + "/covers/" + hash + ".jpg"); +} + void FolderModel::deleteFolder(const QModelIndex &mi) { beginRemoveRows(mi.parent(), mi.row(), mi.row()); diff --git a/YACReaderLibrary/db/folder_model.h b/YACReaderLibrary/db/folder_model.h index 39ca7242..dbf056fc 100644 --- a/YACReaderLibrary/db/folder_model.h +++ b/YACReaderLibrary/db/folder_model.h @@ -7,9 +7,11 @@ #include #include #include +#include -#include "yacreader_global.h" +#include "folder.h" #include "folder_query_result_processor.h" +#include "yacreader_global.h" class FolderItem; @@ -54,8 +56,10 @@ public: QModelIndex parent(const QModelIndex &index) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QHash roleNames() const override; // Convenience methods + void reload(); void setupModelData(QString path); QString getDatabase(); QString getFolderPath(const QModelIndex &folder); @@ -68,31 +72,44 @@ public: void updateFolderManga(const QModelIndexList &list, bool manga); QStringList getSubfoldersNames(const QModelIndex &mi); + FolderModel *getSubfoldersModel(const QModelIndex &mi); + + Folder getFolder(const QModelIndex &mi); + QModelIndex getIndexFromFolder(const Folder &folder, const QModelIndex &parent = QModelIndex()); void fetchMoreFromDB(const QModelIndex &parent); QModelIndex addFolderAtParent(const QString &folderName, const QModelIndex &parent); + Q_INVOKABLE QUrl getCoverUrlPathForComicHash(const QString &hash) const; + enum Columns { Name = 0, Path = 1, Finished = 2, Completed = 3, - Manga = 4 + Manga = 4, + FirstChildHash = 5 }; // id INTEGER PRIMARY KEY, parentId INTEGER NOT NULL, name TEXT NOT NULL, path TEXT NOT NULL enum Roles { FinishedRole = Qt::UserRole + 1, CompletedRole, IdRole, - MangaRole + MangaRole, + CoverPathRole, + FolderName, }; + bool isSubfolder; + public slots: void deleteFolder(const QModelIndex &mi); void updateFolderChildrenInfo(qulonglong folderId); private: + void fullSetup(QSqlQuery &sqlquery, FolderItem *parent); + void setupModelData(QSqlQuery &sqlquery, FolderItem *parent); void updateFolderModelData(QSqlQuery &sqlquery, FolderItem *parent); From f25524e64cec7ffe195aba09cf5a40b74be613ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Sun, 9 Oct 2022 11:25:29 +0200 Subject: [PATCH 5/7] Update the cover size when the grid comics view is shown The size may have changed while it was away --- YACReaderLibrary/grid_comics_view.cpp | 9 +++++++++ YACReaderLibrary/grid_comics_view.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/YACReaderLibrary/grid_comics_view.cpp b/YACReaderLibrary/grid_comics_view.cpp index 0480bb1e..0e262345 100644 --- a/YACReaderLibrary/grid_comics_view.cpp +++ b/YACReaderLibrary/grid_comics_view.cpp @@ -478,6 +478,15 @@ void GridComicsView::resetScroll() QMetaObject::invokeMethod(scrollView, "scrollToOrigin"); } +void GridComicsView::showEvent(QShowEvent *event) +{ + ComicsView::showEvent(event); + int coverSize = settings->value(COMICS_GRID_COVER_SIZES, YACREADER_MIN_COVER_WIDTH).toInt(); + + coverSizeSlider->setValue(coverSize); + setCoversSize(coverSize); +} + QByteArray GridComicsView::getMimeDataFromSelection() { QByteArray data; diff --git a/YACReaderLibrary/grid_comics_view.h b/YACReaderLibrary/grid_comics_view.h index 29c9e31d..7d7455e7 100644 --- a/YACReaderLibrary/grid_comics_view.h +++ b/YACReaderLibrary/grid_comics_view.h @@ -73,6 +73,8 @@ protected slots: void resetScroll(); + virtual void showEvent(QShowEvent *event) override; + signals: void onScrollToOrigin(); From d2dff7b4a5389c93c1f5412ef85f024a1be2ec33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Sun, 9 Oct 2022 11:30:12 +0200 Subject: [PATCH 6/7] Add new type of view to display the content of a folder that doesn't contain comics It is a replacement for EmptyFolderWidget for now, but it should evolve to show comics and folders --- YACReaderLibrary/YACReaderLibrary.pro | 2 + YACReaderLibrary/folder_content_view.cpp | 271 +++++++++++++++++++++++ YACReaderLibrary/folder_content_view.h | 70 ++++++ YACReaderLibrary/grid_comics_view.cpp | 18 +- YACReaderLibrary/grid_comics_view.h | 16 ++ 5 files changed, 361 insertions(+), 16 deletions(-) create mode 100644 YACReaderLibrary/folder_content_view.cpp create mode 100644 YACReaderLibrary/folder_content_view.h diff --git a/YACReaderLibrary/YACReaderLibrary.pro b/YACReaderLibrary/YACReaderLibrary.pro index 6e77aca7..ab7c8eef 100644 --- a/YACReaderLibrary/YACReaderLibrary.pro +++ b/YACReaderLibrary/YACReaderLibrary.pro @@ -83,6 +83,7 @@ HEADERS += comic_flow.h \ db/comic_query_result_processor.h \ db/folder_query_result_processor.h \ db/query_lexer.h \ + folder_content_view.h \ initial_comic_info_extractor.h \ library_comic_opener.h \ library_creator.h \ @@ -165,6 +166,7 @@ SOURCES += comic_flow.cpp \ db/comic_query_result_processor.cpp \ db/folder_query_result_processor.cpp \ db/query_lexer.cpp \ + folder_content_view.cpp \ initial_comic_info_extractor.cpp \ library_comic_opener.cpp \ library_creator.cpp \ diff --git a/YACReaderLibrary/folder_content_view.cpp b/YACReaderLibrary/folder_content_view.cpp new file mode 100644 index 00000000..78c9a3b4 --- /dev/null +++ b/YACReaderLibrary/folder_content_view.cpp @@ -0,0 +1,271 @@ +#include "folder_content_view.h" + +#include "folder_model.h" +#include "grid_comics_view.h" +#include "yacreader_global_gui.h" +#include "yacreader_tool_bar_stretch.h" + +#include "QsLog.h" + +#include +#include +#include + +using namespace YACReader; + +FolderContentView::FolderContentView(QWidget *parent) + : QWidget { parent }, parent(QModelIndex()), comicModel(new ComicModel()), folderModel(new FolderModel()) +{ + qmlRegisterType("com.yacreader.FolderModel", 1, 0, "FolderModel"); + + settings = new QSettings(YACReader::getSettingsPath() + "/YACReaderLibrary.ini", QSettings::IniFormat, this); + settings->beginGroup("libraryConfig"); + + view = new QQuickWidget(); + + // QQuickWidget requires rendering into OpenGL framebuffer objects +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + view->quickWindow()->setGraphicsApi(QSGRendererInterface::OpenGL); +#endif + + view->setResizeMode(QQuickWidget::SizeRootObjectToView); + connect( + view, &QQuickWidget::statusChanged, + [=](QQuickWidget::Status status) { + if (status == QQuickWidget::Error) { + QLOG_ERROR() << view->errors(); + } + }); + + coverSizeSliderWidget = new QWidget(this); + coverSizeSliderWidget->setFixedWidth(200); + coverSizeSlider = new QSlider(coverSizeSliderWidget); + coverSizeSlider->setOrientation(Qt::Horizontal); + coverSizeSlider->setRange(YACREADER_MIN_GRID_ZOOM_WIDTH, YACREADER_MAX_GRID_ZOOM_WIDTH); + + auto horizontalLayout = new QHBoxLayout(); + QLabel *smallLabel = new QLabel(); + smallLabel->setPixmap(QPixmap(":/images/comics_view_toolbar/small_size_grid_zoom.png")); + horizontalLayout->addWidget(smallLabel); + horizontalLayout->addWidget(coverSizeSlider, 0, Qt::AlignVCenter); + QLabel *bigLabel = new QLabel(); + bigLabel->setPixmap(QPixmap(":/images/comics_view_toolbar/big_size_grid_zoom.png")); + horizontalLayout->addWidget(bigLabel); + horizontalLayout->addSpacing(10); + horizontalLayout->setContentsMargins(0, 0, 0, 0); + + coverSizeSliderWidget->setLayout(horizontalLayout); + + connect(coverSizeSlider, &QAbstractSlider::valueChanged, this, &FolderContentView::setCoversSize); + + toolbar = new QToolBar(); + toolbar->addWidget(new YACReaderToolBarStretch); + toolbar->addWidget(coverSizeSliderWidget); + + auto l = new QVBoxLayout; + setContentsMargins(0, 0, 0, 0); + l->setContentsMargins(0, 0, 0, 0); + l->setSpacing(0); + l->addWidget(view); + l->addWidget(toolbar); + this->setLayout(l); + + QQmlContext *ctxt = view->rootContext(); + + LibraryUITheme theme; +#ifdef Q_OS_MAC + theme = Light; +#else + theme = Dark; +#endif + + if (theme == Light) { + ctxt->setContextProperty("continueReadingBackgroundColor", "#DDDDDD"); + ctxt->setContextProperty("continueReadingColor", "#000000"); + + ctxt->setContextProperty("backgroundColor", "#F6F6F6"); + ctxt->setContextProperty("cellColor", "#FFFFFF"); + ctxt->setContextProperty("selectedColor", "#FFFFFF"); + ctxt->setContextProperty("selectedBorderColor", "#007AFF"); + ctxt->setContextProperty("borderColor", "#DBDBDB"); + ctxt->setContextProperty("titleColor", "#121212"); + ctxt->setContextProperty("textColor", "#636363"); + // fonts settings + ctxt->setContextProperty("fontSize", 11); + ctxt->setContextProperty("fontFamily", QApplication::font().family()); + ctxt->setContextProperty("fontSpacing", 0.5); + + // info - copy/pasted from info_comics_view TODO create helpers for setting the UI config + ctxt->setContextProperty("infoBackgroundColor", "#FFFFFF"); + ctxt->setContextProperty("topShadow", QUrl()); + ctxt->setContextProperty("infoShadow", "info-shadow-light.png"); + ctxt->setContextProperty("infoIndicator", "info-indicator-light.png"); + + ctxt->setContextProperty("infoTextColor", "#404040"); + ctxt->setContextProperty("infoTitleColor", "#2E2E2E"); + + ctxt->setContextProperty("ratingUnselectedColor", "#DEDEDE"); + ctxt->setContextProperty("ratingSelectedColor", "#2B2B2B"); + + ctxt->setContextProperty("favUncheckedColor", "#DEDEDE"); + ctxt->setContextProperty("favCheckedColor", "#E84852"); + + ctxt->setContextProperty("readTickUncheckedColor", "#DEDEDE"); + ctxt->setContextProperty("readTickCheckedColor", "#E84852"); + } else { + ctxt->setContextProperty("continueReadingBackgroundColor", "#88000000"); + ctxt->setContextProperty("continueReadingColor", "#FFFFFF"); + + ctxt->setContextProperty("backgroundColor", "#2A2A2A"); + ctxt->setContextProperty("cellColor", "#212121"); + ctxt->setContextProperty("selectedColor", "#121212"); + ctxt->setContextProperty("selectedBorderColor", "#121212"); + ctxt->setContextProperty("borderColor", "#121212"); + ctxt->setContextProperty("titleColor", "#FFFFFF"); + ctxt->setContextProperty("textColor", "#A8A8A8"); + ctxt->setContextProperty("dropShadow", QVariant(false)); + // fonts settings + int fontSize = QApplication::font().pointSize(); + if (fontSize == -1) + fontSize = QApplication::font().pixelSize(); + ctxt->setContextProperty("fontSize", fontSize); + ctxt->setContextProperty("fontFamily", QApplication::font().family()); + ctxt->setContextProperty("fontSpacing", 0.5); + + // info - copy/pasted from info_comics_view TODO create helpers for setting the UI config + ctxt->setContextProperty("infoBackgroundColor", "#2E2E2E"); + ctxt->setContextProperty("topShadow", "info-top-shadow.png"); + ctxt->setContextProperty("infoShadow", "info-shadow.png"); + ctxt->setContextProperty("infoIndicator", "info-indicator.png"); + + ctxt->setContextProperty("infoTextColor", "#B0B0B0"); + ctxt->setContextProperty("infoTitleColor", "#FFFFFF"); + + ctxt->setContextProperty("ratingUnselectedColor", "#1C1C1C"); + ctxt->setContextProperty("ratingSelectedColor", "#FFFFFF"); + + ctxt->setContextProperty("favUncheckedColor", "#1C1C1C"); + ctxt->setContextProperty("favCheckedColor", "#E84852"); + + ctxt->setContextProperty("readTickUncheckedColor", "#1C1C1C"); + ctxt->setContextProperty("readTickCheckedColor", "#E84852"); + } + + updateCoversSizeInContext(YACREADER_MIN_COVER_WIDTH, ctxt); + + ctxt->setContextProperty("comicsList", comicModel.get()); + ctxt->setContextProperty("foldersList", folderModel); + + ctxt->setContextProperty("showCurrentComic", QVariant(false)); + + ctxt->setContextProperty("openHelper", this); + ctxt->setContextProperty("contextMenuHelper", this); + + view->setSource(QUrl("qrc:/qml/FolderContentView.qml")); +} + +void FolderContentView::setModel(const QModelIndex &parent, FolderModel *model) +{ + this->parent = parent; + QQmlContext *ctxt = view->rootContext(); + + ctxt->setContextProperty("foldersList", model); + + // when the root folder is set, FolderModel just returns itself in `getSubfoldersModel`, I need to measure the performance of create a deep copy... + if (folderModel->isSubfolder) { + delete folderModel; + } + folderModel = model; + + auto grid = view->rootObject()->findChild(QStringLiteral("grid")); + + if (grid != nullptr) { + grid->setProperty("currentIndex", 0); + } +} + +void FolderContentView::setContinueReadingModel(ComicModel *model) +{ + QQmlContext *ctxt = view->rootContext(); + + ctxt->setContextProperty("comicsList", model); + this->comicModel.reset(model); + + auto list = view->rootObject()->findChild(QStringLiteral("list")); + + if (list != nullptr) { + list->setProperty("currentIndex", 0); + } +} + +void FolderContentView::openFolder(int index) +{ + emit subfolderSelected(this->parent, index); +} + +void FolderContentView::openComicFromContinueReadingList(int index) +{ + auto comic = comicModel->getComic(comicModel->index(index, 0)); + emit openComic(comic, ComicModel::Folder); +} + +void FolderContentView::requestedFolderContextMenu(QPoint point, int index) +{ + auto folder = folderModel->getFolder(folderModel->index(index, 0)); + emit openFolderContextMenu(point, folder); +} + +void FolderContentView::requestedContinueReadingComicContextMenu(QPoint point, int index) +{ + auto comic = comicModel->getComic(comicModel->index(index, 0)); + emit openContinueReadingComicContextMenu(point, comic); +} + +void FolderContentView::updateCoversSizeInContext(int width, QQmlContext *ctxt) +{ + int cellBottomMarging = 8 * (1 + 2 * (1 - (float(YACREADER_MAX_GRID_ZOOM_WIDTH - width) / (YACREADER_MAX_GRID_ZOOM_WIDTH - YACREADER_MIN_GRID_ZOOM_WIDTH)))); + + ctxt->setContextProperty("cellCustomHeight", ((width * YACREADER_MAX_COVER_HEIGHT) / YACREADER_MIN_COVER_WIDTH) + 51 + cellBottomMarging); + ctxt->setContextProperty("cellCustomWidth", (width * YACREADER_MIN_CELL_CUSTOM_WIDTH) / YACREADER_MIN_COVER_WIDTH); + + ctxt->setContextProperty("itemWidth", width); + ctxt->setContextProperty("itemHeight", ((width * YACREADER_MAX_COVER_HEIGHT) / YACREADER_MIN_COVER_WIDTH) + 51); + + ctxt->setContextProperty("coverWidth", width); + ctxt->setContextProperty("coverHeight", (width * YACREADER_MAX_COVER_HEIGHT) / YACREADER_MIN_COVER_WIDTH); +} + +void FolderContentView::setCoversSize(int width) +{ + QQmlContext *ctxt = view->rootContext(); + + auto grid = view->rootObject()->findChild(QStringLiteral("grid")); + + if (grid != 0) { + QVariant cellCustomWidth = (width * YACREADER_MIN_CELL_CUSTOM_WIDTH) / YACREADER_MIN_GRID_ZOOM_WIDTH; + QMetaObject::invokeMethod(grid, "calculateCellWidths", + Q_ARG(QVariant, cellCustomWidth)); + } + + updateCoversSizeInContext(width, ctxt); + + settings->setValue(COMICS_GRID_COVER_SIZES, coverSizeSlider->value()); +} + +void FolderContentView::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + + int coverSize = settings->value(COMICS_GRID_COVER_SIZES, YACREADER_MIN_COVER_WIDTH).toInt(); + + coverSizeSlider->setValue(coverSize); + setCoversSize(coverSize); +} + +void FolderContentView::dragEnterEvent(QDragEnterEvent *event) +{ +} + +void FolderContentView::dropEvent(QDropEvent *event) +{ +} diff --git a/YACReaderLibrary/folder_content_view.h b/YACReaderLibrary/folder_content_view.h new file mode 100644 index 00000000..56adc699 --- /dev/null +++ b/YACReaderLibrary/folder_content_view.h @@ -0,0 +1,70 @@ +#ifndef FOLDERCONTENTVIEW_H +#define FOLDERCONTENTVIEW_H + +#include + +#include "comic_model.h" + +#include "folder.h" +#include "comic_db.h" + +class FolderModel; +class ComicModel; +class YACReaderToolBarStretch; + +class QQuickWidget; +class QQmlContext; + +class FolderContentView : public QWidget +{ + Q_OBJECT +public: + explicit FolderContentView(QWidget *parent = nullptr); + void setModel(const QModelIndex &parent, FolderModel *model); + void setContinueReadingModel(ComicModel *model); + +signals: + void subfolderSelected(QModelIndex, int); + void openComic(const ComicDB &comic, const ComicModel::Mode mode); + + // Drops + void copyComicsToCurrentFolder(QList>); + void moveComicsToCurrentFolder(QList>); + + void openFolderContextMenu(QPoint point, Folder folder); + void openContinueReadingComicContextMenu(QPoint point, ComicDB comic); + +protected slots: + // void onItemClicked(const QModelIndex &mi); + void updateCoversSizeInContext(int width, QQmlContext *ctxt); + void setCoversSize(int width); + virtual void showEvent(QShowEvent *event) override; + void openFolder(int index); + void openComicFromContinueReadingList(int index); + void requestedFolderContextMenu(QPoint point, int index); + void requestedContinueReadingComicContextMenu(QPoint point, int index); + +protected: + QQuickWidget *view; + QModelIndex parent; + + std::unique_ptr comicModel; + FolderModel *folderModel; + + // Drop to import + void dragEnterEvent(QDragEnterEvent *event) override; + void dropEvent(QDropEvent *event) override; + +private: + QSettings *settings; + QToolBar *toolbar; + YACReaderToolBarStretch *toolBarStretch; + QAction *toolBarStretchAction; + QWidget *coverSizeSliderWidget; + QSlider *coverSizeSlider; + QAction *coverSizeSliderAction; + QAction *showInfoAction; + QAction *showInfoSeparatorAction; +}; + +#endif // FOLDERCONTENTVIEW_H diff --git a/YACReaderLibrary/grid_comics_view.cpp b/YACReaderLibrary/grid_comics_view.cpp index 0e262345..4ec4ff87 100644 --- a/YACReaderLibrary/grid_comics_view.cpp +++ b/YACReaderLibrary/grid_comics_view.cpp @@ -14,22 +14,6 @@ #include "yacreader_comic_info_helper.h" #include "current_comic_view_helper.h" -// values relative to visible cells -const unsigned int YACREADER_MIN_GRID_ZOOM_WIDTH = 156; -const unsigned int YACREADER_MAX_GRID_ZOOM_WIDTH = 312; - -// GridView cells -const unsigned int YACREADER_MIN_CELL_CUSTOM_HEIGHT = 295; -const unsigned int YACREADER_MIN_CELL_CUSTOM_WIDTH = 185; - -// Covers -const unsigned int YACREADER_MAX_COVER_HEIGHT = 236; -const unsigned int YACREADER_MIN_COVER_WIDTH = YACREADER_MIN_GRID_ZOOM_WIDTH; - -// visible cells (realCell in qml), grid cells size is used to create faux inner margings -const unsigned int YACREADER_MIN_ITEM_HEIGHT = YACREADER_MAX_COVER_HEIGHT + 51; // 51 is the height of the bottom rectangle used for title and other info -const unsigned int YACREADER_MIN_ITEM_WIDTH = YACREADER_MIN_COVER_WIDTH; - GridComicsView::GridComicsView(QWidget *parent) : ComicsView(parent), filterEnabled(false) { @@ -422,6 +406,8 @@ void GridComicsView::setCoversSize(int width) } updateCoversSizeInContext(width, ctxt); + + settings->setValue(COMICS_GRID_COVER_SIZES, coverSizeSlider->value()); } void GridComicsView::updateCoversSizeInContext(int width, QQmlContext *ctxt) diff --git a/YACReaderLibrary/grid_comics_view.h b/YACReaderLibrary/grid_comics_view.h index 7d7455e7..158ebc6a 100644 --- a/YACReaderLibrary/grid_comics_view.h +++ b/YACReaderLibrary/grid_comics_view.h @@ -16,6 +16,22 @@ class YACReaderToolBarStretch; class YACReaderComicsSelectionHelper; class YACReaderComicInfoHelper; +// values relative to visible cells +const unsigned int YACREADER_MIN_GRID_ZOOM_WIDTH = 156; +const unsigned int YACREADER_MAX_GRID_ZOOM_WIDTH = 312; + +// GridView cells +const unsigned int YACREADER_MIN_CELL_CUSTOM_HEIGHT = 295; +const unsigned int YACREADER_MIN_CELL_CUSTOM_WIDTH = 185; + +// Covers +const unsigned int YACREADER_MAX_COVER_HEIGHT = 236; +const unsigned int YACREADER_MIN_COVER_WIDTH = YACREADER_MIN_GRID_ZOOM_WIDTH; + +// visible cells (realCell in qml), grid cells size is used to create faux inner margings +const unsigned int YACREADER_MIN_ITEM_HEIGHT = YACREADER_MAX_COVER_HEIGHT + 51; // 51 is the height of the bottom rectangle used for title and other info +const unsigned int YACREADER_MIN_ITEM_WIDTH = YACREADER_MIN_COVER_WIDTH; + class GridComicsView : public ComicsView { Q_OBJECT From abfa03fa16bb177c3dea64f2c44865fd36d6a0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Sun, 9 Oct 2022 11:31:33 +0200 Subject: [PATCH 7/7] Use the new widget instead of EmptyFolderWidget --- YACReaderLibrary/library_window.cpp | 86 ++++++++++++++++++- YACReaderLibrary/library_window.h | 4 + .../yacreader_comics_views_manager.cpp | 10 +-- .../yacreader_comics_views_manager.h | 5 +- .../yacreader_navigation_controller.cpp | 22 +++-- 5 files changed, 113 insertions(+), 14 deletions(-) diff --git a/YACReaderLibrary/library_window.cpp b/YACReaderLibrary/library_window.cpp index f8a31f5a..003a3aa5 100644 --- a/YACReaderLibrary/library_window.cpp +++ b/YACReaderLibrary/library_window.cpp @@ -79,6 +79,7 @@ #include "opengl_checker.h" #include "yacreader_comics_views_manager.h" +#include "folder_content_view.h" #include "trayicon_controller.h" @@ -1544,7 +1545,7 @@ void LibraryWindow::reloadAfterCopyMove(const QModelIndex &mi) navigationController->loadFolderInfo(mi); } - foldersModel->fetchMoreFromDB(mi); + foldersModel->reload(); enableNeededActions(); } @@ -1783,6 +1784,87 @@ void LibraryWindow::showComicsItemContextMenu(const QPoint &point) menu.exec(comicsViewsManager->comicsView->mapToGlobal(point)); } +void LibraryWindow::showGridFoldersContextMenu(QPoint point, Folder folder) +{ + QMenu menu; + + auto openContainingFolderAction = new QAction(); + openContainingFolderAction->setText(tr("Open folder...")); + openContainingFolderAction->setIcon(QIcon(":/images/menus_icons/open.png")); + + auto updateFolderAction = new QAction(tr("Update folder"), this); + updateFolderAction->setIcon(QIcon(":/images/menus_icons/updateLibraryIcon.png")); + + auto setFolderAsNotCompletedAction = new QAction(); + setFolderAsNotCompletedAction->setText(tr("Set as uncompleted")); + + auto setFolderAsCompletedAction = new QAction(); + setFolderAsCompletedAction->setText(tr("Set as completed")); + + auto setFolderAsReadAction = new QAction(); + setFolderAsReadAction->setText(tr("Set as read")); + + auto setFolderAsUnreadAction = new QAction(); + setFolderAsUnreadAction->setText(tr("Set as unread")); + + auto setFolderAsMangaAction = new QAction(); + setFolderAsMangaAction->setText(tr("Set as manga")); + + auto setFolderAsNormalAction = new QAction(); + setFolderAsNormalAction->setText(tr("Set as comic")); + + menu.addAction(openContainingFolderAction); + menu.addAction(updateFolderAction); + menu.addSeparator(); + if (folder.isCompleted()) + menu.addAction(setFolderAsNotCompletedAction); + else + menu.addAction(setFolderAsCompletedAction); + menu.addSeparator(); + if (folder.isFinished()) + menu.addAction(setFolderAsUnreadAction); + else + menu.addAction(setFolderAsReadAction); + menu.addSeparator(); + if (folder.isManga()) + menu.addAction(setFolderAsNormalAction); + else + menu.addAction(setFolderAsMangaAction); + + // TODO update the subfolder model loaded in folderContentView + connect(openContainingFolderAction, &QAction::triggered, this, [=]() { + QDesktopServices::openUrl(QUrl("file:///" + QDir::cleanPath(currentPath() + "/" + folder.path), QUrl::TolerantMode)); + }); + connect(updateFolderAction, &QAction::triggered, this, [=]() { + updateFolder(foldersModel->getIndexFromFolder(folder)); + }); + connect(setFolderAsNotCompletedAction, &QAction::triggered, this, [=]() { + foldersModel->updateFolderCompletedStatus(QModelIndexList() << foldersModel->getIndexFromFolder(folder), false); + }); + connect(setFolderAsCompletedAction, &QAction::triggered, this, [=]() { + foldersModel->updateFolderCompletedStatus(QModelIndexList() << foldersModel->getIndexFromFolder(folder), true); + }); + connect(setFolderAsReadAction, &QAction::triggered, this, [=]() { + foldersModel->updateFolderFinishedStatus(QModelIndexList() << foldersModel->getIndexFromFolder(folder), true); + }); + connect(setFolderAsUnreadAction, &QAction::triggered, this, [=]() { + foldersModel->updateFolderFinishedStatus(QModelIndexList() << foldersModel->getIndexFromFolder(folder), false); + }); + connect(setFolderAsMangaAction, &QAction::triggered, this, [=]() { + foldersModel->updateFolderManga(QModelIndexList() << foldersModel->getIndexFromFolder(folder), true); + }); + connect(setFolderAsNormalAction, &QAction::triggered, this, [=]() { + foldersModel->updateFolderManga(QModelIndexList() << foldersModel->getIndexFromFolder(folder), false); + }); + + menu.exec(comicsViewsManager->folderContentView->mapToGlobal(point)); +} + +void LibraryWindow::showContinueReadingContextMenu(QPoint point, ComicDB comic) +{ + qDebug() << "openContinueReadingComicContextMenu" << comic.name; +} + void LibraryWindow::setupAddToSubmenu(QMenu &menu) { menu.addAction(addToFavoritesAction); @@ -1832,7 +1914,7 @@ void LibraryWindow::saveSelectedCoversTo() if (!folderPath.isEmpty()) { QModelIndexList comics = getSelectedComics(); foreach (QModelIndex comic, comics) { - QString origin = comic.data(ComicModel::CoverPathRole).toString().remove("file:///"); + QString origin = comic.data(ComicModel::CoverPathRole).toString().remove("file:///").remove("file:"); QString destination = QDir(folderPath).filePath(comic.data(ComicModel::FileNameRole).toString() + ".jpg"); QLOG_DEBUG() << "From : " << origin; diff --git a/YACReaderLibrary/library_window.h b/YACReaderLibrary/library_window.h index db201a3b..ef51c657 100644 --- a/YACReaderLibrary/library_window.h +++ b/YACReaderLibrary/library_window.h @@ -14,6 +14,8 @@ #include "folder_query_result_processor.h" #include "comic_model.h" +#include "comic_db.h" +#include "folder.h" #include #include @@ -379,6 +381,8 @@ public slots: void deleteComicsFromList(); // void showSocial(); void showFoldersContextMenu(const QPoint &point); + void showGridFoldersContextMenu(QPoint point, Folder folder); + void showContinueReadingContextMenu(QPoint point, ComicDB comic); void libraryAlreadyExists(const QString &name); void importLibraryPackage(); void updateComicsView(quint64 libraryId, const ComicDB &comic); diff --git a/YACReaderLibrary/yacreader_comics_views_manager.cpp b/YACReaderLibrary/yacreader_comics_views_manager.cpp index ad7e5c49..36634017 100644 --- a/YACReaderLibrary/yacreader_comics_views_manager.cpp +++ b/YACReaderLibrary/yacreader_comics_views_manager.cpp @@ -6,7 +6,7 @@ #include "grid_comics_view.h" #include "info_comics_view.h" #include "comics_view_transition.h" -#include "empty_folder_widget.h" +#include "folder_content_view.h" #include "empty_label_widget.h" #include "empty_special_list.h" #include "empty_reading_list_widget.h" @@ -48,7 +48,7 @@ YACReaderComicsViewsManager::YACReaderComicsViewsManager(QSettings *settings, Li doComicsViewConnections(); comicsViewStack->addWidget(comicsViewTransition = new ComicsViewTransition()); - comicsViewStack->addWidget(emptyFolderWidget = new EmptyFolderWidget()); + comicsViewStack->addWidget(folderContentView = new FolderContentView()); comicsViewStack->addWidget(emptyLabelWidget = new EmptyLabelWidget()); comicsViewStack->addWidget(emptySpecialList = new EmptySpecialListWidget()); comicsViewStack->addWidget(emptyReadingList = new EmptyReadingListWidget()); @@ -59,8 +59,8 @@ YACReaderComicsViewsManager::YACReaderComicsViewsManager(QSettings *settings, Li comicsViewStack->setCurrentWidget(comicsView); // connections - connect(emptyFolderWidget, &EmptyFolderWidget::copyComicsToCurrentFolder, libraryWindow, &LibraryWindow::copyAndImportComicsToCurrentFolder); - connect(emptyFolderWidget, &EmptyFolderWidget::moveComicsToCurrentFolder, libraryWindow, &LibraryWindow::moveAndImportComicsToCurrentFolder); + connect(folderContentView, &FolderContentView::copyComicsToCurrentFolder, libraryWindow, &LibraryWindow::copyAndImportComicsToCurrentFolder); + connect(folderContentView, &FolderContentView::moveComicsToCurrentFolder, libraryWindow, &LibraryWindow::moveAndImportComicsToCurrentFolder); } QWidget *YACReaderComicsViewsManager::containerWidget() @@ -86,7 +86,7 @@ void YACReaderComicsViewsManager::showComicsView() void YACReaderComicsViewsManager::showEmptyFolderView() { - comicsViewStack->setCurrentWidget(emptyFolderWidget); + comicsViewStack->setCurrentWidget(folderContentView); } void YACReaderComicsViewsManager::showEmptyLabelView() diff --git a/YACReaderLibrary/yacreader_comics_views_manager.h b/YACReaderLibrary/yacreader_comics_views_manager.h index 4e79dd25..280bbd41 100644 --- a/YACReaderLibrary/yacreader_comics_views_manager.h +++ b/YACReaderLibrary/yacreader_comics_views_manager.h @@ -12,7 +12,7 @@ class ClassicComicsView; class GridComicsView; class InfoComicsView; class ComicsViewTransition; -class EmptyFolderWidget; +class FolderContentView; class EmptyLabelWidget; class EmptySpecialListWidget; class EmptyReadingListWidget; @@ -20,6 +20,7 @@ class NoSearchResultsWidget; using namespace YACReader; +// TODO rename to YACReaderContentViewsManager class YACReaderComicsViewsManager : public QObject { Q_OBJECT @@ -32,7 +33,7 @@ public: ComicsViewTransition *comicsViewTransition; - EmptyFolderWidget *emptyFolderWidget; + FolderContentView *folderContentView; EmptyLabelWidget *emptyLabelWidget; EmptySpecialListWidget *emptySpecialList; EmptyReadingListWidget *emptyReadingList; diff --git a/YACReaderLibrary/yacreader_navigation_controller.cpp b/YACReaderLibrary/yacreader_navigation_controller.cpp index 87c2b93f..9a313583 100644 --- a/YACReaderLibrary/yacreader_navigation_controller.cpp +++ b/YACReaderLibrary/yacreader_navigation_controller.cpp @@ -11,7 +11,7 @@ #include "folder_model.h" #include "reading_list_model.h" #include "comics_view.h" -#include "empty_folder_widget.h" +#include "folder_content_view.h" #include "yacreader_search_line_edit.h" #include "yacreader_global.h" #include "empty_label_widget.h" @@ -249,9 +249,18 @@ void YACReaderNavigationController::selectSubfolder(const QModelIndex &sourceMIP void YACReaderNavigationController::loadEmptyFolderInfo(const QModelIndex &modelIndex) { - QStringList subfolders; - subfolders = libraryWindow->foldersModel->getSubfoldersNames(modelIndex); - comicsViewsManager->emptyFolderWidget->setSubfolders(modelIndex, subfolders); + auto readingComicsModel = new ComicModel(); + + auto isRoot = !modelIndex.isValid(); + + if (isRoot) { + readingComicsModel->setupReadingModelData(libraryWindow->foldersModel->getDatabase()); + } + + comicsViewsManager->folderContentView->setContinueReadingModel(readingComicsModel); + + auto subFolderModel = libraryWindow->foldersModel->getSubfoldersModel(modelIndex); + comicsViewsManager->folderContentView->setModel(modelIndex, subFolderModel); } void YACReaderNavigationController::loadPreviousStatus() @@ -266,7 +275,10 @@ void YACReaderNavigationController::setupConnections() connect(libraryWindow->foldersView, &YACReaderTreeView::clicked, this, &YACReaderNavigationController::selectedFolder); connect(libraryWindow->listsView, &QAbstractItemView::clicked, this, &YACReaderNavigationController::selectedList); connect(libraryWindow->historyController, &YACReaderHistoryController::modelIndexSelected, this, &YACReaderNavigationController::selectedIndexFromHistory); - connect(comicsViewsManager->emptyFolderWidget, &EmptyFolderWidget::subfolderSelected, this, &YACReaderNavigationController::selectSubfolder); + connect(comicsViewsManager->folderContentView, &FolderContentView::subfolderSelected, this, &YACReaderNavigationController::selectSubfolder); + connect(comicsViewsManager->folderContentView, &FolderContentView::openComic, libraryWindow, QOverload::of(&LibraryWindow::openComic)); + connect(comicsViewsManager->folderContentView, &FolderContentView::openFolderContextMenu, libraryWindow, &LibraryWindow::showGridFoldersContextMenu); + connect(comicsViewsManager->folderContentView, &FolderContentView::openContinueReadingComicContextMenu, libraryWindow, &LibraryWindow::showContinueReadingContextMenu); connect(libraryWindow->comicsModel, &ComicModel::isEmpty, this, &YACReaderNavigationController::reselectCurrentSource); }