passwordmanager/model/fieldmodel.cpp

408 lines
14 KiB
C++
Raw Normal View History

2015-09-06 20:33:09 +02:00
#include "./fieldmodel.h"
2016-04-08 00:04:23 +02:00
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
#include "../gui/undocommands.h"
2015-04-22 19:30:09 +02:00
#endif
#include <passwordfile/io/field.h>
#include <QMimeData>
#include <QStringList>
2018-06-10 22:51:43 +02:00
#include <memory>
2015-04-22 19:30:09 +02:00
using namespace std;
using namespace Io;
namespace QtGui {
/*!
* \class FieldModel
* \brief The FieldModel class provides a model interface for the fields of an AccountEntry.
*
* 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/qabstracttablemodel.html
* \sa http://qt-project.org/doc/qt-5/qundo.html
*/
/*!
* \brief Constructs a new field model.
*/
2018-03-14 00:17:14 +01:00
FieldModel::FieldModel(QObject *parent)
: QAbstractTableModel(parent)
, m_accountEntry(nullptr)
, m_fields(nullptr)
, m_passwordVisibility(PasswordVisibility::OnlyWhenEditing)
{
}
2015-04-22 19:30:09 +02:00
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
2015-04-22 19:30:09 +02:00
/*!
* \brief Constructs a new field model with the specified \a undoStack.
*
* 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
FieldModel::FieldModel(QUndoStack *undoStack, QObject *parent)
: QAbstractTableModel(parent)
, StackSupport(undoStack)
, m_accountEntry(nullptr)
, m_fields(nullptr)
{
}
2015-04-22 19:30:09 +02:00
#endif
QHash<int, QByteArray> FieldModel::roleNames() const
{
static const QHash<int, QByteArray> roles{
{ FieldModelRoles::FieldTypeRole, "fieldType" },
{ FieldModelRoles::Key, "key" },
{ FieldModelRoles::Value, "value" },
{ FieldModelRoles::IsPassword, "isPassword" },
2018-06-13 00:41:24 +02:00
{ FieldModelRoles::AlwaysActualValue, "actualValue" },
2018-11-22 21:58:05 +01:00
{ FieldModelRoles::IsLastRow, "isLastRow" },
};
return roles;
}
2015-04-22 19:30:09 +02:00
/*!
* \brief Sets the account entry. Causes a model reset.
*
* The \a entry might be nullptr.
* The caller keeps the ownership and should not destroy the \a entry as long it is assigned.
*/
void FieldModel::setAccountEntry(AccountEntry *entry)
{
2018-03-14 00:17:14 +01:00
if (entry == m_accountEntry) {
2018-03-14 00:15:12 +01:00
return;
}
beginResetModel();
2018-03-14 00:17:14 +01:00
if ((m_accountEntry = entry)) {
2018-03-14 00:15:12 +01:00
m_fields = &entry->fields();
} else {
m_fields = nullptr;
2015-04-22 19:30:09 +02:00
}
2018-03-14 00:15:12 +01:00
endResetModel();
2015-04-22 19:30:09 +02:00
}
2018-12-08 19:55:41 +01:00
inline QVariant FieldModel::passwordValue(const QModelIndex &index, int role) const
{
return (m_passwordVisibility == PasswordVisibility::Always || role == Qt::EditRole
|| (*m_fields)[static_cast<size_t>(index.row())].type() != FieldType::Password)
? QString::fromStdString((*m_fields)[static_cast<size_t>(index.row())].value())
2021-03-20 21:57:47 +01:00
: QString(static_cast<QString::size_type>((*m_fields)[static_cast<size_t>(index.row())].value().size()), QChar(0x2022));
2018-12-08 19:55:41 +01:00
}
2015-04-22 19:30:09 +02:00
QVariant FieldModel::data(const QModelIndex &index, int role) const
{
2018-03-14 00:17:14 +01:00
if (!index.isValid() || !m_fields || index.row() < 0) {
2018-03-14 00:15:12 +01:00
return QVariant();
}
// return data for existent field
2018-03-14 00:17:14 +01:00
if (static_cast<size_t>(index.row()) < m_fields->size()) {
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((*m_fields)[static_cast<size_t>(index.row())].name());
case 1:
2018-12-08 19:55:41 +01:00
return passwordValue(index, role);
2018-03-14 00:17:14 +01:00
default:;
2015-04-22 19:30:09 +02:00
}
2018-03-14 00:15:12 +01:00
break;
case FieldTypeRole:
return static_cast<int>((*m_fields)[static_cast<size_t>(index.row())].type());
case Key:
return QString::fromStdString((*m_fields)[static_cast<size_t>(index.row())].name());
case Value:
2018-12-08 19:55:41 +01:00
return passwordValue(index, role);
2018-06-13 00:41:24 +02:00
case AlwaysActualValue:
return QString::fromStdString((*m_fields)[static_cast<size_t>(index.row())].value());
case IsPassword:
return (*m_fields)[static_cast<size_t>(index.row())].type() == FieldType::Password;
2018-11-22 21:58:05 +01:00
case IsLastRow:
return false;
2018-03-14 00:17:14 +01:00
default:;
2018-03-14 00:15:12 +01:00
}
2018-03-14 00:17:14 +01:00
} else if (static_cast<size_t>(index.row()) == m_fields->size()) {
// return data for empty field at the end which enables the user to append fields
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();
case 1:
return QString();
2018-03-14 00:17:14 +01:00
default:;
2015-04-22 19:30:09 +02:00
}
2018-03-14 00:15:12 +01:00
break;
case Key:
case Value:
case AlwaysActualValue:
return QString();
case IsPassword:
return false;
case IsLastRow:
return true;
2018-03-14 00:17:14 +01:00
default:;
2015-04-22 19:30:09 +02:00
}
}
return QVariant();
}
QMap<int, QVariant> FieldModel::itemData(const QModelIndex &index) const
{
2018-03-14 00:15:12 +01:00
static const auto roleMap = [this, index] {
QMap<int, QVariant> roleMap;
2018-03-14 00:17:14 +01:00
for (const auto role : initializer_list<int>{ Qt::EditRole, FieldTypeRole }) {
2018-03-14 00:15:12 +01:00
const auto variantData(data(index, role));
if (variantData.isValid()) {
roleMap.insert(role, variantData);
}
2015-04-22 19:30:09 +02:00
}
2018-03-14 00:15:12 +01:00
return roleMap;
}();
2015-04-22 19:30:09 +02:00
return roleMap;
}
bool FieldModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
#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<FieldModelSetValueCommand>(this, index, value, role));
2015-04-22 19:30:09 +02:00
}
#endif
2018-03-14 00:17:14 +01:00
if (!index.isValid() || !m_fields || index.row() < 0) {
2018-03-14 00:15:12 +01:00
return false;
}
2015-04-22 19:30:09 +02:00
QVector<int> roles;
2018-03-14 00:17:14 +01:00
if (static_cast<size_t>(index.row()) < m_fields->size()) {
2015-04-22 19:30:09 +02:00
// set data for existing field
2018-03-14 00:17:14 +01:00
switch (role) {
2018-03-14 00:15:12 +01:00
case Qt::EditRole:
2018-03-14 00:17:14 +01:00
switch (index.column()) {
2018-03-14 00:15:12 +01:00
case 0:
2018-11-22 21:57:31 +01:00
m_fields->at(static_cast<size_t>(index.row())).setName(value.toString().toStdString());
2018-06-13 00:41:24 +02:00
roles << Qt::DisplayRole << Qt::EditRole << Key;
2015-04-22 19:30:09 +02:00
break;
2018-03-14 00:15:12 +01:00
case 1:
2018-11-22 21:57:31 +01:00
m_fields->at(static_cast<size_t>(index.row())).setValue(value.toString().toStdString());
2018-06-13 00:41:24 +02:00
roles << Qt::DisplayRole << Qt::EditRole << Value << AlwaysActualValue;
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
}
2018-03-14 00:15:12 +01:00
break;
case FieldTypeRole: {
bool ok;
int fieldType = value.toInt(&ok);
2018-03-14 00:17:14 +01:00
if (ok && Field::isValidType(fieldType)) {
2018-11-22 21:57:31 +01:00
m_fields->at(static_cast<size_t>(index.row())).setType(static_cast<FieldType>(fieldType));
2018-11-20 18:06:19 +01:00
roles << Qt::DisplayRole << Value << FieldTypeRole << IsPassword;
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
}
case Key:
2018-11-22 21:57:31 +01:00
m_fields->at(static_cast<size_t>(index.row())).setName(value.toString().toStdString());
2018-06-13 00:41:24 +02:00
roles << Qt::DisplayRole << Qt::EditRole << Key;
break;
case Value:
2018-06-13 00:41:24 +02:00
case AlwaysActualValue:
2018-11-22 21:57:31 +01:00
m_fields->at(static_cast<size_t>(index.row())).setValue(value.toString().toStdString());
2018-06-13 00:41:24 +02:00
roles << Qt::DisplayRole << Qt::EditRole << Value << AlwaysActualValue;
break;
case IsPassword:
2018-11-22 21:57:31 +01:00
m_fields->at(static_cast<size_t>(index.row())).setType(value.toBool() ? FieldType::Password : FieldType::Normal);
2018-11-20 18:06:19 +01:00
roles << Qt::DisplayRole << Value << FieldTypeRole << IsPassword;
break;
2018-03-14 00:17:14 +01:00
default:;
2018-03-14 00:15:12 +01:00
}
// remove last field if empty, showing an empty field at the end to enabled appending new rows is provided by the data method
2018-11-22 21:57:31 +01:00
if (!roles.isEmpty() && static_cast<size_t>(index.row()) == m_fields->size() - 1
&& m_fields->at(static_cast<size_t>(index.row())).isEmpty()) {
2018-03-14 00:15:12 +01:00
beginRemoveRows(index.parent(), index.row(), index.row());
m_fields->pop_back();
endRemoveRows();
}
2018-03-14 00:17:14 +01:00
} else if (static_cast<size_t>(index.row()) == m_fields->size() && !value.toString().isEmpty()) {
2015-04-22 19:30:09 +02:00
// set data for a new field emplaced at the end of the field list
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:
beginInsertRows(index.parent(), rowCount(), rowCount());
m_fields->emplace_back(m_accountEntry);
m_fields->back().setName(value.toString().toStdString());
endInsertRows();
2018-11-22 21:58:05 +01:00
roles << Qt::DisplayRole << Qt::EditRole << IsLastRow;
2018-03-14 00:15:12 +01:00
break;
case 1:
beginInsertRows(index.parent(), rowCount(), rowCount());
m_fields->emplace_back(m_accountEntry);
m_fields->back().setValue(value.toString().toStdString());
endInsertRows();
2018-11-22 21:58:05 +01:00
roles << Qt::DisplayRole << Qt::EditRole << IsLastRow;
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
}
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 if nothing could be changed
2018-03-14 00:17:14 +01:00
if (roles.isEmpty()) {
2015-04-22 19:30:09 +02:00
return false;
2018-03-14 00:15:12 +01:00
}
// emit data changed signal on success
2015-04-22 19:30:09 +02:00
emit dataChanged(index, index, roles);
return true;
}
Qt::ItemFlags FieldModel::flags(const QModelIndex &index) const
{
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
QVariant FieldModel::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");
case 1:
return tr("Value");
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 FieldModel::rowCount(const QModelIndex &parent) const
{
2021-03-20 21:57:47 +01:00
return (!parent.isValid() && m_fields) ? static_cast<int>(m_fields->size()) + 1 : 0;
2015-04-22 19:30:09 +02:00
}
int FieldModel::columnCount(const QModelIndex &parent) const
{
return !parent.isValid() ? 2 : 0;
}
bool FieldModel::insertRows(int row, int count, const QModelIndex &parent)
{
#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<FieldModelInsertRowsCommand>(this, row, count));
2015-04-22 19:30:09 +02:00
}
#endif
if (parent.isValid() || row < 0 || count <= 0 || static_cast<size_t>(row) > m_fields->size()) {
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
beginInsertRows(parent, row, row + count - 1);
m_fields->insert(m_fields->begin() + row, static_cast<size_t>(count), Field(m_accountEntry));
endInsertRows();
return true;
2015-04-22 19:30:09 +02:00
}
bool FieldModel::removeRows(int row, int count, const QModelIndex &parent)
{
#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<FieldModelRemoveRowsCommand>(this, row, count));
2015-04-22 19:30:09 +02:00
}
#endif
2018-03-14 00:17:14 +01:00
if (parent.isValid() || row < 0 || count <= 0 || static_cast<size_t>(row + count) > m_fields->size()) {
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
beginRemoveRows(parent, row, row + count - 1);
m_fields->erase(m_fields->begin() + row, m_fields->begin() + row + count);
endRemoveRows();
2018-03-14 00:39:46 +01:00
return true;
2015-04-22 19:30:09 +02:00
}
2018-06-13 00:41:24 +02:00
bool FieldModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
{
// FIXME: implement an undo/redo command
// validate input parameter
if (sourceParent.isValid() || destinationParent.isValid() || sourceRow < 0 || count <= 0 || destinationChild < 0
|| static_cast<size_t>(sourceRow + count) > m_fields->size() || static_cast<size_t>(destinationChild) >= m_fields->size()
|| (destinationChild >= sourceRow && destinationChild < (sourceRow + count))) {
return false;
}
// begin the move
if (destinationChild > sourceRow) {
// move rows down: the third param is still counted in the initial array!
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild + count);
} else {
// move rows up
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild);
}
// reserve space for temporary copies (FIXME: possible to avoid this?)
m_fields->reserve(m_fields->size() + static_cast<size_t>(count));
vector<Io::Field> tmp(static_cast<size_t>(count));
// move rows to temporary array
move(m_fields->begin() + sourceRow, m_fields->begin() + sourceRow + count, tmp.begin());
// erase slots of rows to be moved
m_fields->erase(m_fields->begin() + sourceRow, m_fields->begin() + sourceRow + count);
// insert rows again at their new position
m_fields->insert(m_fields->begin() + destinationChild, tmp.begin(), tmp.end());
endMoveRows();
return true;
}
2015-04-22 19:30:09 +02:00
bool FieldModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
2018-03-14 00:17:14 +01:00
if (!QAbstractTableModel::dropMimeData(data, action, row, column, parent) && data->hasText()) {
2018-03-14 00:15:12 +01:00
return setData(parent, data->text(), Qt::EditRole);
2015-04-22 19:30:09 +02:00
}
return false;
}
QStringList FieldModel::mimeTypes() const
{
return QAbstractTableModel::mimeTypes() << QStringLiteral("text/plain");
}
2018-06-13 00:41:24 +02:00
QMimeData *FieldModel::mimeData(const QModelIndexList &indices) const
2015-04-22 19:30:09 +02:00
{
2018-06-13 00:41:24 +02:00
QMimeData *const data = QAbstractTableModel::mimeData(indices);
if (indices.isEmpty()) {
2018-03-14 00:15:12 +01:00
return data;
}
QStringList result;
2018-06-13 00:41:24 +02:00
result.reserve(indices.size());
for (const QModelIndex &index : indices) {
2018-03-14 00:15:12 +01:00
result << index.data(Qt::EditRole).toString();
2015-04-22 19:30:09 +02:00
}
2018-03-14 00:15:12 +01:00
data->setText(result.join(QChar('\n')));
2015-04-22 19:30:09 +02:00
return data;
}
/*!
* \brief Returns the field for the specified row.
*
* Might be nullptr if no account entry is assigned or if the row is out of range.
*/
const Field *FieldModel::field(size_t row) const
{
2018-03-14 00:17:14 +01:00
if (m_fields && row < m_fields->size()) {
2018-03-14 00:15:12 +01:00
return &(*m_fields)[row];
2015-04-22 19:30:09 +02:00
}
return nullptr;
}
2018-03-14 00:17:14 +01:00
} // namespace QtGui