mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-08 10:55:30 -05:00
447 lines
12 KiB
C++
447 lines
12 KiB
C++
/*
|
|
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 or (at your option)
|
|
* version 3 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "GroupModel.h"
|
|
|
|
#include <QMimeData>
|
|
|
|
#include "core/Group.h"
|
|
#include "core/Metadata.h"
|
|
#include "keeshare/KeeShare.h"
|
|
|
|
GroupModel::GroupModel(Database* db, QObject* parent)
|
|
: QAbstractItemModel(parent)
|
|
, m_db(nullptr)
|
|
{
|
|
changeDatabase(db);
|
|
}
|
|
|
|
void GroupModel::changeDatabase(Database* newDb)
|
|
{
|
|
beginResetModel();
|
|
|
|
m_db = newDb;
|
|
|
|
// clang-format off
|
|
connect(m_db, SIGNAL(groupDataChanged(Group*)), SLOT(groupDataChanged(Group*)));
|
|
connect(m_db, SIGNAL(groupAboutToAdd(Group*,int)), SLOT(groupAboutToAdd(Group*,int)));
|
|
connect(m_db, SIGNAL(groupAdded()), SLOT(groupAdded()));
|
|
connect(m_db, SIGNAL(groupAboutToRemove(Group*)), SLOT(groupAboutToRemove(Group*)));
|
|
connect(m_db, SIGNAL(groupRemoved()), SLOT(groupRemoved()));
|
|
connect(m_db, SIGNAL(groupAboutToMove(Group*,Group*,int)), SLOT(groupAboutToMove(Group*,Group*,int)));
|
|
connect(m_db, SIGNAL(groupMoved()), SLOT(groupMoved()));
|
|
// clang-format on
|
|
|
|
endResetModel();
|
|
}
|
|
|
|
int GroupModel::rowCount(const QModelIndex& parent) const
|
|
{
|
|
if (!parent.isValid()) {
|
|
// we have exactly 1 root item
|
|
return 1;
|
|
} else {
|
|
const Group* group = groupFromIndex(parent);
|
|
return group->children().size();
|
|
}
|
|
}
|
|
|
|
int GroupModel::columnCount(const QModelIndex& parent) const
|
|
{
|
|
Q_UNUSED(parent);
|
|
|
|
return 1;
|
|
}
|
|
|
|
QModelIndex GroupModel::index(int row, int column, const QModelIndex& parent) const
|
|
{
|
|
if (!hasIndex(row, column, parent)) {
|
|
return QModelIndex();
|
|
}
|
|
|
|
Group* group;
|
|
|
|
if (!parent.isValid()) {
|
|
group = m_db->rootGroup();
|
|
} else {
|
|
group = groupFromIndex(parent)->children().at(row);
|
|
}
|
|
|
|
return createIndex(row, column, group);
|
|
}
|
|
|
|
QModelIndex GroupModel::parent(const QModelIndex& index) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return QModelIndex();
|
|
}
|
|
|
|
return parent(groupFromIndex(index));
|
|
}
|
|
|
|
QModelIndex GroupModel::parent(Group* group) const
|
|
{
|
|
Group* parentGroup = group->parentGroup();
|
|
|
|
if (!parentGroup) {
|
|
// index is already the root group
|
|
return QModelIndex();
|
|
} else {
|
|
const Group* grandParentGroup = parentGroup->parentGroup();
|
|
if (!grandParentGroup) {
|
|
// parent is the root group
|
|
return createIndex(0, 0, parentGroup);
|
|
} else {
|
|
return createIndex(grandParentGroup->children().indexOf(parentGroup), 0, parentGroup);
|
|
}
|
|
}
|
|
}
|
|
|
|
QVariant GroupModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return QVariant();
|
|
}
|
|
|
|
Group* group = groupFromIndex(index);
|
|
|
|
if (role == Qt::DisplayRole) {
|
|
QString nameTemplate = "%1";
|
|
#if defined(WITH_XC_KEESHARE)
|
|
nameTemplate = KeeShare::indicatorSuffix(group, nameTemplate);
|
|
#endif
|
|
return nameTemplate.arg(group->name());
|
|
} else if (role == Qt::DecorationRole) {
|
|
return group->iconPixmap();
|
|
} else if (role == Qt::FontRole) {
|
|
QFont font;
|
|
if (group->isExpired()) {
|
|
font.setStrikeOut(true);
|
|
}
|
|
return font;
|
|
} else if (role == Qt::ToolTipRole) {
|
|
QString tooltip;
|
|
if (!group->parentGroup()) {
|
|
// only show a tooltip for the root group
|
|
tooltip = m_db->filePath();
|
|
}
|
|
return tooltip;
|
|
} else {
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
QVariant GroupModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
Q_UNUSED(section);
|
|
Q_UNUSED(orientation);
|
|
Q_UNUSED(role);
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
QModelIndex GroupModel::index(Group* group) const
|
|
{
|
|
int row;
|
|
|
|
if (!group->parentGroup()) {
|
|
row = 0;
|
|
} else {
|
|
row = group->parentGroup()->children().indexOf(group);
|
|
}
|
|
|
|
return createIndex(row, 0, group);
|
|
}
|
|
|
|
Group* GroupModel::groupFromIndex(const QModelIndex& index) const
|
|
{
|
|
Q_ASSERT(index.internalPointer());
|
|
|
|
return static_cast<Group*>(index.internalPointer());
|
|
}
|
|
|
|
Qt::DropActions GroupModel::supportedDropActions() const
|
|
{
|
|
return Qt::MoveAction | Qt::CopyAction;
|
|
}
|
|
|
|
Qt::ItemFlags GroupModel::flags(const QModelIndex& modelIndex) const
|
|
{
|
|
if (!modelIndex.isValid()) {
|
|
return Qt::NoItemFlags;
|
|
} else if (modelIndex == index(0, 0)) {
|
|
return QAbstractItemModel::flags(modelIndex) | Qt::ItemIsDropEnabled;
|
|
} else {
|
|
return QAbstractItemModel::flags(modelIndex) | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled;
|
|
}
|
|
}
|
|
|
|
bool GroupModel::dropMimeData(const QMimeData* data,
|
|
Qt::DropAction action,
|
|
int row,
|
|
int column,
|
|
const QModelIndex& parent)
|
|
{
|
|
Q_UNUSED(column);
|
|
|
|
if (action == Qt::IgnoreAction) {
|
|
return true;
|
|
}
|
|
|
|
if (!data || (action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
// check if the format is supported
|
|
QStringList types = mimeTypes();
|
|
Q_ASSERT(types.size() == 2);
|
|
bool isGroup = data->hasFormat(types.at(0));
|
|
bool isEntry = data->hasFormat(types.at(1));
|
|
if (!isGroup && !isEntry) {
|
|
return false;
|
|
}
|
|
|
|
if (row > rowCount(parent)) {
|
|
row = rowCount(parent);
|
|
}
|
|
|
|
// decode and insert
|
|
QByteArray encoded = data->data(isGroup ? types.at(0) : types.at(1));
|
|
QDataStream stream(&encoded, QIODevice::ReadOnly);
|
|
|
|
Group* parentGroup = groupFromIndex(parent);
|
|
|
|
if (isGroup) {
|
|
QUuid dbUuid;
|
|
QUuid groupUuid;
|
|
stream >> dbUuid >> groupUuid;
|
|
|
|
Database* db = Database::databaseByUuid(dbUuid);
|
|
if (!db) {
|
|
return false;
|
|
}
|
|
|
|
Group* dragGroup = db->rootGroup()->findGroupByUuid(groupUuid);
|
|
if (!dragGroup || !db->rootGroup()->findGroupByUuid(dragGroup->uuid()) || dragGroup == db->rootGroup()) {
|
|
return false;
|
|
}
|
|
|
|
if (dragGroup == parentGroup || dragGroup->findGroupByUuid(parentGroup->uuid())) {
|
|
return false;
|
|
}
|
|
|
|
if (parentGroup == dragGroup->parent() && row > parentGroup->children().indexOf(dragGroup)) {
|
|
row--;
|
|
}
|
|
|
|
Database* sourceDb = dragGroup->database();
|
|
Database* targetDb = parentGroup->database();
|
|
|
|
Group* group = dragGroup;
|
|
|
|
if (sourceDb != targetDb) {
|
|
QSet<QUuid> customIcons = group->customIconsRecursive();
|
|
targetDb->metadata()->copyCustomIcons(customIcons, sourceDb->metadata());
|
|
|
|
// Always clone the group across db's to reset UUIDs
|
|
group = dragGroup->clone(Entry::CloneDefault | Entry::CloneIncludeHistory);
|
|
if (action == Qt::MoveAction) {
|
|
// Remove the original group from the sourceDb
|
|
delete dragGroup;
|
|
}
|
|
} else if (action == Qt::CopyAction) {
|
|
group = dragGroup->clone(Entry::CloneCopy);
|
|
}
|
|
|
|
group->setParent(parentGroup, row);
|
|
} else {
|
|
if (row != -1) {
|
|
return false;
|
|
}
|
|
|
|
while (!stream.atEnd()) {
|
|
QUuid dbUuid;
|
|
QUuid entryUuid;
|
|
stream >> dbUuid >> entryUuid;
|
|
|
|
Database* db = Database::databaseByUuid(dbUuid);
|
|
if (!db) {
|
|
continue;
|
|
}
|
|
|
|
Entry* dragEntry = db->rootGroup()->findEntryByUuid(entryUuid);
|
|
if (!dragEntry || !db->rootGroup()->findEntryByUuid(dragEntry->uuid())) {
|
|
continue;
|
|
}
|
|
|
|
Database* sourceDb = dragEntry->group()->database();
|
|
Database* targetDb = parentGroup->database();
|
|
|
|
Entry* entry = dragEntry;
|
|
|
|
if (sourceDb != targetDb) {
|
|
QUuid customIcon = entry->iconUuid();
|
|
if (!customIcon.isNull() && !targetDb->metadata()->hasCustomIcon(customIcon)) {
|
|
targetDb->metadata()->addCustomIcon(customIcon, sourceDb->metadata()->customIcon(customIcon));
|
|
}
|
|
|
|
// Reset the UUID when moving across db boundary
|
|
entry = dragEntry->clone(Entry::CloneDefault | Entry::CloneIncludeHistory);
|
|
if (action == Qt::MoveAction) {
|
|
delete dragEntry;
|
|
}
|
|
} else if (action == Qt::CopyAction) {
|
|
entry = dragEntry->clone(Entry::CloneCopy);
|
|
}
|
|
|
|
entry->setGroup(parentGroup);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QStringList GroupModel::mimeTypes() const
|
|
{
|
|
QStringList types;
|
|
types << "application/x-keepassx-group";
|
|
types << "application/x-keepassx-entry";
|
|
return types;
|
|
}
|
|
|
|
QMimeData* GroupModel::mimeData(const QModelIndexList& indexes) const
|
|
{
|
|
if (indexes.isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
QMimeData* data = new QMimeData();
|
|
QByteArray encoded;
|
|
QDataStream stream(&encoded, QIODevice::WriteOnly);
|
|
|
|
QSet<Group*> seenGroups;
|
|
|
|
for (const QModelIndex& index : indexes) {
|
|
if (!index.isValid()) {
|
|
continue;
|
|
}
|
|
|
|
Group* group = groupFromIndex(index);
|
|
if (!seenGroups.contains(group)) {
|
|
// make sure we don't add groups multiple times when we get indexes
|
|
// with the same row but different columns
|
|
stream << m_db->uuid() << group->uuid();
|
|
seenGroups.insert(group);
|
|
}
|
|
}
|
|
|
|
if (seenGroups.isEmpty()) {
|
|
delete data;
|
|
return nullptr;
|
|
} else {
|
|
data->setData(mimeTypes().at(0), encoded);
|
|
return data;
|
|
}
|
|
}
|
|
|
|
void GroupModel::groupDataChanged(Group* group)
|
|
{
|
|
QModelIndex ix = index(group);
|
|
emit dataChanged(ix, ix);
|
|
}
|
|
|
|
void GroupModel::groupAboutToRemove(Group* group)
|
|
{
|
|
Q_ASSERT(group->parentGroup());
|
|
|
|
QModelIndex parentIndex = parent(group);
|
|
Q_ASSERT(parentIndex.isValid());
|
|
int pos = group->parentGroup()->children().indexOf(group);
|
|
Q_ASSERT(pos != -1);
|
|
|
|
beginRemoveRows(parentIndex, pos, pos);
|
|
}
|
|
|
|
void GroupModel::groupRemoved()
|
|
{
|
|
endRemoveRows();
|
|
}
|
|
|
|
void GroupModel::groupAboutToAdd(Group* group, int index)
|
|
{
|
|
Q_ASSERT(group->parentGroup());
|
|
|
|
QModelIndex parentIndex = parent(group);
|
|
|
|
beginInsertRows(parentIndex, index, index);
|
|
}
|
|
|
|
void GroupModel::groupAdded()
|
|
{
|
|
endInsertRows();
|
|
}
|
|
|
|
void GroupModel::groupAboutToMove(Group* group, Group* toGroup, int pos)
|
|
{
|
|
Q_ASSERT(group->parentGroup());
|
|
|
|
QModelIndex oldParentIndex = parent(group);
|
|
QModelIndex newParentIndex = index(toGroup);
|
|
int oldPos = group->parentGroup()->children().indexOf(group);
|
|
if (group->parentGroup() == toGroup && pos > oldPos) {
|
|
// beginMoveRows() has a bit different semantics than Group::setParent() and
|
|
// QList::move() when the new position is greater than the old
|
|
pos++;
|
|
}
|
|
|
|
bool moveResult = beginMoveRows(oldParentIndex, oldPos, oldPos, newParentIndex, pos);
|
|
Q_UNUSED(moveResult);
|
|
Q_ASSERT(moveResult);
|
|
}
|
|
|
|
void GroupModel::groupMoved()
|
|
{
|
|
endMoveRows();
|
|
}
|
|
|
|
void GroupModel::sortChildren(Group* rootGroup, bool reverse)
|
|
{
|
|
emit layoutAboutToBeChanged();
|
|
|
|
QList<QModelIndex> oldIndexes;
|
|
collectIndexesRecursively(oldIndexes, rootGroup->children());
|
|
|
|
rootGroup->sortChildrenRecursively(reverse);
|
|
|
|
QList<QModelIndex> newIndexes;
|
|
collectIndexesRecursively(newIndexes, rootGroup->children());
|
|
|
|
for (int i = 0; i < oldIndexes.count(); i++) {
|
|
changePersistentIndex(oldIndexes[i], newIndexes[i]);
|
|
}
|
|
|
|
emit layoutChanged();
|
|
}
|
|
|
|
void GroupModel::collectIndexesRecursively(QList<QModelIndex>& indexes, QList<Group*> groups)
|
|
{
|
|
for (auto group : groups) {
|
|
indexes.append(index(group));
|
|
collectIndexesRecursively(indexes, group->children());
|
|
}
|
|
}
|