#include "reading_list_model.h" #include "reading_list_item.h" #include "data_base_management.h" #include "qnaturalsorting.h" #include "db_helper.h" #include "QsLog.h" #include ReadingListModel::ReadingListModel(QObject *parent) : QAbstractItemModel(parent), rootItem(0) { separator1 = new ReadingListSeparatorItem; separator2 = new ReadingListSeparatorItem; } int ReadingListModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) // TOP { int separatorsCount = 2; // labels.isEmpty()?1:2; return specialLists.count() + labels.count() + rootItem->childCount() + separatorsCount; } else { auto item = static_cast(parent.internalPointer()); if (typeid(*item) == typeid(ReadingListItem)) { auto item = static_cast(parent.internalPointer()); return item->childCount(); } } return 0; } int ReadingListModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) { auto item = static_cast(parent.internalPointer()); if (typeid(*item) == typeid(ReadingListSeparatorItem)) return 0; } return 1; /*if (parent.isValid()) return static_cast(parent.internalPointer())->columnCount(); else return rootItem->columnCount();*/ } QVariant ReadingListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); auto item = static_cast(index.internalPointer()); if (role == ReadingListModel::TypeListsRole) { if (typeid(*item) == typeid(SpecialListItem)) return QVariant(ReadingListModel::SpecialList); if (typeid(*item) == typeid(LabelItem)) return QVariant(ReadingListModel::Label); if (typeid(*item) == typeid(ReadingListItem)) return QVariant(ReadingListModel::ReadingList); if (typeid(*item) == typeid(ReadingListSeparatorItem)) return QVariant(ReadingListModel::Separator); } if (role == ReadingListModel::LabelColorRole && typeid(*item) == typeid(LabelItem)) { auto labelItem = static_cast(item); return QVariant(labelItem->colorid()); } if (role == ReadingListModel::IDRole) { QLOG_DEBUG() << "getting role"; return item->getId(); } if (role == ReadingListModel::SpecialListTypeRole && typeid(*item) == typeid(SpecialListItem)) { auto specialListItem = static_cast(item); return QVariant(specialListItem->getType()); } if (typeid(*item) == typeid(ReadingListSeparatorItem)) return QVariant(); if (role == Qt::DecorationRole) { return QVariant(item->getIcon()); } if (role != Qt::DisplayRole) return QVariant(); return item->data(index.column()); } Qt::ItemFlags ReadingListModel::flags(const QModelIndex &index) const { if (!index.isValid()) return {}; auto item = static_cast(index.internalPointer()); if (typeid(*item) == typeid(ReadingListSeparatorItem)) return {}; if (typeid(*item) == typeid(ReadingListItem) && static_cast(item)->parent->getId() != 0) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled; // only sublists are dragable return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled; } QVariant ReadingListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return rootItem->data(section); return QVariant(); } QModelIndex ReadingListModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); if (!parent.isValid()) { int separatorsCount = 2; // labels.isEmpty()?1:2; if (rowIsSpecialList(row, parent)) return createIndex(row, column, specialLists.at(row)); if (row == specialLists.count()) return createIndex(row, column, separator1); if (rowIsLabel(row, parent)) return createIndex(row, column, labels.at(row - specialLists.count() - 1)); if (separatorsCount == 2) if (row == specialLists.count() + labels.count() + 1) return createIndex(row, column, separator2); if (rowIsReadingList(row, parent)) return createIndex(row, column, rootItem->child(row - (specialLists.count() + labels.count() + separatorsCount))); } else // sublist { ReadingListItem *parentItem; if (!parent.isValid()) parentItem = rootItem; // this should be impossible else parentItem = static_cast(parent.internalPointer()); ReadingListItem *childItem = parentItem->child(row); return createIndex(row, column, childItem); } /*FolderItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); else*/ return QModelIndex(); } QModelIndex ReadingListModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); auto item = static_cast(index.internalPointer()); if (typeid(*item) == typeid(ReadingListItem)) { auto childItem = static_cast(index.internalPointer()); ReadingListItem *parent = childItem->parent; if (parent->getId() != 0) return createIndex(parent->row() + specialLists.count() + labels.count() + 2, 0, parent); } return QModelIndex(); } bool ReadingListModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const { Q_UNUSED(action); QLOG_DEBUG() << "trying to drop into row = " << row << "column column = " << column << "parent" << parent; if (row == -1) return false; if (!parent.isValid()) // top level items { if (row == -1) // no list return false; if (row == 1) // reading is just an smart list return false; if (rowIsSeparator(row, parent)) return false; } if (data->formats().contains(YACReader::YACReaderLibrarComiscSelectionMimeDataFormat)) return true; if (rowIsReadingList(row, parent)) // TODO avoid droping in a different parent { if (!parent.isValid()) return false; else { QList> sublistsRows; QByteArray rawData = data->data(YACReader::YACReaderLibrarSubReadingListMimeDataFormat); QDataStream in(&rawData, QIODevice::ReadOnly); in >> sublistsRows; // deserialize the list of indentifiers if (parent.row() != sublistsRows.at(0).second) return false; return data->formats().contains(YACReader::YACReaderLibrarSubReadingListMimeDataFormat); } } return false; } bool ReadingListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { QLOG_DEBUG() << "drop mimedata into row = " << row << " column = " << column << "parent" << parent; if (data->formats().contains(YACReader::YACReaderLibrarComiscSelectionMimeDataFormat)) return dropComics(data, action, row, column, parent); if (data->formats().contains(YACReader::YACReaderLibrarSubReadingListMimeDataFormat)) return dropSublist(data, action, row, column, parent); return false; } bool ReadingListModel::dropComics(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(action); QList comicIds = YACReader::mimeDataToComicsIds(data); QLOG_DEBUG() << "dropped : " << comicIds; QModelIndex dest; QModelIndex parentDest; if (row == -1) { dest = parent; } else dest = index(row, column, parent); parentDest = dest.parent(); if (rowIsSpecialList(dest.row(), parentDest)) { if (dest.row() == 0) // add to favorites { QLOG_DEBUG() << "-------addComicsToFavorites : " << comicIds << " to " << dest.data(IDRole).toULongLong(); emit addComicsToFavorites(comicIds); return true; } } if (rowIsLabel(dest.row(), parentDest)) { QLOG_DEBUG() << "+++++++++++addComicsToLabel : " << comicIds << " to " << dest.data(IDRole).toULongLong(); emit addComicsToLabel(comicIds, dest.data(IDRole).toULongLong()); return true; } if (rowIsReadingList(dest.row(), parentDest)) { QLOG_DEBUG() << "///////////addComicsToReadingList : " << comicIds << " to " << dest.data(IDRole).toULongLong(); emit addComicsToReadingList(comicIds, dest.data(IDRole).toULongLong()); return true; } return false; } bool ReadingListModel::dropSublist(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(action); Q_UNUSED(column); QList> sublistsRows; QByteArray rawData = data->data(YACReader::YACReaderLibrarSubReadingListMimeDataFormat); QDataStream in(&rawData, QIODevice::ReadOnly); in >> sublistsRows; // deserialize the list of indentifiers QLOG_DEBUG() << "dropped : " << sublistsRows; int sourceRow = sublistsRows.at(0).first; int destRow = row; QModelIndex destParent = parent; if (row == -1) { QLOG_DEBUG() << "droping inside parent"; destRow = parent.row(); destParent = parent.parent(); } QLOG_DEBUG() << "move " << sourceRow << "-" << destRow; if (sourceRow == destRow) return false; // beginMoveRows(destParent,sourceRow,sourceRow,destParent,destRow); auto parentItem = static_cast(destParent.internalPointer()); ReadingListItem *child = parentItem->child(sourceRow); parentItem->removeChild(child); parentItem->appendChild(child, destRow); reorderingChildren(parentItem->children()); // endMoveRows(); return true; } QMimeData *ReadingListModel::mimeData(const QModelIndexList &indexes) const { QLOG_DEBUG() << "mimeData requested" << indexes; if (indexes.length() == 0) { QLOG_ERROR() << "mimeData requested: indexes is empty"; return new QMimeData(); // TODO what happens if 0 is returned? } if (indexes.length() > 1) { QLOG_DEBUG() << "mimeData requested for more than one index, this shouldn't be possible"; } QModelIndex modelIndex = indexes.at(0); QList> rows; rows << QPair(modelIndex.row(), modelIndex.parent().row()); QLOG_DEBUG() << "mimeData requested for row : " << modelIndex.row(); QByteArray data; QDataStream out(&data, QIODevice::WriteOnly); out << rows; // serialize the list of identifiers auto mimeData = new QMimeData(); mimeData->setData(YACReader::YACReaderLibrarSubReadingListMimeDataFormat, data); return mimeData; } void ReadingListModel::setupReadingListsData(QString path) { beginResetModel(); cleanAll(); _databasePath = path; QSqlDatabase db = DataBaseManagement::loadDatabase(path); // setup special lists specialLists = setupSpecialLists(db); // separator-------------------------------------------- // setup labels setupLabels(db); // separator-------------------------------------------- // setup reading list setupReadingLists(db); endResetModel(); } void ReadingListModel::addNewLabel(const QString &name, YACReader::LabelColors color) { QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); qulonglong id = DBHelper::insertLabel(name, color, db); int newPos = addLabelIntoList(new LabelItem(QList() << name << YACReader::colorToName(color) << id << color)); beginInsertRows(QModelIndex(), specialLists.count() + 1 + newPos + 1, specialLists.count() + 1 + newPos + 1); endInsertRows(); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } void ReadingListModel::addReadingList(const QString &name) { QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); beginInsertRows(QModelIndex(), 0, 0); // TODO calculate the right coordinates before inserting qulonglong id = DBHelper::insertReadingList(name, db); ReadingListItem *newItem; rootItem->appendChild(newItem = new ReadingListItem(QList() << name << id << false << true << 0)); items.insert(id, newItem); endInsertRows(); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } void ReadingListModel::addReadingListAt(const QString &name, const QModelIndex &mi) { QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); beginInsertRows(mi, 0, 0); // TODO calculate the right coordinates before inserting auto readingListParent = static_cast(mi.internalPointer()); qulonglong id = DBHelper::insertReadingSubList(name, mi.data(IDRole).toULongLong(), readingListParent->childCount(), db); ReadingListItem *newItem; readingListParent->appendChild(newItem = new ReadingListItem(QList() << name << id << false << true << readingListParent->childCount())); items.insert(id, newItem); endInsertRows(); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } bool ReadingListModel::isEditable(const QModelIndex &mi) { if (!mi.isValid()) return false; auto item = static_cast(mi.internalPointer()); return typeid(*item) != typeid(SpecialListItem); } bool ReadingListModel::isReadingList(const QModelIndex &mi) { if (!mi.isValid()) return false; auto item = static_cast(mi.internalPointer()); return typeid(*item) == typeid(ReadingListItem); } bool ReadingListModel::isReadingSubList(const QModelIndex &mi) { if (!mi.isValid()) return false; auto item = static_cast(mi.internalPointer()); if (typeid(*item) == typeid(ReadingListItem)) { auto readingListItem = static_cast(item); if (readingListItem->parent == rootItem) return false; else return true; } else return false; } QString ReadingListModel::name(const QModelIndex &mi) { return data(mi, Qt::DisplayRole).toString(); } void ReadingListModel::rename(const QModelIndex &mi, const QString &name) { if (!isEditable(mi)) return; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); auto item = static_cast(mi.internalPointer()); if (typeid(*item) == typeid(ReadingListItem)) { auto rli = static_cast(item); rli->setName(name); DBHelper::renameList(item->getId(), name, db); if (rli->parent->getId() != 0) { // TODO // move row depending on the name } else emit dataChanged(index(mi.row(), 0), index(mi.row(), 0)); } else if (typeid(*item) == typeid(LabelItem)) { auto li = static_cast(item); li->setName(name); DBHelper::renameLabel(item->getId(), name, db); emit dataChanged(index(mi.row(), 0), index(mi.row(), 0)); } connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } void ReadingListModel::deleteItem(const QModelIndex &mi) { if (isEditable(mi)) { QLOG_DEBUG() << "parent row :" << mi.parent().data() << "-" << mi.row(); beginRemoveRows(mi.parent(), mi.row(), mi.row()); QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); auto item = static_cast(mi.internalPointer()); if (typeid(*item) == typeid(ReadingListItem)) { auto rli = static_cast(item); QLOG_DEBUG() << "num children : " << rli->parent->childCount(); rli->parent->removeChild(rli); QLOG_DEBUG() << "num children : " << rli->parent->childCount(); DBHelper::removeListFromDB(item->getId(), db); if (rli->parent->getId() != 0) { reorderingChildren(rli->parent->children()); } QLOG_DEBUG() << "num children : " << rli->parent->childCount(); } else if (typeid(*item) == typeid(LabelItem)) { auto li = static_cast(item); labels.removeOne(li); DBHelper::removeLabelFromDB(item->getId(), db); } connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); endRemoveRows(); } } const QList ReadingListModel::getLabels() { return labels; } void ReadingListModel::cleanAll() { if (rootItem != 0) { delete rootItem; qDeleteAll(specialLists); qDeleteAll(labels); specialLists.clear(); labels.clear(); items.clear(); } rootItem = 0; } void ReadingListModel::setupReadingListsData(QSqlQuery &sqlquery, ReadingListItem *parent) { items.insert(parent->getId(), parent); QSqlRecord record = sqlquery.record(); int name = record.indexOf("name"); int id = record.indexOf("id"); int finished = record.indexOf("finished"); int completed = record.indexOf("completed"); int ordering = record.indexOf("ordering"); int parentId = record.indexOf("parentId"); while (sqlquery.next()) { ReadingListItem *rli = new ReadingListItem(QList() << sqlquery.value(name) << sqlquery.value(id) << sqlquery.value(finished) << sqlquery.value(completed) << sqlquery.value(ordering)); ReadingListItem *currentParent; if (sqlquery.value(parentId).isNull()) currentParent = rootItem; else currentParent = items.value(sqlquery.value(parentId).toULongLong()); currentParent->appendChild(rli); items.insert(rli->getId(), rli); } } QList ReadingListModel::setupSpecialLists(QSqlDatabase &db) { QList list; QSqlQuery selectQuery("SELECT * FROM default_reading_list ORDER BY id,name", db); QSqlRecord record = selectQuery.record(); int name = record.indexOf("name"); int id = record.indexOf("id"); while (selectQuery.next()) { list << new SpecialListItem(QList() << selectQuery.value(name) << selectQuery.value(id)); } // Reading after Favorites, Why? Because I want to :P list.insert(1, new SpecialListItem(QList() << "Reading" << 0)); return list; } void ReadingListModel::setupLabels(QSqlDatabase &db) { QSqlQuery selectQuery("SELECT * FROM label ORDER BY ordering,name", db); QSqlRecord record = selectQuery.record(); int name = record.indexOf("name"); int color = record.indexOf("color"); int id = record.indexOf("id"); int ordering = record.indexOf("ordering"); while (selectQuery.next()) { addLabelIntoList(new LabelItem(QList() << selectQuery.value(name) << selectQuery.value(color) << selectQuery.value(id) << selectQuery.value(ordering))); } // TEST // INSERT INTO label (name, color, ordering) VALUES ("Oh Oh", "red", 1); // INSERT INTO label (name, color, ordering) VALUES ("lalala", "orange", 2); // INSERT INTO label (name, color, ordering) VALUES ("we are not sorry", "yellow", 3); // INSERT INTO label (name, color, ordering) VALUES ("there we go", "green", 4); // INSERT INTO label (name, color, ordering) VALUES ("oklabunga", "cyan", 5); // INSERT INTO label (name, color, ordering) VALUES ("hailer mailer", "blue", 6); // INSERT INTO label (name, color, ordering) VALUES ("lol", "violet", 7); // INSERT INTO label (name, color, ordering) VALUES ("problems", "purple", 8); // INSERT INTO label (name, color, ordering) VALUES ("me gussssta", "pink", 9); // INSERT INTO label (name, color, ordering) VALUES (":D", "white", 10); // INSERT INTO label (name, color, ordering) VALUES ("ainsss", "light", 11); // INSERT INTO label (name, color, ordering) VALUES ("put a smile on my face", "dark", 12); } void ReadingListModel::setupReadingLists(QSqlDatabase &db) { // setup root item rootItem = new ReadingListItem(QList() << "ROOT" << 0 << true << false); QSqlQuery selectQuery("select * from reading_list order by parentId IS NULL DESC", db); // setup reading lists setupReadingListsData(selectQuery, rootItem); // TEST // ReadingListItem * node1; // rootItem->appendChild(node1 = new ReadingListItem(QList() /*<< 0*/ << "My reading list" << "atr")); // rootItem->appendChild(new ReadingListItem(QList() /*<< 0*/ << "X timeline" << "atr")); // node1->appendChild(new ReadingListItem(QList() /*<< 0*/ << "sublist" << "atr",node1)); } int ReadingListModel::addLabelIntoList(LabelItem *item) { if (labels.isEmpty()) labels << item; else { int i = 0; while (i < labels.count() && (labels.at(i)->colorid() < item->colorid())) i++; if (i < labels.count()) { if (labels.at(i)->colorid() == item->colorid()) // sort by name { while (i < labels.count() && labels.at(i)->colorid() == item->colorid() && naturalSortLessThanCI(labels.at(i)->name(), item->name())) i++; } } if (i >= labels.count()) { QLOG_DEBUG() << "insertando label al final " << item->name(); labels << item; } else { QLOG_DEBUG() << "insertando label en " << i << "-" << item->name(); labels.insert(i, item); } return i; } return 0; } void ReadingListModel::reorderingChildren(QList children) { QList childrenIds; int i = 0; foreach (ReadingListItem *item, children) { item->setOrdering(i++); childrenIds << item->getId(); } QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); DBHelper::reasignOrderToSublists(childrenIds, db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } bool ReadingListModel::rowIsSpecialList(int row, const QModelIndex &parent) const { if (parent.isValid()) return false; // by now no sublists in special list if (row >= 0 && row < specialLists.count()) return true; return false; } bool ReadingListModel::rowIsLabel(int row, const QModelIndex &parent) const { if (parent.isValid()) return false; // by now no sublists in labels if (row > specialLists.count() && row <= specialLists.count() + labels.count()) return true; return false; } bool ReadingListModel::rowIsReadingList(int row, const QModelIndex &parent) const { if (parent.isValid()) return true; // only lists with sublists int separatorsCount = labels.isEmpty() ? 1 : 2; if (row >= specialLists.count() + labels.count() + separatorsCount) return true; return false; } bool ReadingListModel::rowIsSeparator(int row, const QModelIndex &parent) const { if (parent.isValid()) return false; // only separators at top level if (row == specialLists.count()) return true; int separatorsCount = labels.isEmpty() ? 1 : 2; if (separatorsCount == 2 && row == specialLists.count() + labels.count() + 1) return true; return false; } ReadingListModelProxy::ReadingListModelProxy(QObject *parent) : QSortFilterProxyModel(parent) { }