diff --git a/src/gui/EditWidget.cpp b/src/gui/EditWidget.cpp index 65c6306e1..5f05a428a 100644 --- a/src/gui/EditWidget.cpp +++ b/src/gui/EditWidget.cpp @@ -117,6 +117,14 @@ bool EditWidget::readOnly() const return m_readOnly; } +void EditWidget::enableApplyButton(bool enabled) +{ + QPushButton* applyButton = m_ui->buttonBox->button(QDialogButtonBox::Apply); + if (applyButton) { + applyButton->setEnabled(enabled); + } +} + void EditWidget::showMessage(const QString& text, MessageWidget::MessageType type) { m_ui->messageWidget->setCloseButtonVisible(false); diff --git a/src/gui/EditWidget.h b/src/gui/EditWidget.h index 442365b96..38179a773 100644 --- a/src/gui/EditWidget.h +++ b/src/gui/EditWidget.h @@ -47,6 +47,7 @@ public: QLabel* headlineLabel(); void setReadOnly(bool readOnly); bool readOnly() const; + void enableApplyButton(bool enabled); signals: void apply(); diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index a70e0dca2..e02ad952f 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -66,6 +66,13 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) connect(m_ui->deleteButton, SIGNAL(clicked()), SLOT(removeCustomIcon())); connect(m_ui->faviconButton, SIGNAL(clicked()), SLOT(downloadFavicon())); + connect(m_ui->defaultIconsRadio, SIGNAL(toggled(bool)), this, SIGNAL(widgetUpdated())); + connect(m_ui->defaultIconsRadio, SIGNAL(toggled(bool)), this, SIGNAL(widgetUpdated())); + connect(m_ui->defaultIconsView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SIGNAL(widgetUpdated())); + connect(m_ui->customIconsView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SIGNAL(widgetUpdated())); + m_ui->faviconButton->setVisible(false); } @@ -268,6 +275,8 @@ void EditWidgetIcons::addCustomIcon(const QImage& icon) updateRadioButtonCustomIcons(); QModelIndex index = m_customIconModel->indexFromUuid(uuid); m_ui->customIconsView->setCurrentIndex(index); + + emit widgetUpdated(); } } @@ -347,6 +356,8 @@ void EditWidgetIcons::removeCustomIcon() } else { m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(Group::DefaultIconNumber)); } + + emit widgetUpdated(); } } } diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h index 7b5edf80c..0f875a8a3 100644 --- a/src/gui/EditWidgetIcons.h +++ b/src/gui/EditWidgetIcons.h @@ -62,6 +62,7 @@ public slots: signals: void messageEditEntry(QString, MessageWidget::MessageType); void messageEditEntryDismiss(); + void widgetUpdated(); private slots: void downloadFavicon(); diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 95ce7ee6a..8163a648c 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -95,6 +95,7 @@ EditEntryWidget::EditEntryWidget(QWidget* parent) #endif setupProperties(); setupHistory(); + setupEntryUpdate(); connect(this, SIGNAL(accepted()), SLOT(acceptEntry())); connect(this, SIGNAL(rejected()), SLOT(cancel())); @@ -227,6 +228,59 @@ void EditEntryWidget::setupHistory() connect(m_historyUi->deleteAllButton, SIGNAL(clicked()), SLOT(deleteAllHistoryEntries())); } +void EditEntryWidget::setupEntryUpdate() +{ + // Entry tab + connect(m_mainUi->titleEdit, SIGNAL(textChanged(const QString&)), this, SLOT(setUnsavedChanges())); + connect(m_mainUi->usernameEdit, SIGNAL(textChanged(const QString&)), this, SLOT(setUnsavedChanges())); + connect(m_mainUi->passwordEdit, SIGNAL(textChanged(const QString&)), this, SLOT(setUnsavedChanges())); + connect(m_mainUi->passwordRepeatEdit, SIGNAL(textChanged(const QString&)), this, SLOT(setUnsavedChanges())); + connect(m_mainUi->urlEdit, SIGNAL(textChanged(const QString&)), this, SLOT(setUnsavedChanges())); + connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_mainUi->notesEnabled, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(const QDateTime&)), this, SLOT(setUnsavedChanges())); + connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setUnsavedChanges())); + + // Advanced tab + connect(m_advancedUi->attributesEdit, SIGNAL(textChanged()), this, SLOT(setUnsavedChanges())); + connect(m_advancedUi->protectAttributeButton, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_advancedUi->fgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_advancedUi->bgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_advancedUi->attachmentsWidget, SIGNAL(widgetUpdated()), this, SLOT(setUnsavedChanges())); + + // Icon tab + connect(m_iconsWidget, SIGNAL(widgetUpdated()), this, SLOT(setUnsavedChanges())); + + // Auto-Type tab + connect(m_autoTypeUi->enableButton, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_autoTypeUi->inheritSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setUnsavedChanges())); + connect(m_autoTypeUi->customSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setUnsavedChanges())); + connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(const QString&)), this, SLOT(setUnsavedChanges())); + connect(m_autoTypeUi->sequenceEdit, SIGNAL(textChanged(const QString&)), this, SLOT(setUnsavedChanges())); + connect(m_autoTypeUi->windowTitleCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(const QString&)), this, SLOT(setUnsavedChanges())); + + // Properties and History tabs don't need extra connections + +#ifdef WITH_XC_SSHAGENT + // SSH Agent tab + if (config()->get("SSHAgent", false).toBool()) { + connect(m_sshAgentUi->attachmentRadioButton, SIGNAL(toggled(bool)), this, SLOT(setUnsavedChanges())); + connect(m_sshAgentUi->externalFileRadioButton, SIGNAL(toggled(bool)), this, SLOT(setUnsavedChanges())); + connect(m_sshAgentUi->attachmentComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_sshAgentUi->attachmentComboBox, SIGNAL(editTextChanged(const QString&)), this, SLOT(setUnsavedChanges())); + connect(m_sshAgentUi->externalFileEdit, SIGNAL(textChanged(const QString&)), this, SLOT(setUnsavedChanges())); + connect(m_sshAgentUi->publicKeyEdit, SIGNAL(textChanged()), this, SLOT(setUnsavedChanges())); + connect(m_sshAgentUi->addKeyToAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_sshAgentUi->removeKeyFromAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_sshAgentUi->requireUserConfirmationCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_sshAgentUi->lifetimeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); + connect(m_sshAgentUi->lifetimeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setUnsavedChanges())); + } +#endif +} + void EditEntryWidget::emitHistoryEntryActivated(const QModelIndex& index) { Q_ASSERT(!m_history); @@ -581,7 +635,6 @@ void EditEntryWidget::loadEntry(Entry* entry, bool create, bool history, const Q m_database = database; m_create = create; m_history = history; - m_saved = false; if (history) { setHeadline(QString("%1 > %2").arg(parentName, tr("Entry history"))); @@ -601,6 +654,9 @@ void EditEntryWidget::loadEntry(Entry* entry, bool create, bool history, const Q setCurrentPage(0); setPageHidden(m_historyWidget, m_history || m_entry->historyItems().count() < 1); + + // Force the user to Save/Apply/Discard new entries + setUnsavedChanges(m_create); } void EditEntryWidget::setForms(const Entry* entry, bool restore) @@ -780,7 +836,7 @@ bool EditEntryWidget::commitEntry() } updateEntryData(m_entry); - m_saved = true; + setUnsavedChanges(false); if (!m_create) { m_entry->endUpdate(); @@ -871,6 +927,19 @@ void EditEntryWidget::cancel() m_entry->setIcon(Entry::DefaultIconNumber); } + if (!m_saved) { + auto result = MessageBox::question(this, QString(), tr("Entry has unsaved changes"), + QMessageBox::Cancel | QMessageBox::Save | QMessageBox::Discard, + QMessageBox::Cancel); + if (result == QMessageBox::Cancel) { + return; + } + if (result == QMessageBox::Save) { + commitEntry(); + m_saved = true; + } + } + clear(); emit editFinished(m_saved); @@ -940,6 +1009,8 @@ void EditEntryWidget::insertAttribute() m_advancedUi->attributesView->setCurrentIndex(index); m_advancedUi->attributesView->edit(index); + + setUnsavedChanges(true); } void EditEntryWidget::editCurrentAttribute() @@ -950,6 +1021,7 @@ void EditEntryWidget::editCurrentAttribute() if (index.isValid()) { m_advancedUi->attributesView->edit(index); + setUnsavedChanges(true); } } @@ -963,6 +1035,7 @@ void EditEntryWidget::removeCurrentAttribute() if (MessageBox::question(this, tr("Confirm Remove"), tr("Are you sure you want to remove this attribute?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { m_entryAttributes->remove(m_attributesModel->keyByIndex(index)); + setUnsavedChanges(true); } } } @@ -1047,9 +1120,11 @@ void EditEntryWidget::revealCurrentAttribute() if (! m_advancedUi->attributesEdit->isEnabled()) { QModelIndex index = m_advancedUi->attributesView->currentIndex(); if (index.isValid()) { + bool oldBlockSignals = m_advancedUi->attributesEdit->blockSignals(true); QString key = m_attributesModel->keyByIndex(index); m_advancedUi->attributesEdit->setPlainText(m_entryAttributes->value(key)); m_advancedUi->attributesEdit->setEnabled(true); + m_advancedUi->attributesEdit->blockSignals(oldBlockSignals); } } } @@ -1083,6 +1158,7 @@ void EditEntryWidget::insertAutoTypeAssoc() m_autoTypeUi->assocView->setCurrentIndex(newIndex); loadCurrentAssoc(newIndex); m_autoTypeUi->windowTitleCombo->setFocus(); + setUnsavedChanges(true); } void EditEntryWidget::removeAutoTypeAssoc() @@ -1091,6 +1167,7 @@ void EditEntryWidget::removeAutoTypeAssoc() if (currentIndex.isValid()) { m_autoTypeAssoc->remove(currentIndex.row()); + setUnsavedChanges(true); } } @@ -1153,6 +1230,7 @@ void EditEntryWidget::restoreHistoryEntry() QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex()); if (index.isValid()) { setForms(m_historyModel->entryFromIndex(index), true); + setUnsavedChanges(true); } } @@ -1166,6 +1244,7 @@ void EditEntryWidget::deleteHistoryEntry() } else { m_historyUi->deleteAllButton->setEnabled(false); } + setUnsavedChanges(true); } } @@ -1178,6 +1257,7 @@ void EditEntryWidget::deleteAllHistoryEntries() else { m_historyUi->deleteAllButton->setEnabled(false); } + setUnsavedChanges(true); } QMenu* EditEntryWidget::createPresetsMenu() @@ -1229,5 +1309,12 @@ void EditEntryWidget::pickColor() QColor newColor = colorDialog.getColor(oldColor); if (newColor.isValid()) { setupColorButton(isForeground, newColor); + setUnsavedChanges(true); } } + +void EditEntryWidget::setUnsavedChanges(bool hasUnsaved) +{ + m_saved = !hasUnsaved; + enableApplyButton(hasUnsaved); +} diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h index 9b2a919c6..863d6505b 100644 --- a/src/gui/entry/EditEntryWidget.h +++ b/src/gui/entry/EditEntryWidget.h @@ -100,6 +100,7 @@ private slots: void useExpiryPreset(QAction* action); void toggleHideNotes(bool visible); void pickColor(); + void setUnsavedChanges(bool hasUnsaved = true); #ifdef WITH_XC_SSHAGENT void updateSSHAgent(); void updateSSHAgentAttachment(); @@ -122,6 +123,7 @@ private: #endif void setupProperties(); void setupHistory(); + void setupEntryUpdate(); void setupColorButton(bool foreground, const QColor& color); bool passwordsEqual(); diff --git a/src/gui/entry/EntryAttachmentsWidget.cpp b/src/gui/entry/EntryAttachmentsWidget.cpp index 78b6d741b..d420d9d65 100644 --- a/src/gui/entry/EntryAttachmentsWidget.cpp +++ b/src/gui/entry/EntryAttachmentsWidget.cpp @@ -147,6 +147,7 @@ void EntryAttachmentsWidget::insertAttachments() if (!insertAttachments(filenames, errorMessage)) { errorOccurred(errorMessage); } + emit widgetUpdated(); } void EntryAttachmentsWidget::removeSelectedAttachments() @@ -170,6 +171,7 @@ void EntryAttachmentsWidget::removeSelectedAttachments() keys.append(m_attachmentsModel->keyByIndex(index)); } m_entryAttachments->remove(keys); + emit widgetUpdated(); } } diff --git a/src/gui/entry/EntryAttachmentsWidget.h b/src/gui/entry/EntryAttachmentsWidget.h index 5fd19415e..590060ae1 100644 --- a/src/gui/entry/EntryAttachmentsWidget.h +++ b/src/gui/entry/EntryAttachmentsWidget.h @@ -39,6 +39,7 @@ signals: void errorOccurred(const QString& error); void readOnlyChanged(bool readOnly); void buttonsVisibleChanged(bool isButtonsVisible); + void widgetUpdated(); private slots: void insertAttachments(); diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 54203c284..c736ea485 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -492,6 +492,7 @@ void TestGui::testAddEntry() // Add entry "something 5" but click cancel button (does NOT add entry) QTest::mouseClick(entryNewWidget, Qt::LeftButton); QTest::keyClicks(titleEdit, "something 5"); + MessageBox::setNextAnswer(QMessageBox::Discard); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Cancel), Qt::LeftButton); QApplication::processEvents();