diff --git a/src/confignode.cpp b/src/confignode.cpp new file mode 100644 index 00000000000..b03d85a887e --- /dev/null +++ b/src/confignode.cpp @@ -0,0 +1,302 @@ +#include "confignode.hpp" +#include "treeviewmodel.hpp" + +using namespace kdb; + +ConfigNode::ConfigNode(const QString& name, const QString& path, const Key &key, TreeViewModel *parentModel) + : m_name(name) + , m_path(path) + , m_key(key) + , m_children(new TreeViewModel) + , m_metaData(new TreeViewModel) + , m_parentModel(parentModel) +{ + if (m_key && m_key.isString()) + m_value = QVariant::fromValue(QString::fromStdString(m_key.getString())); + else if (m_key && m_key.isBinary()) + m_value = QVariant::fromValue(QString::fromStdString(m_key.getBinary())); + + if(m_key) + populateMetaModel(); +} + +ConfigNode::ConfigNode(const ConfigNode& other) + : QObject() + , m_name(other.m_name) + , m_path(other.m_path) + , m_value(other.m_value) + , m_key(other.m_key.dup()) + , m_children(new TreeViewModel()) + , m_metaData(new TreeViewModel()) + , m_parentModel(other.m_parentModel) +{ + + foreach(ConfigNode *node, other.getChildren()->model()) + { + m_children->append(new ConfigNode(*node)); + } + + foreach(ConfigNode *node, other.getMetaKeys()->model()) + { + m_metaData->append(new ConfigNode(*node)); + } +} + +ConfigNode::ConfigNode() + : m_children(new TreeViewModel) + , m_metaData(new TreeViewModel) + , m_parentModel(new TreeViewModel) +{ +} + +ConfigNode::~ConfigNode() +{ + delete m_children; + delete m_metaData; + m_parentModel = NULL; + + if(m_key) + m_key.clear(); +} + +int ConfigNode::getChildCount() const +{ + return m_children->model().count(); +} + +QString ConfigNode::getName() const +{ + return m_name; +} + +QString ConfigNode::getPath() const +{ + return m_path; +} + +QVariant ConfigNode::getValue() const +{ + return m_value; +} + +void ConfigNode::setName(const QString& name) +{ + // qDebug() << "ConfigNode::setName: Node with name " << m_name << " has new name " << name; + m_name = name; + + if(QString::fromStdString(m_key.getName()) != ""){ + // qDebug() << "ConfigNode::setName: Key with name " << QString::fromStdString(m_key.getName()) << " has new base name " << name; + m_key.setBaseName(name.toStdString()); + } +} + +void ConfigNode::setValue(const QVariant& value) +{ + m_value = value; + + if(m_key) + m_key.setString(value.toString().toStdString()); +} + +void ConfigNode::setMeta(const QString &name, const QVariant &value) +{ + // qDebug() << "ConfigNode::setMeta: metaNode " << m_name << " has new metaname " << name; + m_name = name; + m_value = value; + + if(m_key) + { + // deleteMeta(m_name); + // qDebug() << "ConfigNode::setMeta: key " << QString::fromStdString(m_key.getName()) << " has new metakey " << name; + m_key.setMeta(name.toStdString(), value.toString().toStdString()); + } +} + +void ConfigNode::setMeta(const QVariantMap &metaData) +{ + for(int i = 0; i < m_metaData->model().size(); i++) + { + m_metaData->model().at(i)->deleteMeta(m_metaData->model().at(i)->getName()); + } + + m_metaData->clear(); + + for(int i = 0; i < metaData.size(); i++) + { + m_metaData->insertMetaRow(i, this); + } + + int counter = 0; + + for(QVariantMap::const_iterator iter = metaData.begin(); iter != metaData.end(); iter++) + { + QVariantList tmp; + tmp << iter.key() << iter.value(); + m_metaData->setData(counter, tmp, "MetaValue"); + counter++; + } +} + +void ConfigNode::deleteMeta(const QString &name) +{ + if(m_key) + { + // qDebug() << "metakey " << name << " of node " << m_name << " deleted"; + m_key.delMeta(name.toStdString()); + } +} + +void ConfigNode::accept(Visitor &visitor) +{ + visitor.visit(this); + + foreach (ConfigNode *node, m_children->model()) + node->accept(visitor); +} + +Key ConfigNode::getKey() const +{ + return m_key; +} + +void ConfigNode::invalidateKey() +{ + // qDebug() << "ConfigNode::deleteKey: clearing key " << QString::fromStdString(m_key.getName()); + m_key.clear(); +} + +void ConfigNode::deletePath(QStringList &path) +{ + if(path.count() == 0) + return; + + QString name = path.takeFirst(); + int index = getIndexByName(name); + ConfigNode *node = getChildByName(name); + + node->deletePath(path); + + if(node->getChildCount() == 0) + m_children->removeRow(index); +} + +int ConfigNode::getIndexByName(const QString &name) +{ + for(int i = 0; i < m_children->model().count(); i++) + { + if(m_children->model().at(i)->getName() == name) + return i; + } + + return -1; +} + +TreeViewModel *ConfigNode::getParentModel() +{ + return m_parentModel; +} + +void ConfigNode::setParentModel(TreeViewModel *parent) +{ + m_parentModel = parent; +} + +void ConfigNode::populateMetaModel() +{ + if (m_key) + { + m_key.rewindMeta(); + m_metaData->model().clear(); + + while (m_key.nextMeta()) + { + // qDebug() << "ConfigNode::populateMetaModel: key " << QString::fromStdString(m_key.getName()) << " has metakey " << QString::fromStdString(m_key.currentMeta().getName()); + ConfigNode* node = new ConfigNode(); + + node->setName(QString::fromStdString(m_key.getName())); + node->setKey(m_key); + node->setMeta(QString::fromStdString(m_key.currentMeta().getName()), QVariant::fromValue(QString::fromStdString(m_key.currentMeta().getString()))); + + m_metaData->model().append(node); + } + } +} + +void ConfigNode::setKey(Key key) +{ + m_key = key; +} + +void ConfigNode::setKeyName(const QString &name) +{ + if(!m_key) + m_key = Key(); + + m_key.setName(name.toStdString()); +} + +void ConfigNode::appendChild(ConfigNode* node) +{ + m_children->append(node); +} + +bool ConfigNode::hasChild(const QString& name) const +{ + foreach (ConfigNode * node, m_children->model()) + { + if (node->getName() == name) + { + return true; + } + } + + return false; +} + +TreeViewModel* ConfigNode::getChildren() const +{ + return m_children; +} + +TreeViewModel* ConfigNode::getMetaKeys() const +{ + return m_metaData; +} + +ConfigNode* ConfigNode::getChildByName(QString& name) const +{ + foreach (ConfigNode * node, m_children->model()) + { + if (node->getName() == name) + { + return node; + } + } + + return NULL; +} + +ConfigNode* ConfigNode::getChildByIndex(int index) const +{ + if (index >= 0 && index < m_children->model().length()) + return m_children->model().at(index); + + return NULL; +} + +void ConfigNode::setPath(const QString &path) +{ + m_path = path; +} + +bool ConfigNode::childrenHaveNoChildren() const +{ + int children = 0; + + foreach (ConfigNode * node, m_children->model()) + { + children += node->getChildCount(); + } + + return children == 0; +} diff --git a/src/confignode.hpp b/src/confignode.hpp new file mode 100644 index 00000000000..c624bea0ab7 --- /dev/null +++ b/src/confignode.hpp @@ -0,0 +1,143 @@ +#ifndef CONFIGNODE_H +#define CONFIGNODE_H + +#include +#include +#include +#include +#include + +#include "treeviewmodel.hpp" +#include "printvisitor.hpp" + +class TreeViewModel; +class PrintVisitor; + +class ConfigNode : public QObject +{ + Q_OBJECT + +public: + + explicit ConfigNode(const QString& name, const QString& path, const kdb::Key &key, TreeViewModel *parentModel); + /// Needed by Qt + ConfigNode(const ConfigNode& other); + /// Needed by Qt + ConfigNode(); + ~ConfigNode(); + + /** + * @brief Returns the number of children of this ConfigNode. + * @return The number of children of this ConfigNode. + */ + int getChildCount() const; + + /** + * @brief Returns the name of this ConfigNode. + * @return The name of this ConfigNode. + */ + QString getName() const; + + /** + * @brief Returns the path of this ConfigNode. + * @return The path of this ConfigNode. + */ + QString getPath() const; + + /** + * @brief Returns the value of this ConfigNode. + * @return The value of this ConfigNode. + */ + QVariant getValue() const; + + /** + * @brief Rename this ConfigNode. + * @param name The new name for this ConfigNode. + */ + void setName(const QString& name); + + /** + * @brief Change the value of this ConfigNode. + * @param value The new value for this ConfigNode. + */ + void setValue(const QVariant& value); + + /** + * @brief Append a new child to this ConfigNode. + * @param node The new child of this ConfigNode. + */ + void appendChild(ConfigNode* node); + + /** + * @brief Returns if this ConfigNode has a child with a certain name. + * @param name The name of the child node. + * @return True if this node has a child with a certain name. + */ + bool hasChild(const QString& name) const; + + /** + * @brief Get the children of this ConfigNode. + * @return The children of this ConfigNode as model. + */ + TreeViewModel* getChildren() const; + + /** + * @brief Get the metakeys of this ConfigNode. + * @return The metakeys of this ConfigNode as model. + */ + TreeViewModel* getMetaKeys() const; + + /** + * @brief Returns if the children of this ConfigNode have any children themselves. + * @return True if no child of this ConfigNode has any children. + */ + bool childrenHaveNoChildren() const; + + /** + * @brief Returns a child with a certain name. + * @param name The name of the child which is looked for. + * @return The child with the given name if it is a child of this ConfigNode. + */ + ConfigNode* getChildByName(QString& name) const; + + /** + * @brief Returns a child on a given index. + * @param index The index of the wanted child. + * @return The child on the given index. + */ + Q_INVOKABLE ConfigNode* getChildByIndex(int index) const; + + void setPath(const QString &path); + + void setMeta(const QString &name, const QVariant &value); + Q_INVOKABLE void setMeta(const QVariantMap &metaData); + Q_INVOKABLE void deleteMeta(const QString &name); + + void accept(Visitor &visitor); + kdb::Key getKey() const; + void setKey(kdb::Key key); + void setKeyName(const QString &name); + void invalidateKey(); + void deletePath(QStringList &path); + int getIndexByName(const QString &name); + TreeViewModel* getParentModel(); + void setParentModel(TreeViewModel *parent); + +private: + // TODO: not needed if we hold the Key + QString m_name; + QString m_path; + QVariant m_value; + + // that is the only part we need: + kdb::Key m_key; + TreeViewModel* m_children; + TreeViewModel* m_metaData; + TreeViewModel* m_parentModel; + + void populateMetaModel(); +}; + +Q_DECLARE_METATYPE(ConfigNode) + +#endif // CONFIGNODE_H diff --git a/src/copykeycommand.cpp b/src/copykeycommand.cpp new file mode 100644 index 00000000000..42b39a1d59c --- /dev/null +++ b/src/copykeycommand.cpp @@ -0,0 +1,24 @@ +#include "copykeycommand.hpp" +#include "treeviewmodel.hpp" + +CopyKeyCommand::CopyKeyCommand(ConfigNode *source, ConfigNode *target, QUndoCommand *parent) + : QUndoCommand(parent) + , m_source(*source) + , m_target(target) +{ + setText("copy"); +} + +void CopyKeyCommand::undo() +{ + m_target->getChildren()->removeRow(m_target->getIndexByName(m_source.getName())); +} + +void CopyKeyCommand::redo() +{ + QString newPath = m_target->getPath() + "/" + m_source.getName(); + m_source.setPath(newPath); + m_source.setKeyName(newPath); + + m_target->appendChild(new ConfigNode(m_source)); +} diff --git a/src/copykeycommand.hpp b/src/copykeycommand.hpp new file mode 100644 index 00000000000..7425459f9b0 --- /dev/null +++ b/src/copykeycommand.hpp @@ -0,0 +1,23 @@ +#ifndef COPYKEYCOMMAND_H +#define COPYKEYCOMMAND_H + +#include +#include "confignode.hpp" + +#include + +class CopyKeyCommand : public QUndoCommand +{ +public: + explicit CopyKeyCommand(ConfigNode *source, ConfigNode *target, QUndoCommand *parent = 0); + + virtual void undo(); + virtual void redo(); + +private: + + ConfigNode m_source; + ConfigNode *m_target; +}; + +#endif // COPYKEYCOMMAND_H diff --git a/src/cutkeycommand.cpp b/src/cutkeycommand.cpp new file mode 100644 index 00000000000..66c9891c8df --- /dev/null +++ b/src/cutkeycommand.cpp @@ -0,0 +1,28 @@ +#include "cutkeycommand.hpp" + +CutKeyCommand::CutKeyCommand(TreeViewModel *model, ConfigNode *source, ConfigNode *target, int index, QUndoCommand *parent) + : QUndoCommand(parent) + , m_model(model) + , m_source(*source) + , m_target(target) + , m_index(index) +{ + setText("cut"); +} + +void CutKeyCommand::undo() +{ + m_model->insertRow(m_index, new ConfigNode(m_source)); + m_target->getChildren()->removeRow(m_target->getIndexByName(m_source.getName())); +} + +void CutKeyCommand::redo() +{ + QString newPath = m_target->getPath() + "/" + m_source.getName(); + m_source.setPath(newPath); + m_source.setKeyName(newPath); + + m_target->appendChild(new ConfigNode(m_source)); + + m_model->removeRow(m_index); +} diff --git a/src/cutkeycommand.hpp b/src/cutkeycommand.hpp new file mode 100644 index 00000000000..3153122e70c --- /dev/null +++ b/src/cutkeycommand.hpp @@ -0,0 +1,23 @@ +#ifndef CUTKEYCOMMAND_H +#define CUTKEYCOMMAND_H + +#include +#include "treeviewmodel.hpp" + +class CutKeyCommand : public QUndoCommand +{ +public: + explicit CutKeyCommand(TreeViewModel *model, ConfigNode *source, ConfigNode *target, int index, QUndoCommand *parent = 0); + + virtual void undo(); + virtual void redo(); + +private: + + TreeViewModel *m_model; + ConfigNode m_source; + ConfigNode *m_target; + int m_index; +}; + +#endif // CUTKEYCOMMAND_H diff --git a/src/deletekeycommand.cpp b/src/deletekeycommand.cpp new file mode 100644 index 00000000000..6b1be0e2a1d --- /dev/null +++ b/src/deletekeycommand.cpp @@ -0,0 +1,20 @@ +#include "deletekeycommand.hpp" + +DeleteKeyCommand::DeleteKeyCommand(const QString &type, TreeViewModel *model, ConfigNode *node, int index, QUndoCommand *parent) + : QUndoCommand(parent) + , m_model(model) + , m_node(*node) + , m_index(index) +{ + setText(type); +} + +void DeleteKeyCommand::undo() +{ + m_model->insertRow(m_index, new ConfigNode(m_node)); +} + +void DeleteKeyCommand::redo() +{ + m_model->removeRow(m_index); +} diff --git a/src/deletekeycommand.hpp b/src/deletekeycommand.hpp new file mode 100644 index 00000000000..74d168b87ef --- /dev/null +++ b/src/deletekeycommand.hpp @@ -0,0 +1,24 @@ +#ifndef DELETEKEYCOMMAND_HPP +#define DELETEKEYCOMMAND_HPP + +#include +#include "treeviewmodel.hpp" + +class DeleteKeyCommand : public QUndoCommand +{ + +public: + explicit DeleteKeyCommand(const QString &type, TreeViewModel *model, ConfigNode *node, int index, QUndoCommand *parent = 0); + + virtual void undo(); + virtual void redo(); + +private: + + TreeViewModel *m_model; + ConfigNode m_node; + int m_index; + +}; + +#endif // DELETEKEYCOMMAND_HPP diff --git a/src/dynamictreemodel.cpp b/src/dynamictreemodel.cpp new file mode 100644 index 00000000000..6a75a3dd50a --- /dev/null +++ b/src/dynamictreemodel.cpp @@ -0,0 +1,402 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Stephen Kelly +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "dynamictreemodel.h" + +#include +#include +#include +#include + + +DynamicTreeModel::DynamicTreeModel(QObject *parent) + : QAbstractItemModel(parent), + nextId(1) +{ +} + +QModelIndex DynamicTreeModel::index(int row, int column, const QModelIndex &parent) const +{ +// if (column != 0) +// return QModelIndex(); + + + if ( column < 0 || row < 0 ) + return QModelIndex(); + + QList > childIdColumns = m_childItems.value(parent.internalId()); + + const qint64 grandParent = findParentId(parent.internalId()); + if (grandParent >= 0) { + QList > parentTable = m_childItems.value(grandParent); + if (parent.column() >= parentTable.size()) + qFatal("%s: parent.column() must be less than parentTable.size()", Q_FUNC_INFO); + QList parentSiblings = parentTable.at(parent.column()); + if (parent.row() >= parentSiblings.size()) + qFatal("%s: parent.row() must be less than parentSiblings.size()", Q_FUNC_INFO); + } + + if (childIdColumns.size() == 0) + return QModelIndex(); + + if (column >= childIdColumns.size()) + return QModelIndex(); + + QList rowIds = childIdColumns.at(column); + + if ( row >= rowIds.size()) + return QModelIndex(); + + qint64 id = rowIds.at(row); + + return createIndex(row, column, reinterpret_cast(id)); + +} + +qint64 DynamicTreeModel::findParentId(qint64 searchId) const +{ + if (searchId <= 0) + return -1; + + QHashIterator > > i(m_childItems); + while (i.hasNext()) + { + i.next(); + QListIterator > j(i.value()); + while (j.hasNext()) + { + QList l = j.next(); + if (l.contains(searchId)) + { + return i.key(); + } + } + } + return -1; +} + +QModelIndex DynamicTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + qint64 searchId = index.internalId(); + qint64 parentId = findParentId(searchId); + // Will never happen for valid index, but what the hey... + if (parentId <= 0) + return QModelIndex(); + + qint64 grandParentId = findParentId(parentId); + if (grandParentId < 0) + grandParentId = 0; + + int column = 0; + QList childList = m_childItems.value(grandParentId).at(column); + + int row = childList.indexOf(parentId); + + return createIndex(row, column, reinterpret_cast(parentId)); + +} + +int DynamicTreeModel::rowCount(const QModelIndex &index ) const +{ + QList > cols = m_childItems.value(index.internalId()); + + if (cols.size() == 0 ) + return 0; + + if (index.column() > 0) + return 0; + + return cols.at(0).size(); +} + +int DynamicTreeModel::columnCount(const QModelIndex &index ) const +{ +// Q_UNUSED(index); + return m_childItems.value(index.internalId()).size(); +} + +QVariant DynamicTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (Qt::DisplayRole == role) + { + return m_items.value(index.internalId()); + } + return QVariant(); +} + +void DynamicTreeModel::clear() +{ + beginResetModel(); + m_items.clear(); + m_childItems.clear(); + nextId = 1; + endResetModel(); +} + + +ModelChangeCommand::ModelChangeCommand( DynamicTreeModel *model, QObject *parent ) + : QObject(parent), m_model(model), m_numCols(1), m_startRow(-1), m_endRow(-1) +{ + +} + +QModelIndex ModelChangeCommand::findIndex(QList rows) +{ + const int col = 0; + QModelIndex parent = QModelIndex(); + QListIterator i(rows); + while (i.hasNext()) + { + parent = m_model->index(i.next(), col, parent); + if (!parent.isValid()) + qFatal("%s: parent must be valid", Q_FUNC_INFO); + } + return parent; +} + +ModelInsertCommand::ModelInsertCommand(DynamicTreeModel *model, QObject *parent ) + : ModelChangeCommand(model, parent) +{ + +} + +void ModelInsertCommand::doCommand() +{ + QModelIndex parent = findIndex(m_rowNumbers); + m_model->beginInsertRows(parent, m_startRow, m_endRow); + qint64 parentId = parent.internalId(); + for (int row = m_startRow; row <= m_endRow; row++) + { + for(int col = 0; col < m_numCols; col++ ) + { + if (m_model->m_childItems[parentId].size() <= col) + { + m_model->m_childItems[parentId].append(QList()); + } +// QString name = QUuid::createUuid().toString(); + qint64 id = m_model->newId(); + QString name = QString::number(id); + + m_model->m_items.insert(id, name); + m_model->m_childItems[parentId][col].insert(row, id); + + } + } + m_model->endInsertRows(); +} + + +ModelMoveCommand::ModelMoveCommand(DynamicTreeModel *model, QObject *parent) + : ModelChangeCommand(model, parent) +{ + +} +bool ModelMoveCommand::emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow) +{ + return m_model->beginMoveRows(srcParent, srcStart, srcEnd, destParent, destRow); +} + +void ModelMoveCommand::doCommand() +{ + QModelIndex srcParent = findIndex(m_rowNumbers); + QModelIndex destParent = findIndex(m_destRowNumbers); + + if (!emitPreSignal(srcParent, m_startRow, m_endRow, destParent, m_destRow)) + { + return; + } + + for (int column = 0; column < m_numCols; ++column) + { + QList l = m_model->m_childItems.value(srcParent.internalId())[column].mid(m_startRow, m_endRow - m_startRow + 1 ); + + for (int i = m_startRow; i <= m_endRow ; i++) + { + m_model->m_childItems[srcParent.internalId()][column].removeAt(m_startRow); + } + int d; + if (m_destRow < m_startRow) + d = m_destRow; + else + { + if (srcParent == destParent) + d = m_destRow - (m_endRow - m_startRow + 1); + else + d = m_destRow - (m_endRow - m_startRow) + 1; + } + + foreach(const qint64 id, l) + { + m_model->m_childItems[destParent.internalId()][column].insert(d++, id); + } + } + + emitPostSignal(); +} + +void ModelMoveCommand::emitPostSignal() +{ + m_model->endMoveRows(); +} + +ModelResetCommand::ModelResetCommand(DynamicTreeModel* model, QObject* parent) + : ModelMoveCommand(model, parent) +{ + +} + +ModelResetCommand::~ModelResetCommand() +{ + +} + +bool ModelResetCommand::emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow) +{ + Q_UNUSED(srcParent); + Q_UNUSED(srcStart); + Q_UNUSED(srcEnd); + Q_UNUSED(destParent); + Q_UNUSED(destRow); + + return true; +} + +void ModelResetCommand::emitPostSignal() +{ + m_model->reset(); +} + +ModelResetCommandFixed::ModelResetCommandFixed(DynamicTreeModel* model, QObject* parent) + : ModelMoveCommand(model, parent) +{ + +} + +ModelResetCommandFixed::~ModelResetCommandFixed() +{ + +} + +bool ModelResetCommandFixed::emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow) +{ + Q_UNUSED(srcParent); + Q_UNUSED(srcStart); + Q_UNUSED(srcEnd); + Q_UNUSED(destParent); + Q_UNUSED(destRow); + + m_model->beginResetModel(); + return true; +} + +void ModelResetCommandFixed::emitPostSignal() +{ + m_model->endResetModel(); +} + +ModelChangeChildrenLayoutsCommand::ModelChangeChildrenLayoutsCommand(DynamicTreeModel* model, QObject* parent) + : ModelChangeCommand(model, parent) +{ + +} + +void ModelChangeChildrenLayoutsCommand::doCommand() +{ + const QPersistentModelIndex parent1 = findIndex(m_rowNumbers); + const QPersistentModelIndex parent2 = findIndex(m_secondRowNumbers); + + QList parents; + parents << parent1; + parents << parent2; + + emit m_model->layoutAboutToBeChanged(parents); + + int rowSize1 = -1; + int rowSize2 = -1; + + for (int column = 0; column < m_numCols; ++column) + { + { + QList &l = m_model->m_childItems[parent1.internalId()][column]; + rowSize1 = l.size(); + l.prepend(l.takeLast()); + } + { + QList &l = m_model->m_childItems[parent2.internalId()][column]; + rowSize2 = l.size(); + l.append(l.takeFirst()); + } + } + + // If we're changing one of the parent indexes, we need to ensure that we do that before + // changing any children of that parent. The reason is that we're keeping parent1 and parent2 + // around as QPersistentModelIndex instances, and we query idx.parent() in the loop. + QModelIndexList persistent = m_model->persistentIndexList(); + foreach (const QModelIndex &parent, parents) { + int idx = persistent.indexOf(parent); + if (idx != -1) + persistent.move(idx, 0); + } + + foreach (const QModelIndex &idx, persistent) { + if (idx.parent() == parent1) { + if (idx.row() == rowSize1 - 1) { + m_model->changePersistentIndex(idx, m_model->createIndex(0, idx.column(), idx.internalPointer())); + } else { + m_model->changePersistentIndex(idx, m_model->createIndex(idx.row() + 1, idx.column(), idx.internalPointer())); + } + } else if (idx.parent() == parent2) { + if (idx.row() == 0) { + m_model->changePersistentIndex(idx, m_model->createIndex(rowSize2 - 1, idx.column(), idx.internalPointer())); + } else { + m_model->changePersistentIndex(idx, m_model->createIndex(idx.row() - 1, idx.column(), idx.internalPointer())); + } + } + } + + emit m_model->layoutChanged(parents); +} diff --git a/src/dynamictreemodel.h b/src/dynamictreemodel.h new file mode 100644 index 00000000000..8fd2a54dacf --- /dev/null +++ b/src/dynamictreemodel.h @@ -0,0 +1,214 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Stephen Kelly +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef DYNAMICTREEMODEL_H +#define DYNAMICTREEMODEL_H + +#include + +#include +#include + + +class DynamicTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + DynamicTreeModel(QObject *parent = 0); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + int rowCount(const QModelIndex &index = QModelIndex()) const; + int columnCount(const QModelIndex &index = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + void clear(); + +protected slots: + + /** + Finds the parent id of the string with id @p searchId. + + Returns -1 if not found. + */ + qint64 findParentId(qint64 searchId) const; + +private: + QHash m_items; + QHash > > m_childItems; + qint64 nextId; + qint64 newId() { return nextId++; }; + + QModelIndex m_nextParentIndex; + int m_nextRow; + + int m_depth; + int maxDepth; + + friend class ModelInsertCommand; + friend class ModelMoveCommand; + friend class ModelResetCommand; + friend class ModelResetCommandFixed; + friend class ModelChangeChildrenLayoutsCommand; + +}; + + +class ModelChangeCommand : public QObject +{ + Q_OBJECT +public: + + ModelChangeCommand( DynamicTreeModel *model, QObject *parent = 0 ); + + virtual ~ModelChangeCommand() {} + + void setAncestorRowNumbers(QList rowNumbers) { m_rowNumbers = rowNumbers; } + + QModelIndex findIndex(QList rows); + + void setStartRow(int row) { m_startRow = row; } + + void setEndRow(int row) { m_endRow = row; } + + void setNumCols(int cols) { m_numCols = cols; } + + virtual void doCommand() = 0; + +protected: + DynamicTreeModel* m_model; + QList m_rowNumbers; + int m_numCols; + int m_startRow; + int m_endRow; + +}; + +typedef QList ModelChangeCommandList; + +class ModelInsertCommand : public ModelChangeCommand +{ + Q_OBJECT + +public: + + ModelInsertCommand(DynamicTreeModel *model, QObject *parent = 0 ); + virtual ~ModelInsertCommand() {} + + virtual void doCommand(); +}; + + +class ModelMoveCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + ModelMoveCommand(DynamicTreeModel *model, QObject *parent); + + virtual ~ModelMoveCommand() {} + + virtual bool emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow); + + virtual void doCommand(); + + virtual void emitPostSignal(); + + void setDestAncestors( QList rows ) { m_destRowNumbers = rows; } + + void setDestRow(int row) { m_destRow = row; } + +protected: + QList m_destRowNumbers; + int m_destRow; +}; + +/** + A command which does a move and emits a reset signal. +*/ +class ModelResetCommand : public ModelMoveCommand +{ + Q_OBJECT +public: + ModelResetCommand(DynamicTreeModel* model, QObject* parent = 0); + + virtual ~ModelResetCommand(); + + virtual bool emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow); + virtual void emitPostSignal(); + +}; + +/** + A command which does a move and emits a beginResetModel and endResetModel signals. +*/ +class ModelResetCommandFixed : public ModelMoveCommand +{ + Q_OBJECT +public: + ModelResetCommandFixed(DynamicTreeModel* model, QObject* parent = 0); + + virtual ~ModelResetCommandFixed(); + + virtual bool emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow); + virtual void emitPostSignal(); + +}; + +class ModelChangeChildrenLayoutsCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + ModelChangeChildrenLayoutsCommand(DynamicTreeModel *model, QObject *parent); + + virtual ~ModelChangeChildrenLayoutsCommand() {} + + virtual void doCommand(); + + void setSecondAncestorRowNumbers( QList rows ) { m_secondRowNumbers = rows; } + +protected: + QList m_secondRowNumbers; + int m_destRow; +}; + +#endif diff --git a/src/editkeycommand.cpp b/src/editkeycommand.cpp new file mode 100644 index 00000000000..434e94d7dec --- /dev/null +++ b/src/editkeycommand.cpp @@ -0,0 +1,30 @@ +#include "editkeycommand.hpp" + +EditKeyCommand::EditKeyCommand(TreeViewModel *model, int index, const QString &oldName, const QVariant &oldValue, const QVariantMap &oldMetaData, + const QString &newName, const QVariant &newValue, const QVariantMap &newMetaData, QUndoCommand *parent) + : QUndoCommand(parent) + , m_model(model) + , m_index(index) + , m_oldName(oldName) + , m_oldValue(oldValue) + , m_oldMetaData(oldMetaData) + , m_newName(newName) + , m_newValue(newValue) + , m_newMetaData(newMetaData) +{ + setText("edit"); +} + +void EditKeyCommand::undo() +{ + m_model->setData(m_index, m_oldName, "Name"); + m_model->setData(m_index, m_oldValue, "Value"); + m_model->model().at(m_index)->setMeta(m_oldMetaData); +} + +void EditKeyCommand::redo() +{ + m_model->setData(m_index, m_newName, "Name"); + m_model->setData(m_index, m_newValue, "Value"); + m_model->model().at(m_index)->setMeta(m_newMetaData); +} diff --git a/src/editkeycommand.hpp b/src/editkeycommand.hpp new file mode 100644 index 00000000000..a1b1ef6a1ae --- /dev/null +++ b/src/editkeycommand.hpp @@ -0,0 +1,30 @@ +#ifndef EDITKEYCOMMAND_HPP +#define EDITKEYCOMMAND_HPP + +#include +#include "treeviewmodel.hpp" + +class EditKeyCommand : public QUndoCommand +{ + +public: + explicit EditKeyCommand(TreeViewModel *model, int index, const QString &oldName, const QVariant &oldValue, const QVariantMap &oldMetaData, + const QString &newName, const QVariant &newValue, const QVariantMap &newMetaData, QUndoCommand *parent = 0); + virtual void undo(); + virtual void redo(); + +private: + + TreeViewModel* m_model; + int m_index; + + QString m_oldName; + QVariant m_oldValue; + QVariantMap m_oldMetaData; + + QString m_newName; + QVariant m_newValue; + QVariantMap m_newMetaData; +}; + +#endif // EDITKEYCOMMAND_HPP diff --git a/src/keysetvisitor.cpp b/src/keysetvisitor.cpp new file mode 100644 index 00000000000..7d260fde0ee --- /dev/null +++ b/src/keysetvisitor.cpp @@ -0,0 +1,36 @@ +#include "keysetvisitor.hpp" + +using namespace kdb; + +KeySetVisitor::KeySetVisitor(KeySet &keySet) +{ + m_set = keySet; +} + +void KeySetVisitor::visit(ConfigNode *node) +{ + Key key = node->getKey(); + + if(key && key.isValid()){ + qDebug() << "Appending key " << QString::fromStdString(key.getName()); + m_set.append(key); + } + else{ + qDebug() << "Key of node " << node->getName() << " is null or invalid"; + } +} + +void KeySetVisitor::visit(TreeViewModel *model) +{ + qDebug() << "==================================="; + foreach (ConfigNode *node, model->model()) + node->accept(*this); + qDebug() << "==================================="; + + model->populateModel(); +} + +KeySet &KeySetVisitor::getKeySet() +{ + return m_set; +} diff --git a/src/keysetvisitor.hpp b/src/keysetvisitor.hpp new file mode 100644 index 00000000000..e0cf7607fec --- /dev/null +++ b/src/keysetvisitor.hpp @@ -0,0 +1,20 @@ +#ifndef KEYSETVISITOR_HPP +#define KEYSETVISITOR_HPP + +#include "visitor.hpp" +#include "confignode.hpp" +#include + +class KeySetVisitor : public Visitor +{ +public: + explicit KeySetVisitor(kdb::KeySet &keySet); + void visit(ConfigNode *node); + void visit(TreeViewModel *model); + kdb::KeySet& getKeySet(); + +private: + kdb::KeySet m_set; +}; + +#endif // KEYSETVISITOR_HPP diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 00000000000..40fa9f71a39 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include + +#include "treeviewmodel.hpp" +#include "confignode.hpp" +#include "undomanager.hpp" +#include "modeltest/modeltest.h" + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + + qRegisterMetaType ("TreeViewModel"); + qRegisterMetaType ("ConfigNode"); + qRegisterMetaType ("UndoManager"); + + QString locale = QLocale::system().name(); + + QTranslator tr; + tr.load(QString(":/qml/i18n/lang_") + locale + QString(".qm")); + app.installTranslator(&tr); + + QQmlApplicationEngine engine; + QQmlContext* ctxt = engine.rootContext(); + + UndoManager manager; + kdb::KeySet set; + + TreeViewModel* model = new TreeViewModel(set); + //new ModelTest(model); + + model->populateModel(); + + ctxt->setContextProperty("undoManager", &manager); + ctxt->setContextProperty("externTreeModel", model); + engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); + +// PrintVisitor printer; +// model->accept(printer); + + return app.exec(); +} diff --git a/src/newkeycommand.cpp b/src/newkeycommand.cpp new file mode 100644 index 00000000000..3d8f83e5679 --- /dev/null +++ b/src/newkeycommand.cpp @@ -0,0 +1,21 @@ +#include "newkeycommand.hpp" + +NewKeyCommand::NewKeyCommand(TreeViewModel *model, const QString &path, const QString &value, const QVariantMap &metaData, QUndoCommand *parent) + : QUndoCommand(parent) + , m_model(model) + , m_path(path) + , m_value(value) + , m_metaData(metaData) +{ + setText("new"); +} + +void NewKeyCommand::undo() +{ + m_model->deletePath(m_path); +} + +void NewKeyCommand::redo() +{ + m_model->createNewNode(m_path, m_value, m_metaData); +} diff --git a/src/newkeycommand.hpp b/src/newkeycommand.hpp new file mode 100644 index 00000000000..606a07dca7c --- /dev/null +++ b/src/newkeycommand.hpp @@ -0,0 +1,25 @@ +#ifndef NEWKEYCOMMAND_HPP +#define NEWKEYCOMMAND_HPP + +#include +#include "treeviewmodel.hpp" + +class NewKeyCommand : public QUndoCommand +{ + +public: + explicit NewKeyCommand(TreeViewModel *model, const QString &path, const QString &value, const QVariantMap &metaData, QUndoCommand *parent = 0); + + virtual void undo(); + virtual void redo(); + +private: + + TreeViewModel *m_model; + QString m_path; + QString m_value; + QVariantMap m_metaData; + +}; + +#endif // NEWKEYCOMMAND_HPP diff --git a/src/printvisitor.cpp b/src/printvisitor.cpp new file mode 100644 index 00000000000..1f2253250a3 --- /dev/null +++ b/src/printvisitor.cpp @@ -0,0 +1,12 @@ +#include "printvisitor.hpp" + +void PrintVisitor::visit(ConfigNode *node) +{ + qDebug() << node->getName(); +} + +void PrintVisitor::visit(TreeViewModel *model) +{ + foreach (ConfigNode *node, model->model()) + node->accept(*this); +} diff --git a/src/printvisitor.hpp b/src/printvisitor.hpp new file mode 100644 index 00000000000..68e29cf8d11 --- /dev/null +++ b/src/printvisitor.hpp @@ -0,0 +1,15 @@ +#ifndef PRINTVISITOR_HPP +#define PRINTVISITOR_HPP + +#include +#include "visitor.hpp" +#include "confignode.hpp" + +class PrintVisitor : public Visitor +{ +public: + void visit(ConfigNode *node); + void visit(TreeViewModel *model); +}; + +#endif // PRINTVISITOR_HPP diff --git a/src/treeviewmodel.cpp b/src/treeviewmodel.cpp new file mode 100644 index 00000000000..5f07c1432ca --- /dev/null +++ b/src/treeviewmodel.cpp @@ -0,0 +1,447 @@ +#include "treeviewmodel.hpp" + +using namespace std; +using namespace kdb; + +TreeViewModel::TreeViewModel(QObject* parent) +{ + Q_UNUSED(parent); +} + +TreeViewModel::TreeViewModel(KeySet &keySet) + : m_keySet(keySet) +{ + m_kdb.get(m_keySet, ""); +} + +TreeViewModel::TreeViewModel(const TreeViewModel& other) + : QAbstractListModel() +{ + m_model = other.m_model; // copy from other list +} + +TreeViewModel::~TreeViewModel() +{ + // TODO: is this needed? + qDeleteAll(m_model); +} + +int TreeViewModel::rowCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent); + return m_model.count(); +} + +QVariant TreeViewModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + { + qDebug() << "TreeViewModel::data: index not valid. Index = " << index.row() << " Model size = " << m_model.size(); + // TODO: why is this function called with wrong index? + return QVariant(); + } + + if (index.row() > (m_model.size() - 1)) + { + qDebug() << "TreeViewModel::data: row too high" << index.row(); + // TODO: why is this function called with wrong index? + return QVariant(); + } + + ConfigNode* node = m_model.at(index.row()); + + switch (role) + { + + case Qt::DisplayRole: + // TODO: document fallthrough if it was desired + case NameRole: + return QVariant::fromValue(node->getName()); + + case PathRole: + return QVariant::fromValue(node->getPath()); + + case ValueRole: + return QVariant::fromValue(node->getValue()); + + case ChildCountRole: + return QVariant::fromValue(node->getChildCount()); + + case ChildrenRole: + return QVariant::fromValue(node->getChildren()); + + case ChildrenHaveNoChildrenRole: + return QVariant::fromValue(node->childrenHaveNoChildren()); + + case MetaValueRole: + return QVariant::fromValue(node->getMetaKeys()); + + case NodeRole: + return QVariant::fromValue(node); + + case ParentModelRole: + return QVariant::fromValue(node->getParentModel()); + + case IndexRole: + return QVariant::fromValue(index.row()); + + default: + qDebug() << "Unknown role " << role; + return QVariant(); + + } + +} + +bool TreeViewModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid() || index.row() > (m_model.size() - 1)) + { + qDebug() << "TreeViewModel::setData: Wrong index called"; + return false; + } + + ConfigNode* node = m_model.at(index.row()); + + switch (role) + { + + case NameRole: + node->setName(value.toString()); + break; + + case ValueRole: + node->setValue(value); + break; + + case MetaValueRole: + QVariantList valueList = value.toList(); + node->setMeta(valueList.at(0).toString(), valueList.at(1)); + } + + emit dataChanged(index, index); + + return true; +} + +// TODO: Why are there two implementations of setData needed? +// Because QML cannot call setData() directly (see https://bugreports.qt-project.org/browse/QTBUG-7932) +void TreeViewModel::setData(int index, const QVariant& value, const QString& role) +{ + if (index < 0 || index > m_model.size() - 1) + { + qDebug() << "TreeViewModel::setData: Wrong index called. model.size = " << m_model.size() << " index = " << index; + return; + } + + QModelIndex modelIndex = this->index(index); + + if (role == "Name") + { + setData(modelIndex, value, NameRole); + } + else if (role == "Value") + { + setData(modelIndex, value, ValueRole); + } + else if (role == "MetaValue") + { + setData(modelIndex, value, MetaValueRole); + } + else + return; +} + +QString TreeViewModel::toString() +{ + QString model = "\n"; + + foreach(ConfigNode *node, m_model){ + model += node->getPath(); + model += "\n"; + } + + return model; +} + +void TreeViewModel::deletePath(const QString &path) +{ + QStringList splittedPath = path.split("/"); + + QString root = splittedPath.takeFirst(); + + if(root == "system") + { + m_model.at(0)->deletePath(splittedPath); + } + else if(root == "user") + { + m_model.at(1)->deletePath(splittedPath); + } + else + { + qDebug() << "TreeViewModel::deletePath: INVALID_PATH"; + } +} + +int TreeViewModel::getIndexByName(const QString &name) const +{ + for(int i = 0; i < m_model.count(); i++){ + if(m_model.at(i)->getName() == name) + return i; + } + + return -1; +} + +Qt::ItemFlags TreeViewModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return Qt::ItemIsEnabled; + + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; +} + +void TreeViewModel::sink(ConfigNode* node, QStringList keys, QString path, Key key) +{ + if (keys.length() == 0) + return; + + bool isLeaf = (keys.length() == 1); + + // qDebug() << "in sink: " << keys << " with path: " << path; + + QString name = keys.takeFirst(); + // qDebug() << "with name: " << name << " and node " << node; + + if (node->hasChild(name)) + { + // qDebug() << "has child: " << name << " with path: " << node->getPath(); + sink(node->getChildByName(name), keys, node->getPath() + "/" + name, key); + } + else + { + ConfigNode* newNode; + + if(isLeaf) + newNode = new ConfigNode(name, (path + "/" + name), key, node->getChildren()); + else + newNode = new ConfigNode(name, (path + "/" + name), NULL, node->getChildren()); + + node->appendChild(newNode); + + sink(newNode, keys, node->getPath() + "/" + name, key); + } +} + + +void TreeViewModel::populateModel() +{ + ConfigNode* system = new ConfigNode("system", "system", 0, this); + ConfigNode* user = new ConfigNode("user", "user", 0, this); + + m_model << system << user; + + m_keySet.rewind(); + + while (m_keySet.next()) + { + QString currentKey = QString::fromStdString(m_keySet.current().getName()); + + // qDebug() << "TreeViewModel::populateModel: currentKey: " << currentKey; + + QStringList keys = currentKey.split("/"); + QString root = keys.takeFirst(); + + if (root == "system") + { + sink(m_model.at(0), keys, "system", m_keySet.current()); + } + else if (root == "user") + { + sink(m_model.at(1), keys, "user", m_keySet.current()); + } + else + { + qDebug() << "TreeViewModel::populateModel: INVALID_KEY: " << currentKey; + } + + } + +} + +void TreeViewModel::accept(Visitor &visitor) +{ + visitor.visit(this); +} + +QVariantMap TreeViewModel::get(int idx) const +{ + QVariantMap map; + + foreach (int k, roleNames().keys()) + { + map[roleNames().value(k)] = data(index(idx, 0), k); + } + + return map; +} + +QVariant TreeViewModel::find(const QString& term) +{ + TreeViewModel* searchResults = new TreeViewModel; + + foreach (ConfigNode* node, m_model) + { + find(node, searchResults, term); + } + + if (searchResults->model().count() == 0) + { + searchResults->model().append(new ConfigNode("NotfoundNode", "There were no results matching your query.", 0, this)); + } + + return QVariant::fromValue(searchResults); +} + +void TreeViewModel::find(ConfigNode* node, TreeViewModel *searchResults, const QString term) +{ + + int tmpChildCount = node->getChildCount(); + + if (tmpChildCount > 0) + { + for (int i = 0; i < tmpChildCount; i++) + { + find(node->getChildByIndex(i), searchResults, term); + } + } + + if (node->getName().contains(term) || node->getValue().toString().contains(term)) + { + searchResults->model().append(node); + } + +} + +bool TreeViewModel::removeRow(int row, const QModelIndex& parent) +{ + Q_UNUSED(parent); + + if (row < 0 || row > m_model.size() - 1) + { + qDebug() << "Tried to remove row out of bounds. model.size = " << m_model.size() << ", index = " << row; + return false; + } + + beginRemoveRows(QModelIndex(), row, row); + m_model.at(row)->invalidateKey(); + delete m_model.takeAt(row); + endRemoveRows(); + + return true; +} + +bool TreeViewModel::insertRow(int row, const QModelIndex& parent) +{ + Q_UNUSED(parent); + ConfigNode *node = new ConfigNode; + node->setName(QString::fromStdString(m_metaModelParent.getName())); + node->setKey(m_metaModelParent); + node->setParentModel(this); + + beginInsertRows(QModelIndex(), row, row); + m_model.insert(row, node); + endInsertRows(); + + return true; +} + +void TreeViewModel::insertRow(int row, ConfigNode *node) +{ + beginInsertRows(QModelIndex(), row, row); + m_model.insert(row, node); + endInsertRows(); +} + + +void TreeViewModel::insertMetaRow(int row, ConfigNode *node) +{ + m_metaModelParent = node->getKey(); + + if(m_metaModelParent){ + insertRow(row); + } + else + qDebug() << "Key " << QString::fromStdString(node->getKey().getFullName()) << " not valid!"; +} + +void TreeViewModel::createNewNode(const QString &path, const QString &value, const QVariantMap metaData) +{ + qDebug() << "TreeViewModel::createNewNode: path = " << path << " value = " << value; + Key key; + key.setName(path.toStdString()); + key.setString(value.toStdString()); + + for(QVariantMap::const_iterator iter = metaData.begin(); iter != metaData.end(); iter++) + { + qDebug() << iter.key() << iter.value(); + key.setMeta(iter.key().toStdString(), iter.value().toString().toStdString()); + } + + QStringList splittedKey = path.split("/"); + + if (splittedKey.at(0) == "system") + { + splittedKey.removeFirst(); + sink(m_model.at(0), splittedKey, "system", key); + } + else if (splittedKey.at(0) == "user") + { + splittedKey.removeFirst(); + sink(m_model.at(1), splittedKey, "user", key); + } + else + { + qDebug() << "TreeViewModel::createNewNode: INVALID_KEY: " << path; + } +} + +void TreeViewModel::append(ConfigNode *node) +{ + insertRow(rowCount(), node); +} + +void TreeViewModel::synchronize() +{ + KeySetVisitor ksVisit(m_keySet); + accept(ksVisit); + m_keySet = ksVisit.getKeySet(); + m_kdb.set(m_keySet, "/"); + populateModel(); +} + +void TreeViewModel::clear() +{ + beginResetModel(); + m_model.clear(); + endResetModel(); +} + +QHash TreeViewModel::roleNames() const +{ + QHash roles; + + roles[NameRole] = "name"; + roles[PathRole] = "path"; + roles[ValueRole] = "value"; + roles[ChildCountRole] = "childCount"; + roles[ChildrenRole] = "children"; + roles[ChildrenHaveNoChildrenRole] = "childrenHaveNoChildren"; + roles[MetaValueRole] = "metaValue"; + roles[NodeRole] = "node"; + roles[ParentModelRole] = "parentModel"; + roles[IndexRole] = "index"; + + return roles; +} diff --git a/src/treeviewmodel.hpp b/src/treeviewmodel.hpp new file mode 100644 index 00000000000..63e1f236863 --- /dev/null +++ b/src/treeviewmodel.hpp @@ -0,0 +1,108 @@ +#ifndef TREEVIEWMODEL_H +#define TREEVIEWMODEL_H + +#include +#include +#include +#include +#include +#include + +#include "confignode.hpp" +#include "printvisitor.hpp" +#include "keysetvisitor.hpp" + +class ConfigNode; +class Visitor; + +class TreeViewModel : public QAbstractListModel +{ + + Q_OBJECT + +public: + + /** + * @brief The TreeViewModelRoles enum + * @ + */ + enum TreeViewModelRoles + { + NameRole = Qt::UserRole + 1, ///< The role QML can access the name of a node at a specified index. + PathRole, ///< The role QML can access the path of a node at a specified index. + ValueRole, ///< The role QML can access the value of a node at a specified index. + ChildCountRole, ///< The role QML can access the number of children of a node at a specified index. + ChildrenRole, ///< The role QML can access the children model of a node at a specified index. + ChildrenHaveNoChildrenRole, ///< The role QML can access if children of a node at a specified index do have children on their own. + MetaValueRole, ///< The role QML can access the meta model of a node at a specified index. + NodeRole, ///< The role QML can retrieve the node at a specified index. + ParentModelRole, + IndexRole + }; + + explicit TreeViewModel(QObject* parent = 0); + explicit TreeViewModel(kdb::KeySet &keySet); + + // Needed for Qt + TreeViewModel(TreeViewModel const & other); + ~TreeViewModel(); + + // @return the underlying model + QList& model() + { + return m_model; + } + + //mandatory methods inherited from QAbstractItemModel + Q_INVOKABLE int rowCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + Q_INVOKABLE bool insertRow(int row, const QModelIndex &parent = QModelIndex()); + Q_INVOKABLE bool removeRow(int row, const QModelIndex& parent = QModelIndex()); + Qt::ItemFlags flags(const QModelIndex& index) const; + + // recursively populate the model + Q_INVOKABLE void populateModel(); + + void accept(Visitor &visitor); + + /** + * @brief Get the roles of a ConfigNode at the specifies index. Needed to access roles from outside a delegate in QML. + * @param idx The index of the ConfigNode. + * @return A map of the roles of the ConfigNode at the specified index. + */ + Q_INVOKABLE QVariantMap get(int idx) const; + + /** + * @brief Find a search term in the model. + * @param term The search term of interest. + * @return A model which includes all ConfigNodes that have the search term in their name or value. + */ + Q_INVOKABLE QVariant find(const QString& term); + void insertMetaRow(int row, ConfigNode *node); + void insertRow(int row, ConfigNode* node); + Q_INVOKABLE void clear(); + Q_INVOKABLE void synchronize(); + Q_INVOKABLE void createNewNode(const QString &path, const QString &value, const QVariantMap metaData); + void append(ConfigNode *node); + Q_INVOKABLE void setData(int index, const QVariant& value, const QString& role); + QString toString(); + void deletePath(const QString &path); + Q_INVOKABLE int getIndexByName(const QString &name) const; + +private: + void sink(ConfigNode* node, QStringList keys, QString path, kdb::Key key); + void find(ConfigNode *node, TreeViewModel *searchResults, const QString term); + + QList m_model; + kdb::Key m_metaModelParent; + kdb::KDB m_kdb; + kdb::KeySet m_keySet; + +protected: + QHash roleNames() const; +}; + +Q_DECLARE_METATYPE(TreeViewModel) + +#endif // TREEVIEWMODEL_H diff --git a/src/tst_modeltest.cpp b/src/tst_modeltest.cpp new file mode 100644 index 00000000000..131ad05b53f --- /dev/null +++ b/src/tst_modeltest.cpp @@ -0,0 +1,342 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include +#include +#include + +#include "modeltest.h" +#include "dynamictreemodel.h" + + +class tst_ModelTest : public QObject +{ + Q_OBJECT + +public: + tst_ModelTest() {} + virtual ~tst_ModelTest() {} + +public slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + +private slots: + void stringListModel(); + void treeWidgetModel(); + void standardItemModel(); + void testInsertThroughProxy(); + void moveSourceItems(); + void testResetThroughProxy(); +}; + + + +void tst_ModelTest::initTestCase() +{ +} + +void tst_ModelTest::cleanupTestCase() +{ +} + +void tst_ModelTest::init() +{ + +} + +void tst_ModelTest::cleanup() +{ +} +/* + tests +*/ + +void tst_ModelTest::stringListModel() +{ + QStringListModel model; + QSortFilterProxyModel proxy; + + ModelTest t1(&model); + ModelTest t2(&proxy); + + proxy.setSourceModel(&model); + + model.setStringList(QStringList() << "2" << "3" << "1"); + model.setStringList(QStringList() << "a" << "e" << "plop" << "b" << "c" ); + + proxy.setDynamicSortFilter(true); + proxy.setFilterRegExp(QRegExp("[^b]")); +} + +void tst_ModelTest::treeWidgetModel() +{ + QTreeWidget widget; + + ModelTest t1(widget.model()); + + QTreeWidgetItem *root = new QTreeWidgetItem(&widget, QStringList("root")); + for (int i = 0; i < 20; ++i) { + new QTreeWidgetItem(root, QStringList(QString::number(i))); + } + QTreeWidgetItem *remove = root->child(2); + root->removeChild(remove); + QTreeWidgetItem *parent = new QTreeWidgetItem(&widget, QStringList("parent")); + new QTreeWidgetItem(parent, QStringList("child")); + widget.setItemHidden(parent, true); + + widget.sortByColumn(0); +} + +void tst_ModelTest::standardItemModel() +{ + QStandardItemModel model(10,10); + QSortFilterProxyModel proxy; + + + ModelTest t1(&model); + ModelTest t2(&proxy); + + proxy.setSourceModel(&model); + + model.insertRows(2, 5); + model.removeRows(4, 5); + + model.insertColumns(2, 5); + model.removeColumns(4, 5); + + model.insertRows(0,5, model.index(1,1)); + model.insertColumns(0,5, model.index(1,3)); +} + +void tst_ModelTest::testInsertThroughProxy() +{ + DynamicTreeModel *model = new DynamicTreeModel(this); + + QSortFilterProxyModel *proxy = new QSortFilterProxyModel(this); + proxy->setSourceModel(model); + + new ModelTest(proxy, this); + + ModelInsertCommand *insertCommand = new ModelInsertCommand(model, this); + insertCommand->setNumCols(4); + insertCommand->setStartRow(0); + insertCommand->setEndRow(9); + // Parent is QModelIndex() + insertCommand->doCommand(); + + insertCommand = new ModelInsertCommand(model, this); + insertCommand->setNumCols(4); + insertCommand->setAncestorRowNumbers(QList() << 5); + insertCommand->setStartRow(0); + insertCommand->setEndRow(9); + insertCommand->doCommand(); + + ModelMoveCommand *moveCommand = new ModelMoveCommand(model, this); + moveCommand->setNumCols(4); + moveCommand->setStartRow(0); + moveCommand->setEndRow(0); + moveCommand->setDestRow(9); + moveCommand->setDestAncestors(QList() << 5); + moveCommand->doCommand(); +} + +/** + Makes the persistent index list publicly accessible +*/ +class AccessibleProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + AccessibleProxyModel(QObject *parent = 0) : QSortFilterProxyModel(parent) {} + + QModelIndexList persistent() + { + return persistentIndexList(); + } +}; + +class ObservingObject : public QObject +{ + Q_OBJECT +public: + ObservingObject(AccessibleProxyModel *proxy, QObject *parent = 0) + : QObject(parent) + , m_proxy(proxy) + , storePersistentFailureCount(0) + , checkPersistentFailureCount(0) + { + connect(m_proxy, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(storePersistent())); + connect(m_proxy, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(checkPersistent())); + } + +public slots: + + void storePersistent(const QModelIndex &parent) + { + for (int row = 0; row < m_proxy->rowCount(parent); ++row) { + QModelIndex proxyIndex = m_proxy->index(row, 0, parent); + QModelIndex sourceIndex = m_proxy->mapToSource(proxyIndex); + if (!proxyIndex.isValid()) { + qWarning("%s: Invalid proxy index", Q_FUNC_INFO); + ++storePersistentFailureCount; + } + if (!sourceIndex.isValid()) { + qWarning("%s: invalid source index", Q_FUNC_INFO); + ++storePersistentFailureCount; + } + m_persistentSourceIndexes.append(sourceIndex); + m_persistentProxyIndexes.append(proxyIndex); + if (m_proxy->hasChildren(proxyIndex)) + storePersistent(proxyIndex); + } + } + + void storePersistent() + { + // This method is called from rowsAboutToBeMoved. Persistent indexes should be valid + foreach(const QModelIndex &idx, m_persistentProxyIndexes) + if (!idx.isValid()) { + qWarning("%s: persistentProxyIndexes contains invalid index", Q_FUNC_INFO); + ++storePersistentFailureCount; + } + + if (!m_proxy->persistent().isEmpty()) { + qWarning("%s: proxy should have no persistent indexes when storePersistent called", + Q_FUNC_INFO); + ++storePersistentFailureCount; + } + storePersistent(QModelIndex()); + if (m_proxy->persistent().isEmpty()) { + qWarning("%s: proxy should have persistent index after storePersistent called", + Q_FUNC_INFO); + ++storePersistentFailureCount; + } + } + + void checkPersistent() + { + for (int row = 0; row < m_persistentProxyIndexes.size(); ++row) { + m_persistentProxyIndexes.at(row); + m_persistentSourceIndexes.at(row); + } + for (int row = 0; row < m_persistentProxyIndexes.size(); ++row) { + QModelIndex updatedProxy = m_persistentProxyIndexes.at(row); + QModelIndex updatedSource = m_persistentSourceIndexes.at(row); + if (m_proxy->mapToSource(updatedProxy) != updatedSource) { + qWarning("%s: check failed at row %d", Q_FUNC_INFO, row); + ++checkPersistentFailureCount; + } + } + m_persistentSourceIndexes.clear(); + m_persistentProxyIndexes.clear(); + } + +private: + AccessibleProxyModel *m_proxy; + QList m_persistentSourceIndexes; + QList m_persistentProxyIndexes; +public: + int storePersistentFailureCount; + int checkPersistentFailureCount; +}; + +void tst_ModelTest::moveSourceItems() +{ + DynamicTreeModel *model = new DynamicTreeModel(this); + AccessibleProxyModel *proxy = new AccessibleProxyModel(this); + proxy->setSourceModel(model); + + ModelInsertCommand *insertCommand = new ModelInsertCommand(model, this); + insertCommand->setStartRow(0); + insertCommand->setEndRow(2); + insertCommand->doCommand(); + + insertCommand = new ModelInsertCommand(model, this); + insertCommand->setAncestorRowNumbers(QList() << 1); + insertCommand->setStartRow(0); + insertCommand->setEndRow(2); + insertCommand->doCommand(); + + ObservingObject observer(proxy); + + ModelMoveCommand *moveCommand = new ModelMoveCommand(model, this); + moveCommand->setStartRow(0); + moveCommand->setEndRow(0); + moveCommand->setDestAncestors(QList() << 1); + moveCommand->setDestRow(0); + moveCommand->doCommand(); + + QCOMPARE(observer.storePersistentFailureCount, 0); + QCOMPARE(observer.checkPersistentFailureCount, 0); +} + +void tst_ModelTest::testResetThroughProxy() +{ + DynamicTreeModel *model = new DynamicTreeModel(this); + + ModelInsertCommand *insertCommand = new ModelInsertCommand(model, this); + insertCommand->setStartRow(0); + insertCommand->setEndRow(2); + insertCommand->doCommand(); + + QPersistentModelIndex persistent = model->index(0, 0); + + AccessibleProxyModel *proxy = new AccessibleProxyModel(this); + proxy->setSourceModel(model); + + ObservingObject observer(proxy); + observer.storePersistent(); + + ModelResetCommand *resetCommand = new ModelResetCommand(model, this); + resetCommand->setNumCols(0); + resetCommand->doCommand(); + + QCOMPARE(observer.storePersistentFailureCount, 0); + QCOMPARE(observer.checkPersistentFailureCount, 0); +} + + +QTEST_MAIN(tst_ModelTest) +#include "tst_modeltest.moc" diff --git a/src/undomanager.cpp b/src/undomanager.cpp new file mode 100644 index 00000000000..e8db69d227c --- /dev/null +++ b/src/undomanager.cpp @@ -0,0 +1,121 @@ +#include +#include "undomanager.hpp" +#include "editkeycommand.hpp" +#include "deletekeycommand.hpp" +#include "newkeycommand.hpp" +#include "copykeycommand.hpp" +#include "cutkeycommand.hpp" + +UndoManager::UndoManager(QObject *parent) : + QObject(parent) + , m_undoStack(new QUndoStack(this)) +{ + connect(m_undoStack, SIGNAL(canRedoChanged(bool)), this, SIGNAL(canRedoChanged())); + connect(m_undoStack, SIGNAL(canUndoChanged(bool)), this, SIGNAL(canUndoChanged())); + connect(m_undoStack, SIGNAL(redoTextChanged(QString)), this, SIGNAL(redoTextChanged())); + connect(m_undoStack, SIGNAL(undoTextChanged(QString)), this, SIGNAL(undoTextChanged())); + + m_clipboard = QApplication::clipboard(); +} + +UndoManager::UndoManager(const UndoManager &other) +{ + +} + +UndoManager::~UndoManager() +{ + +} + +bool UndoManager::canUndo() const +{ + return m_undoStack->canUndo(); +} + +bool UndoManager::canRedo() const +{ + return m_undoStack->canRedo(); +} + +void UndoManager::createEditKeyCommand(TreeViewModel *model, int index, const QString &oldName, const QVariant &oldValue, const QVariant &oldMetaData, + const QString &newName, const QVariant &newValue, const QVariant &newMetaData) +{ + //convert TreeViewModel to QVariantMap + TreeViewModel *tmpModel = qvariant_cast(oldMetaData); + QVariantMap oldMDMap; + + foreach(ConfigNode *node, tmpModel->model()){ + oldMDMap.insert(node->getName(), node->getValue()); + } + + m_undoStack->push(new EditKeyCommand(model, index, oldName, oldValue, oldMDMap, newName, newValue, newMetaData.toMap())); +} + +void UndoManager::createDeleteKeyCommand(const QString &type, TreeViewModel *model, ConfigNode *node, int index) +{ + m_undoStack->push(new DeleteKeyCommand(type, model, node, index)); +} + +void UndoManager::createNewKeyCommand(TreeViewModel *model, const QString &name, const QString &value, const QVariantMap &metaData) +{ + m_undoStack->push(new NewKeyCommand(model, name, value, metaData)); +} + +void UndoManager::createCopyKeyCommand(ConfigNode *target) +{ + m_undoStack->push(new CopyKeyCommand(qvariant_cast(m_clipboard->property("node")), target)); +} + +void UndoManager::createCutKeyCommand(ConfigNode *target) +{ + m_undoStack->push(new CutKeyCommand(qvariant_cast(m_clipboard->property("model")), qvariant_cast(m_clipboard->property("node")), target, m_clipboard->property("index").toInt())); +} + +void UndoManager::setClean() +{ + m_undoStack->setClean(); +} + +bool UndoManager::isClean() +{ + return m_undoStack->isClean(); +} + +void UndoManager::undo() +{ + m_undoStack->undo(); +} + +void UndoManager::redo() +{ + m_undoStack->redo(); +} + +QString UndoManager::undoText() const +{ + return m_undoStack->undoText(); +} + +QString UndoManager::clipboardType() const +{ + return m_clipboardType; +} + +void UndoManager::putToClipboard(const QString &type, TreeViewModel *model, ConfigNode *node, int index) +{ + m_clipboardType = type; + + m_clipboard->clear(); + + m_clipboard->setProperty("model", QVariant::fromValue(model)); + m_clipboard->setProperty("node", QVariant::fromValue(new ConfigNode(*node))); + m_clipboard->setProperty("index", QVariant::fromValue(index)); + + emit clipboardTypeChanged(); +} + +QString UndoManager::redoText() const +{ + return m_undoStack->redoText(); +} diff --git a/src/undomanager.hpp b/src/undomanager.hpp new file mode 100644 index 00000000000..6c6dc7d9f0b --- /dev/null +++ b/src/undomanager.hpp @@ -0,0 +1,69 @@ +#ifndef UNDOMANAGER_HPP +#define UNDOMANAGER_HPP + +#include +#include +#include +#include "treeviewmodel.hpp" + +class QUndoStack; + +class UndoManager : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool canUndo READ canUndo() NOTIFY canUndoChanged()) + Q_PROPERTY(bool canRedo READ canRedo() NOTIFY canRedoChanged()) + + Q_PROPERTY(QString redoText READ redoText() NOTIFY redoTextChanged()) + Q_PROPERTY(QString undoText READ undoText() NOTIFY undoTextChanged()) + Q_PROPERTY(QString clipboardType READ clipboardType() NOTIFY clipboardTypeChanged()) + +public: + + explicit UndoManager(QObject *parent = 0); + UndoManager(UndoManager const & other); + ~UndoManager(); + + bool canUndo() const; + bool canRedo() const; + + QString redoText() const; + QString undoText() const; + QString clipboardType() const; + + Q_INVOKABLE void putToClipboard(const QString &type, TreeViewModel *model, ConfigNode *node, int index); + + Q_INVOKABLE void createEditKeyCommand(TreeViewModel *model, int index, const QString &oldName, const QVariant &oldValue, const QVariant &oldMetaData, + const QString &newName, const QVariant &newValue, const QVariant &newMetaData); + + Q_INVOKABLE void createDeleteKeyCommand(const QString &type, TreeViewModel *model, ConfigNode *node, int index); + Q_INVOKABLE void createNewKeyCommand(TreeViewModel *model, const QString &name, const QString &value, const QVariantMap &metaData); + Q_INVOKABLE void createCopyKeyCommand(ConfigNode *target); + Q_INVOKABLE void createCutKeyCommand(ConfigNode *target); + Q_INVOKABLE void setClean(); + Q_INVOKABLE bool isClean(); + +Q_SIGNALS: + + void canUndoChanged(); + void canRedoChanged(); + void redoTextChanged(); + void undoTextChanged(); + void clipboardTypeChanged(); + +public Q_SLOTS: + + void undo(); + void redo(); + +private: + + QUndoStack *m_undoStack; + QClipboard *m_clipboard; + QString m_clipboardType; +}; + +Q_DECLARE_METATYPE(UndoManager) + +#endif // UNDOMANAGER_HPP diff --git a/src/visitor.hpp b/src/visitor.hpp new file mode 100644 index 00000000000..43613f6ee39 --- /dev/null +++ b/src/visitor.hpp @@ -0,0 +1,14 @@ +#ifndef VISITOR_H +#define VISITOR_H + +class ConfigNode; +class TreeViewModel; + +class Visitor { + +public: + virtual void visit(ConfigNode *node) = 0; + virtual void visit(TreeViewModel *model) = 0; +}; + +#endif // VISITOR_H