From d8b6aeb818948180b031e52e804517f98dcbe08d Mon Sep 17 00:00:00 2001 From: Martchus Date: Wed, 13 Jun 2018 00:41:24 +0200 Subject: [PATCH] Improve editing fields in Qt Quick GUI --- model/fieldmodel.cpp | 74 +++++++++++++++++++++++++------------- model/fieldmodel.h | 4 ++- qml/EntriesPage.qml | 85 +++++++++++++++++++++++++++++++++++--------- 3 files changed, 121 insertions(+), 42 deletions(-) diff --git a/model/fieldmodel.cpp b/model/fieldmodel.cpp index 85e2694..79827a1 100644 --- a/model/fieldmodel.cpp +++ b/model/fieldmodel.cpp @@ -58,6 +58,7 @@ QHash FieldModel::roleNames() const { FieldModelRoles::Key, "key" }, { FieldModelRoles::Value, "value" }, { FieldModelRoles::IsPassword, "isPassword" }, + { FieldModelRoles::AlwaysActualValue, "actualValue" }, }; return roles; } @@ -112,6 +113,8 @@ QVariant FieldModel::data(const QModelIndex &index, int role) const || (*m_fields)[static_cast(index.row())].type() != FieldType::Password) ? QString::fromStdString((*m_fields)[static_cast(index.row())].value()) : QString((*m_fields)[static_cast(index.row())].value().size(), QChar(0x2022)); + case AlwaysActualValue: + return QString::fromStdString((*m_fields)[static_cast(index.row())].value()); case IsPassword: return (*m_fields)[static_cast(index.row())].type() == FieldType::Password; default:; @@ -169,11 +172,11 @@ bool FieldModel::setData(const QModelIndex &index, const QVariant &value, int ro switch (index.column()) { case 0: m_fields->at(index.row()).setName(value.toString().toStdString()); - roles << Qt::EditRole << Key; + roles << Qt::DisplayRole << Qt::EditRole << Key; break; case 1: m_fields->at(index.row()).setValue(value.toString().toStdString()); - roles << Qt::EditRole << Value; + roles << Qt::DisplayRole << Qt::EditRole << Value << AlwaysActualValue; break; default:; } @@ -189,15 +192,16 @@ bool FieldModel::setData(const QModelIndex &index, const QVariant &value, int ro } case Key: m_fields->at(index.row()).setName(value.toString().toStdString()); - roles << Qt::EditRole << Key; + roles << Qt::DisplayRole << Qt::EditRole << Key; break; case Value: + case AlwaysActualValue: m_fields->at(index.row()).setValue(value.toString().toStdString()); - roles << Qt::EditRole << Value; + roles << Qt::DisplayRole << Qt::EditRole << Value << AlwaysActualValue; break; case IsPassword: m_fields->at(index.row()).setType(value.toBool() ? FieldType::Password : FieldType::Normal); - roles << FieldTypeRole << IsPassword; + roles << Qt::DisplayRole << FieldTypeRole << IsPassword; break; default:; } @@ -218,14 +222,14 @@ bool FieldModel::setData(const QModelIndex &index, const QVariant &value, int ro m_fields->emplace_back(m_accountEntry); m_fields->back().setName(value.toString().toStdString()); endInsertRows(); - roles << role; + roles << Qt::DisplayRole << Qt::EditRole; break; case 1: beginInsertRows(index.parent(), rowCount(), rowCount()); m_fields->emplace_back(m_accountEntry); m_fields->back().setValue(value.toString().toStdString()); endInsertRows(); - roles << role; + roles << Qt::DisplayRole << Qt::EditRole; break; default:; } @@ -237,19 +241,6 @@ bool FieldModel::setData(const QModelIndex &index, const QVariant &value, int ro if (roles.isEmpty()) { return false; } - // some roles affect other roles - switch (role) { - case Qt::EditRole: - case Key: - case Value: - roles << Qt::DisplayRole; - break; - case FieldTypeRole: - case IsPassword: - roles << Qt::DisplayRole << Qt::EditRole << Key; - break; - default:; - } // emit data changed signal on sucess emit dataChanged(index, index, roles); return true; @@ -324,6 +315,40 @@ bool FieldModel::removeRows(int row, int count, const QModelIndex &parent) return true; } +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(sourceRow + count) > m_fields->size() || static_cast(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(count)); + vector tmp(static_cast(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; +} + bool FieldModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (!QAbstractTableModel::dropMimeData(data, action, row, column, parent) && data->hasText()) { @@ -337,14 +362,15 @@ QStringList FieldModel::mimeTypes() const return QAbstractTableModel::mimeTypes() << QStringLiteral("text/plain"); } -QMimeData *FieldModel::mimeData(const QModelIndexList &indexes) const +QMimeData *FieldModel::mimeData(const QModelIndexList &indices) const { - QMimeData *data = QAbstractTableModel::mimeData(indexes); - if (indexes.isEmpty()) { + QMimeData *const data = QAbstractTableModel::mimeData(indices); + if (indices.isEmpty()) { return data; } QStringList result; - for (const QModelIndex &index : indexes) { + result.reserve(indices.size()); + for (const QModelIndex &index : indices) { result << index.data(Qt::EditRole).toString(); } data->setText(result.join(QChar('\n'))); diff --git a/model/fieldmodel.h b/model/fieldmodel.h index 75b1d3f..ca6cdc9 100644 --- a/model/fieldmodel.h +++ b/model/fieldmodel.h @@ -25,6 +25,7 @@ enum FieldModelRoles { Key, Value, IsPassword, + AlwaysActualValue, }; /*! @@ -67,9 +68,10 @@ public: Q_INVOKABLE int columnCount(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + Q_INVOKABLE bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild); bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); QStringList mimeTypes() const; - QMimeData *mimeData(const QModelIndexList &indexes) const; + QMimeData *mimeData(const QModelIndexList &indices) const; Q_INVOKABLE const Io::Field *field(std::size_t row) const; public Q_SLOTS: diff --git a/qml/EntriesPage.qml b/qml/EntriesPage.qml index ede955a..34f9e01 100644 --- a/qml/EntriesPage.qml +++ b/qml/EntriesPage.qml @@ -119,6 +119,64 @@ Kirigami.ScrollablePage { } } + // component representing a field + Component { + id: fieldsListDelegateComponent + RowLayout { + id: fieldsListItem + + width: fieldsSheet.width + + Kirigami.ListItemDragHandle { + listItem: fieldsListItem + listView: fieldsListView + onMoveRequested: fieldsListView.model.moveRows( + fieldsListView.model.index(-1, 0), + oldIndex, 1, + fieldsListView.model.index(-1, + 0), newIndex) + } + Controls.TextField { + text: model.key ? model.key : "" + onEditingFinished: fieldsListView.model.setData( + fieldsListView.model.index(index, 0), + text) + Layout.fillWidth: true + } + Controls.TextField { + text: model.actualValue ? model.actualValue : "" + echoMode: model.isPassword ? TextInput.PasswordEchoOnEdit : TextInput.Normal + onEditingFinished: fieldsListView.model.setData( + fieldsListView.model.index(index, 1), + text) + Layout.fillWidth: true + } + Controls.Button { + flat: true + icon.name: model.isPassword ? "password-show-off" : "password-show-on" + onClicked: fieldsListView.model.setData( + fieldsListView.model.index(index, 0), + model.isPassword ? 0 : 1, 0x0100 + 1) + Layout.maximumWidth: Kirigami.Units.iconSizes.large + Layout.maximumHeight: Kirigami.Units.iconSizes.large + } + Controls.Button { + flat: true + icon.name: "edit-delete" + onClicked: fieldsListView.model.removeRows(index, 1) + Layout.maximumWidth: Kirigami.Units.iconSizes.large + Layout.maximumHeight: Kirigami.Units.iconSizes.large + } + Controls.Button { + flat: true + icon.name: "list-add" + onClicked: fieldsListView.model.insertRows(index + 1, 1) + Layout.maximumWidth: Kirigami.Units.iconSizes.large + Layout.maximumHeight: Kirigami.Units.iconSizes.large + } + } + } + // "sheet" to display field model Kirigami.OverlaySheet { id: fieldsSheet @@ -130,21 +188,16 @@ Kirigami.ScrollablePage { id: fieldsListView implicitWidth: Kirigami.Units.gridUnit * 30 model: nativeInterface.fieldModel - delegate: RowLayout { - Controls.TextField { - text: key ? key : "" - onEditingFinished: fieldsListView.model.setData( - fieldsListView.model.index(index, - 0), text) - } - Controls.TextField { - text: value ? value : "" - echoMode: isPassword ? TextInput.PasswordEchoOnEdit : TextInput.Normal - onEditingFinished: fieldsListView.model.setData( - fieldsListView.model.index(index, - 1), text) + moveDisplaced: Transition { + YAnimator { + duration: Kirigami.Units.longDuration + easing.type: Easing.InOutQuad } } + delegate: Kirigami.DelegateRecycler { + width: parent ? parent.width : implicitWidth + sourceComponent: fieldsListDelegateComponent + } } } @@ -158,10 +211,8 @@ Kirigami.ScrollablePage { Kirigami.ListItemDragHandle { listItem: listItem listView: entriesListView - onMoveRequested: { - entryModel.moveRows(rootIndex, oldIndex, 1, - rootIndex, newIndex) - } + onMoveRequested: entryModel.moveRows(rootIndex, oldIndex, + 1, rootIndex, newIndex) } Kirigami.Icon { width: Kirigami.Units.iconSizes.smallMedium