Merge pull request #331 from YACReader/folder_content_view

feature: Folder content view
This commit is contained in:
Luis Ángel San Martín 2022-10-09 13:21:25 +02:00 committed by GitHub
commit 551726d522
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1608 additions and 49 deletions

View File

@ -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 \

View File

@ -1,6 +1,7 @@
#include <QtGui>
#include <QtDebug>
#include <QStringBuilder>
#include <limits>
#include "comic_item.h"
@ -237,6 +238,7 @@ QHash<int, QByteArray> 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);

View File

@ -54,6 +54,7 @@ public:
HasBeenOpenedRole,
CoverPathRole,
PublicationDateRole,
ReadableTitle,
};
enum Mode {

View File

@ -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<72>z no necesite tener informaci<63>n
QList<QVariant> 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<FolderItem *>(parent.internalPointer())->columnCount();
else
else {
if (rootItem == nullptr) {
return 0;
}
return rootItem->columnCount();
}
}
QHash<int, QByteArray> FolderModel::roleNames() const
{
QHash<int, QByteArray> 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<FolderItem *>(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<72>z
QList<QVariant> 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<73>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<QVariant> 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<QVariant> 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<FolderItem *>(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<FolderItem *>(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<FolderItem *>(modelIndex.internalPointer());
if (folderItem->id == folder.id) {
return modelIndex;
}
auto childModelIndex = getIndexFromFolder(folder, modelIndex);
if (childModelIndex.isValid()) {
auto folderItem = static_cast<FolderItem *>(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());

View File

@ -7,9 +7,11 @@
#include <QVariant>
#include <QSqlQuery>
#include <QSqlDatabase>
#include <QUrl>
#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<int, QByteArray> 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);

View File

@ -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 <QQmlContext>
#include <QQuickItem>
#include <QQuickWidget>
using namespace YACReader;
FolderContentView::FolderContentView(QWidget *parent)
: QWidget { parent }, parent(QModelIndex()), comicModel(new ComicModel()), folderModel(new FolderModel())
{
qmlRegisterType<FolderModel>("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<QQuickItem *>(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<QQuickItem *>(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<QQuickItem *>(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)
{
}

View File

@ -0,0 +1,70 @@
#ifndef FOLDERCONTENTVIEW_H
#define FOLDERCONTENTVIEW_H
#include <QtWidgets>
#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<QPair<QString, QString>>);
void moveComicsToCurrentFolder(QList<QPair<QString, QString>>);
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> 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

View File

@ -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)
@ -478,6 +464,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;

View File

@ -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
@ -73,6 +89,8 @@ protected slots:
void resetScroll();
virtual void showEvent(QShowEvent *event) override;
signals:
void onScrollToOrigin();

View File

@ -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;

View File

@ -14,6 +14,8 @@
#include "folder_query_result_processor.h"
#include "comic_model.h"
#include "comic_db.h"
#include "folder.h"
#include <future>
#include <memory>
@ -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);

View File

@ -1,6 +1,7 @@
<RCC>
<qresource prefix="/">
<file>qml/GridComicsView.qml</file>
<file>qml/FolderContentView.qml</file>
<file>qml/tick.png</file>
<file>qml/reading.png</file>
<file>qml/star_menu.png</file>

View File

@ -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
}
}
}
}
}
}

View File

@ -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
}
}
}
}
}
}

View File

@ -1,6 +1,7 @@
<RCC>
<qresource prefix="/">
<file alias="qml/GridComicsView.qml">qml/GridComicsView6.qml</file>
<file alias="qml/FolderContentView.qml">qml/FolderContentView6.qml</file>
<file alias="qml/FlowView.qml">qml/FlowView6.qml</file>
<file alias="qml/InfoTick.qml">qml/InfoTick6.qml</file>
<file alias="qml/InfoFavorites.qml">qml/InfoFavorites6.qml</file>

View File

@ -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()

View File

@ -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;

View File

@ -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<const ComicDB &, const ComicModel::Mode>::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);
}

View File

@ -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);

View File

@ -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