keepassxc/src/gui/entry/EditEntryWidget.cpp

1560 lines
60 KiB
C++
Raw Normal View History

2010-10-06 17:40:50 +00:00
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
2010-10-06 17:40:50 +00:00
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "EditEntryWidget.h"
#include "ui_EditEntryWidgetAdvanced.h"
#include "ui_EditEntryWidgetAutoType.h"
#include "ui_EditEntryWidgetBrowser.h"
2012-05-15 18:12:05 +00:00
#include "ui_EditEntryWidgetHistory.h"
2010-10-06 17:40:50 +00:00
#include "ui_EditEntryWidgetMain.h"
2018-03-31 20:01:30 +00:00
#include "ui_EditEntryWidgetSSHAgent.h"
2010-10-06 17:40:50 +00:00
2018-03-31 20:01:30 +00:00
#include <QColorDialog>
#include <QDesktopServices>
#include <QSortFilterProxyModel>
#include <QStringListModel>
2017-11-07 16:58:08 +00:00
#include "autotype/AutoType.h"
2021-07-12 02:10:29 +00:00
#include "core/AutoTypeAssociations.h"
#include "core/Clock.h"
#include "core/Config.h"
2012-05-20 21:08:23 +00:00
#include "core/Database.h"
2010-10-06 17:40:50 +00:00
#include "core/Entry.h"
2021-07-12 02:10:29 +00:00
#include "core/EntryAttributes.h"
#include "core/Metadata.h"
#include "core/TimeDelta.h"
2017-10-29 15:17:24 +00:00
#ifdef WITH_XC_SSHAGENT
Replace all crypto libraries with Botan Selected the [Botan crypto library](https://github.com/randombit/botan) due to its feature list, maintainer support, availability across all deployment platforms, and ease of use. Also evaluated Crypto++ as a viable candidate, but the additional features of Botan (PKCS#11, TPM, etc) won out. The random number generator received a backend upgrade. Botan prefers hardware-based RNG's and will provide one if available. This is transparent to KeePassXC and a significant improvement over gcrypt. Replaced Argon2 library with built-in Botan implementation that supports i, d, and id. This requires Botan 2.11.0 or higher. Also simplified the parameter test across KDF's. Aligned SymmetricCipher parameters with available modes. All encrypt and decrypt operations are done in-place instead of returning new objects. This allows use of secure vectors in the future with no additional overhead. Took this opportunity to decouple KeeShare from SSH Agent. Removed leftover code from OpenSSHKey and consolidated the SSH Agent code into the same directory. Removed bcrypt and blowfish inserts since they are provided by Botan. Additionally simplified KeeShare settings interface by removing raw certificate byte data from the user interface. KeeShare will be further refactored in a future PR. NOTE: This PR breaks backwards compatibility with KeeShare certificates due to different RSA key storage with Botan. As a result, new "own" certificates will need to be generated and trust re-established. Removed YKChallengeResponseKeyCLI in favor of just using the original implementation with signal/slots. Removed TestRandom stub since it was just faking random numbers and not actually using the backend. TestRandomGenerator now uses the actual RNG. Greatly simplified Secret Service plugin's use of crypto functions with Botan.
2021-04-04 12:56:00 +00:00
#include "sshagent/OpenSSHKey.h"
2017-10-29 15:17:24 +00:00
#include "sshagent/SSHAgent.h"
#endif
#ifdef WITH_XC_BROWSER
#include "EntryURLModel.h"
#include "browser/BrowserService.h"
#endif
2018-03-31 20:01:30 +00:00
#include "gui/Clipboard.h"
#include "gui/EditWidgetIcons.h"
2012-10-29 21:41:37 +00:00
#include "gui/EditWidgetProperties.h"
#include "gui/FileDialog.h"
2017-11-25 09:12:31 +00:00
#include "gui/Font.h"
#include "gui/Icons.h"
2018-03-31 20:01:30 +00:00
#include "gui/MessageBox.h"
#include "gui/entry/AutoTypeAssociationsModel.h"
#include "gui/entry/EntryAttributesModel.h"
2012-05-15 18:12:05 +00:00
#include "gui/entry/EntryHistoryModel.h"
2010-10-06 17:40:50 +00:00
EditEntryWidget::EditEntryWidget(QWidget* parent)
: EditWidget(parent)
2015-07-24 16:28:12 +00:00
, m_entry(nullptr)
2010-10-06 17:40:50 +00:00
, m_mainUi(new Ui::EditEntryWidgetMain())
, m_advancedUi(new Ui::EditEntryWidgetAdvanced())
, m_autoTypeUi(new Ui::EditEntryWidgetAutoType())
2017-10-29 15:17:24 +00:00
, m_sshAgentUi(new Ui::EditEntryWidgetSSHAgent())
2012-05-15 18:12:05 +00:00
, m_historyUi(new Ui::EditEntryWidgetHistory())
, m_browserUi(new Ui::EditEntryWidgetBrowser())
, m_attachments(new EntryAttachments())
, m_customData(new CustomData())
, m_mainWidget(new QScrollArea())
, m_advancedWidget(new QWidget())
, m_iconsWidget(new EditWidgetIcons())
, m_autoTypeWidget(new QWidget())
#ifdef WITH_XC_SSHAGENT
2017-10-29 15:17:24 +00:00
, m_sshAgentWidget(new QWidget())
#endif
#ifdef WITH_XC_BROWSER
, m_browserSettingsChanged(false)
, m_browserWidget(new QWidget())
, m_additionalURLsDataModel(new EntryURLModel(this))
#endif
2012-10-29 21:41:37 +00:00
, m_editWidgetProperties(new EditWidgetProperties())
2012-05-15 18:12:05 +00:00
, m_historyWidget(new QWidget())
2012-07-16 15:47:21 +00:00
, m_entryAttributes(new EntryAttributes(this))
, m_attributesModel(new EntryAttributesModel(m_advancedWidget))
, m_historyModel(new EntryHistoryModel(this))
, m_sortModel(new QSortFilterProxyModel(this))
, m_autoTypeAssoc(new AutoTypeAssociations(this))
, m_autoTypeAssocModel(new AutoTypeAssociationsModel(this))
, m_autoTypeDefaultSequenceGroup(new QButtonGroup(this))
, m_autoTypeWindowSequenceGroup(new QButtonGroup(this))
, m_usernameCompleter(new QCompleter(this))
, m_usernameCompleterModel(new QStringListModel(this))
2012-10-29 22:11:42 +00:00
{
setupMain();
setupAdvanced();
setupIcon();
setupAutoType();
2017-10-29 15:17:24 +00:00
#ifdef WITH_XC_SSHAGENT
setupSSHAgent();
2017-10-29 15:17:24 +00:00
#endif
#ifdef WITH_XC_BROWSER
setupBrowser();
#endif
2012-10-29 22:11:42 +00:00
setupProperties();
setupHistory();
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.
2018-03-11 03:31:43 +00:00
setupEntryUpdate();
2012-10-29 22:11:42 +00:00
m_entryModifiedTimer.setSingleShot(true);
m_entryModifiedTimer.setInterval(0);
connect(&m_entryModifiedTimer, &QTimer::timeout, this, [this] {
if (isVisible() && m_entry) {
setForms(m_entry);
}
});
connect(this, SIGNAL(accepted()), SLOT(acceptEntry()));
2012-10-29 22:11:42 +00:00
connect(this, SIGNAL(rejected()), SLOT(cancel()));
connect(this, SIGNAL(apply()), SLOT(commitEntry()));
// clang-format off
2018-03-31 20:01:30 +00:00
connect(m_iconsWidget,
SIGNAL(messageEditEntry(QString,MessageWidget::MessageType)),
SLOT(showMessage(QString,MessageWidget::MessageType)));
// clang-format on
connect(m_iconsWidget, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
2018-03-31 20:01:30 +00:00
m_editWidgetProperties->setCustomData(m_customData.data());
2012-10-29 22:11:42 +00:00
}
EditEntryWidget::~EditEntryWidget()
{
}
void EditEntryWidget::setupMain()
2010-10-06 17:40:50 +00:00
{
m_mainUi->setupUi(m_mainWidget);
addPage(tr("Entry"), icons()->icon("document-edit"), m_mainWidget);
2010-10-06 17:40:50 +00:00
m_mainUi->usernameComboBox->setEditable(true);
m_usernameCompleter->setCompletionMode(QCompleter::InlineCompletion);
m_usernameCompleter->setCaseSensitivity(Qt::CaseSensitive);
m_usernameCompleter->setModel(m_usernameCompleterModel);
m_mainUi->usernameComboBox->setCompleter(m_usernameCompleter);
#ifdef WITH_XC_NETWORKING
m_mainUi->fetchFaviconButton->setIcon(icons()->icon("favicon-download"));
m_mainUi->fetchFaviconButton->setDisabled(true);
#else
m_mainUi->fetchFaviconButton->setVisible(false);
#endif
#ifdef WITH_XC_NETWORKING
connect(m_mainUi->fetchFaviconButton, SIGNAL(clicked()), m_iconsWidget, SLOT(downloadFavicon()));
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
2019-11-22 11:54:28 +00:00
m_mainUi->urlEdit->enableVerifyMode();
#endif
connect(m_mainUi->expireCheck, &QCheckBox::toggled, [&](bool enabled) {
m_mainUi->expireDatePicker->setEnabled(enabled);
if (enabled) {
m_mainUi->expireDatePicker->setDateTime(Clock::currentDateTime());
}
});
connect(m_mainUi->notesEnabled, SIGNAL(toggled(bool)), this, SLOT(toggleHideNotes(bool)));
2012-10-29 22:11:42 +00:00
m_mainUi->expirePresets->setMenu(createPresetsMenu());
connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*)));
}
void EditEntryWidget::setupAdvanced()
{
m_advancedUi->setupUi(m_advancedWidget);
addPage(tr("Advanced"), icons()->icon("preferences-other"), m_advancedWidget);
m_advancedUi->attachmentsWidget->setReadOnly(false);
m_advancedUi->attachmentsWidget->setButtonsVisible(true);
2018-03-31 20:01:30 +00:00
connect(m_advancedUi->attachmentsWidget,
&EntryAttachmentsWidget::errorOccurred,
this,
[this](const QString& error) { showMessage(error, MessageWidget::Error); });
m_attributesModel->setEntryAttributes(m_entryAttributes);
m_advancedUi->attributesView->setModel(m_attributesModel);
// clang-format off
connect(m_advancedUi->addAttributeButton, SIGNAL(clicked()), SLOT(insertAttribute()));
connect(m_advancedUi->editAttributeButton, SIGNAL(clicked()), SLOT(editCurrentAttribute()));
connect(m_advancedUi->removeAttributeButton, SIGNAL(clicked()), SLOT(removeCurrentAttribute()));
connect(m_advancedUi->protectAttributeButton, SIGNAL(toggled(bool)), SLOT(protectCurrentAttribute(bool)));
connect(m_advancedUi->revealAttributeButton, SIGNAL(clicked(bool)), SLOT(toggleCurrentAttributeVisibility()));
connect(m_advancedUi->attributesView->selectionModel(),
SIGNAL(currentChanged(QModelIndex,QModelIndex)),
SLOT(updateCurrentAttribute()));
connect(m_advancedUi->fgColorButton, SIGNAL(clicked()), SLOT(pickColor()));
connect(m_advancedUi->bgColorButton, SIGNAL(clicked()), SLOT(pickColor()));
// clang-format on
2012-10-29 22:11:42 +00:00
}
2012-10-29 22:11:42 +00:00
void EditEntryWidget::setupIcon()
{
m_iconsWidget->setShowApplyIconToButton(false);
addPage(tr("Icon"), icons()->icon("preferences-desktop-icons"), m_iconsWidget);
connect(this, SIGNAL(accepted()), m_iconsWidget, SLOT(abortRequests()));
connect(this, SIGNAL(rejected()), m_iconsWidget, SLOT(abortRequests()));
2012-10-29 22:11:42 +00:00
}
void EditEntryWidget::openAutotypeHelp()
{
QDesktopServices::openUrl(
QUrl("https://keepassxc.org/docs/KeePassXC_UserGuide.html#_configure_auto_type_sequences"));
}
2012-10-29 22:11:42 +00:00
void EditEntryWidget::setupAutoType()
{
m_autoTypeUi->setupUi(m_autoTypeWidget);
addPage(tr("Auto-Type"), icons()->icon("key-enter"), m_autoTypeWidget);
m_autoTypeUi->openHelpButton->setIcon(icons()->icon("system-help"));
m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->inheritSequenceButton);
m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->customSequenceButton);
m_autoTypeAssocModel->setAutoTypeAssociations(m_autoTypeAssoc);
m_autoTypeUi->assocView->setModel(m_autoTypeAssocModel);
m_autoTypeUi->assocView->setColumnHidden(1, true);
// clang-format off
connect(m_autoTypeUi->enableButton, SIGNAL(toggled(bool)), SLOT(updateAutoTypeEnabled()));
connect(m_autoTypeUi->customSequenceButton, SIGNAL(toggled(bool)),
m_autoTypeUi->sequenceEdit, SLOT(setEnabled(bool)));
connect(m_autoTypeUi->customSequenceButton, SIGNAL(toggled(bool)),
m_autoTypeUi->openHelpButton, SLOT(setEnabled(bool)));
connect(m_autoTypeUi->openHelpButton, SIGNAL(clicked()), SLOT(openAutotypeHelp()));
connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(toggled(bool)),
m_autoTypeUi->windowSequenceEdit, SLOT(setEnabled(bool)));
connect(m_autoTypeUi->assocAddButton, SIGNAL(clicked()), SLOT(insertAutoTypeAssoc()));
connect(m_autoTypeUi->assocRemoveButton, SIGNAL(clicked()), SLOT(removeAutoTypeAssoc()));
2018-03-31 20:01:30 +00:00
connect(m_autoTypeUi->assocView->selectionModel(),
SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
SLOT(updateAutoTypeEnabled()));
2018-03-31 20:01:30 +00:00
connect(m_autoTypeUi->assocView->selectionModel(),
SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
SLOT(loadCurrentAssoc(QModelIndex)));
connect(m_autoTypeAssocModel, SIGNAL(modelReset()), SLOT(updateAutoTypeEnabled()));
connect(m_autoTypeAssocModel, SIGNAL(modelReset()), SLOT(clearCurrentAssoc()));
2018-03-31 20:01:30 +00:00
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)), SLOT(applyCurrentAssoc()));
connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(toggled(bool)), SLOT(applyCurrentAssoc()));
connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)), SLOT(applyCurrentAssoc()));
// clang-format on
2012-10-29 22:11:42 +00:00
}
#ifdef WITH_XC_BROWSER
void EditEntryWidget::setupBrowser()
{
if (config()->get(Config::Browser_Enabled).toBool()) {
m_browserUi->setupUi(m_browserWidget);
addPage(tr("Browser Integration"), icons()->icon("internet-web-browser"), m_browserWidget);
m_additionalURLsDataModel->setEntryAttributes(m_entryAttributes);
m_browserUi->additionalURLsView->setModel(m_additionalURLsDataModel);
2019-11-22 11:54:28 +00:00
// Use a custom item delegate to align the icon to the right side
auto iconDelegate = new URLModelIconDelegate(m_browserUi->additionalURLsView);
m_browserUi->additionalURLsView->setItemDelegate(iconDelegate);
// clang-format off
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
connect(m_browserUi->onlyHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
connect(m_browserUi->notHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
connect(m_browserUi->addURLButton, SIGNAL(clicked()), SLOT(insertURL()));
connect(m_browserUi->removeURLButton, SIGNAL(clicked()), SLOT(removeCurrentURL()));
connect(m_browserUi->editURLButton, SIGNAL(clicked()), SLOT(editCurrentURL()));
connect(m_browserUi->additionalURLsView->selectionModel(),
SIGNAL(currentChanged(QModelIndex,QModelIndex)),
SLOT(updateCurrentURL()));
connect(m_additionalURLsDataModel,
SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
SLOT(updateCurrentAttribute()));
// clang-format on
}
}
void EditEntryWidget::updateBrowserModified()
{
m_browserSettingsChanged = true;
}
void EditEntryWidget::updateBrowser()
{
if (!m_browserSettingsChanged) {
return;
}
auto skip = m_browserUi->skipAutoSubmitCheckbox->isChecked();
auto hide = m_browserUi->hideEntryCheckbox->isChecked();
auto onlyHttpAuth = m_browserUi->onlyHttpAuthCheckbox->isChecked();
auto notHttpAuth = m_browserUi->notHttpAuthCheckbox->isChecked();
m_customData->set(BrowserService::OPTION_SKIP_AUTO_SUBMIT, (skip ? TRUE_STR : FALSE_STR));
m_customData->set(BrowserService::OPTION_HIDE_ENTRY, (hide ? TRUE_STR : FALSE_STR));
m_customData->set(BrowserService::OPTION_ONLY_HTTP_AUTH, (onlyHttpAuth ? TRUE_STR : FALSE_STR));
m_customData->set(BrowserService::OPTION_NOT_HTTP_AUTH, (notHttpAuth ? TRUE_STR : FALSE_STR));
}
void EditEntryWidget::insertURL()
{
Q_ASSERT(!m_history);
QString name("KP2A_URL");
int i = 1;
while (m_entryAttributes->keys().contains(name)) {
name = QString("KP2A_URL_%1").arg(i);
i++;
}
m_entryAttributes->set(name, tr("<empty URL>"));
QModelIndex index = m_additionalURLsDataModel->indexByKey(name);
m_browserUi->additionalURLsView->setCurrentIndex(index);
m_browserUi->additionalURLsView->edit(index);
setModified(true);
}
void EditEntryWidget::removeCurrentURL()
{
Q_ASSERT(!m_history);
QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
if (index.isValid()) {
auto name = m_additionalURLsDataModel->keyByIndex(index);
auto url = m_entryAttributes->value(name);
if (url != tr("<empty URL>")) {
auto result = MessageBox::question(this,
tr("Confirm Removal"),
tr("Are you sure you want to remove this URL?"),
MessageBox::Remove | MessageBox::Cancel,
MessageBox::Cancel);
if (result != MessageBox::Remove) {
return;
}
}
m_entryAttributes->remove(m_additionalURLsDataModel->keyByIndex(index));
if (m_additionalURLsDataModel->rowCount() == 0) {
m_browserUi->editURLButton->setEnabled(false);
m_browserUi->removeURLButton->setEnabled(false);
}
setModified(true);
}
}
void EditEntryWidget::editCurrentURL()
{
Q_ASSERT(!m_history);
QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
if (index.isValid()) {
m_browserUi->additionalURLsView->edit(index);
setModified(true);
}
}
void EditEntryWidget::updateCurrentURL()
{
QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
if (index.isValid()) {
// Don't allow editing in history view
m_browserUi->editURLButton->setEnabled(!m_history);
m_browserUi->removeURLButton->setEnabled(!m_history);
} else {
m_browserUi->editURLButton->setEnabled(false);
m_browserUi->removeURLButton->setEnabled(false);
}
}
#endif
2012-10-29 22:11:42 +00:00
void EditEntryWidget::setupProperties()
{
addPage(tr("Properties"), icons()->icon("document-properties"), m_editWidgetProperties);
2012-10-29 22:11:42 +00:00
}
void EditEntryWidget::setupHistory()
{
m_historyUi->setupUi(m_historyWidget);
addPage(tr("History"), icons()->icon("view-history"), m_historyWidget);
2012-06-10 14:02:03 +00:00
m_sortModel->setSourceModel(m_historyModel);
m_sortModel->setDynamicSortFilter(true);
m_sortModel->setSortLocaleAware(true);
m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive);
m_sortModel->setSortRole(Qt::UserRole);
m_historyUi->historyView->setModel(m_sortModel);
2012-05-15 18:12:05 +00:00
m_historyUi->historyView->setRootIsDecorated(false);
// clang-format off
2018-03-31 20:01:30 +00:00
connect(m_historyUi->historyView, SIGNAL(activated(QModelIndex)), SLOT(histEntryActivated(QModelIndex)));
connect(m_historyUi->historyView->selectionModel(),
SIGNAL(currentChanged(QModelIndex,QModelIndex)),
SLOT(updateHistoryButtons(QModelIndex,QModelIndex)));
connect(m_historyUi->showButton, SIGNAL(clicked()), SLOT(showHistoryEntry()));
connect(m_historyUi->restoreButton, SIGNAL(clicked()), SLOT(restoreHistoryEntry()));
connect(m_historyUi->deleteButton, SIGNAL(clicked()), SLOT(deleteHistoryEntry()));
connect(m_historyUi->deleteAllButton, SIGNAL(clicked()), SLOT(deleteAllHistoryEntries()));
// clang-format on
2010-10-06 17:40:50 +00:00
}
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.
2018-03-11 03:31:43 +00:00
void EditEntryWidget::setupEntryUpdate()
{
// Entry tab
connect(m_mainUi->titleEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_mainUi->usernameComboBox->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_mainUi->passwordEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
#ifdef WITH_XC_NETWORKING
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString)));
#endif
connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setModified()));
connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
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.
2018-03-11 03:31:43 +00:00
// Advanced tab
connect(m_advancedUi->attributesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
connect(m_advancedUi->protectAttributeButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_advancedUi->excludeReportsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_advancedUi->fgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_advancedUi->bgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_advancedUi->attachmentsWidget, SIGNAL(widgetUpdated()), this, SLOT(setModified()));
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.
2018-03-11 03:31:43 +00:00
// Icon tab
connect(m_iconsWidget, SIGNAL(widgetUpdated()), this, SLOT(setModified()));
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.
2018-03-11 03:31:43 +00:00
// Auto-Type tab
connect(m_autoTypeUi->enableButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_autoTypeUi->inheritSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
connect(m_autoTypeUi->customSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_autoTypeUi->sequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setModified()));
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)), this, SLOT(setModified()));
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.
2018-03-11 03:31:43 +00:00
// Properties and History tabs don't need extra connections
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.
2018-03-11 03:31:43 +00:00
#ifdef WITH_XC_SSHAGENT
// SSH Agent tab
if (sshAgent()->isEnabled()) {
connect(m_sshAgentUi->attachmentRadioButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
connect(m_sshAgentUi->externalFileRadioButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
connect(m_sshAgentUi->attachmentComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->attachmentComboBox, SIGNAL(editTextChanged(QString)), this, SLOT(setModified()));
connect(m_sshAgentUi->externalFileEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_sshAgentUi->addKeyToAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->removeKeyFromAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->requireUserConfirmationCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->lifetimeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->lifetimeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setModified()));
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.
2018-03-11 03:31:43 +00:00
}
#endif
#ifdef WITH_XC_BROWSER
if (config()->get(Config::Browser_Enabled).toBool()) {
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->onlyHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->notHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->addURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->removeURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->editURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
}
#endif
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.
2018-03-11 03:31:43 +00:00
}
2012-05-15 18:12:05 +00:00
void EditEntryWidget::emitHistoryEntryActivated(const QModelIndex& index)
{
Q_ASSERT(!m_history);
Entry* entry = m_historyModel->entryFromIndex(index);
emit historyEntryActivated(entry);
2012-05-15 18:12:05 +00:00
}
2012-06-10 14:02:03 +00:00
void EditEntryWidget::histEntryActivated(const QModelIndex& index)
{
Q_ASSERT(!m_history);
QModelIndex indexMapped = m_sortModel->mapToSource(index);
if (indexMapped.isValid()) {
emitHistoryEntryActivated(indexMapped);
}
}
void EditEntryWidget::updateHistoryButtons(const QModelIndex& current, const QModelIndex& previous)
{
Q_UNUSED(previous);
if (current.isValid()) {
m_historyUi->showButton->setEnabled(true);
m_historyUi->restoreButton->setEnabled(true);
m_historyUi->deleteButton->setEnabled(true);
2018-03-31 20:01:30 +00:00
} else {
m_historyUi->showButton->setEnabled(false);
m_historyUi->restoreButton->setEnabled(false);
m_historyUi->deleteButton->setEnabled(false);
}
}
2017-10-29 15:17:24 +00:00
#ifdef WITH_XC_SSHAGENT
void EditEntryWidget::setupSSHAgent()
{
m_sshAgentUi->setupUi(m_sshAgentWidget);
2017-11-25 09:12:31 +00:00
QFont fixedFont = Font::fixedFont();
m_sshAgentUi->fingerprintTextLabel->setFont(fixedFont);
m_sshAgentUi->commentTextLabel->setFont(fixedFont);
m_sshAgentUi->publicKeyEdit->setFont(fixedFont);
// clang-format off
connect(m_sshAgentUi->attachmentRadioButton, &QRadioButton::clicked,
this, &EditEntryWidget::updateSSHAgentKeyInfo);
connect(m_sshAgentUi->attachmentComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &EditEntryWidget::updateSSHAgentAttachment);
connect(m_sshAgentUi->externalFileRadioButton, &QRadioButton::clicked,
this, &EditEntryWidget::updateSSHAgentKeyInfo);
connect(m_sshAgentUi->externalFileEdit, &QLineEdit::textChanged, this, &EditEntryWidget::updateSSHAgentKeyInfo);
connect(m_sshAgentUi->browseButton, &QPushButton::clicked, this, &EditEntryWidget::browsePrivateKey);
connect(m_sshAgentUi->addToAgentButton, &QPushButton::clicked, this, &EditEntryWidget::addKeyToAgent);
connect(m_sshAgentUi->removeFromAgentButton, &QPushButton::clicked, this, &EditEntryWidget::removeKeyFromAgent);
connect(m_sshAgentUi->decryptButton, &QPushButton::clicked, this, &EditEntryWidget::decryptPrivateKey);
connect(m_sshAgentUi->copyToClipboardButton, &QPushButton::clicked, this, &EditEntryWidget::copyPublicKey);
connect(m_attachments.data(), &EntryAttachments::modified,
this, &EditEntryWidget::updateSSHAgentAttachments);
// clang-format on
addPage(tr("SSH Agent"), icons()->icon("utilities-terminal"), m_sshAgentWidget);
2017-10-29 15:17:24 +00:00
}
void EditEntryWidget::setSSHAgentSettings()
2017-10-29 15:17:24 +00:00
{
m_sshAgentUi->addKeyToAgentCheckBox->setChecked(m_sshAgentSettings.addAtDatabaseOpen());
m_sshAgentUi->removeKeyFromAgentCheckBox->setChecked(m_sshAgentSettings.removeAtDatabaseClose());
m_sshAgentUi->requireUserConfirmationCheckBox->setChecked(m_sshAgentSettings.useConfirmConstraintWhenAdding());
m_sshAgentUi->lifetimeCheckBox->setChecked(m_sshAgentSettings.useLifetimeConstraintWhenAdding());
m_sshAgentUi->lifetimeSpinBox->setValue(m_sshAgentSettings.lifetimeConstraintDuration());
2017-11-25 09:12:31 +00:00
m_sshAgentUi->attachmentComboBox->clear();
2017-10-29 15:17:24 +00:00
m_sshAgentUi->addToAgentButton->setEnabled(false);
m_sshAgentUi->removeFromAgentButton->setEnabled(false);
m_sshAgentUi->copyToClipboardButton->setEnabled(false);
}
2017-10-29 15:17:24 +00:00
void EditEntryWidget::updateSSHAgent()
{
m_sshAgentSettings.reset();
m_sshAgentSettings.fromEntry(m_entry);
setSSHAgentSettings();
2017-10-29 15:17:24 +00:00
updateSSHAgentAttachments();
2017-10-29 15:17:24 +00:00
}
void EditEntryWidget::updateSSHAgentAttachment()
{
m_sshAgentUi->attachmentRadioButton->setChecked(true);
updateSSHAgentKeyInfo();
}
void EditEntryWidget::updateSSHAgentAttachments()
{
// detect if KeeAgent.settings was removed by hand and reset settings
if (m_entry && KeeAgentSettings::inEntryAttachments(m_entry->attachments())
&& !KeeAgentSettings::inEntryAttachments(m_attachments.data())) {
m_sshAgentSettings.reset();
setSSHAgentSettings();
}
m_sshAgentUi->attachmentComboBox->clear();
m_sshAgentUi->attachmentComboBox->addItem("");
for (const QString& fileName : m_attachments->keys()) {
if (fileName == "KeeAgent.settings") {
continue;
}
m_sshAgentUi->attachmentComboBox->addItem(fileName);
}
m_sshAgentUi->attachmentComboBox->setCurrentText(m_sshAgentSettings.attachmentName());
m_sshAgentUi->externalFileEdit->setText(m_sshAgentSettings.fileName());
if (m_sshAgentSettings.selectedType() == "attachment") {
m_sshAgentUi->attachmentRadioButton->setChecked(true);
} else {
m_sshAgentUi->externalFileRadioButton->setChecked(true);
}
updateSSHAgentKeyInfo();
}
2017-10-29 15:17:24 +00:00
void EditEntryWidget::updateSSHAgentKeyInfo()
{
m_sshAgentUi->addToAgentButton->setEnabled(false);
m_sshAgentUi->removeFromAgentButton->setEnabled(false);
m_sshAgentUi->copyToClipboardButton->setEnabled(false);
2017-11-25 09:12:31 +00:00
m_sshAgentUi->fingerprintTextLabel->setText(tr("n/a"));
m_sshAgentUi->commentTextLabel->setText(tr("n/a"));
2017-10-29 15:17:24 +00:00
m_sshAgentUi->decryptButton->setEnabled(false);
m_sshAgentUi->publicKeyEdit->document()->setPlainText("");
OpenSSHKey key;
if (!getOpenSSHKey(key)) {
return;
}
if (!key.fingerprint().isEmpty()) {
m_sshAgentUi->fingerprintTextLabel->setText(key.fingerprint(QCryptographicHash::Md5) + "\n"
+ key.fingerprint(QCryptographicHash::Sha256));
} else {
m_sshAgentUi->fingerprintTextLabel->setText(tr("(encrypted)"));
}
2017-10-29 15:17:24 +00:00
if (!key.comment().isEmpty() || !key.encrypted()) {
m_sshAgentUi->commentTextLabel->setText(key.comment());
} else {
2017-11-25 09:12:31 +00:00
m_sshAgentUi->commentTextLabel->setText(tr("(encrypted)"));
2017-10-29 15:17:24 +00:00
m_sshAgentUi->decryptButton->setEnabled(true);
}
if (!key.publicKey().isEmpty()) {
m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
m_sshAgentUi->copyToClipboardButton->setEnabled(true);
} else {
m_sshAgentUi->publicKeyEdit->document()->setPlainText(tr("(encrypted)"));
m_sshAgentUi->copyToClipboardButton->setDisabled(true);
}
2017-10-29 15:17:24 +00:00
// enable agent buttons only if we have an agent running
if (sshAgent()->isAgentRunning()) {
2017-10-29 15:17:24 +00:00
m_sshAgentUi->addToAgentButton->setEnabled(true);
m_sshAgentUi->removeFromAgentButton->setEnabled(true);
sshAgent()->setAutoRemoveOnLock(key, m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked());
2017-10-29 15:17:24 +00:00
}
}
void EditEntryWidget::toKeeAgentSettings(KeeAgentSettings& settings) const
2017-10-29 15:17:24 +00:00
{
settings.setAddAtDatabaseOpen(m_sshAgentUi->addKeyToAgentCheckBox->isChecked());
settings.setRemoveAtDatabaseClose(m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked());
settings.setUseConfirmConstraintWhenAdding(m_sshAgentUi->requireUserConfirmationCheckBox->isChecked());
settings.setUseLifetimeConstraintWhenAdding(m_sshAgentUi->lifetimeCheckBox->isChecked());
settings.setLifetimeConstraintDuration(m_sshAgentUi->lifetimeSpinBox->value());
2017-11-25 09:12:31 +00:00
if (m_sshAgentUi->attachmentRadioButton->isChecked()) {
2017-10-29 15:17:24 +00:00
settings.setSelectedType("attachment");
} else {
settings.setSelectedType("file");
}
2017-11-25 09:12:31 +00:00
settings.setAttachmentName(m_sshAgentUi->attachmentComboBox->currentText());
settings.setFileName(m_sshAgentUi->externalFileEdit->text());
2017-10-29 15:17:24 +00:00
// we don't use this as we don't run an agent but for compatibility we set it if necessary
settings.setAllowUseOfSshKey(settings.addAtDatabaseOpen() || settings.removeAtDatabaseClose());
// we don't use this either but we don't want it to dirty flag the config
settings.setSaveAttachmentToTempFile(m_sshAgentSettings.saveAttachmentToTempFile());
}
void EditEntryWidget::browsePrivateKey()
{
auto fileName = fileDialog()->getOpenFileName(this, tr("Select private key"), FileDialog::getLastDir("sshagent"));
2017-10-29 15:17:24 +00:00
if (!fileName.isEmpty()) {
FileDialog::saveLastDir("sshagent", fileName);
2017-11-25 09:12:31 +00:00
m_sshAgentUi->externalFileEdit->setText(fileName);
m_sshAgentUi->externalFileRadioButton->setChecked(true);
updateSSHAgentKeyInfo();
2017-10-29 15:17:24 +00:00
}
}
bool EditEntryWidget::getOpenSSHKey(OpenSSHKey& key, bool decrypt)
2017-10-29 15:17:24 +00:00
{
KeeAgentSettings settings;
toKeeAgentSettings(settings);
2017-10-29 15:17:24 +00:00
if (!m_entry || !settings.keyConfigured()) {
2017-11-25 09:12:31 +00:00
return false;
}
if (!settings.toOpenSSHKey(m_mainUi->usernameComboBox->lineEdit()->text(),
m_mainUi->passwordEdit->text(),
m_db->filePath(),
m_attachments.data(),
key,
decrypt)) {
showMessage(settings.errorString(), MessageWidget::Error);
2017-10-29 15:17:24 +00:00
return false;
}
return true;
}
void EditEntryWidget::addKeyToAgent()
{
OpenSSHKey key;
if (!getOpenSSHKey(key, true)) {
2017-10-29 15:17:24 +00:00
return;
}
m_sshAgentUi->commentTextLabel->setText(key.comment());
m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
2017-10-29 15:17:24 +00:00
KeeAgentSettings settings;
toKeeAgentSettings(settings);
2017-10-29 15:17:24 +00:00
if (!sshAgent()->addIdentity(key, settings, m_db->uuid())) {
showMessage(sshAgent()->errorString(), MessageWidget::Error);
return;
}
2017-10-29 15:17:24 +00:00
}
void EditEntryWidget::removeKeyFromAgent()
{
OpenSSHKey key;
if (!getOpenSSHKey(key)) {
return;
}
if (!sshAgent()->removeIdentity(key)) {
showMessage(sshAgent()->errorString(), MessageWidget::Error);
return;
2017-10-29 15:17:24 +00:00
}
}
void EditEntryWidget::decryptPrivateKey()
{
OpenSSHKey key;
if (!getOpenSSHKey(key, true)) {
2017-10-29 15:17:24 +00:00
return;
}
if (!key.comment().isEmpty()) {
2017-11-25 09:12:31 +00:00
m_sshAgentUi->commentTextLabel->setText(key.comment());
} else {
m_sshAgentUi->commentTextLabel->setText(tr("n/a"));
2017-10-29 15:17:24 +00:00
}
m_sshAgentUi->fingerprintTextLabel->setText(key.fingerprint(QCryptographicHash::Md5) + "\n"
+ key.fingerprint(QCryptographicHash::Sha256));
m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
m_sshAgentUi->copyToClipboardButton->setEnabled(true);
2017-10-29 15:17:24 +00:00
}
void EditEntryWidget::copyPublicKey()
{
clipboard()->setText(m_sshAgentUi->publicKeyEdit->document()->toPlainText());
}
#endif
void EditEntryWidget::useExpiryPreset(QAction* action)
{
m_mainUi->expireCheck->setChecked(true);
TimeDelta delta = action->data().value<TimeDelta>();
QDateTime now = Clock::currentDateTime();
QDateTime expiryDateTime = now + delta;
m_mainUi->expireDatePicker->setDateTime(expiryDateTime);
}
void EditEntryWidget::toggleHideNotes(bool visible)
{
m_mainUi->notesEdit->setVisible(visible);
m_mainUi->notesHint->setVisible(!visible);
}
Entry* EditEntryWidget::currentEntry() const
2012-10-21 19:45:54 +00:00
{
return m_entry;
2012-10-21 19:45:54 +00:00
}
void EditEntryWidget::loadEntry(Entry* entry,
bool create,
bool history,
const QString& parentName,
QSharedPointer<Database> database)
2010-10-06 17:40:50 +00:00
{
m_entry = entry;
m_db = std::move(database);
m_create = create;
2012-05-15 18:12:05 +00:00
m_history = history;
2010-10-06 17:40:50 +00:00
connect(m_entry, &Entry::modified, this, [this] { m_entryModifiedTimer.start(); });
2012-05-15 18:12:05 +00:00
if (history) {
setHeadline(QString("%1 \u2022 %2").arg(parentName, tr("Entry history")));
2018-03-31 20:01:30 +00:00
} else {
2012-05-15 18:12:05 +00:00
if (create) {
setHeadline(QString("%1 \u2022 %2").arg(parentName, tr("Add entry")));
2018-03-31 20:01:30 +00:00
} else {
setHeadline(QString("%1 \u2022 %2 \u2022 %3").arg(parentName, entry->title(), tr("Edit entry")));
2012-05-15 18:12:05 +00:00
}
2011-12-27 15:04:59 +00:00
}
2010-10-06 20:54:07 +00:00
setForms(entry);
setReadOnly(m_history);
setCurrentPage(0);
setPageHidden(m_historyWidget, m_history || m_entry->historyItems().count() < 1);
#ifdef WITH_XC_SSHAGENT
setPageHidden(m_sshAgentWidget, !sshAgent()->isEnabled());
#endif
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.
2018-03-11 03:31:43 +00:00
// Force the user to Save/Discard new entries
showApplyButton(!m_create);
setModified(false);
}
void EditEntryWidget::setForms(Entry* entry, bool restore)
{
m_attachments->copyDataFrom(entry->attachments());
m_customData->copyDataFrom(entry->customData());
m_mainUi->titleEdit->setReadOnly(m_history);
m_mainUi->usernameComboBox->lineEdit()->setReadOnly(m_history);
m_mainUi->urlEdit->setReadOnly(m_history);
m_mainUi->passwordEdit->setReadOnly(m_history);
m_mainUi->expireCheck->setEnabled(!m_history);
m_mainUi->expireDatePicker->setReadOnly(m_history);
m_mainUi->notesEnabled->setChecked(!config()->get(Config::Security_HideNotes).toBool());
m_mainUi->notesEdit->setReadOnly(m_history);
m_mainUi->notesEdit->setVisible(!config()->get(Config::Security_HideNotes).toBool());
m_mainUi->notesHint->setVisible(config()->get(Config::Security_HideNotes).toBool());
if (config()->get(Config::GUI_MonospaceNotes).toBool()) {
m_mainUi->notesEdit->setFont(Font::fixedFont());
} else {
m_mainUi->notesEdit->setFont(Font::defaultFont());
}
m_advancedUi->attachmentsWidget->setReadOnly(m_history);
m_advancedUi->addAttributeButton->setEnabled(!m_history);
2012-05-15 18:12:05 +00:00
m_advancedUi->editAttributeButton->setEnabled(false);
m_advancedUi->removeAttributeButton->setEnabled(false);
m_advancedUi->attributesEdit->setReadOnly(m_history);
2012-05-15 18:12:05 +00:00
QAbstractItemView::EditTriggers editTriggers;
if (m_history) {
2012-05-15 18:12:05 +00:00
editTriggers = QAbstractItemView::NoEditTriggers;
2018-03-31 20:01:30 +00:00
} else {
2012-05-15 18:12:05 +00:00
editTriggers = QAbstractItemView::DoubleClicked;
}
m_advancedUi->attributesView->setEditTriggers(editTriggers);
m_advancedUi->excludeReportsCheckBox->setChecked(entry->excludeFromReports());
setupColorButton(true, entry->foregroundColor());
setupColorButton(false, entry->backgroundColor());
m_iconsWidget->setEnabled(!m_history);
m_autoTypeUi->sequenceEdit->setReadOnly(m_history);
m_autoTypeUi->windowTitleCombo->lineEdit()->setReadOnly(m_history);
m_autoTypeUi->windowSequenceEdit->setReadOnly(m_history);
m_historyWidget->setEnabled(!m_history);
2012-05-15 18:12:05 +00:00
2010-10-06 17:40:50 +00:00
m_mainUi->titleEdit->setText(entry->title());
m_mainUi->usernameComboBox->lineEdit()->setText(entry->username());
2010-10-06 17:40:50 +00:00
m_mainUi->urlEdit->setText(entry->url());
m_mainUi->passwordEdit->setText(entry->password());
m_mainUi->passwordEdit->setShowPassword(!config()->get(Config::Security_PasswordsHidden).toBool());
if (!m_history) {
m_mainUi->passwordEdit->enablePasswordGenerator();
}
m_mainUi->expireCheck->setChecked(entry->timeInfo().expires());
m_mainUi->expireDatePicker->setDateTime(entry->timeInfo().expiryTime().toLocalTime());
m_mainUi->expirePresets->setEnabled(!m_history);
QList<QString> commonUsernames = m_db->commonUsernames();
m_usernameCompleterModel->setStringList(commonUsernames);
QString usernameToRestore = m_mainUi->usernameComboBox->lineEdit()->text();
m_mainUi->usernameComboBox->clear();
m_mainUi->usernameComboBox->addItems(commonUsernames);
m_mainUi->usernameComboBox->lineEdit()->setText(usernameToRestore);
m_mainUi->notesEdit->setPlainText(entry->notes());
2010-10-06 17:40:50 +00:00
m_advancedUi->attachmentsWidget->linkAttachments(m_attachments.data());
2012-05-15 18:12:05 +00:00
m_entryAttributes->copyCustomKeysFrom(entry->attributes());
if (m_attributesModel->rowCount() != 0) {
m_advancedUi->attributesView->setCurrentIndex(m_attributesModel->index(0, 0));
2018-03-31 20:01:30 +00:00
} else {
m_advancedUi->attributesEdit->setPlainText("");
m_advancedUi->attributesEdit->setEnabled(false);
}
QList<int> sizes = m_advancedUi->attributesSplitter->sizes();
sizes.replace(0, m_advancedUi->attributesSplitter->width() * 0.3);
sizes.replace(1, m_advancedUi->attributesSplitter->width() * 0.7);
m_advancedUi->attributesSplitter->setSizes(sizes);
IconStruct iconStruct;
iconStruct.uuid = entry->iconUuid();
iconStruct.number = entry->iconNumber();
m_iconsWidget->load(entry->uuid(), m_db, iconStruct, entry->webUrl());
m_autoTypeUi->enableButton->setChecked(entry->autoTypeEnabled());
if (entry->defaultAutoTypeSequence().isEmpty()) {
m_autoTypeUi->inheritSequenceButton->setChecked(true);
2018-03-31 20:01:30 +00:00
} else {
m_autoTypeUi->customSequenceButton->setChecked(true);
}
m_autoTypeUi->sequenceEdit->setText(entry->effectiveAutoTypeSequence());
m_autoTypeUi->windowTitleCombo->lineEdit()->clear();
2018-01-18 23:50:22 +00:00
m_autoTypeUi->customWindowSequenceButton->setChecked(false);
m_autoTypeUi->windowSequenceEdit->setText("");
m_autoTypeAssoc->copyDataFrom(entry->autoTypeAssociations());
m_autoTypeAssocModel->setEntry(entry);
if (m_autoTypeAssoc->size() != 0) {
m_autoTypeUi->assocView->setCurrentIndex(m_autoTypeAssocModel->index(0, 0));
}
if (!m_history) {
m_autoTypeUi->windowTitleCombo->refreshWindowList();
}
updateAutoTypeEnabled();
2017-10-29 15:17:24 +00:00
#ifdef WITH_XC_SSHAGENT
if (sshAgent()->isEnabled()) {
2017-10-29 15:17:24 +00:00
updateSSHAgent();
}
#endif
#ifdef WITH_XC_BROWSER
if (config()->get(Config::Browser_Enabled).toBool()) {
if (!hasPage(m_browserWidget)) {
setupBrowser();
}
if (m_customData->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) {
// clang-format off
m_browserUi->skipAutoSubmitCheckbox->setChecked(m_customData->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == TRUE_STR);
// clang-format on
} else {
m_browserUi->skipAutoSubmitCheckbox->setChecked(false);
}
if (m_customData->contains(BrowserService::OPTION_HIDE_ENTRY)) {
m_browserUi->hideEntryCheckbox->setChecked(m_customData->value(BrowserService::OPTION_HIDE_ENTRY)
== TRUE_STR);
} else {
m_browserUi->hideEntryCheckbox->setChecked(false);
}
if (m_customData->contains(BrowserService::OPTION_ONLY_HTTP_AUTH)) {
m_browserUi->onlyHttpAuthCheckbox->setChecked(m_customData->value(BrowserService::OPTION_ONLY_HTTP_AUTH)
== TRUE_STR);
} else {
m_browserUi->onlyHttpAuthCheckbox->setChecked(false);
}
2021-01-12 17:24:59 +00:00
if (m_customData->contains(BrowserService::OPTION_NOT_HTTP_AUTH)) {
2021-01-12 22:18:51 +00:00
m_browserUi->notHttpAuthCheckbox->setChecked(m_customData->value(BrowserService::OPTION_NOT_HTTP_AUTH)
== TRUE_STR);
} else {
m_browserUi->notHttpAuthCheckbox->setChecked(false);
}
2021-01-12 22:18:51 +00:00
m_browserUi->addURLButton->setEnabled(!m_history);
m_browserUi->removeURLButton->setEnabled(false);
m_browserUi->editURLButton->setEnabled(false);
m_browserUi->additionalURLsView->setEditTriggers(editTriggers);
if (m_additionalURLsDataModel->rowCount() != 0) {
m_browserUi->additionalURLsView->setCurrentIndex(m_additionalURLsDataModel->index(0, 0));
}
}
setPageHidden(m_browserWidget, !config()->get(Config::Browser_Enabled).toBool());
#endif
2012-10-29 21:41:37 +00:00
m_editWidgetProperties->setFields(entry->timeInfo(), entry->uuid());
if (!m_history && !restore) {
m_historyModel->setEntries(entry->historyItems());
2012-05-30 14:37:46 +00:00
m_historyUi->historyView->sortByColumn(0, Qt::DescendingOrder);
}
if (m_historyModel->rowCount() > 0) {
m_historyUi->deleteAllButton->setEnabled(true);
2018-03-31 20:01:30 +00:00
} else {
m_historyUi->deleteAllButton->setEnabled(false);
}
updateHistoryButtons(m_historyUi->historyView->currentIndex(), QModelIndex());
m_mainUi->titleEdit->setFocus();
2010-10-06 17:40:50 +00:00
}
/**
* Commit the form values to in-memory database representation
*
* @return true is commit successful, otherwise false
*/
bool EditEntryWidget::commitEntry()
2010-10-06 17:40:50 +00:00
{
2012-05-15 18:12:05 +00:00
if (m_history) {
clear();
hideMessage();
emit editFinished(false);
return true;
2012-05-15 18:12:05 +00:00
}
// HACK: Check that entry pointer is still valid, see https://github.com/keepassxreboot/keepassxc/issues/5722
if (!m_entry) {
QMessageBox::information(this,
tr("Invalid Entry"),
tr("An external merge operation has invalidated this entry.\n"
"Unfortunately, any changes made have been lost."));
return true;
}
// Check Auto-Type validity early
QString error;
if (m_autoTypeUi->customSequenceButton->isChecked()
&& !AutoType::verifyAutoTypeSyntax(m_autoTypeUi->sequenceEdit->text(), m_entry, error)) {
auto res = MessageBox::question(this,
tr("Auto-Type Validation Error"),
tr("An error occurred while validating the custom Auto-Type sequence:\n%1\n"
"Would you like to correct it?")
.arg(error),
MessageBox::Yes | MessageBox::No,
MessageBox::Yes);
if (res == MessageBox::Yes) {
setCurrentPage(3);
return false;
}
}
for (const auto& assoc : m_autoTypeAssoc->getAll()) {
if (!AutoType::verifyAutoTypeSyntax(assoc.sequence, m_entry, error)) {
auto res =
MessageBox::question(this,
tr("Auto-Type Validation Error"),
tr("An error occurred while validating the Auto-Type sequence for \"%1\":\n%2\n"
"Would you like to correct it?")
.arg(assoc.window.left(40), error),
MessageBox::Yes | MessageBox::No,
MessageBox::Yes);
if (res == MessageBox::Yes) {
setCurrentPage(3);
return false;
}
}
}
if (m_advancedUi->attributesView->currentIndex().isValid() && m_advancedUi->attributesEdit->isEnabled()) {
QString key = m_attributesModel->keyByIndex(m_advancedUi->attributesView->currentIndex());
2018-03-31 20:01:30 +00:00
m_entryAttributes->set(key, m_advancedUi->attributesEdit->toPlainText(), m_entryAttributes->isProtected(key));
}
m_currentAttribute = QPersistentModelIndex();
// must stand before beginUpdate()
// we don't want to create a new history item, if only the history has changed
m_entry->removeHistoryItems(m_historyModel->deletedEntries());
m_historyModel->clearDeletedEntries();
m_autoTypeAssoc->removeEmpty();
2017-10-29 15:17:24 +00:00
#ifdef WITH_XC_SSHAGENT
toKeeAgentSettings(m_sshAgentSettings);
2017-10-29 15:17:24 +00:00
#endif
#ifdef WITH_XC_BROWSER
if (config()->get(Config::Browser_Enabled).toBool()) {
updateBrowser();
}
#endif
if (!m_create) {
m_entry->beginUpdate();
}
updateEntryData(m_entry);
if (!m_create) {
m_entry->endUpdate();
}
2017-10-29 15:17:24 +00:00
m_historyModel->setEntries(m_entry->historyItems());
showMessage(tr("Entry updated successfully."), MessageWidget::Positive);
setModified(false);
return true;
}
void EditEntryWidget::acceptEntry()
{
if (commitEntry()) {
clear();
emit editFinished(true);
}
}
void EditEntryWidget::updateEntryData(Entry* entry) const
{
QRegularExpression newLineRegex("(?:\r?\n|\r)");
entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
entry->attachments()->copyDataFrom(m_attachments.data());
entry->customData()->copyDataFrom(m_customData.data());
entry->setTitle(m_mainUi->titleEdit->text().replace(newLineRegex, " "));
entry->setUsername(m_mainUi->usernameComboBox->lineEdit()->text().replace(newLineRegex, " "));
entry->setUrl(m_mainUi->urlEdit->text().replace(newLineRegex, " "));
entry->setPassword(m_mainUi->passwordEdit->text());
entry->setExpires(m_mainUi->expireCheck->isChecked());
entry->setExpiryTime(m_mainUi->expireDatePicker->dateTime().toUTC());
2010-10-06 17:40:50 +00:00
entry->setNotes(m_mainUi->notesEdit->toPlainText());
2010-10-06 17:40:50 +00:00
if (entry->excludeFromReports() != m_advancedUi->excludeReportsCheckBox->isChecked()) {
entry->setExcludeFromReports(m_advancedUi->excludeReportsCheckBox->isChecked());
}
2018-03-31 20:01:30 +00:00
if (m_advancedUi->fgColorCheckBox->isChecked() && m_advancedUi->fgColorButton->property("color").isValid()) {
entry->setForegroundColor(m_advancedUi->fgColorButton->property("color").toString());
} else {
entry->setForegroundColor(QString());
}
2018-03-31 20:01:30 +00:00
if (m_advancedUi->bgColorCheckBox->isChecked() && m_advancedUi->bgColorButton->property("color").isValid()) {
entry->setBackgroundColor(m_advancedUi->bgColorButton->property("color").toString());
} else {
entry->setBackgroundColor(QString());
}
IconStruct iconStruct = m_iconsWidget->state();
if (iconStruct.number < 0) {
entry->setIcon(Entry::DefaultIconNumber);
} else if (iconStruct.uuid.isNull()) {
entry->setIcon(iconStruct.number);
} else {
entry->setIcon(iconStruct.uuid);
}
entry->setAutoTypeEnabled(m_autoTypeUi->enableButton->isChecked());
if (m_autoTypeUi->inheritSequenceButton->isChecked()) {
entry->setDefaultAutoTypeSequence(QString());
} else {
entry->setDefaultAutoTypeSequence(m_autoTypeUi->sequenceEdit->text());
}
entry->autoTypeAssociations()->copyDataFrom(m_autoTypeAssoc);
#ifdef WITH_XC_SSHAGENT
if (sshAgent()->isEnabled()) {
m_sshAgentSettings.toEntry(entry);
}
#endif
2010-10-06 17:40:50 +00:00
}
void EditEntryWidget::cancel()
{
2012-05-15 18:12:05 +00:00
if (m_history) {
clear();
hideMessage();
emit editFinished(false);
return;
2012-05-15 18:12:05 +00:00
}
if (!m_entry->iconUuid().isNull() && !m_db->metadata()->hasCustomIcon(m_entry->iconUuid())) {
m_entry->setIcon(Entry::DefaultIconNumber);
}
bool accepted = false;
if (isModified()) {
2018-03-31 20:01:30 +00:00
auto result = MessageBox::question(this,
tr("Unsaved Changes"),
tr("Would you like to save changes to this entry?"),
Customize buttons on MessageBox and confirm before recycling (#2376) * Add confirmation prompt before moving groups to the recycling bin Spawn a yes/no QMessage box when "Delete Group" is selected on a group that is not already in the recycle bin (note: the prompt for deletion from the recycle bin was already implemented). This follows the same pattern and language as entry deletion. Fixes #2125 * Make prompts for destructive operations use action words on buttons Replace yes/no, yes/cancel (and other such buttons on prompts that cause data to be destroyed) use language that indicates the action that it is going to take. This makes destructive/unsafe and/or irreversible operations more clear to the user. Address feedback on PR #2376 * Refactor MessageBox class to allow for custom buttons Replaces arguments and return values of type QMessageBox::StandardButton(s) with MessageBox::Button(s), which reimplements the entire set of QMessageBox::StandardButton and allows for custom KeePassXC buttons, such as "Skip". Modifies all calls to MessageBox functions to use MessageBox::Button(s). Addresses feedback on #2376 * Remove MessageBox::addButton in favor of map lookup Replaced the switch statement mechanism in MessageBox::addButton with a map lookup to address CodeFactor Complex Method issue. This has a side-effect of a small performance/cleanliness increase, as an extra QPushButton is no longer created/destroyed (to obtain it's label text) everytime a MessageBox button based on QMessageBox::StandardButton is created; now the text is obtained once, at application start up.
2018-12-20 04:14:11 +00:00
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
MessageBox::Cancel);
if (result == MessageBox::Cancel) {
return;
} else if (result == MessageBox::Save) {
accepted = true;
if (!commitEntry()) {
return;
}
}
}
clear();
emit editFinished(accepted);
}
void EditEntryWidget::clear()
{
if (m_entry) {
m_entry->disconnect(this);
}
2015-07-24 16:28:12 +00:00
m_entry = nullptr;
m_db.reset();
m_mainUi->titleEdit->setText("");
m_mainUi->passwordEdit->setText("");
m_mainUi->urlEdit->setText("");
m_mainUi->notesEdit->clear();
m_entryAttributes->clear();
m_attachments->clear();
m_customData->clear();
m_autoTypeAssoc->clear();
2012-05-15 18:12:05 +00:00
m_historyModel->clear();
m_iconsWidget->reset();
hideMessage();
2010-10-06 17:40:50 +00:00
}
#ifdef WITH_XC_NETWORKING
void EditEntryWidget::updateFaviconButtonEnable(const QString& url)
{
m_mainUi->fetchFaviconButton->setDisabled(url.isEmpty());
}
#endif
void EditEntryWidget::insertAttribute()
{
2012-05-15 18:12:05 +00:00
Q_ASSERT(!m_history);
QString name = tr("New attribute");
int i = 1;
while (m_entryAttributes->keys().contains(name)) {
name = tr("New attribute %1").arg(i);
i++;
}
m_entryAttributes->set(name, "");
QModelIndex index = m_attributesModel->indexByKey(name);
m_advancedUi->attributesView->setCurrentIndex(index);
m_advancedUi->attributesView->edit(index);
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.
2018-03-11 03:31:43 +00:00
setModified(true);
}
void EditEntryWidget::editCurrentAttribute()
{
2012-05-15 18:12:05 +00:00
Q_ASSERT(!m_history);
QModelIndex index = m_advancedUi->attributesView->currentIndex();
if (index.isValid()) {
m_advancedUi->attributesView->edit(index);
setModified(true);
}
}
void EditEntryWidget::removeCurrentAttribute()
{
2012-05-15 18:12:05 +00:00
Q_ASSERT(!m_history);
QModelIndex index = m_advancedUi->attributesView->currentIndex();
if (index.isValid()) {
Customize buttons on MessageBox and confirm before recycling (#2376) * Add confirmation prompt before moving groups to the recycling bin Spawn a yes/no QMessage box when "Delete Group" is selected on a group that is not already in the recycle bin (note: the prompt for deletion from the recycle bin was already implemented). This follows the same pattern and language as entry deletion. Fixes #2125 * Make prompts for destructive operations use action words on buttons Replace yes/no, yes/cancel (and other such buttons on prompts that cause data to be destroyed) use language that indicates the action that it is going to take. This makes destructive/unsafe and/or irreversible operations more clear to the user. Address feedback on PR #2376 * Refactor MessageBox class to allow for custom buttons Replaces arguments and return values of type QMessageBox::StandardButton(s) with MessageBox::Button(s), which reimplements the entire set of QMessageBox::StandardButton and allows for custom KeePassXC buttons, such as "Skip". Modifies all calls to MessageBox functions to use MessageBox::Button(s). Addresses feedback on #2376 * Remove MessageBox::addButton in favor of map lookup Replaced the switch statement mechanism in MessageBox::addButton with a map lookup to address CodeFactor Complex Method issue. This has a side-effect of a small performance/cleanliness increase, as an extra QPushButton is no longer created/destroyed (to obtain it's label text) everytime a MessageBox button based on QMessageBox::StandardButton is created; now the text is obtained once, at application start up.
2018-12-20 04:14:11 +00:00
auto result = MessageBox::question(this,
tr("Confirm Removal"),
tr("Are you sure you want to remove this attribute?"),
MessageBox::Remove | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Remove) {
m_entryAttributes->remove(m_attributesModel->keyByIndex(index));
setModified(true);
}
}
}
void EditEntryWidget::updateCurrentAttribute()
{
QModelIndex newIndex = m_advancedUi->attributesView->currentIndex();
QString newKey = m_attributesModel->keyByIndex(newIndex);
if (!m_history && m_currentAttribute != newIndex) {
// Save changes to the currently selected attribute if editing is enabled
if (m_currentAttribute.isValid() && m_advancedUi->attributesEdit->isEnabled()) {
QString currKey = m_attributesModel->keyByIndex(m_currentAttribute);
2018-03-31 20:01:30 +00:00
m_entryAttributes->set(
currKey, m_advancedUi->attributesEdit->toPlainText(), m_entryAttributes->isProtected(currKey));
}
}
displayAttribute(newIndex, m_entryAttributes->isProtected(newKey));
m_currentAttribute = newIndex;
}
void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
{
// Block signals to prevent modified being set
m_advancedUi->protectAttributeButton->blockSignals(true);
m_advancedUi->attributesEdit->blockSignals(true);
m_advancedUi->revealAttributeButton->setText(tr("Reveal"));
if (index.isValid()) {
QString key = m_attributesModel->keyByIndex(index);
if (showProtected) {
m_advancedUi->attributesEdit->setPlainText(tr("[PROTECTED] Press Reveal to view or edit"));
m_advancedUi->attributesEdit->setEnabled(false);
m_advancedUi->revealAttributeButton->setEnabled(true);
m_advancedUi->protectAttributeButton->setChecked(true);
2018-03-31 20:01:30 +00:00
} else {
m_advancedUi->attributesEdit->setPlainText(m_entryAttributes->value(key));
m_advancedUi->attributesEdit->setEnabled(true);
m_advancedUi->revealAttributeButton->setEnabled(false);
m_advancedUi->protectAttributeButton->setChecked(false);
}
// Don't allow editing in history view
m_advancedUi->protectAttributeButton->setEnabled(!m_history);
m_advancedUi->editAttributeButton->setEnabled(!m_history);
m_advancedUi->removeAttributeButton->setEnabled(!m_history);
2018-03-31 20:01:30 +00:00
} else {
m_advancedUi->attributesEdit->setPlainText("");
m_advancedUi->attributesEdit->setEnabled(false);
m_advancedUi->revealAttributeButton->setEnabled(false);
m_advancedUi->protectAttributeButton->setChecked(false);
m_advancedUi->protectAttributeButton->setEnabled(false);
m_advancedUi->editAttributeButton->setEnabled(false);
m_advancedUi->removeAttributeButton->setEnabled(false);
}
m_advancedUi->protectAttributeButton->blockSignals(false);
m_advancedUi->attributesEdit->blockSignals(false);
}
void EditEntryWidget::protectCurrentAttribute(bool state)
{
QModelIndex index = m_advancedUi->attributesView->currentIndex();
if (!m_history && index.isValid()) {
QString key = m_attributesModel->keyByIndex(index);
if (state) {
// Save the current text and protect the attribute
m_entryAttributes->set(key, m_advancedUi->attributesEdit->toPlainText(), true);
} else {
// Unprotect the current attribute value (don't save text as it is obscured)
m_entryAttributes->set(key, m_entryAttributes->value(key), false);
}
// Display the attribute
displayAttribute(index, state);
}
}
void EditEntryWidget::toggleCurrentAttributeVisibility()
{
2018-03-31 20:01:30 +00:00
if (!m_advancedUi->attributesEdit->isEnabled()) {
QModelIndex index = m_advancedUi->attributesView->currentIndex();
if (index.isValid()) {
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.
2018-03-11 03:31:43 +00:00
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);
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.
2018-03-11 03:31:43 +00:00
m_advancedUi->attributesEdit->blockSignals(oldBlockSignals);
2012-05-15 18:12:05 +00:00
}
m_advancedUi->revealAttributeButton->setText(tr("Hide"));
} else {
protectCurrentAttribute(true);
m_advancedUi->revealAttributeButton->setText(tr("Reveal"));
}
}
void EditEntryWidget::updateAutoTypeEnabled()
{
bool autoTypeEnabled = m_autoTypeUi->enableButton->isChecked();
bool validIndex = m_autoTypeUi->assocView->currentIndex().isValid() && m_autoTypeAssoc->size() != 0;
m_autoTypeUi->enableButton->setEnabled(!m_history);
m_autoTypeUi->inheritSequenceButton->setEnabled(!m_history && autoTypeEnabled);
m_autoTypeUi->customSequenceButton->setEnabled(!m_history && autoTypeEnabled);
m_autoTypeUi->sequenceEdit->setEnabled(autoTypeEnabled && m_autoTypeUi->customSequenceButton->isChecked());
m_autoTypeUi->openHelpButton->setEnabled(autoTypeEnabled && m_autoTypeUi->customSequenceButton->isChecked());
m_autoTypeUi->assocView->setEnabled(autoTypeEnabled);
m_autoTypeUi->assocAddButton->setEnabled(!m_history);
m_autoTypeUi->assocRemoveButton->setEnabled(!m_history && validIndex);
m_autoTypeUi->windowTitleLabel->setEnabled(autoTypeEnabled && validIndex);
m_autoTypeUi->windowTitleCombo->setEnabled(autoTypeEnabled && validIndex);
m_autoTypeUi->customWindowSequenceButton->setEnabled(!m_history && autoTypeEnabled && validIndex);
m_autoTypeUi->windowSequenceEdit->setEnabled(autoTypeEnabled && validIndex
&& m_autoTypeUi->customWindowSequenceButton->isChecked());
}
void EditEntryWidget::insertAutoTypeAssoc()
{
AutoTypeAssociations::Association assoc;
m_autoTypeAssoc->add(assoc);
QModelIndex newIndex = m_autoTypeAssocModel->index(m_autoTypeAssoc->size() - 1, 0);
m_autoTypeUi->assocView->setCurrentIndex(newIndex);
loadCurrentAssoc(newIndex);
m_autoTypeUi->windowTitleCombo->setFocus();
setModified(true);
}
void EditEntryWidget::removeAutoTypeAssoc()
{
QModelIndex currentIndex = m_autoTypeUi->assocView->currentIndex();
if (currentIndex.isValid()) {
m_autoTypeAssoc->remove(currentIndex.row());
setModified(true);
}
}
void EditEntryWidget::loadCurrentAssoc(const QModelIndex& current)
{
bool modified = isModified();
if (current.isValid() && current.row() < m_autoTypeAssoc->size()) {
AutoTypeAssociations::Association assoc = m_autoTypeAssoc->get(current.row());
m_autoTypeUi->windowTitleCombo->setEditText(assoc.window);
if (assoc.sequence.isEmpty()) {
2018-01-18 23:50:22 +00:00
m_autoTypeUi->customWindowSequenceButton->setChecked(false);
m_autoTypeUi->windowSequenceEdit->setText(m_entry->effectiveAutoTypeSequence());
} else {
m_autoTypeUi->customWindowSequenceButton->setChecked(true);
2018-01-18 23:50:22 +00:00
m_autoTypeUi->windowSequenceEdit->setText(assoc.sequence);
}
updateAutoTypeEnabled();
2018-01-18 23:50:22 +00:00
} else {
clearCurrentAssoc();
}
setModified(modified);
}
void EditEntryWidget::clearCurrentAssoc()
{
m_autoTypeUi->windowTitleCombo->setEditText("");
2018-01-18 23:50:22 +00:00
m_autoTypeUi->customWindowSequenceButton->setChecked(false);
m_autoTypeUi->windowSequenceEdit->setText("");
updateAutoTypeEnabled();
}
void EditEntryWidget::applyCurrentAssoc()
{
QModelIndex index = m_autoTypeUi->assocView->currentIndex();
if (!index.isValid() || m_autoTypeAssoc->size() == 0 || m_history) {
return;
}
AutoTypeAssociations::Association assoc;
assoc.window = m_autoTypeUi->windowTitleCombo->currentText();
if (m_autoTypeUi->customWindowSequenceButton->isChecked()) {
assoc.sequence = m_autoTypeUi->windowSequenceEdit->text();
}
m_autoTypeAssoc->update(index.row(), assoc);
}
void EditEntryWidget::showHistoryEntry()
{
2012-06-10 14:02:03 +00:00
QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex());
if (index.isValid()) {
emitHistoryEntryActivated(index);
}
}
void EditEntryWidget::restoreHistoryEntry()
{
2012-06-10 14:02:03 +00:00
QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex());
if (index.isValid()) {
setForms(m_historyModel->entryFromIndex(index), true);
setModified(true);
}
}
void EditEntryWidget::deleteHistoryEntry()
{
2012-06-10 14:02:03 +00:00
QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex());
if (index.isValid()) {
m_historyModel->deleteIndex(index);
if (m_historyModel->rowCount() > 0) {
m_historyUi->deleteAllButton->setEnabled(true);
} else {
m_historyUi->deleteAllButton->setEnabled(false);
}
setModified(true);
}
}
void EditEntryWidget::deleteAllHistoryEntries()
{
m_historyModel->deleteAll();
m_historyUi->deleteAllButton->setEnabled(m_historyModel->rowCount() > 0);
setModified(true);
}
QMenu* EditEntryWidget::createPresetsMenu()
{
auto* expirePresetsMenu = new QMenu(this);
expirePresetsMenu->addAction(tr("Tomorrow"))->setData(QVariant::fromValue(TimeDelta::fromDays(1)));
expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromDays(7)));
expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 2))->setData(QVariant::fromValue(TimeDelta::fromDays(14)));
expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromDays(21)));
expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromMonths(1)));
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromMonths(3)));
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 6))->setData(QVariant::fromValue(TimeDelta::fromMonths(6)));
expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromYears(1)));
expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 2))->setData(QVariant::fromValue(TimeDelta::fromYears(2)));
expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromYears(3)));
return expirePresetsMenu;
}
2018-02-19 23:22:49 +00:00
void EditEntryWidget::setupColorButton(bool foreground, const QColor& color)
{
QWidget* button = m_advancedUi->fgColorButton;
QCheckBox* checkBox = m_advancedUi->fgColorCheckBox;
if (!foreground) {
button = m_advancedUi->bgColorButton;
checkBox = m_advancedUi->bgColorCheckBox;
}
if (color.isValid()) {
button->setStyleSheet(QString("background-color:%1").arg(color.name()));
button->setProperty("color", color.name());
checkBox->setChecked(true);
} else {
button->setStyleSheet("");
button->setProperty("color", QVariant());
checkBox->setChecked(false);
}
}
void EditEntryWidget::pickColor()
{
bool isForeground = (sender() == m_advancedUi->fgColorButton);
QColor oldColor = QColor(m_advancedUi->fgColorButton->property("color").toString());
if (!isForeground) {
oldColor = QColor(m_advancedUi->bgColorButton->property("color").toString());
}
QColor newColor = QColorDialog::getColor(oldColor);
if (newColor.isValid()) {
setupColorButton(isForeground, newColor);
setModified(true);
}
}