Correct issues with apply button

* Don't show apply button when creating new entries or groups (Fix #2191)
* Don't mark entry/group as dirty when first creating a new one (prevents unnecessary discard dialog on cancel)
* Properly enable/disable apply button when changes are made to entries and groups
* Don't show discard change warning when locking database unless their are actual changes made

NOTE: Extra pages in the group edit widget are not watched for changes yet. Requires a major refactor.
This commit is contained in:
Jonathan White 2019-04-07 09:56:25 -04:00
parent 71e375aff0
commit 4b1258f585
8 changed files with 155 additions and 89 deletions

View File

@ -261,12 +261,11 @@ bool DatabaseWidget::isSearchActive() const
bool DatabaseWidget::isEditWidgetModified() const bool DatabaseWidget::isEditWidgetModified() const
{ {
if (currentWidget() == m_editEntryWidget) { if (currentWidget() == m_editEntryWidget) {
return m_editEntryWidget->hasBeenModified(); return m_editEntryWidget->isModified();
} else { } else if (currentWidget() == m_editGroupWidget) {
// other edit widget don't have a hasBeenModified() method yet return m_editGroupWidget->isModified();
// assume that they already have been modified
return true;
} }
return false;
} }
QList<int> DatabaseWidget::mainSplitterSizes() const QList<int> DatabaseWidget::mainSplitterSizes() const
@ -1249,7 +1248,7 @@ bool DatabaseWidget::lock()
clipboard()->clearCopiedText(); clipboard()->clearCopiedText();
if (currentMode() == DatabaseWidget::Mode::EditMode) { if (isEditWidgetModified()) {
auto result = MessageBox::question(this, auto result = MessageBox::question(this,
tr("Lock Database?"), tr("Lock Database?"),
tr("You are editing an entry. Discard changes and lock anyway?"), tr("You are editing an entry. Discard changes and lock anyway?"),

View File

@ -30,6 +30,7 @@ EditWidget::EditWidget(QWidget* parent)
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
setReadOnly(false); setReadOnly(false);
setModified(false);
m_ui->messageWidget->setHidden(true); m_ui->messageWidget->setHidden(true);
@ -43,6 +44,7 @@ EditWidget::EditWidget(QWidget* parent)
connect(m_ui->buttonBox, SIGNAL(accepted()), SIGNAL(accepted())); connect(m_ui->buttonBox, SIGNAL(accepted()), SIGNAL(accepted()));
connect(m_ui->buttonBox, SIGNAL(rejected()), SIGNAL(rejected())); connect(m_ui->buttonBox, SIGNAL(rejected()), SIGNAL(rejected()));
connect(m_ui->buttonBox, SIGNAL(clicked(QAbstractButton*)), SLOT(buttonClicked(QAbstractButton*)));
} }
EditWidget::~EditWidget() EditWidget::~EditWidget()
@ -106,9 +108,6 @@ void EditWidget::setReadOnly(bool readOnly)
m_ui->buttonBox->setStandardButtons(QDialogButtonBox::Close); m_ui->buttonBox->setStandardButtons(QDialogButtonBox::Close);
} else { } else {
m_ui->buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply); m_ui->buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply);
// Find and connect the apply button
QPushButton* applyButton = m_ui->buttonBox->button(QDialogButtonBox::Apply);
connect(applyButton, SIGNAL(clicked()), SIGNAL(apply()));
} }
} }
@ -117,6 +116,17 @@ bool EditWidget::readOnly() const
return m_readOnly; return m_readOnly;
} }
void EditWidget::setModified(bool state)
{
m_modified = state;
enableApplyButton(state);
}
bool EditWidget::isModified() const
{
return m_modified;
}
void EditWidget::enableApplyButton(bool enabled) void EditWidget::enableApplyButton(bool enabled)
{ {
QPushButton* applyButton = m_ui->buttonBox->button(QDialogButtonBox::Apply); QPushButton* applyButton = m_ui->buttonBox->button(QDialogButtonBox::Apply);
@ -125,6 +135,27 @@ void EditWidget::enableApplyButton(bool enabled)
} }
} }
void EditWidget::showApplyButton(bool state)
{
if (!m_readOnly) {
auto buttons = m_ui->buttonBox->standardButtons();
if (state) {
buttons |= QDialogButtonBox::Apply;
} else {
buttons &= ~QDialogButtonBox::Apply;
}
m_ui->buttonBox->setStandardButtons(buttons);
}
}
void EditWidget::buttonClicked(QAbstractButton* button)
{
auto stdButton = m_ui->buttonBox->standardButton(button);
if (stdButton == QDialogButtonBox::Apply) {
emit apply();
}
}
void EditWidget::showMessage(const QString& text, MessageWidget::MessageType type) void EditWidget::showMessage(const QString& text, MessageWidget::MessageType type)
{ {
// Show error messages for a longer time to make sure the user can read them // Show error messages for a longer time to make sure the user can read them

View File

@ -49,6 +49,8 @@ public:
void setReadOnly(bool readOnly); void setReadOnly(bool readOnly);
bool readOnly() const; bool readOnly() const;
void enableApplyButton(bool enabled); void enableApplyButton(bool enabled);
void showApplyButton(bool state);
virtual bool isModified() const;
signals: signals:
void apply(); void apply();
@ -58,10 +60,13 @@ signals:
protected slots: protected slots:
void showMessage(const QString& text, MessageWidget::MessageType type); void showMessage(const QString& text, MessageWidget::MessageType type);
void hideMessage(); void hideMessage();
void setModified(bool state = true);
void buttonClicked(QAbstractButton* button);
private: private:
const QScopedPointer<Ui::EditWidget> m_ui; const QScopedPointer<Ui::EditWidget> m_ui;
bool m_readOnly; bool m_readOnly;
bool m_modified;
Q_DISABLE_COPY(EditWidget) Q_DISABLE_COPY(EditWidget)
}; };

