diff --git a/src/basketscenemodel.cpp b/src/basketscenemodel.cpp index 6ca4bef..5efadb2 100644 --- a/src/basketscenemodel.cpp +++ b/src/basketscenemodel.cpp @@ -1,297 +1,308 @@ /* - * SPDX-FileCopyrightText: 2020 by Ismael Asensio + * SPDX-FileCopyrightText: (C) 2020 by Ismael Asensio * SPDX-License-Identifier: GPL-2.0-or-later */ #include "basketscenemodel.h" #include BasketSceneModel::BasketSceneModel(QObject *parent) : QAbstractItemModel(parent) , m_root(new NoteItem()) { qRegisterMetaType(); qRegisterMetaType(); } BasketSceneModel::~BasketSceneModel() { delete m_root; } int BasketSceneModel::rowCount(const QModelIndex &parent) const { return itemAtIndex(parent)->childrenCount(); } int BasketSceneModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return 1; } QModelIndex BasketSceneModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } NoteItem *item = itemAtIndex(parent)->children().at(row); if (item) { return createIndex(row, column, item); } return QModelIndex(); } QModelIndex BasketSceneModel::parent(const QModelIndex& index) const { if (!index.isValid()) return QModelIndex(); NoteItem *parentItem = itemAtIndex(index)->parent(); return indexOfItem(parentItem); } QVariant BasketSceneModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section) if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return QStringLiteral("BasketSceneModel"); return QVariant(); } QHash BasketSceneModel::roleNames() const { static const auto roles = QHash { { ContentRole, "content" }, { TypeRole, "type" }, { GeometryRole, "geometry" }, { EditionRole, "edition" }, { AddressRole, "address" } }.unite(QAbstractItemModel::roleNames()); return roles; } QVariant BasketSceneModel::data(const QModelIndex &index, int role) const { if (!checkIndex(index, CheckIndexOption::IndexIsValid)) { return QVariant(); } const NoteItem *item = itemAtIndex(index); switch (role) { case Qt::DisplayRole: return item->displayText(); case Qt::DecorationRole: return item->decoration(); case Qt::ToolTipRole: // To ease debugging return item->toolTipInfo(); case BasketSceneModel::ContentRole: return QVariant::fromValue(item->content()); case BasketSceneModel::TypeRole: return item->type(); case BasketSceneModel::GeometryRole: return item->bounds(); case BasketSceneModel::EditionRole: { QVariant info; info.setValue(item->editInformation()); return info; } case BasketSceneModel::AddressRole: return item->address(); } return QVariant(); } -void BasketSceneModel::clear() -{ - qDeleteAll(m_root->children()); -} - NoteItem *BasketSceneModel::itemAtIndex(const QModelIndex &index) const { if (!index.isValid()) { return m_root; } return static_cast(index.internalPointer()); } QModelIndex BasketSceneModel::indexOfItem(NoteItem *item) const { if (!item || item == m_root) { return QModelIndex(); } return createIndex(item->row(), 0, item); } QModelIndex BasketSceneModel::findNote(NotePtr note) const { if (!note || m_root->childrenCount() == 0) { return QModelIndex(); } const QModelIndexList matchResult = match(indexOfItem(m_root->children().at(0)), BasketSceneModel::AddressRole, NoteItem::formatAddress(note), 1, Qt::MatchExactly | Qt::MatchWrap | Qt::MatchRecursive); if (matchResult.isEmpty()) { return QModelIndex(); } return matchResult[0]; } bool BasketSceneModel::insertRows(int row, int count, const QModelIndex& parent) { if(!checkIndex(parent)) { return false; } beginInsertRows(parent, row, row + count - 1); for (int i = 0; i < count; i++) { itemAtIndex(parent)->insertAt(row + i, new NoteItem()); } endInsertRows(); return true; } bool BasketSceneModel::removeRows(int row, int count, const QModelIndex& parent) { if(!checkIndex(parent)) { return false; } beginRemoveRows(parent, row, row + count - 1); for (int i = 0; i < count; i++) { itemAtIndex(parent)->removeAt(row); } endRemoveRows(); return true; } bool BasketSceneModel::moveRows(const QModelIndex& sourceParent, int sourceRow, int count, const QModelIndex& destinationParent, int destinationChild) { const bool isMoveDown = (destinationParent == sourceParent && destinationChild > sourceRow); if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count -1, destinationParent, (isMoveDown) ? destinationChild + 1 : destinationChild)) { return false; } for (int i = 0; i < count; i++) { const int oldRow = (isMoveDown) ? sourceRow : sourceRow + i; itemAtIndex(destinationParent)->insertAt(destinationChild + i, itemAtIndex(sourceParent)->takeAt(oldRow)); } endMoveRows(); return true; } +void BasketSceneModel::clear() +{ + beginResetModel(); + qDeleteAll(m_root->children()); + endResetModel(); +} + +void BasketSceneModel::loadFromXML(const QDomElement &node) +{ + beginResetModel(); + qDeleteAll(m_root->children()); + m_root->loadFromXMLNode(node); + endResetModel(); +} + + void BasketSceneModel::insertNote(NotePtr note, NotePtr parentNote, int row) { if (!note) { return; } const QModelIndex parentIndex = findNote(parentNote); if (!checkIndex(parentIndex)) { return; } if (row < 0 || row >= rowCount(parentIndex)) { // row == -1 : insert at the end row = rowCount(parentIndex); } qDebug() << "BasketSceneModel::insertNote " << NoteItem::formatAddress(note) << " at " << itemAtIndex(parentIndex)->address() << "[" << row << "]"; // Protection against adding the same Note * twice (same pointer) // This should not be necessary once the old code in BasketScene has been cleaned-up if (findNote(note).isValid()) { QModelIndex prevIndex = findNote(note); qDebug() << " · Note " << itemAtIndex(prevIndex)->address() << " was alreadyPresent at " << itemAtIndex(parentIndex)->address() << "[" << prevIndex.row() << "]" << ". Moving it instead to new location"; moveNoteTo(note, parentNote, row); return; } if (insertRow(row, parentIndex)) { itemAtIndex(parentIndex)->children().at(row)->setNote(note); connect(note, &QObject::destroyed, this, [=](QObject *note) { BasketSceneModel::removeNote(static_cast(note)); }); } } void BasketSceneModel::removeNote(NotePtr note) { if (!note) { return; } const QModelIndex index = findNote(note); const QModelIndex parentIndex = index.parent(); qDebug() << "BasketSceneModel::removeNote " << itemAtIndex(index)->address(); removeRow(index.row(), parentIndex); } void BasketSceneModel::moveNoteTo(NotePtr note, NotePtr parentNote, int row) { if (!note) { return; } const QModelIndex currentIndex = findNote(note); const QModelIndex srcParentIndex = currentIndex.parent(); const QModelIndex destParentIndex = findNote(parentNote); if (!checkIndex(currentIndex, CheckIndexOption::IndexIsValid)) { return; } if (row < 0 || row >= rowCount(destParentIndex)) { row = rowCount(destParentIndex); // row == -1 : insert at the end } qDebug() << "BasketSceneModel::moveNote " << itemAtIndex(currentIndex)->address() << "\n from: " << itemAtIndex(srcParentIndex)->address() << "[" << currentIndex.row() << "]" << "\n to: " << itemAtIndex(destParentIndex)->address() << "[" << row << "]"; moveRow(srcParentIndex, currentIndex.row(), destParentIndex, row); } void BasketSceneModel::collapseItem(const QModelIndex &index) { if (hasChildren(index)) { itemAtIndex(index)->children().at(0)->note()->tryFoldParent(); } } void BasketSceneModel::expandItem(const QModelIndex &index) { if (hasChildren(index)) { itemAtIndex(index)->children().at(0)->note()->tryExpandParent(); } } void BasketSceneModel::changeSelection(const QItemSelection& selected, const QItemSelection& deselected) { for (const auto index : deselected.indexes()) { itemAtIndex(index)->note()->setSelected(false); } for (const auto index : selected.indexes()) { itemAtIndex(index)->note()->setSelected(true); } } diff --git a/src/basketscenemodel.h b/src/basketscenemodel.h index b750a6e..cbe5c2e 100644 --- a/src/basketscenemodel.h +++ b/src/basketscenemodel.h @@ -1,68 +1,69 @@ /* * SPDX-FileCopyrightText: 2020 by Ismael Asensio * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "basket_export.h" #include "noteitem.h" #include #include class BASKET_EXPORT BasketSceneModel : public QAbstractItemModel { Q_OBJECT public: enum AdditionalRoles { ContentRole = Qt::UserRole + 1, // NoteContent * : the internal NoteContent TypeRole, // NoteContent::Type: the type of the NoteContent GeometryRole, // QRect: the bounds of the Note EditionRole, // NoteItem::EditInfo: the edition information about the Note AddressRole // QString: representation of the internal NotePtr address }; public: explicit BasketSceneModel(QObject *parent = nullptr); ~BasketSceneModel(); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QHash roleNames() const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; // bool setData(const QModelIndex &index, const QVariant &value, int role) override; - // Implemented model operations bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override; + void clear(); + void loadFromXML(const QDomElement &node); + // Specific methods to call from BasketScene (temporary) void insertNote(NotePtr note, NotePtr parentNote, int row = -1); void removeNote(NotePtr note); void moveNoteTo(NotePtr note, NotePtr parentNote, int row = -1); - void clear(); public slots: // Interaction between the model and BasketScene, mainly for demo and debugging purposes void expandItem(const QModelIndex &index); void collapseItem(const QModelIndex &index); void changeSelection(const QItemSelection &selected, const QItemSelection &deselected); private: NoteItem *itemAtIndex(const QModelIndex &index) const; QModelIndex indexOfItem(NoteItem *item) const; QModelIndex findNote(NotePtr note) const; private: NoteItem *m_root; }; diff --git a/src/noteitem.cpp b/src/noteitem.cpp index 90da13e..669a600 100644 --- a/src/noteitem.cpp +++ b/src/noteitem.cpp @@ -1,210 +1,281 @@ /* - * SPDX-FileCopyrightText: 2020 by Ismael Asensio + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-FileCopyrightText: (C) 2020 by Ismael Asensio * SPDX-License-Identifier: GPL-2.0-or-later */ #include "basketscenemodel.h" +#include "notefactory.h" +#include "xmlwork.h" + +#include + namespace { QString iconNameForType(NoteType::Id type) { switch (type) { case NoteType::Group: return "package"; case NoteType::Text: return "text-x-plain"; case NoteType::Html: return "text-html"; case NoteType::Image: return "folder-pictures-symbolic"; case NoteType::Animation: return "folder-video-symbolic"; case NoteType::Sound: return "folder-music-symbolic"; case NoteType::File: return "application-x-zerosize"; case NoteType::Link: return "edit-link"; case NoteType::CrossReference: return "basket"; case NoteType::Launcher: return "run-build"; case NoteType::Color: return "color-profile"; case NoteType::Unknown: return "application-octet-stream"; } return QString(); } } NoteItem::NoteItem() : m_parent(nullptr) , m_note(nullptr) + , m_helperNote(new Note()) { } NoteItem::~NoteItem() { + delete m_helperNote; qDeleteAll(m_children); } int NoteItem::row() const { if (!m_parent) { return 0; } return m_parent->m_children.indexOf(const_cast(this)); } NoteItem *NoteItem::parent() const { return m_parent; } void NoteItem::setParent(NoteItem *parent) { m_parent = parent; } QVector NoteItem::children() const { return m_children; } int NoteItem::childrenCount() const { return m_children.count(); } void NoteItem::insertAt(int row, NoteItem *item) { if (!item) return; item->setParent(this); if (row >= 0 && row < m_children.count() ) { m_children.insert(row, item); } else { m_children.append(item); } } void NoteItem::removeAt(int row) { if (row < 0 || row >= m_children.count()) { return; } delete m_children[row]; m_children.remove(row); } NoteItem *NoteItem::takeAt(int row) { if (row < 0 || row >= m_children.count()) { return nullptr; } return m_children.takeAt(row); } NotePtr NoteItem::note() const { + if (!m_note) { + return m_helperNote; + } return m_note; } void NoteItem::setNote(NotePtr note) { m_note = note; } QString NoteItem::displayText() const { - if (!m_note) { return QStringLiteral("NULL NOTE"); } + if (!note()) { return QStringLiteral("NULL NOTE"); } if (type() == NoteType::Group) { return QStringLiteral("GROUP"); } return content()->toText(QString()); } QIcon NoteItem::decoration() const { - if (!m_note) { return QIcon::fromTheme("data-error"); } + if (!note()) { return QIcon::fromTheme("data-error"); } if (type() == NoteType::Group) { return QIcon::fromTheme("package"); } if (!content()) { return QIcon::fromTheme("empty"); } return QIcon::fromTheme(iconNameForType(content()->type())); } NoteContent *NoteItem::content() const { - if (!m_note) { + if (!note()) { return nullptr; } - return m_note->content(); + return note()->content(); } NoteType::Id NoteItem::type() const { - if (!m_note) { + if (!note()) { return NoteType::Unknown; } - if (!m_note->content()) { + if (!note()->content()) { return NoteType::Group; } - return m_note->content()->type(); + return note()->content()->type(); } QRect NoteItem::bounds() const { - if (!m_note) { - return {}; - } - return QRect( - m_note->x(), - m_note->y(), - m_note->width(), - m_note->height() + note()->x(), + note()->y(), + note()->width(), + note()->height() ); } NoteItem::EditInfo NoteItem::editInformation() const { return EditInfo { - m_note->addedDate(), - m_note->lastModificationDate() + note()->addedDate(), + note()->lastModificationDate() }; } QString NoteItem::formatAddress(void *ptr) { return QString::number(reinterpret_cast(ptr), 16); } QString NoteItem::address() const { - if (!m_note) { + if (!note()) { return QStringLiteral("root"); } - return formatAddress(m_note); + return formatAddress(note()); } QString NoteItem::toolTipInfo() const { QStringList toolTip; // fullAddress and position within parent (debug) toolTip << QStringLiteral("%1 (@ <%2>[%3])") - .arg(formatAddress(m_note)) + .arg(formatAddress(note())) .arg(m_parent->address()) .arg(row()); - // type (TODO: slighly modify NoteContent to have a translation function) + // type toolTip << QStringLiteral("Type: %1").arg(content() ? content()->typeName() : "Group"); // geometry const QRect geometry = bounds(); toolTip << QStringLiteral("x:%1 y:%2 w:%3 h:%4") .arg(geometry.x()).arg(geometry.y()) .arg(geometry.width()).arg(geometry.height()); // edition information const EditInfo info = editInformation(); toolTip << QStringLiteral("created: %1\nmodified: %2") .arg(info.created.toString()) .arg(info.modified.toString()); return toolTip.join(QStringLiteral("\n")); } + + +void NoteItem::loadFromXMLNode(const QDomElement& node) +{ + for (QDomNode n = node.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if (e.isNull()) { + continue; + } + + NoteItem *noteItem = new NoteItem(); + noteItem->setParent(this); + m_children.append(noteItem); + + NotePtr note = noteItem->note(); // Helper Note object + + if (e.tagName() == "group") { + // Node is a group. Recursively load from this element + noteItem->loadFromXMLNode(e); + } else if (e.tagName() == "note" || e.tagName() == "item") { // "item" is to keep compatible with 0.6.0 Alpha 1 (required?) + // Load note content +/* IT CRASHES. Too dependant on QObject hierarchy? + NoteFactory::loadNode(XMLWork::getElement(e, "content"), + e.attribute("type"), + note, + true); //lazyload +*/ + } + + // Load dates + if (e.hasAttribute("added")) { + note->setAddedDate(QDateTime::fromString(e.attribute("added"), Qt::ISODate)); + } + if (e.hasAttribute("lastModification")) { + note->setLastModificationDate(QDateTime::fromString(e.attribute("lastModification"), Qt::ISODate)); + } + // Free Note Properties: + if (note->isFree()) { + int x = e.attribute("x").toInt(); + int y = e.attribute("y").toInt(); + note->setX(x < 0 ? 0 : x); + note->setY(y < 0 ? 0 : y); + } + // Resizeable Note Properties: + if (note->hasResizer() || note->isColumn()) { + note->setGroupWidth(e.attribute("width", "200").toInt()); + } + // Group Properties: + if (note->isGroup() && !note->isColumn() && XMLWork::trueOrFalse(e.attribute("folded", "false"))) { + note->toggleFolded(); + } + // Tags: + if (note->content()) { + const QString tagsString = XMLWork::getElementText(e, QStringLiteral("tags"), QString()); + const QStringList tagsId = tagsString.split(';'); + for (const QString &tagId : tagsId) { + State *state = Tag::stateForId(tagId); + if (state) { + note->addState(state, /*orReplace=*/true); + } + } + } + } +} diff --git a/src/noteitem.h b/src/noteitem.h index 7a8a841..55ba138 100644 --- a/src/noteitem.h +++ b/src/noteitem.h @@ -1,73 +1,81 @@ /* * SPDX-FileCopyrightText: 2020 by Ismael Asensio * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "note.h" #include "notecontent.h" #include +class QDomElement; + typedef Note * NotePtr; /** NoteItem: Container that stores a Note object within a tree model * Eventually implement the managing functionallity here and use directly the `NoteContent` object */ class NoteItem { public: struct EditInfo { QDateTime created; QDateTime modified; }; public: explicit NoteItem(); ~NoteItem(); + // Tree structure int row() const; NoteItem *parent() const; void setParent(NoteItem *parent); QVector children() const; int childrenCount() const; void insertAt(int row, NoteItem *item); void removeAt(int row); NoteItem *takeAt(int row); + // Accesors to Note object (compatibility with current code) NotePtr note() const; void setNote(NotePtr note); + // NoteItem property getters QString displayText() const; QIcon decoration() const; - NoteContent *content() const; NoteType::Id type() const; QRect bounds() const; EditInfo editInformation() const; + // Recursive loader from an XML node + void loadFromXMLNode(const QDomElement &node); + // Find and debug Notes by its pointer address static QString formatAddress(void *ptr); QString address() const; QString toolTipInfo() const; private: NoteItem *m_parent; QVector m_children; // TODO: Remove m_note and store the information here NotePtr m_note; + NotePtr m_helperNote; // Dummy note to help with the code transition. // NoteContent *m_content; // QRect m_bounds; // EditInfo m_editInfo; // QVector m_tags; }; Q_DECLARE_METATYPE(NoteContent *) Q_DECLARE_METATYPE(NoteItem::EditInfo)