#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 #include #ifdef Q_OS_MAC #include 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�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; 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(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(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(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(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(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�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; // 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�n que soporta sqlit 2^64 // el diccionario permitir� encontrar cualquier nodo del �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 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�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 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�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(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(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(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(mi.internalPointer()); std::function 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(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(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 items; QList 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(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 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(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(source_parent.internalPointer()); if (parent == 0) parent = static_cast(sourceModel())->rootItem; FolderItem *item = parent->children().at(source_row); return filteredItems.contains(item->id); } void FolderModelProxy::setFilterData(QMap *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::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(); }