View File

@ -276,55 +276,54 @@ void EditEntryWidget::setupHistory()
void EditEntryWidget::setupEntryUpdate() void EditEntryWidget::setupEntryUpdate()
{ {
// Entry tab // Entry tab
connect(m_mainUi->titleEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges())); connect(m_mainUi->titleEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_mainUi->usernameEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges())); connect(m_mainUi->usernameEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_mainUi->passwordEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges())); connect(m_mainUi->passwordEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_mainUi->passwordRepeatEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges())); connect(m_mainUi->passwordRepeatEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges())); connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
#ifdef WITH_XC_NETWORKING #ifdef WITH_XC_NETWORKING
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString))); connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString)));
#endif #endif
connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_mainUi->notesEnabled, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_mainUi->notesEnabled, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setUnsavedChanges())); connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setModified()));
connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setUnsavedChanges())); connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
// Advanced tab // Advanced tab
connect(m_advancedUi->attributesEdit, SIGNAL(textChanged()), this, SLOT(setUnsavedChanges())); connect(m_advancedUi->attributesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
connect(m_advancedUi->protectAttributeButton, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_advancedUi->protectAttributeButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_advancedUi->fgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_advancedUi->fgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_advancedUi->bgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_advancedUi->bgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_advancedUi->attachmentsWidget, SIGNAL(widgetUpdated()), this, SLOT(setUnsavedChanges())); connect(m_advancedUi->attachmentsWidget, SIGNAL(widgetUpdated()), this, SLOT(setModified()));
// Icon tab // Icon tab
connect(m_iconsWidget, SIGNAL(widgetUpdated()), this, SLOT(setUnsavedChanges())); connect(m_iconsWidget, SIGNAL(widgetUpdated()), this, SLOT(setModified()));
// Auto-Type tab // Auto-Type tab
connect(m_autoTypeUi->enableButton, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_autoTypeUi->enableButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_autoTypeUi->inheritSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setUnsavedChanges())); connect(m_autoTypeUi->inheritSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
connect(m_autoTypeUi->customSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setUnsavedChanges())); connect(m_autoTypeUi->customSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges())); connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_autoTypeUi->sequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges())); connect(m_autoTypeUi->sequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_autoTypeUi->windowTitleCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setModified()));
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)), this, SLOT(setUnsavedChanges())); connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)), this, SLOT(setModified()));
// Properties and History tabs don't need extra connections // Properties and History tabs don't need extra connections
#ifdef WITH_XC_SSHAGENT #ifdef WITH_XC_SSHAGENT
// SSH Agent tab // SSH Agent tab
if (config()->get("SSHAgent", false).toBool()) { if (config()->get("SSHAgent", false).toBool()) {
connect(m_sshAgentUi->attachmentRadioButton, SIGNAL(toggled(bool)), this, SLOT(setUnsavedChanges())); connect(m_sshAgentUi->attachmentRadioButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
connect(m_sshAgentUi->externalFileRadioButton, SIGNAL(toggled(bool)), this, SLOT(setUnsavedChanges())); connect(m_sshAgentUi->externalFileRadioButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
connect(m_sshAgentUi->attachmentComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_sshAgentUi->attachmentComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->attachmentComboBox, SIGNAL(editTextChanged(QString)), this, SLOT(setUnsavedChanges())); connect(m_sshAgentUi->attachmentComboBox, SIGNAL(editTextChanged(QString)), this, SLOT(setModified()));
connect(m_sshAgentUi->externalFileEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges())); connect(m_sshAgentUi->externalFileEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_sshAgentUi->addKeyToAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_sshAgentUi->addKeyToAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->removeKeyFromAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_sshAgentUi->removeKeyFromAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect( connect(m_sshAgentUi->requireUserConfirmationCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
m_sshAgentUi->requireUserConfirmationCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_sshAgentUi->lifetimeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->lifetimeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges())); connect(m_sshAgentUi->lifetimeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->lifetimeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setUnsavedChanges()));
} }
#endif #endif
} }
@ -703,8 +702,10 @@ void EditEntryWidget::loadEntry(Entry* entry,
setCurrentPage(0); setCurrentPage(0);
setPageHidden(m_historyWidget, m_history || m_entry->historyItems().count() < 1); setPageHidden(m_historyWidget, m_history || m_entry->historyItems().count() < 1);
// Force the user to Save/Apply/Discard new entries // Force the user to Save/Discard new entries
setUnsavedChanges(m_create); showApplyButton(!m_create);
setModified(false);
} }
void EditEntryWidget::setForms(Entry* entry, bool restore) void EditEntryWidget::setForms(Entry* entry, bool restore)
@ -881,7 +882,6 @@ bool EditEntryWidget::commitEntry()
} }
updateEntryData(m_entry); updateEntryData(m_entry);
setUnsavedChanges(false);
if (!m_create) { if (!m_create) {
m_entry->endUpdate(); m_entry->endUpdate();
@ -896,6 +896,7 @@ bool EditEntryWidget::commitEntry()
m_historyModel->setEntries(m_entry->historyItems()); m_historyModel->setEntries(m_entry->historyItems());
showMessage(tr("Entry updated successfully."), MessageWidget::Positive); showMessage(tr("Entry updated successfully."), MessageWidget::Positive);
setModified(false);
return true; return true;
} }
@ -968,7 +969,7 @@ void EditEntryWidget::cancel()
m_entry->setIcon(Entry::DefaultIconNumber); m_entry->setIcon(Entry::DefaultIconNumber);
} }
if (!m_saved) { if (isModified()) {
auto result = MessageBox::question(this, auto result = MessageBox::question(this,
QString(), QString(),
tr("Entry has unsaved changes"), tr("Entry has unsaved changes"),
@ -980,13 +981,13 @@ void EditEntryWidget::cancel()
} }
if (result == MessageBox::Save) { if (result == MessageBox::Save) {
commitEntry(); commitEntry();
m_saved = true; setModified(false);
} }
} }
clear(); clear();
emit editFinished(m_saved); emit editFinished(!isModified());
} }
void EditEntryWidget::clear() void EditEntryWidget::clear()
@ -1008,22 +1009,6 @@ void EditEntryWidget::clear()
hideMessage(); hideMessage();
} }
bool EditEntryWidget::hasBeenModified() const
{
// entry has been modified if a history item is to be deleted
if (!m_historyModel->deletedEntries().isEmpty()) {
return true;
}
// check if updating the entry would modify it
auto* entry = new Entry();
entry->copyDataFrom(m_entry.data());
entry->beginUpdate();
updateEntryData(entry);
return entry->endUpdate();
}
void EditEntryWidget::togglePasswordGeneratorButton(bool checked) void EditEntryWidget::togglePasswordGeneratorButton(bool checked)
{ {
if (checked) { if (checked) {
@ -1070,7 +1055,7 @@ void EditEntryWidget::insertAttribute()
m_advancedUi->attributesView->setCurrentIndex(index); m_advancedUi->attributesView->setCurrentIndex(index);
m_advancedUi->attributesView->edit(index); m_advancedUi->attributesView->edit(index);
setUnsavedChanges(true); setModified(true);
} }
void EditEntryWidget::editCurrentAttribute() void EditEntryWidget::editCurrentAttribute()
@ -1081,7 +1066,7 @@ void EditEntryWidget::editCurrentAttribute()
if (index.isValid()) { if (index.isValid()) {
m_advancedUi->attributesView->edit(index); m_advancedUi->attributesView->edit(index);
setUnsavedChanges(true); setModified(true);
} }
} }
@ -1101,7 +1086,7 @@ void EditEntryWidget::removeCurrentAttribute()
if (result == MessageBox::Remove) { if (result == MessageBox::Remove) {
m_entryAttributes->remove(m_attributesModel->keyByIndex(index)); m_entryAttributes->remove(m_attributesModel->keyByIndex(index));
setUnsavedChanges(true); setModified(true);
} }
} }
} }
@ -1223,7 +1208,7 @@ void EditEntryWidget::insertAutoTypeAssoc()
m_autoTypeUi->assocView->setCurrentIndex(newIndex); m_autoTypeUi->assocView->setCurrentIndex(newIndex);
loadCurrentAssoc(newIndex); loadCurrentAssoc(newIndex);
m_autoTypeUi->windowTitleCombo->setFocus(); m_autoTypeUi->windowTitleCombo->setFocus();
setUnsavedChanges(true); setModified(true);
} }
void EditEntryWidget::removeAutoTypeAssoc() void EditEntryWidget::removeAutoTypeAssoc()
@ -1232,7 +1217,7 @@ void EditEntryWidget::removeAutoTypeAssoc()
if (currentIndex.isValid()) { if (currentIndex.isValid()) {
m_autoTypeAssoc->remove(currentIndex.row()); m_autoTypeAssoc->remove(currentIndex.row());
setUnsavedChanges(true); setModified(true);
} }
} }
@ -1295,7 +1280,7 @@ void EditEntryWidget::restoreHistoryEntry()
QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex()); QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex());
if (index.isValid()) { if (index.isValid()) {
setForms(m_historyModel->entryFromIndex(index), true); setForms(m_historyModel->entryFromIndex(index), true);
setUnsavedChanges(true); setModified(true);
} }
} }
@ -1309,7 +1294,7 @@ void EditEntryWidget::deleteHistoryEntry()
} else { } else {
m_historyUi->deleteAllButton->setEnabled(false); m_historyUi->deleteAllButton->setEnabled(false);
} }
setUnsavedChanges(true); setModified(true);
} }
} }
@ -1317,7 +1302,7 @@ void EditEntryWidget::deleteAllHistoryEntries()
{ {
m_historyModel->deleteAll(); m_historyModel->deleteAll();
m_historyUi->deleteAllButton->setEnabled(m_historyModel->rowCount() > 0); m_historyUi->deleteAllButton->setEnabled(m_historyModel->rowCount() > 0);
setUnsavedChanges(true); setModified(true);
} }
QMenu* EditEntryWidget::createPresetsMenu() QMenu* EditEntryWidget::createPresetsMenu()
@ -1370,12 +1355,6 @@ void EditEntryWidget::pickColor()
QColor newColor = QColorDialog::getColor(oldColor); QColor newColor = QColorDialog::getColor(oldColor);
if (newColor.isValid()) { if (newColor.isValid()) {
setupColorButton(isForeground, newColor); setupColorButton(isForeground, newColor);
setUnsavedChanges(true); setModified(true);
} }
} }
void EditEntryWidget::setUnsavedChanges(bool hasUnsaved)
{
m_saved = !hasUnsaved;
enableApplyButton(hasUnsaved);
}

