Grey out Apply button when there are no changes

Resolves #1313

What this commit does:
* Whenever the Apply button is pressed, and if the save was successful, then the Apply button is disabled.
* Each subwidget used by EditEntryWidget has now a signal called `widgetUpdated` that is emitted when the widgets' internal content changes. The EditEntryWidget subscribes to that signal to know when to enable the Apply button (by calling `entryUpdated()`).
* There are some views that are not isolated in their own widgets (`m_advancedUi`, for example) so in those cases I invoked `entryUpdated()` directly whenever I detected an update:
  * some updates occur directly in a Qt widget like when editing the text of a QLineItem, so in that case I connected the widget's signals directly to the `entryUpdated()` slot.
  * some updates occur in EditEntryWidget, so in those cases the invocation to `entryUpdated()` is made as soon as the change is detected (for example when the user has confirmed an action in a dialog).

A known problem: there are some situations when the Apply button will get enabled even if there are no changes, this is because the app changes the value of a field by itself so it's considered an update (for example, clicking on the "Reveal" button changes the text shown in a text field).
The solution to this can be a bit complicated: disabling temporarily the `entryUpdated()` whenever the app is going to do an action with such side-effects.
So I preferred to let the Apply button get enabled in those cases.
This commit is contained in:
Daniel Wilches 2018-03-10 19:31:43 -08:00
parent e92d5e80ee
commit 78ef6f0d04
8 changed files with 102 additions and 2 deletions

View File

@ -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);

View File

@ -47,6 +47,7 @@ public:
QLabel* headlineLabel();
void setReadOnly(bool readOnly);
bool readOnly() const;
void enableApplyButton(bool enabled);
signals:
void apply();

View File

@ -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();
}
}
}

View File

@ -62,6 +62,7 @@ public slots:
signals:
void messageEditEntry(QString, MessageWidget::MessageType);
void messageEditEntryDismiss();
void widgetUpdated();
private slots:
void downloadFavicon();

View File

@ -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();
@ -940,6 +996,8 @@ void EditEntryWidget::insertAttribute()
m_advancedUi->attributesView->setCurrentIndex(index);
m_advancedUi->attributesView->edit(index);
setUnsavedChanges(true);
}
void EditEntryWidget::editCurrentAttribute()
@ -950,6 +1008,7 @@ void EditEntryWidget::editCurrentAttribute()
if (index.isValid()) {
m_advancedUi->attributesView->edit(index);
setUnsavedChanges(true);
}
}
@ -963,6 +1022,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 +1107,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 +1145,7 @@ void EditEntryWidget::insertAutoTypeAssoc()
m_autoTypeUi->assocView->setCurrentIndex(newIndex);
loadCurrentAssoc(newIndex);
m_autoTypeUi->windowTitleCombo->setFocus();
setUnsavedChanges(true);
}
void EditEntryWidget::removeAutoTypeAssoc()
@ -1091,6 +1154,7 @@ void EditEntryWidget::removeAutoTypeAssoc()
if (currentIndex.isValid()) {
m_autoTypeAssoc->remove(currentIndex.row());
setUnsavedChanges(true);
}
}
@ -1153,6 +1217,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 +1231,7 @@ void EditEntryWidget::deleteHistoryEntry()
} else {
m_historyUi->deleteAllButton->setEnabled(false);
}
setUnsavedChanges(true);
}
}
@ -1178,6 +1244,7 @@ void EditEntryWidget::deleteAllHistoryEntries()
else {
m_historyUi->deleteAllButton->setEnabled(false);
}
setUnsavedChanges(true);
}
QMenu* EditEntryWidget::createPresetsMenu()
@ -1229,5 +1296,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);
}

View File

@ -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();

View File

@ -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();
}
}

View File

@ -39,6 +39,7 @@ signals:
void errorOccurred(const QString& error);
void readOnlyChanged(bool readOnly);
void buttonsVisibleChanged(bool isButtonsVisible);
void widgetUpdated();
private slots:
void insertAttachments();