2015-09-06 20:33:09 +02:00
|
|
|
#include "./entrymodel.h"
|
2015-04-22 19:30:09 +02:00
|
|
|
|
2018-12-03 00:29:54 +01:00
|
|
|
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
|
2018-05-20 01:48:31 +02:00
|
|
|
#include "../gui/undocommands.h"
|
2015-04-22 19:30:09 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <passwordfile/io/entry.h>
|
2018-06-10 23:01:17 +02:00
|
|
|
#include <passwordfile/io/parsingexception.h>
|
2015-04-22 19:30:09 +02:00
|
|
|
|
2016-06-14 22:57:39 +02:00
|
|
|
#include <c++utilities/io/catchiofailure.h>
|
|
|
|
|
2015-04-22 19:30:09 +02:00
|
|
|
#include <QBuffer>
|
|
|
|
#include <QDebug>
|
2018-03-14 00:17:14 +01:00
|
|
|
#include <QIcon>
|
|
|
|
#include <QMimeData>
|
2015-04-22 19:30:09 +02:00
|
|
|
|
2018-06-10 23:01:17 +02:00
|
|
|
#include <iostream>
|
2018-06-10 22:51:43 +02:00
|
|
|
#include <memory>
|
2015-04-22 19:30:09 +02:00
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace Io;
|
|
|
|
|
|
|
|
namespace QtGui {
|
|
|
|
/*!
|
|
|
|
* \class EntryModel
|
|
|
|
* \brief The EntryModel class provides a model interface for a hierarchy of Entry instances.
|
|
|
|
*
|
2017-04-27 22:16:26 +02:00
|
|
|
* When building the Qt Widgets GUI, the model also supports Qt Widgets' undo framework.
|
2015-04-22 19:30:09 +02:00
|
|
|
* \sa http://qt-project.org/doc/qt-5/qabstractitemmodel.html
|
|
|
|
* \sa http://qt-project.org/doc/qt-5/qundo.html
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new entry model.
|
|
|
|
*/
|
2018-03-14 00:17:14 +01:00
|
|
|
EntryModel::EntryModel(QObject *parent)
|
|
|
|
: QAbstractItemModel(parent)
|
|
|
|
, m_rootEntry(nullptr)
|
|
|
|
, m_insertType(EntryType::Node)
|
|
|
|
{
|
|
|
|
}
|
2015-04-22 19:30:09 +02:00
|
|
|
|
2018-12-03 00:29:54 +01:00
|
|
|
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
|
2015-04-22 19:30:09 +02:00
|
|
|
/*!
|
|
|
|
* \brief Constructs a new entry model with the specified \a undoStack.
|
|
|
|
*
|
2017-04-27 22:16:26 +02:00
|
|
|
* This constructor is only available when PASSWORD_MANAGER_GUI_QTWIDGETS is defined.
|
2015-04-22 19:30:09 +02:00
|
|
|
*/
|
2018-03-14 00:17:14 +01:00
|
|
|
EntryModel::EntryModel(QUndoStack *undoStack, QObject *parent)
|
|
|
|
: QAbstractItemModel(parent)
|
|
|
|
, StackSupport(undoStack)
|
|
|
|
, m_rootEntry(nullptr)
|
|
|
|
, m_insertType(EntryType::Node)
|
|
|
|
{
|
|
|
|
}
|
2015-04-22 19:30:09 +02:00
|
|
|
#endif
|
|
|
|
|
2018-04-22 21:00:39 +02:00
|
|
|
QHash<int, QByteArray> EntryModel::roleNames() const
|
|
|
|
{
|
|
|
|
static const QHash<int, QByteArray> roles{
|
|
|
|
{ Qt::DisplayRole, "name" },
|
|
|
|
{ EntryModelRoles::DefaultExpandedRole, "isDefaultExpanded" },
|
|
|
|
};
|
|
|
|
return roles;
|
|
|
|
}
|
|
|
|
|
2015-04-22 19:30:09 +02:00
|
|
|
/*!
|
|
|
|
* \brief Returns the Entry for the specified \a index of nullptr if the \a index is invalid.
|
|
|
|
*
|
|
|
|
* Modifications should be done using the common methods defined by the QAbstractItemModel class.
|
|
|
|
* This method is intended to be used only internally to implement the QAbstractItemModel interface
|
|
|
|
* or when implementing a QUndoCommand to support Qt's undo framework.
|
|
|
|
*/
|
|
|
|
Entry *EntryModel::entry(const QModelIndex &index)
|
|
|
|
{
|
2018-03-14 00:15:12 +01:00
|
|
|
return index.isValid() ? static_cast<Entry *>(index.internalPointer()) : nullptr;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Removes \a count number of entries at the specified \a row within the specified \a parent.
|
|
|
|
* \returns Returns the removed entries (rather then deleting them).
|
|
|
|
*
|
|
|
|
* Modifications should be done using the common methods defined by the QAbstractItemModel class.
|
|
|
|
* This method is intended to be used only internally to implement the QAbstractItemModel interface
|
|
|
|
* or when implementing a QUndoCommand to support Qt's undo framework.
|
|
|
|
*/
|
|
|
|
QList<Entry *> EntryModel::takeEntries(int row, int count, const QModelIndex &parent)
|
|
|
|
{
|
2018-03-14 00:15:12 +01:00
|
|
|
Entry *const parentEntry = entry(parent);
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!parentEntry || parentEntry->type() != EntryType::Node) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return QList<Entry *>();
|
|
|
|
}
|
2015-04-22 19:30:09 +02:00
|
|
|
QList<Entry *> res;
|
2018-03-14 00:15:12 +01:00
|
|
|
NodeEntry *const parentNodeEntry = static_cast<NodeEntry *>(parentEntry);
|
|
|
|
int lastIndex = row + count - 1;
|
|
|
|
const vector<Entry *> &children = parentNodeEntry->children();
|
2018-03-14 00:17:14 +01:00
|
|
|
if (lastIndex < 0 || static_cast<size_t>(lastIndex) >= children.size()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
lastIndex = children.size() - 1;
|
|
|
|
}
|
|
|
|
beginRemoveRows(parent, row, lastIndex);
|
2018-03-14 00:17:14 +01:00
|
|
|
for (int index = lastIndex; index >= row; --index) {
|
2018-12-08 19:55:41 +01:00
|
|
|
Entry *const child = children[static_cast<size_t>(index)];
|
2018-03-14 00:15:12 +01:00
|
|
|
child->setParent(nullptr);
|
|
|
|
res << child;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
endRemoveRows();
|
2015-04-22 19:30:09 +02:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Inserts the specified \a entries before the specified \a row within the specified \a parent.
|
|
|
|
*
|
|
|
|
* Modifications should be done using the common methods defined by the QAbstractItemModel class.
|
|
|
|
* This method is intended to be used only internally to implement the QAbstractItemModel interface
|
|
|
|
* or when implementing a QUndoCommand to support Qt's undo framework.
|
|
|
|
*/
|
|
|
|
bool EntryModel::insertEntries(int row, const QModelIndex &parent, const QList<Entry *> &entries)
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
if (entries.isEmpty()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return true;
|
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
Entry *const parentEntry = entry(parent);
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!parentEntry || parentEntry->type() != EntryType::Node) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return false;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
NodeEntry *const parentNodeEntry = static_cast<NodeEntry *>(parentEntry);
|
|
|
|
const vector<Entry *> &children = parentNodeEntry->children();
|
2018-03-14 00:17:14 +01:00
|
|
|
if (row < 0 || static_cast<size_t>(row) > children.size()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
row = children.size();
|
|
|
|
}
|
|
|
|
beginInsertRows(parent, row, row + entries.size() - 1);
|
2018-03-14 00:17:14 +01:00
|
|
|
for (Entry *const entry : entries) {
|
2018-03-14 00:15:12 +01:00
|
|
|
entry->setParent(parentNodeEntry, row);
|
|
|
|
++row;
|
|
|
|
}
|
|
|
|
endInsertRows();
|
|
|
|
return true;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QModelIndex EntryModel::index(int row, int column, const QModelIndex &parent) const
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!parent.isValid()) {
|
|
|
|
if (m_rootEntry && row == 0) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return createIndex(row, column, m_rootEntry);
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
return QModelIndex();
|
|
|
|
}
|
|
|
|
const auto *const parentEntry = static_cast<const Entry *>(parent.internalPointer());
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!parentEntry) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return QModelIndex();
|
|
|
|
}
|
2018-03-14 00:17:14 +01:00
|
|
|
switch (parentEntry->type()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
case EntryType::Node: {
|
|
|
|
const std::vector<Entry *> &children = static_cast<const NodeEntry *>(parentEntry)->children();
|
2018-03-14 00:17:14 +01:00
|
|
|
if (row >= 0 && static_cast<size_t>(row) < children.size()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return createIndex(row, column, children[static_cast<size_t>(row)]);
|
|
|
|
}
|
|
|
|
break;
|
2018-03-14 00:17:14 +01:00
|
|
|
}
|
|
|
|
case EntryType::Account:;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
return QModelIndex();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the index of the specified \a entry.
|
|
|
|
* \remarks It is up to the caller to ensure that \a entry is a child of the root element.
|
|
|
|
*/
|
|
|
|
QModelIndex EntryModel::index(Entry *entry) const
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
if (entry->parent()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return createIndex(entry->index(), 0, entry);
|
|
|
|
} else {
|
|
|
|
return createIndex(0, 0, m_rootEntry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QModelIndex EntryModel::parent(const QModelIndex &child) const
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!child.isValid()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return QModelIndex();
|
|
|
|
}
|
|
|
|
const auto *const entry = static_cast<Entry *>(child.internalPointer());
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!entry) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return QModelIndex();
|
|
|
|
}
|
|
|
|
NodeEntry *const parent = entry->parent();
|
2018-03-14 00:17:14 +01:00
|
|
|
if (parent && (child.row() >= 0 && static_cast<size_t>(child.row()) < parent->children().size())) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return createIndex(parent->index() > 0 ? parent->index() : 0, 0, parent);
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
return QModelIndex();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EntryModel::hasChildren(const QModelIndex &parent) const
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!parent.isValid()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return true;
|
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
const auto *const entry = static_cast<Entry *>(parent.internalPointer());
|
|
|
|
return entry && entry->type() == EntryType::Node && !static_cast<const NodeEntry *>(entry)->children().empty();
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns an indication whether the specified \a parent might have children.
|
|
|
|
* \remarks Only node entries might have childs.
|
|
|
|
*/
|
|
|
|
bool EntryModel::isNode(const QModelIndex &parent) const
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!parent.isValid()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return false;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
const auto *const entry = static_cast<const Entry *>(parent.internalPointer());
|
|
|
|
return entry && entry->type() == EntryType::Node;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QVariant EntryModel::data(const QModelIndex &index, int role) const
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!index.isValid()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
const auto *const entry = static_cast<const Entry *>(index.internalPointer());
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!entry) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return QVariant();
|
|
|
|
}
|
2018-03-14 00:17:14 +01:00
|
|
|
switch (role) {
|
2018-03-14 00:15:12 +01:00
|
|
|
case Qt::DisplayRole:
|
|
|
|
case Qt::EditRole:
|
2018-03-14 00:17:14 +01:00
|
|
|
switch (index.column()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
case 0:
|
|
|
|
return QString::fromStdString(entry->label());
|
2018-03-14 00:17:14 +01:00
|
|
|
default:;
|
2018-03-14 00:15:12 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Qt::DecorationRole:
|
2018-03-14 00:17:14 +01:00
|
|
|
if (index.column() == 0 && entry->type() == EntryType::Node) {
|
2018-03-14 00:15:12 +01:00
|
|
|
static const QVariant folderIcon = QIcon::fromTheme(QStringLiteral("folder"));
|
|
|
|
return folderIcon;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SerializedRole: {
|
2018-03-14 00:17:14 +01:00
|
|
|
stringstream ss(stringstream::in | stringstream::out | stringstream::binary);
|
|
|
|
ss.exceptions(std::stringstream::failbit | std::stringstream::badbit);
|
|
|
|
try {
|
|
|
|
entry->make(ss);
|
2018-12-08 19:55:41 +01:00
|
|
|
// FIXME: make conversion to QByteArray more efficient
|
2018-03-14 00:17:14 +01:00
|
|
|
const auto str(ss.str());
|
|
|
|
return QByteArray(str.data(), str.size());
|
|
|
|
} catch (...) {
|
|
|
|
IoUtilities::catchIoFailure();
|
|
|
|
return false;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2018-12-08 19:55:41 +01:00
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
case DefaultExpandedRole:
|
|
|
|
return entry->type() == EntryType::Node && static_cast<const NodeEntry *>(entry)->isExpandedByDefault();
|
2018-03-14 00:17:14 +01:00
|
|
|
default:;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
QMap<int, QVariant> EntryModel::itemData(const QModelIndex &index) const
|
|
|
|
{
|
2018-03-14 00:15:12 +01:00
|
|
|
return QMap<int, QVariant>{
|
2018-03-14 00:17:14 +01:00
|
|
|
{ Qt::DisplayRole, data(index, Qt::DisplayRole) },
|
|
|
|
{ SerializedRole, data(index, SerializedRole) },
|
2018-03-14 00:15:12 +01:00
|
|
|
};
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool EntryModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
|
|
{
|
2018-12-03 00:29:54 +01:00
|
|
|
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
|
2018-03-14 00:17:14 +01:00
|
|
|
if (undoStack()) {
|
2018-06-10 22:51:43 +02:00
|
|
|
return push(make_unique<EntryModelSetValueCommand>(this, index, value, role));
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
#endif
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!index.isValid()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
auto *const entry = static_cast<Entry *>(index.internalPointer());
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!entry) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return false;
|
|
|
|
}
|
2018-03-14 00:17:14 +01:00
|
|
|
switch (role) {
|
2018-03-14 00:15:12 +01:00
|
|
|
case Qt::DisplayRole:
|
|
|
|
case Qt::EditRole:
|
2018-03-14 00:17:14 +01:00
|
|
|
switch (index.column()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
case 0:
|
|
|
|
entry->setLabel(value.toString().toStdString());
|
2018-06-10 22:39:57 +02:00
|
|
|
emit dataChanged(index, index, QVector<int>({ Qt::DisplayRole, Qt::EditRole }));
|
2018-03-14 00:15:12 +01:00
|
|
|
return true;
|
2018-03-14 00:17:14 +01:00
|
|
|
default:;
|
2018-03-14 00:15:12 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SerializedRole: {
|
2018-06-10 23:01:17 +02:00
|
|
|
NodeEntry *const parent = entry->parent();
|
|
|
|
const QModelIndex parentIndex = index.parent();
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!parent || !parentIndex.isValid()) {
|
2018-06-10 23:01:17 +02:00
|
|
|
return false;
|
2018-03-14 00:15:12 +01:00
|
|
|
}
|
2018-06-10 23:01:17 +02:00
|
|
|
QByteArray array(value.toByteArray());
|
2018-03-14 00:17:14 +01:00
|
|
|
if (array.isEmpty()) {
|
2018-06-10 23:01:17 +02:00
|
|
|
return false;
|
2018-03-14 00:17:14 +01:00
|
|
|
}
|
|
|
|
try {
|
2018-06-10 23:01:17 +02:00
|
|
|
stringstream ss(stringstream::in | stringstream::out | stringstream::binary);
|
|
|
|
ss.exceptions(std::stringstream::failbit | std::stringstream::badbit);
|
|
|
|
ss.rdbuf()->pubsetbuf(array.data(), array.size());
|
|
|
|
|
|
|
|
Entry *const newEntry = Entry::parse(ss);
|
|
|
|
const int row = entry->index();
|
2018-03-14 00:17:14 +01:00
|
|
|
beginRemoveRows(parentIndex, row, row);
|
|
|
|
delete entry;
|
|
|
|
endRemoveRows();
|
|
|
|
beginInsertRows(parentIndex, row, row);
|
|
|
|
newEntry->setParent(parent, row);
|
|
|
|
endInsertRows();
|
|
|
|
return true;
|
2018-06-10 23:01:17 +02:00
|
|
|
} catch (const Io::ParsingException &parsingError) {
|
|
|
|
cerr << "EntryModel::setData: parsing exception: " << parsingError.what() << endl;
|
|
|
|
return false;
|
2018-03-14 00:17:14 +01:00
|
|
|
} catch (...) {
|
2018-06-10 23:01:17 +02:00
|
|
|
const char *const errorMessage(IoUtilities::catchIoFailure());
|
|
|
|
cerr << "EntryModel::setData: IO exception: " << errorMessage << endl;
|
|
|
|
return false;
|
2018-03-14 00:17:14 +01:00
|
|
|
}
|
2018-06-10 23:01:17 +02:00
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
case DefaultExpandedRole:
|
2018-03-14 00:17:14 +01:00
|
|
|
switch (entry->type()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
case EntryType::Account:
|
|
|
|
return false;
|
|
|
|
case EntryType::Node:
|
|
|
|
static_cast<NodeEntry *>(entry)->setExpandedByDefault(value.toBool());
|
|
|
|
emit dataChanged(index, index, QVector<int>() << role);
|
|
|
|
return true;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
break;
|
2018-03-14 00:17:14 +01:00
|
|
|
default:;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EntryModel::setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles)
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
for (QMap<int, QVariant>::ConstIterator it = roles.constBegin(); it != roles.constEnd(); ++it) {
|
2015-04-22 19:30:09 +02:00
|
|
|
setData(index, it.value(), it.key());
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::ItemFlags EntryModel::flags(const QModelIndex &index) const
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
return isNode(index) ? QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled
|
|
|
|
: QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
switch (orientation) {
|
2015-04-22 19:30:09 +02:00
|
|
|
case Qt::Horizontal:
|
2018-03-14 00:17:14 +01:00
|
|
|
switch (role) {
|
2015-04-22 19:30:09 +02:00
|
|
|
case Qt::DisplayRole:
|
2018-03-14 00:17:14 +01:00
|
|
|
switch (section) {
|
2015-04-22 19:30:09 +02:00
|
|
|
case 0:
|
|
|
|
return tr("Name");
|
2018-03-14 00:17:14 +01:00
|
|
|
default:;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
break;
|
2018-03-14 00:17:14 +01:00
|
|
|
default:;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
break;
|
2018-03-14 00:17:14 +01:00
|
|
|
default:;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
int EntryModel::rowCount(const QModelIndex &parent) const
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
if (parent.isValid()) {
|
|
|
|
if (Entry *parentEntry = static_cast<Entry *>(parent.internalPointer())) {
|
|
|
|
switch (parentEntry->type()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
case EntryType::Node:
|
|
|
|
return static_cast<NodeEntry *>(parentEntry)->children().size();
|
2018-03-14 00:17:14 +01:00
|
|
|
case EntryType::Account:;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
2018-03-14 00:17:14 +01:00
|
|
|
} else if (m_rootEntry) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int EntryModel::columnCount(const QModelIndex &) const
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EntryModel::insertRows(int row, int count, const QModelIndex &parent)
|
|
|
|
{
|
2018-12-03 00:29:54 +01:00
|
|
|
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
|
2018-03-14 00:17:14 +01:00
|
|
|
if (undoStack()) {
|
2018-06-10 22:51:43 +02:00
|
|
|
return push(make_unique<EntryModelInsertRowsCommand>(this, row, count, parent));
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
#endif
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!parent.isValid()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
auto *const parentEntry = static_cast<Entry *>(parent.internalPointer());
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!parentEntry || parentEntry->type() != EntryType::Node) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
beginInsertRows(parent, row, row + count - 1);
|
2018-03-14 00:17:14 +01:00
|
|
|
for (int end = row + count; row < end; ++row) {
|
2018-03-14 00:15:12 +01:00
|
|
|
Entry *newEntry;
|
2018-03-14 00:17:14 +01:00
|
|
|
switch (m_insertType) {
|
2018-03-14 00:15:12 +01:00
|
|
|
case EntryType::Node:
|
|
|
|
newEntry = new NodeEntry;
|
|
|
|
break;
|
|
|
|
case EntryType::Account:
|
|
|
|
newEntry = new AccountEntry;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return false; // should never be reached, just to suppress compiler warning
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
newEntry->setParent(static_cast<NodeEntry *>(parentEntry), row);
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
endInsertRows();
|
|
|
|
return true;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool EntryModel::removeRows(int row, int count, const QModelIndex &parent)
|
|
|
|
{
|
2018-12-03 00:29:54 +01:00
|
|
|
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
|
2018-03-14 00:17:14 +01:00
|
|
|
if (undoStack()) {
|
2018-06-10 22:51:43 +02:00
|
|
|
return push(make_unique<EntryModelRemoveRowsCommand>(this, row, count, parent));
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
#endif
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!parent.isValid() || count <= 0) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return false;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
auto *const parentEntry = static_cast<Entry *>(parent.internalPointer());
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!parentEntry || parentEntry->type() != EntryType::Node) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
beginRemoveRows(parent, row, row + count - 1);
|
|
|
|
static_cast<NodeEntry *>(parentEntry)->deleteChildren(row, row + count);
|
|
|
|
endRemoveRows();
|
|
|
|
return true;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool EntryModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
|
|
|
|
{
|
2018-12-03 00:29:54 +01:00
|
|
|
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
|
2018-03-14 00:17:14 +01:00
|
|
|
if (undoStack()) {
|
2018-06-10 22:51:43 +02:00
|
|
|
return push(make_unique<EntryModelMoveRowsCommand>(this, sourceParent, sourceRow, count, destinationParent, destinationChild));
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
#endif
|
2018-06-12 22:20:43 +02:00
|
|
|
// check validation of specified arguments: source and destination parent entries need to be node entries
|
|
|
|
if (!sourceParent.isValid() || !destinationParent.isValid() || sourceRow < 0 || count <= 0 || entry(sourceParent)->type() != EntryType::Node //
|
|
|
|
|| entry(destinationParent)->type() != EntryType::Node) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// determine the source parent entry and dest parent entry as node entries
|
|
|
|
auto *const srcParentEntry = static_cast<NodeEntry *>(sourceParent.internalPointer());
|
|
|
|
auto *const destParentEntry = static_cast<NodeEntry *>(destinationParent.internalPointer());
|
2018-09-08 19:59:15 +02:00
|
|
|
#if DEBUG_BUILD
|
|
|
|
cout << "destinationChild: " << destinationChild << endl;
|
|
|
|
#endif
|
2018-03-14 00:15:12 +01:00
|
|
|
// source rows must be within the valid range
|
2018-03-14 00:17:14 +01:00
|
|
|
if (static_cast<size_t>(sourceRow + count) > srcParentEntry->children().size()
|
|
|
|
// if source and destination parent are the same the destination child mustn't be in the source range
|
|
|
|
|| !(srcParentEntry != destParentEntry || (destinationChild < sourceRow || (sourceRow + count) < destinationChild))) {
|
2018-03-14 00:15:12 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// do not move a row to one of its own children! -> check before
|
2018-03-14 00:17:14 +01:00
|
|
|
for (int index = 0; index < count; ++index) {
|
2018-06-12 22:20:43 +02:00
|
|
|
Entry *const toMove = srcParentEntry->children()[static_cast<size_t>(sourceRow + index)];
|
|
|
|
if (toMove->type() != EntryType::Node) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (toMove == destParentEntry || destParentEntry->isIndirectChildOf(static_cast<NodeEntry *>(toMove))) {
|
|
|
|
return false;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
// actually perform the move operation
|
|
|
|
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild);
|
2018-03-14 00:17:14 +01:00
|
|
|
for (int index = 0; index < count; ++index) {
|
2018-03-14 00:15:12 +01:00
|
|
|
Entry *toMove = srcParentEntry->children()[static_cast<size_t>(sourceRow + index)];
|
2018-03-14 00:17:14 +01:00
|
|
|
if (srcParentEntry == destParentEntry && sourceRow < destinationChild) {
|
2018-03-14 00:15:12 +01:00
|
|
|
toMove->setParent(destParentEntry, destinationChild + index - 1);
|
|
|
|
} else {
|
|
|
|
toMove->setParent(destParentEntry, destinationChild + index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
endMoveRows();
|
|
|
|
return true;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QStringList EntryModel::mimeTypes() const
|
|
|
|
{
|
2018-06-11 23:19:42 +02:00
|
|
|
return QStringList({ QStringLiteral("application/x-entrymodelpathlistmove"), QStringLiteral("text/plain") });
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QMimeData *EntryModel::mimeData(const QModelIndexList &indexes) const
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
if (indexes.count() <= 0) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return nullptr;
|
|
|
|
}
|
2018-06-11 23:19:42 +02:00
|
|
|
const QStringList types = mimeTypes();
|
2018-03-14 00:17:14 +01:00
|
|
|
if (types.isEmpty()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return nullptr;
|
|
|
|
}
|
2018-06-11 23:19:42 +02:00
|
|
|
QMimeData *const data = new QMimeData();
|
2015-04-22 19:30:09 +02:00
|
|
|
QStringList plainTextParts;
|
2018-06-11 23:19:42 +02:00
|
|
|
plainTextParts.reserve(indexes.size());
|
2015-04-22 19:30:09 +02:00
|
|
|
QByteArray encoded;
|
|
|
|
QDataStream dataStream(&encoded, QIODevice::WriteOnly);
|
2018-03-14 00:17:14 +01:00
|
|
|
for (const QModelIndex &index : indexes) {
|
|
|
|
if (!index.isValid()) {
|
2018-03-14 00:15:12 +01:00
|
|
|
continue;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
const auto *const entry = static_cast<const Entry *>(index.internalPointer());
|
|
|
|
const auto path(entry->path());
|
|
|
|
dataStream << static_cast<quint32>(path.size());
|
2018-03-14 00:17:14 +01:00
|
|
|
for (const string &part : path) {
|
2018-03-14 00:15:12 +01:00
|
|
|
dataStream << QString::fromStdString(part);
|
|
|
|
}
|
|
|
|
plainTextParts << QString::fromStdString(entry->label());
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
data->setData(types.at(0), encoded);
|
|
|
|
data->setText(plainTextParts.join(QStringLiteral(", ")));
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EntryModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
|
|
|
{
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!m_rootEntry || !data || action != Qt::MoveAction) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return false;
|
|
|
|
}
|
2018-06-11 23:19:42 +02:00
|
|
|
const QStringList types = mimeTypes();
|
2018-03-14 00:17:14 +01:00
|
|
|
if (types.isEmpty()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return false;
|
|
|
|
}
|
2018-06-11 23:19:42 +02:00
|
|
|
const QString format = types.at(0);
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!data->hasFormat(format)) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return false;
|
|
|
|
}
|
2018-03-14 00:17:14 +01:00
|
|
|
if (row > rowCount(parent) || row < 0) {
|
2015-04-22 19:30:09 +02:00
|
|
|
row = rowCount(parent);
|
|
|
|
}
|
2018-03-14 00:17:14 +01:00
|
|
|
if (column > columnCount(parent) || column < 0) {
|
2015-04-22 19:30:09 +02:00
|
|
|
column = 0;
|
|
|
|
}
|
|
|
|
// decode and insert
|
2018-03-14 00:15:12 +01:00
|
|
|
QByteArray encoded(data->data(format));
|
2015-04-22 19:30:09 +02:00
|
|
|
QDataStream stream(&encoded, QIODevice::ReadOnly);
|
|
|
|
int moved = 0;
|
2018-03-14 00:17:14 +01:00
|
|
|
while (!stream.atEnd()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
quint32 size;
|
|
|
|
stream >> size;
|
|
|
|
list<string> path;
|
2018-03-14 00:17:14 +01:00
|
|
|
for (quint32 i = 0; i < size; ++i) {
|
2015-04-22 19:30:09 +02:00
|
|
|
QString part;
|
|
|
|
stream >> part;
|
|
|
|
path.push_back(part.toStdString());
|
|
|
|
}
|
2018-03-14 00:15:12 +01:00
|
|
|
auto *const entry = m_rootEntry->entryByPath(path, true);
|
2018-03-14 00:17:14 +01:00
|
|
|
if (!entry) {
|
2018-03-14 00:15:12 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto *const srcParentEntry = entry->parent();
|
2018-03-14 00:17:14 +01:00
|
|
|
if (srcParentEntry && moveRows(index(srcParentEntry), entry->index(), 1, parent, row)) {
|
2018-03-14 00:15:12 +01:00
|
|
|
++moved;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::DropActions EntryModel::supportedDropActions() const
|
|
|
|
{
|
|
|
|
return Qt::MoveAction;
|
|
|
|
}
|
|
|
|
|
2018-05-20 01:48:31 +02:00
|
|
|
/*!
|
|
|
|
* \brief Sets the insert type to node.
|
|
|
|
* \remarks Intended to prevent the offort of exposing EntryType to QML.
|
|
|
|
*/
|
|
|
|
void EntryModel::setInsertTypeToNode()
|
|
|
|
{
|
|
|
|
setInsertType(Io::EntryType::Node);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Sets the insert type to account.
|
|
|
|
* \remarks Intended to prevent the offort of exposing EntryType to QML.
|
|
|
|
*/
|
|
|
|
void EntryModel::setInsertTypeToAccount()
|
|
|
|
{
|
|
|
|
setInsertType(Io::EntryType::Account);
|
|
|
|
}
|
|
|
|
|
2018-03-14 00:17:14 +01:00
|
|
|
} // namespace QtGui
|