View File

@ -68,7 +68,6 @@ public:
QString entryTitle() const; QString entryTitle() const;
void clear(); void clear();
bool hasBeenModified() const;
signals: signals:
void editFinished(bool accepted); void editFinished(bool accepted);
@ -106,7 +105,6 @@ private slots:
void useExpiryPreset(QAction* action); void useExpiryPreset(QAction* action);
void toggleHideNotes(bool visible); void toggleHideNotes(bool visible);
void pickColor(); void pickColor();
void setUnsavedChanges(bool hasUnsaved = true);
#ifdef WITH_XC_SSHAGENT #ifdef WITH_XC_SSHAGENT
void updateSSHAgent(); void updateSSHAgent();
void updateSSHAgentAttachment(); void updateSSHAgentAttachment();
@ -148,7 +146,6 @@ private:
bool m_create; bool m_create;
bool m_history; bool m_history;
bool m_saved;
#ifdef WITH_XC_SSHAGENT #ifdef WITH_XC_SSHAGENT
bool m_sshAgentEnabled; bool m_sshAgentEnabled;
KeeAgentSettings m_sshAgentSettings; KeeAgentSettings m_sshAgentSettings;

View File

@ -22,6 +22,7 @@
#include "core/Metadata.h" #include "core/Metadata.h"
#include "gui/EditWidgetIcons.h" #include "gui/EditWidgetIcons.h"
#include "gui/EditWidgetProperties.h" #include "gui/EditWidgetProperties.h"
#include "gui/MessageBox.h"
#if defined(WITH_XC_KEESHARE) #if defined(WITH_XC_KEESHARE)
#include "keeshare/group/EditGroupPageKeeShare.h" #include "keeshare/group/EditGroupPageKeeShare.h"
@ -46,6 +47,11 @@ public:
editPage->assign(widget); editPage->assign(widget);
} }
QWidget* getWidget()
{
return widget;
}
private: private:
QSharedPointer<IEditGroupPage> editPage; QSharedPointer<IEditGroupPage> editPage;
QWidget* widget; QWidget* widget;
@ -85,18 +91,38 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
// clang-format on // clang-format on
connect(m_editGroupWidgetIcons, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage())); connect(m_editGroupWidgetIcons, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
setupModifiedTracking();
} }
EditGroupWidget::~EditGroupWidget() EditGroupWidget::~EditGroupWidget()
{ {
} }
void EditGroupWidget::setupModifiedTracking()
{
// Group tab
connect(m_mainUi->editName, SIGNAL(textChanged(QString)), SLOT(setModified()));
connect(m_mainUi->editNotes, SIGNAL(textChanged()), SLOT(setModified()));
connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), SLOT(setModified()));
connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), SLOT(setModified()));
connect(m_mainUi->searchComboBox, SIGNAL(currentIndexChanged(int)), SLOT(setModified()));
connect(m_mainUi->autotypeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(setModified()));
connect(m_mainUi->autoTypeSequenceInherit, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_mainUi->autoTypeSequenceCustomRadio, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_mainUi->autoTypeSequenceCustomEdit, SIGNAL(textChanged(QString)), SLOT(setModified()));
// Icon tab
connect(m_editGroupWidgetIcons, SIGNAL(widgetUpdated()), SLOT(setModified()));
}
void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<Database>& database) void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<Database>& database)
{ {
m_group = group; m_group = group;
m_db = database; m_db = database;
m_temporaryGroup.reset(group->clone(Entry::CloneNoFlags, Group::CloneNoFlags)); m_temporaryGroup.reset(group->clone(Entry::CloneNoFlags, Group::CloneNoFlags));
connect(m_temporaryGroup->customData(), SIGNAL(customDataModified()), SLOT(setModified()));
if (create) { if (create) {
setHeadline(tr("Add group")); setHeadline(tr("Add group"));
@ -139,6 +165,11 @@ void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<
setCurrentPage(0); setCurrentPage(0);
m_mainUi->editName->setFocus(); m_mainUi->editName->setFocus();
// Force the user to Save/Discard new groups
showApplyButton(!create);
setModified(false);
} }
void EditGroupWidget::save() void EditGroupWidget::save()
@ -180,6 +211,8 @@ void EditGroupWidget::apply()
// Icons add/remove are applied globally outside the transaction! // Icons add/remove are applied globally outside the transaction!
m_group->copyDataFrom(m_temporaryGroup.data()); m_group->copyDataFrom(m_temporaryGroup.data());
setModified(false);
} }
void EditGroupWidget::cancel() void EditGroupWidget::cancel()
@ -188,6 +221,18 @@ void EditGroupWidget::cancel()
m_group->setIcon(Entry::DefaultIconNumber); m_group->setIcon(Entry::DefaultIconNumber);
} }
if (isModified()) {
auto result = MessageBox::question(this,
QString(),
tr("Entry has unsaved changes"),
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
MessageBox::Cancel);
if (result == MessageBox::Save) {
apply();
setModified(false);
}
}
clear(); clear();
emit editFinished(false); emit editFinished(false);
} }

