diff --git a/qml/EntriesPage.qml b/qml/EntriesPage.qml index 67ec060..a209dd0 100644 --- a/qml/EntriesPage.qml +++ b/qml/EntriesPage.qml @@ -11,7 +11,7 @@ Kirigami.ScrollablePage { property alias rootIndex: delegateModel.rootIndex Layout.fillWidth: true - title: "?" + title: entryModel.data(rootIndex) actions { main: Kirigami.Action { iconName: "list-add" diff --git a/qml/main.qml b/qml/main.qml index 1474153..83a7bb8 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -7,6 +7,8 @@ import org.kde.kirigami 2.4 as Kirigami Kirigami.ApplicationWindow { id: root property alias showPasswordsOnFocus: showPasswordsOnFocusSwitch.checked + property var fieldsPage: undefined + property var lastEntriesPage: undefined header: Kirigami.ApplicationHeader { backButtonEnabled: false @@ -220,6 +222,25 @@ Kirigami.ApplicationWindow { onNewNotification: { showPassiveNotification(message) } + onCurrentAccountChanged: { + // remove the fields page if the current account has been removed + if (!nativeInterface.hasCurrentAccount) { + pageStack.pop(lastEntriesPage) + } + } + onEntryAboutToBeRemoved: { + // remove all possibly open stack pages of the removed entry and its children + for (var i = pageStack.depth - 1; i >= 0; --i) { + var stackPage = pageStack.get(i) + if (!stackPage) { + continue + } + if (stackPage.rootIndex === removedIndex) { + pageStack.pop(lastEntriesPage = pageStack.get(i - 1)) + return + } + } + } } Component { @@ -251,20 +272,27 @@ Kirigami.ApplicationWindow { } function clearStack() { - pageStack.pop(root.pageStack.initialPage, Controls.StackView.Immediate) + pageStack.pop(lastEntriesPage = root.pageStack.initialPage, + Controls.StackView.Immediate) } function pushStackEntry(entryModel, rootIndex) { - pageStack.push(entriesComponent.createObject(root, { - "entryModel": entryModel, - "rootIndex": rootIndex, - "title": entryModel.data( - rootIndex) - })) + pageStack.push(lastEntriesPage = entriesComponent.createObject(root, { + "entryModel": entryModel, + "rootIndex": rootIndex + })) } function pushAccountEdit() { - pageStack.push(fieldsComponent.createObject(root)) + // lazy-initialize fieldsPage + if (!fieldsPage) { + fieldsPage = fieldsComponent.createObject(root) + } + // remove fieldsPage if already shown to prevent warning + if (pageStack.get(pageStack.depth - 1) === fieldsPage) { + pageStack.pop(lastEntriesPage) + } + pageStack.push(fieldsPage) } function createFileActions(files) { diff --git a/quickgui/controller.cpp b/quickgui/controller.cpp index bfd2962..8a8233a 100644 --- a/quickgui/controller.cpp +++ b/quickgui/controller.cpp @@ -23,6 +23,7 @@ #include #include +#include #include using namespace std; @@ -40,6 +41,7 @@ Controller::Controller(QSettings &settings, const QString &filePath, QObject *pa , m_useNativeFileDialog(false) { m_entryFilterModel.setSourceModel(&m_entryModel); + connect(&m_entryModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &Controller::handleEntriesRemoved); // share settings with main window m_settings.beginGroup(QStringLiteral("mainwindow")); @@ -258,6 +260,50 @@ void Controller::handleFileSelectionCanceled() emit newNotification(tr("Canceled file selection")); } +void Controller::handleEntriesRemoved(const QModelIndex &parentIndex, int first, int last) +{ + // handle deletion of root (currently the view doesn't allow this) + const auto *const parentEntry = m_entryModel.entry(parentIndex); + if (!parentEntry) { + emit entryAboutToBeRemoved(m_entryModel.index(0, 0, QModelIndex())); + setCurrentAccount(nullptr); + return; + } + + // assert arguments + assert(parentEntry->type() == EntryType::Node); + const auto &childEntries = static_cast(parentEntry)->children(); + assert(first >= 0 && static_cast(first) < childEntries.size()); + assert(last >= 0 && static_cast(last) < childEntries.size()); + + // iterate from first to last of the deleted entries + const auto *const currentAccount = this->currentAccount(); + for (; first <= last; ++first) { + // inform view about deletion + emit entryAboutToBeRemoved(m_entryModel.index(first, 0, parentIndex)); + + // unset current account if it is under the deleted node + if (!currentAccount) { + continue; + } + const auto *const childEntry = childEntries[static_cast(first)]; + switch (childEntry->type()) { + case EntryType::Account: + if (currentAccount == static_cast(childEntry)) { + setCurrentAccount(nullptr); + } + break; + case EntryType::Node: + // FIXME: remove const_cast in passwordfile v4 + if (currentAccount->isIndirectChildOf(static_cast(const_cast(childEntry)))) { + setCurrentAccount(nullptr); + } + break; + default:; + } + } +} + QStringList Controller::pasteEntries(const QModelIndex &destinationParent, int row) { if (m_cutEntries.isEmpty() || !m_entryModel.isNode(destinationParent)) { diff --git a/quickgui/controller.h b/quickgui/controller.h index d8513f0..57664e5 100644 --- a/quickgui/controller.h +++ b/quickgui/controller.h @@ -25,8 +25,10 @@ class Controller : public QObject { Q_PROPERTY(EntryModel *entryModel READ entryModel NOTIFY entryModelChanged) Q_PROPERTY(EntryFilterModel *entryFilterModel READ entryFilterModel NOTIFY entryFilterModelChanged) Q_PROPERTY(FieldModel *fieldModel READ fieldModel NOTIFY fieldModelChanged) + Q_PROPERTY(Io::AccountEntry *currentAccount READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged) Q_PROPERTY(QModelIndex currentAccountIndex READ currentAccountIndex WRITE setCurrentAccountIndex NOTIFY currentAccountChanged) Q_PROPERTY(QString currentAccountName READ currentAccountName NOTIFY currentAccountChanged) + Q_PROPERTY(bool hasCurrentAccount READ hasCurrentAccount NOTIFY currentAccountChanged) Q_PROPERTY(QList cutEntries READ cutEntries WRITE setCutEntries NOTIFY cutEntriesChanged) Q_PROPERTY(bool canPaste READ canPaste NOTIFY cutEntriesChanged) Q_PROPERTY(QStringList recentFiles READ recentFiles NOTIFY recentFilesChanged) @@ -48,8 +50,11 @@ public: EntryModel *entryModel(); EntryFilterModel *entryFilterModel(); FieldModel *fieldModel(); + Io::AccountEntry *currentAccount(); + void setCurrentAccount(Io::AccountEntry *entry); QModelIndex currentAccountIndex() const; void setCurrentAccountIndex(const QModelIndex &accountIndex); + bool hasCurrentAccount() const; const QList &cutEntries() const; void setCutEntries(const QList &cutEntries); QString currentAccountName() const; @@ -92,6 +97,10 @@ signals: void newNotification(const QString &message); void useNativeFileDialogChanged(bool useNativeFileDialog); void supportsNativeFileDialogChanged(); + void entryAboutToBeRemoved(const QModelIndex &removedIndex); + +private slots: + void handleEntriesRemoved(const QModelIndex &parentIndex, int first, int last); private: void resetFileStatus(); @@ -166,6 +175,17 @@ inline FieldModel *Controller::fieldModel() return &m_fieldModel; } +inline Io::AccountEntry *Controller::currentAccount() +{ + return m_fieldModel.accountEntry(); +} + +inline void Controller::setCurrentAccount(Io::AccountEntry *entry) +{ + m_fieldModel.setAccountEntry(entry); + emit currentAccountChanged(); +} + inline QModelIndex Controller::currentAccountIndex() const { return m_fieldModel.accountEntry() ? m_entryModel.index(const_cast(m_fieldModel.accountEntry())) : QModelIndex(); @@ -177,6 +197,11 @@ inline void Controller::setCurrentAccountIndex(const QModelIndex &accountIndex) emit currentAccountChanged(); } +inline bool Controller::hasCurrentAccount() const +{ + return m_fieldModel.accountEntry() != nullptr; +} + inline const QList &Controller::cutEntries() const { return m_cutEntries;