yacreader/YACReaderLibrary/db/folder_model.cpp
Luis Ángel San Martín 5aa02a19bb clang-format
2021-10-18 21:56:52 +02:00

628 lines
18 KiB
C++
Raw Blame History

#include "folder_model.h"
#include "folder_item.h"
#include "data_base_management.h"
#include "folder.h"
#include "db_helper.h"
#include "qnaturalsorting.h"
#include "yacreader_global_gui.h"
#include "QsLog.h"
#include "query_parser.h"
#include <QtGui>
#include <algorithm>
#ifdef Q_OS_MAC
#include <QFileIconProvider>
QIcon finishedFolderIcon;
void drawMacOSXFinishedFolderIcon()
{
QIcon ico = QFileIconProvider().icon(QFileIconProvider::Folder);
QPixmap pixNormalOff = ico.pixmap(16, 16, QIcon::Normal, QIcon::Off);
QPixmap pixNormalOn = ico.pixmap(16, 16, QIcon::Normal, QIcon::On);
QPixmap pixSelectedOff = ico.pixmap(16, 16, QIcon::Selected, QIcon::Off);
QPixmap pixSelectedOn = ico.pixmap(16, 16, QIcon::Selected, QIcon::On);
QPixmap tick(":/images/folder_finished_macosx.png");
{
QPainter p(&pixNormalOff);
p.drawPixmap(4, 7, tick);
}
finishedFolderIcon.addPixmap(pixNormalOff, QIcon::Normal, QIcon::Off);
{
QPainter p(&pixNormalOn);
p.drawPixmap(4, 7, tick);
}
finishedFolderIcon.addPixmap(pixNormalOn, QIcon::Normal, QIcon::On);
{
QPainter p(&pixSelectedOff);
p.drawPixmap(4, 7, tick);
}
finishedFolderIcon.addPixmap(pixSelectedOff, QIcon::Selected, QIcon::Off);
{
QPainter p(&pixSelectedOn);
p.drawPixmap(4, 7, tick);
}
finishedFolderIcon.addPixmap(pixSelectedOn, QIcon::Selected, QIcon::On);
}
#endif
#define ROOT 1
FolderModel::FolderModel(QObject *parent)
: QAbstractItemModel(parent), rootItem(0)
{
}
FolderModel::FolderModel(QSqlQuery &sqlquery, QObject *parent)
: QAbstractItemModel(parent), rootItem(0)
{
// 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;
setupModelData(sqlquery, rootItem);
// sqlquery.finish();
}
FolderModel::~FolderModel()
{
if (rootItem != 0)
delete rootItem;
}
int FolderModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return static_cast<FolderItem *>(parent.internalPointer())->columnCount();
else
return rootItem->columnCount();
}
QVariant FolderModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
auto item = static_cast<FolderItem *>(index.internalPointer());
if (role == Qt::ToolTipRole) {
QString toolTip = item->data(FolderModel::Name).toString();
int totalNumOfChildren = item->childCount() + item->comicNames.size();
if (totalNumOfChildren > 0) {
toolTip = toolTip + " - " + QString::number(totalNumOfChildren);
}
return toolTip;
}
if (role == Qt::DecorationRole) {
#ifdef Q_OS_MAC
if (item->data(FolderModel::Finished).toBool()) {
if (finishedFolderIcon.isNull()) {
drawMacOSXFinishedFolderIcon();
}
return QVariant(finishedFolderIcon);
} else {
return QVariant(QFileIconProvider().icon(QFileIconProvider::Folder));
}
#else
if (item->data(FolderModel::Finished).toBool())
return QVariant(YACReader::noHighlightedIcon(":/images/sidebar/folder_finished.png"));
else
return QVariant(YACReader::noHighlightedIcon(":/images/sidebar/folder.png"));
#endif
}
if (role == FolderModel::CompletedRole)
return item->data(FolderModel::Completed);
if (role == FolderModel::FinishedRole)
return item->data(FolderModel::Finished);
if (role == FolderModel::MangaRole)
return item->data(FolderModel::Manga);
if (role == FolderModel::IdRole)
return item->id;
if (role != Qt::DisplayRole)
return QVariant();
return item->data(index.column());
}
Qt::ItemFlags FolderModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return {};
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled;
}
QVariant FolderModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
return rootItem->data(section);
return QVariant();
}
QModelIndex FolderModel::index(int row, int column, const QModelIndex &parent)
const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
FolderItem *parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<FolderItem *>(parent.internalPointer());
FolderItem *childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
QModelIndex FolderModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
auto childItem = static_cast<FolderItem *>(index.internalPointer());
FolderItem *parentItem = childItem->parent();
if (parentItem == rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
int FolderModel::rowCount(const QModelIndex &parent) const
{
FolderItem *parentItem;
if (parent.column() > 0)
return 0;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<FolderItem *>(parent.internalPointer());
return parentItem->childCount();
}
void FolderModel::setupModelData(QString path)
{
beginResetModel();
if (rootItem != 0)
delete rootItem; // TODO comprobar que se libera bien la memoria
rootItem = 0;
// 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;
// cargar la base de datos
_databasePath = path;
// crear la consulta
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(path);
QSqlQuery selectQuery("select * from folder where id <> 1 order by parentId,name", db);
setupModelData(selectQuery, rootItem);
connectionName = db.connectionName();
}
// selectQuery.finish();
QSqlDatabase::removeDatabase(connectionName);
endResetModel();
}
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
// el diccionario permitir<69> encontrar cualquier nodo del <20>rbol r<>pidamente, de forma que a<>adir un hijo a un padre sea O(1)
items.clear();
// se a<>ade el nodo 0
items.insert(parent->id, parent);
QSqlRecord record = sqlquery.record();
int name = record.indexOf("name");
int path = record.indexOf("path");
int finished = record.indexOf("finished");
int completed = record.indexOf("completed");
int manga = record.indexOf("manga");
int id = record.indexOf("id");
int parentId = record.indexOf("parentId");
while (sqlquery.next()) {
QList<QVariant> data;
data << sqlquery.value(name).toString();
data << sqlquery.value(path).toString();
data << sqlquery.value(finished).toBool();
data << sqlquery.value(completed).toBool();
data << sqlquery.value(manga).toBool();
auto item = new FolderItem(data);
item->id = sqlquery.value(id).toULongLong();
// la inserci<63>n de hijos se hace de forma ordenada
FolderItem *parent = items.value(sqlquery.value(parentId).toULongLong());
// if(parent !=0) //TODO if parent==0 the parent of item was removed from the DB and delete on cascade didn't work, ERROR.
parent->appendChild(item);
// se a<>ade el item al map, de forma que se pueda encontrar como padre en siguientes iteraciones
items.insert(item->id, item);
}
}
void FolderModel::updateFolderModelData(QSqlQuery &sqlquery, FolderItem *parent)
{
Q_UNUSED(parent);
QSqlRecord record = sqlquery.record();
int name = record.indexOf("name");
int path = record.indexOf("path");
int finished = record.indexOf("finished");
int completed = record.indexOf("completed");
int manga = record.indexOf("manga");
int id = record.indexOf("id");
int parentId = record.indexOf("parentId");
while (sqlquery.next()) {
QList<QVariant> data;
data << sqlquery.value(name).toString();
data << sqlquery.value(path).toString();
data << sqlquery.value(finished).toBool();
data << sqlquery.value(completed).toBool();
data << sqlquery.value(manga).toBool();
auto item = new FolderItem(data);
item->id = sqlquery.value(id).toULongLong();
// la inserci<63>n de hijos se hace de forma ordenada
FolderItem *parent = items.value(sqlquery.value(parentId).toULongLong());
if (parent != 0) // TODO if parent==0 the parent of item was removed from the DB and delete on cascade didn't work, ERROR.
parent->appendChild(item);
// se a<>ade el item al map, de forma que se pueda encontrar como padre en siguientes iteraciones
items.insert(item->id, item);
}
}
QString FolderModel::getDatabase()
{
return _databasePath;
}
QString FolderModel::getFolderPath(const QModelIndex &folder)
{
if (!folder.isValid()) // root folder
return "/";
return static_cast<FolderItem *>(folder.internalPointer())->data(FolderModel::Path).toString();
}
void FolderModel::updateFolderCompletedStatus(const QModelIndexList &list, bool status)
{
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
db.transaction();
foreach (QModelIndex mi, list) {
auto item = static_cast<FolderItem *>(mi.internalPointer());
item->setData(FolderModel::Completed, status);
Folder f = DBHelper::loadFolder(item->id, db);
f.setCompleted(status);
DBHelper::update(f, db);
}
db.commit();
connectionName = db.connectionName();
}
QSqlDatabase::removeDatabase(connectionName);
emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::Completed));
}
void FolderModel::updateFolderFinishedStatus(const QModelIndexList &list, bool status)
{
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
db.transaction();
foreach (QModelIndex mi, list) {
auto item = static_cast<FolderItem *>(mi.internalPointer());
item->setData(FolderModel::Finished, status);
Folder f = DBHelper::loadFolder(item->id, db);
f.setFinished(status);
DBHelper::update(f, db);
}
db.commit();
connectionName = db.connectionName();
}
QSqlDatabase::removeDatabase(connectionName);
emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::Completed));
}
void FolderModel::updateFolderManga(const QModelIndexList &list, bool manga)
{
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
db.transaction();
foreach (QModelIndex mi, list) {
auto item = static_cast<FolderItem *>(mi.internalPointer());
std::function<void(FolderItem *, bool)> setManga;
setManga = [&setManga](FolderItem *item, bool manga) -> void {
item->setData(FolderModel::Manga, manga);
for (auto child : item->children()) {
setManga(child, manga);
}
};
setManga(item, manga);
DBHelper::updateFolderTreeManga(item->id, db, manga);
}
db.commit();
connectionName = db.connectionName();
}
QSqlDatabase::removeDatabase(connectionName);
emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::Manga));
}
QStringList FolderModel::getSubfoldersNames(const QModelIndex &mi)
{
QStringList result;
qulonglong id = 1;
if (mi.isValid()) {
auto item = static_cast<FolderItem *>(mi.internalPointer());
id = item->id;
}
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
db.transaction();
result = DBHelper::loadSubfoldersNames(id, db);
db.commit();
connectionName = db.connectionName();
}
QSqlDatabase::removeDatabase(connectionName);
std::sort(result.begin(), result.end(), naturalSortLessThanCI);
return result;
}
void FolderModel::fetchMoreFromDB(const QModelIndex &parent)
{
FolderItem *item;
if (parent.isValid())
item = static_cast<FolderItem *>(parent.internalPointer());
else
item = rootItem;
// Remove all children
if (item->childCount() > 0) {
beginRemoveRows(parent, 0, item->childCount() - 1);
item->clearChildren();
endRemoveRows();
}
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
QList<FolderItem *> items;
QList<FolderItem *> nextLevelItems;
QSqlQuery selectQuery(db);
selectQuery.prepare("select * from folder where id <> 1 and parentId = :parentId order by parentId,name");
items << item;
bool firstLevelUpdated = false;
while (items.size() > 0) {
nextLevelItems.clear();
foreach (FolderItem *item, items) {
QLOG_DEBUG() << "ID " << item->id;
selectQuery.bindValue(":parentId", item->id);
selectQuery.exec();
if (!firstLevelUpdated) {
// NO size support
int numResults = 0;
while (selectQuery.next())
numResults++;
if (!selectQuery.seek(-1))
selectQuery.exec();
// END no size support
beginInsertRows(parent, 0, numResults - 1);
}
updateFolderModelData(selectQuery, item);
if (!firstLevelUpdated) {
endInsertRows();
firstLevelUpdated = true;
}
nextLevelItems << item->children();
}
items.clear();
items = nextLevelItems;
}
connectionName = db.connectionName();
}
QLOG_DEBUG() << "item->childCount()-1" << item->childCount() - 1;
QSqlDatabase::removeDatabase(connectionName);
}
QModelIndex FolderModel::addFolderAtParent(const QString &folderName, const QModelIndex &parent)
{
FolderItem *parentItem;
if (parent.isValid())
parentItem = static_cast<FolderItem *>(parent.internalPointer());
else
parentItem = rootItem;
Folder newFolder;
newFolder.name = folderName;
newFolder.parentId = parentItem->id;
newFolder.path = parentItem->data(Columns::Path).toString() + "/" + folderName;
newFolder.setManga(parentItem->data(Columns::Manga).toBool());
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
newFolder.id = DBHelper::insert(&newFolder, db);
DBHelper::updateChildrenInfo(parentItem->id, db);
connectionName = db.connectionName();
}
QSqlDatabase::removeDatabase(connectionName);
int destRow = 0;
QList<QVariant> data;
data << newFolder.name;
data << newFolder.path;
data << false; // finished
data << true; // completed
data << newFolder.isManga();
auto item = new FolderItem(data);
item->id = newFolder.id;
beginInsertRows(parent, 0, 0); // TODO calculate the destRow before inserting the new child
parentItem->appendChild(item);
destRow = parentItem->children().indexOf(item); // TODO optimize this, appendChild should return the index of the new item
items.insert(item->id, item);
endInsertRows();
return index(destRow, 0, parent);
}
void FolderModel::deleteFolder(const QModelIndex &mi)
{
beginRemoveRows(mi.parent(), mi.row(), mi.row());
auto item = static_cast<FolderItem *>(mi.internalPointer());
FolderItem *parent = item->parent();
parent->removeChild(mi.row());
Folder f;
f.setId(item->id);
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
DBHelper::removeFromDB(&f, db);
DBHelper::updateChildrenInfo(item->parent()->id, db);
connectionName = db.connectionName();
}
QSqlDatabase::removeDatabase(connectionName);
endRemoveRows();
}
void FolderModel::updateFolderChildrenInfo(qulonglong folderId)
{
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
DBHelper::updateChildrenInfo(folderId, db);
connectionName = db.connectionName();
}
QSqlDatabase::removeDatabase(connectionName);
}
// PROXY
FolderModelProxy::FolderModelProxy(QObject *parent)
: QSortFilterProxyModel(parent), rootItem(0), filterEnabled(false)
{
}
FolderModelProxy::~FolderModelProxy()
{
}
bool FolderModelProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (!filterEnabled)
return true;
auto parent = static_cast<FolderItem *>(source_parent.internalPointer());
if (parent == 0)
parent = static_cast<FolderModel *>(sourceModel())->rootItem;
FolderItem *item = parent->children().at(source_row);
return filteredItems.contains(item->id);
}
void FolderModelProxy::setFilterData(QMap<unsigned long long, FolderItem *> *filteredItems, FolderItem *root)
{
clear();
filterEnabled = true;
beginResetModel();
if (rootItem != 0)
delete rootItem; // TODO comprobar que se libera bien la memoria
rootItem = root;
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
QMap<unsigned long long, FolderItem *>::iterator i;
for (i = filteredItems->begin(); i != filteredItems->end(); ++i)
this->filteredItems.insert(i.key(), i.value());
#else
this->filteredItems.insert(*filteredItems);
#endif
endResetModel();
delete filteredItems;
}
void FolderModelProxy::clear()
{
filterEnabled = false;
filteredItems.clear();
QSortFilterProxyModel::invalidate();
}