View File

@ -74,6 +74,7 @@ private:
void addTriStateItems(QComboBox* comboBox, bool inheritValue); void addTriStateItems(QComboBox* comboBox, bool inheritValue);
int indexFromTriState(Group::TriState triState); int indexFromTriState(Group::TriState triState);
Group::TriState triStateFromIndex(int index); Group::TriState triStateFromIndex(int index);
void setupModifiedTracking();
const QScopedPointer<Ui::EditGroupWidgetMain> m_mainUi; const QScopedPointer<Ui::EditGroupWidgetMain> m_mainUi;

View File

@ -429,13 +429,20 @@ void TestGui::testEditEntry()
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit"); auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
QTest::keyClicks(titleEdit, "_test"); QTest::keyClicks(titleEdit, "_test");
// Apply the edit
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox"); auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
QVERIFY(editEntryWidgetButtonBox); QVERIFY(editEntryWidgetButtonBox);
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton); auto* okButton = editEntryWidgetButtonBox->button(QDialogButtonBox::Ok);
QVERIFY(okButton);
auto* applyButton = editEntryWidgetButtonBox->button(QDialogButtonBox::Apply);
QVERIFY(applyButton);
// Apply the edit
QTRY_VERIFY(applyButton->isEnabled());
QTest::mouseClick(applyButton, Qt::LeftButton);
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
QCOMPARE(entry->title(), QString("Sample Entry_test")); QCOMPARE(entry->title(), QString("Sample Entry_test"));
QCOMPARE(entry->historyItems().size(), ++editCount); QCOMPARE(entry->historyItems().size(), ++editCount);
QVERIFY(!applyButton->isEnabled());
// Test entry colors (simulate choosing a color) // Test entry colors (simulate choosing a color)
editEntryWidget->setCurrentPage(1); editEntryWidget->setCurrentPage(1);
@ -451,7 +458,7 @@ void TestGui::testEditEntry()
colorCheckBox = editEntryWidget->findChild<QCheckBox*>("bgColorCheckBox"); colorCheckBox = editEntryWidget->findChild<QCheckBox*>("bgColorCheckBox");
colorButton->setProperty("color", bgColor); colorButton->setProperty("color", bgColor);
colorCheckBox->setChecked(true); colorCheckBox->setChecked(true);
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton); QTest::mouseClick(applyButton, Qt::LeftButton);
QCOMPARE(entry->historyItems().size(), ++editCount); QCOMPARE(entry->historyItems().size(), ++editCount);
// Test protected attributes // Test protected attributes
@ -471,7 +478,7 @@ void TestGui::testEditEntry()
auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit"); auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
QString originalPassword = passwordEdit->text(); QString originalPassword = passwordEdit->text();
passwordEdit->setText("newpass"); passwordEdit->setText("newpass");
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); QTest::mouseClick(okButton, Qt::LeftButton);
auto* messageWiget = editEntryWidget->findChild<MessageWidget*>("messageWidget"); auto* messageWiget = editEntryWidget->findChild<MessageWidget*>("messageWidget");
QTRY_VERIFY(messageWiget->isVisible()); QTRY_VERIFY(messageWiget->isVisible());
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
@ -479,7 +486,7 @@ void TestGui::testEditEntry()
passwordEdit->setText(originalPassword); passwordEdit->setText(originalPassword);
// Save the edit (press OK) // Save the edit (press OK)
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); QTest::mouseClick(okButton, Qt::LeftButton);
QApplication::processEvents(); QApplication::processEvents();
// Confirm edit was made // Confirm edit was made
@ -496,13 +503,15 @@ void TestGui::testEditEntry()
// Test copy & paste newline sanitization // Test copy & paste newline sanitization
QTest::mouseClick(entryEditWidget, Qt::LeftButton); QTest::mouseClick(entryEditWidget, Qt::LeftButton);
okButton = editEntryWidgetButtonBox->button(QDialogButtonBox::Ok);
QVERIFY(okButton);
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
titleEdit->setText("multiline\ntitle"); titleEdit->setText("multiline\ntitle");
editEntryWidget->findChild<QLineEdit*>("usernameEdit")->setText("multiline\nusername"); editEntryWidget->findChild<QLineEdit*>("usernameEdit")->setText("multiline\nusername");
editEntryWidget->findChild<QLineEdit*>("passwordEdit")->setText("multiline\npassword"); editEntryWidget->findChild<QLineEdit*>("passwordEdit")->setText("multiline\npassword");
editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit")->setText("multiline\npassword"); editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit")->setText("multiline\npassword");
editEntryWidget->findChild<QLineEdit*>("urlEdit")->setText("multiline\nurl"); editEntryWidget->findChild<QLineEdit*>("urlEdit")->setText("multiline\nurl");
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); QTest::mouseClick(okButton, Qt::LeftButton);
QCOMPARE(entry->title(), QString("multiline title")); QCOMPARE(entry->title(), QString("multiline title"));
QCOMPARE(entry->username(), QString("multiline username")); QCOMPARE(entry->username(), QString("multiline username"));