keepassxc

Форк
0
/
EditEntryWidget.cpp 
1691 строка · 65.3 Кб
1
/*
2
 *  Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
3
 *  Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU General Public License as published by
7
 *  the Free Software Foundation, either version 2 or (at your option)
8
 *  version 3 of the License.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU General Public License
16
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

19
#include "EditEntryWidget.h"
20
#include "ui_EditEntryWidgetAdvanced.h"
21
#include "ui_EditEntryWidgetAutoType.h"
22
#include "ui_EditEntryWidgetBrowser.h"
23
#include "ui_EditEntryWidgetHistory.h"
24
#include "ui_EditEntryWidgetMain.h"
25
#include "ui_EditEntryWidgetSSHAgent.h"
26

27
#include <QColorDialog>
28
#include <QDesktopServices>
29
#include <QSortFilterProxyModel>
30
#include <QStringListModel>
31

32
#include "autotype/AutoType.h"
33
#include "core/AutoTypeAssociations.h"
34
#include "core/Clock.h"
35
#include "core/Config.h"
36
#include "core/Database.h"
37
#include "core/Entry.h"
38
#include "core/EntryAttributes.h"
39
#include "core/Group.h"
40
#include "core/Metadata.h"
41
#include "core/TimeDelta.h"
42
#ifdef WITH_XC_SSHAGENT
43
#include "sshagent/OpenSSHKey.h"
44
#include "sshagent/OpenSSHKeyGenDialog.h"
45
#include "sshagent/SSHAgent.h"
46
#endif
47
#ifdef WITH_XC_BROWSER
48
#include "EntryURLModel.h"
49
#include "browser/BrowserService.h"
50
#endif
51
#include "gui/Clipboard.h"
52
#include "gui/EditWidgetIcons.h"
53
#include "gui/EditWidgetProperties.h"
54
#include "gui/FileDialog.h"
55
#include "gui/Font.h"
56
#include "gui/Icons.h"
57
#include "gui/MessageBox.h"
58
#include "gui/entry/AutoTypeAssociationsModel.h"
59
#include "gui/entry/EntryAttributesModel.h"
60
#include "gui/entry/EntryHistoryModel.h"
61

62
EditEntryWidget::EditEntryWidget(QWidget* parent)
63
    : EditWidget(parent)
64
    , m_entry(nullptr)
65
    , m_mainUi(new Ui::EditEntryWidgetMain())
66
    , m_advancedUi(new Ui::EditEntryWidgetAdvanced())
67
    , m_autoTypeUi(new Ui::EditEntryWidgetAutoType())
68
    , m_sshAgentUi(new Ui::EditEntryWidgetSSHAgent())
69
    , m_historyUi(new Ui::EditEntryWidgetHistory())
70
    , m_browserUi(new Ui::EditEntryWidgetBrowser())
71
    , m_attachments(new EntryAttachments())
72
    , m_customData(new CustomData())
73
    , m_mainWidget(new QScrollArea(this))
74
    , m_advancedWidget(new QWidget(this))
75
    , m_iconsWidget(new EditWidgetIcons(this))
76
    , m_autoTypeWidget(new QWidget(this))
77
#ifdef WITH_XC_SSHAGENT
78
    , m_sshAgentWidget(new QWidget(this))
79
#endif
80
#ifdef WITH_XC_BROWSER
81
    , m_browserSettingsChanged(false)
82
    , m_browserWidget(new QWidget(this))
83
    , m_additionalURLsDataModel(new EntryURLModel(this))
84
#endif
85
    , m_editWidgetProperties(new EditWidgetProperties(this))
86
    , m_historyWidget(new QWidget(this))
87
    , m_entryAttributes(new EntryAttributes(this))
88
    , m_attributesModel(new EntryAttributesModel(m_advancedWidget))
89
    , m_historyModel(new EntryHistoryModel(this))
90
    , m_sortModel(new QSortFilterProxyModel(this))
91
    , m_autoTypeAssoc(new AutoTypeAssociations(this))
92
    , m_autoTypeAssocModel(new AutoTypeAssociationsModel(this))
93
    , m_autoTypeDefaultSequenceGroup(new QButtonGroup(this))
94
    , m_autoTypeWindowSequenceGroup(new QButtonGroup(this))
95
    , m_usernameCompleter(new QCompleter(this))
96
    , m_usernameCompleterModel(new QStringListModel(this))
97
{
98
    setupMain();
99
    setupAdvanced();
100
    setupIcon();
101
    setupAutoType();
102

103
#ifdef WITH_XC_SSHAGENT
104
    setupSSHAgent();
105
#endif
106

107
#ifdef WITH_XC_BROWSER
108
    setupBrowser();
109
#endif
110

111
    setupProperties();
112
    setupHistory();
113
    setupEntryUpdate();
114

115
    m_entryModifiedTimer.setSingleShot(true);
116
    m_entryModifiedTimer.setInterval(0);
117
    connect(&m_entryModifiedTimer, &QTimer::timeout, this, [this] {
118
        // TODO: Upon refactor of this widget, this needs to merge unsaved changes in the UI
119
        if (isVisible() && m_entry) {
120
            setForms(m_entry);
121
        }
122
    });
123

124
    connect(this, SIGNAL(accepted()), SLOT(acceptEntry()));
125
    connect(this, SIGNAL(rejected()), SLOT(cancel()));
126
    connect(this, SIGNAL(apply()), SLOT(commitEntry()));
127
    // clang-format off
128
    connect(m_iconsWidget,
129
            SIGNAL(messageEditEntry(QString,MessageWidget::MessageType)),
130
            SLOT(showMessage(QString,MessageWidget::MessageType)));
131
    // clang-format on
132

133
    connect(m_iconsWidget, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
134

135
    m_editWidgetProperties->setCustomData(m_customData.data());
136

137
    m_mainUi->passwordEdit->setQualityVisible(true);
138
}
139

140
EditEntryWidget::~EditEntryWidget() = default;
141

142
void EditEntryWidget::setupMain()
143
{
144
    m_mainUi->setupUi(m_mainWidget);
145
    addPage(tr("Entry"), icons()->icon("document-edit"), m_mainWidget);
146

147
    m_mainUi->usernameComboBox->setEditable(true);
148
    m_usernameCompleter->setCompletionMode(QCompleter::InlineCompletion);
149
    m_usernameCompleter->setCaseSensitivity(Qt::CaseSensitive);
150
    m_usernameCompleter->setModel(m_usernameCompleterModel);
151
    m_mainUi->usernameComboBox->setCompleter(m_usernameCompleter);
152

153
#ifdef WITH_XC_NETWORKING
154
    m_mainUi->fetchFaviconButton->setIcon(icons()->icon("favicon-download"));
155
    m_mainUi->fetchFaviconButton->setDisabled(true);
156
#else
157
    m_mainUi->fetchFaviconButton->setVisible(false);
158
#endif
159

160
#ifdef WITH_XC_NETWORKING
161
    connect(m_mainUi->fetchFaviconButton, SIGNAL(clicked()), m_iconsWidget, SLOT(downloadFavicon()));
162
    connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
163
    m_mainUi->urlEdit->enableVerifyMode();
164
#endif
165
#ifdef WITH_XC_BROWSER
166
    connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(entryURLEdited(const QString&)));
167
#endif
168
    connect(m_mainUi->expireCheck, &QCheckBox::toggled, [&](bool enabled) {
169
        m_mainUi->expireDatePicker->setEnabled(enabled);
170
        if (enabled) {
171
            m_mainUi->expireDatePicker->setDateTime(Clock::currentDateTime());
172
        }
173
    });
174

175
    connect(m_mainUi->revealNotesButton, &QToolButton::clicked, this, &EditEntryWidget::toggleHideNotes);
176

177
    m_mainUi->expirePresets->setMenu(createPresetsMenu());
178
    connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*)));
179
}
180

181
void EditEntryWidget::setupAdvanced()
182
{
183
    m_advancedUi->setupUi(m_advancedWidget);
184
    addPage(tr("Advanced"), icons()->icon("preferences-other"), m_advancedWidget);
185

186
    m_advancedUi->attachmentsWidget->setReadOnly(false);
187
    m_advancedUi->attachmentsWidget->setButtonsVisible(true);
188

189
    connect(m_advancedUi->attachmentsWidget,
190
            &EntryAttachmentsWidget::errorOccurred,
191
            this,
192
            [this](const QString& error) { showMessage(error, MessageWidget::Error); });
193

194
    m_attributesModel->setEntryAttributes(m_entryAttributes);
195
    m_advancedUi->attributesView->setModel(m_attributesModel);
196

197
    // clang-format off
198
    connect(m_advancedUi->addAttributeButton, SIGNAL(clicked()), SLOT(insertAttribute()));
199
    connect(m_advancedUi->editAttributeButton, SIGNAL(clicked()), SLOT(editCurrentAttribute()));
200
    connect(m_advancedUi->removeAttributeButton, SIGNAL(clicked()), SLOT(removeCurrentAttribute()));
201
    connect(m_advancedUi->protectAttributeButton, SIGNAL(toggled(bool)), SLOT(protectCurrentAttribute(bool)));
202
    connect(m_advancedUi->revealAttributeButton, SIGNAL(clicked(bool)), SLOT(toggleCurrentAttributeVisibility()));
203
    connect(m_advancedUi->attributesView->selectionModel(),
204
            SIGNAL(currentChanged(QModelIndex,QModelIndex)),
205
            SLOT(updateCurrentAttribute()));
206
    connect(m_advancedUi->fgColorButton, SIGNAL(clicked()), SLOT(pickColor()));
207
    connect(m_advancedUi->bgColorButton, SIGNAL(clicked()), SLOT(pickColor()));
208
    // clang-format on
209
}
210

211
void EditEntryWidget::setupIcon()
212
{
213
    m_iconsWidget->setShowApplyIconToButton(false);
214
    addPage(tr("Icon"), icons()->icon("preferences-desktop-icons"), m_iconsWidget);
215
    connect(this, SIGNAL(accepted()), m_iconsWidget, SLOT(abortRequests()));
216
    connect(this, SIGNAL(rejected()), m_iconsWidget, SLOT(abortRequests()));
217
}
218

219
void EditEntryWidget::openAutotypeHelp()
220
{
221
    QDesktopServices::openUrl(
222
        QUrl("https://keepassxc.org/docs/KeePassXC_UserGuide.html#_configure_auto_type_sequences"));
223
}
224

225
void EditEntryWidget::setupAutoType()
226
{
227
    m_autoTypeUi->setupUi(m_autoTypeWidget);
228
    addPage(tr("Auto-Type"), icons()->icon("auto-type"), m_autoTypeWidget);
229

230
    m_autoTypeUi->openHelpButton->setIcon(icons()->icon("system-help"));
231

232
    m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->inheritSequenceButton);
233
    m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->customSequenceButton);
234
    m_autoTypeAssocModel->setAutoTypeAssociations(m_autoTypeAssoc);
235
    m_autoTypeUi->assocView->setModel(m_autoTypeAssocModel);
236
    m_autoTypeUi->assocView->setColumnHidden(1, true);
237

238
    // clang-format off
239
    connect(m_autoTypeUi->enableButton, SIGNAL(toggled(bool)), SLOT(updateAutoTypeEnabled()));
240
    connect(m_autoTypeUi->customSequenceButton, &QRadioButton::toggled, this, &EditEntryWidget::updateAutoTypeEnabled);
241
    connect(m_autoTypeUi->openHelpButton, SIGNAL(clicked()), SLOT(openAutotypeHelp()));
242
    connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(toggled(bool)),
243
            m_autoTypeUi->windowSequenceEdit, SLOT(setEnabled(bool)));
244
    connect(m_autoTypeUi->assocAddButton, SIGNAL(clicked()), SLOT(insertAutoTypeAssoc()));
245
    connect(m_autoTypeUi->assocRemoveButton, SIGNAL(clicked()), SLOT(removeAutoTypeAssoc()));
246
    connect(m_autoTypeUi->assocView->selectionModel(),
247
            SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
248
            SLOT(updateAutoTypeEnabled()));
249
    connect(m_autoTypeUi->assocView->selectionModel(),
250
            SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
251
            SLOT(loadCurrentAssoc(QModelIndex)));
252
    connect(m_autoTypeAssocModel, SIGNAL(modelReset()), SLOT(updateAutoTypeEnabled()));
253
    connect(m_autoTypeAssocModel, SIGNAL(modelReset()), SLOT(clearCurrentAssoc()));
254
    connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)), SLOT(applyCurrentAssoc()));
255
    connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(toggled(bool)), SLOT(applyCurrentAssoc()));
256
    connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)), SLOT(applyCurrentAssoc()));
257
    // clang-format on
258
}
259

260
#ifdef WITH_XC_BROWSER
261
void EditEntryWidget::setupBrowser()
262
{
263
    if (config()->get(Config::Browser_Enabled).toBool()) {
264
        m_browserUi->setupUi(m_browserWidget);
265
        addPage(tr("Browser Integration"), icons()->icon("internet-web-browser"), m_browserWidget);
266
        m_additionalURLsDataModel->setEntryAttributes(m_entryAttributes);
267
        m_browserUi->additionalURLsView->setModel(m_additionalURLsDataModel);
268

269
        m_browserUi->messageWidget->setCloseButtonVisible(false);
270
        m_browserUi->messageWidget->setAutoHideTimeout(-1);
271
        m_browserUi->messageWidget->setAnimate(false);
272
        m_browserUi->messageWidget->setVisible(false);
273

274
        // Use a custom item delegate to align the icon to the right side
275
        auto iconDelegate = new URLModelIconDelegate(m_browserUi->additionalURLsView);
276
        m_browserUi->additionalURLsView->setItemDelegate(iconDelegate);
277

278
        // clang-format off
279
        connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
280
        connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
281
        connect(m_browserUi->onlyHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
282
        connect(m_browserUi->notHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
283
        connect(m_browserUi->addURLButton, SIGNAL(clicked()), SLOT(insertURL()));
284
        connect(m_browserUi->removeURLButton, SIGNAL(clicked()), SLOT(removeCurrentURL()));
285
        connect(m_browserUi->editURLButton, SIGNAL(clicked()), SLOT(editCurrentURL()));
286
        connect(m_browserUi->additionalURLsView->selectionModel(),
287
            SIGNAL(currentChanged(QModelIndex,QModelIndex)),
288
            SLOT(updateCurrentURL()));
289
        connect(m_additionalURLsDataModel,
290
            SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
291
            SLOT(updateCurrentAttribute()));
292
        // clang-format on
293
    }
294
}
295

296
void EditEntryWidget::updateBrowserModified()
297
{
298
    m_browserSettingsChanged = true;
299
}
300

301
void EditEntryWidget::updateBrowser()
302
{
303
    if (!m_browserSettingsChanged) {
304
        return;
305
    }
306

307
    // Only update the custom data if no group level settings are used (checkbox is enabled)
308
    if (m_browserUi->hideEntryCheckbox->isEnabled()) {
309
        auto hide = m_browserUi->hideEntryCheckbox->isChecked();
310
        m_customData->set(BrowserService::OPTION_HIDE_ENTRY, (hide ? TRUE_STR : FALSE_STR));
311
    }
312

313
    if (m_browserUi->skipAutoSubmitCheckbox->isEnabled()) {
314
        auto skip = m_browserUi->skipAutoSubmitCheckbox->isChecked();
315
        m_customData->set(BrowserService::OPTION_SKIP_AUTO_SUBMIT, (skip ? TRUE_STR : FALSE_STR));
316
    }
317

318
    if (m_browserUi->onlyHttpAuthCheckbox->isEnabled()) {
319
        auto onlyHttpAuth = m_browserUi->onlyHttpAuthCheckbox->isChecked();
320
        m_customData->set(BrowserService::OPTION_ONLY_HTTP_AUTH, (onlyHttpAuth ? TRUE_STR : FALSE_STR));
321
    }
322

323
    if (m_browserUi->notHttpAuthCheckbox->isEnabled()) {
324
        auto notHttpAuth = m_browserUi->notHttpAuthCheckbox->isChecked();
325
        m_customData->set(BrowserService::OPTION_NOT_HTTP_AUTH, (notHttpAuth ? TRUE_STR : FALSE_STR));
326
    }
327
}
328

329
void EditEntryWidget::insertURL()
330
{
331
    Q_ASSERT(!m_history);
332

333
    QString name(EntryAttributes::AdditionalUrlAttribute);
334
    int i = 1;
335

336
    while (m_entryAttributes->keys().contains(name)) {
337
        name = QString("%1_%2").arg(EntryAttributes::AdditionalUrlAttribute, QString::number(i));
338
        i++;
339
    }
340

341
    m_entryAttributes->set(name, tr("<empty URL>"));
342
    QModelIndex index = m_additionalURLsDataModel->indexByKey(name);
343

344
    m_additionalURLsDataModel->setEntryUrl(m_entry->url());
345
    m_browserUi->additionalURLsView->setCurrentIndex(index);
346
    m_browserUi->additionalURLsView->edit(index);
347

348
    setModified(true);
349
}
350

351
void EditEntryWidget::removeCurrentURL()
352
{
353
    Q_ASSERT(!m_history);
354

355
    QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
356

357
    if (index.isValid()) {
358
        auto name = m_additionalURLsDataModel->keyByIndex(index);
359
        auto url = m_entryAttributes->value(name);
360
        if (url != tr("<empty URL>")) {
361
            auto result = MessageBox::question(this,
362
                                               tr("Confirm Removal"),
363
                                               tr("Are you sure you want to remove this URL?"),
364
                                               MessageBox::Remove | MessageBox::Cancel,
365
                                               MessageBox::Cancel);
366

367
            if (result != MessageBox::Remove) {
368
                return;
369
            }
370
        }
371
        m_entryAttributes->remove(m_additionalURLsDataModel->keyByIndex(index));
372
        if (m_additionalURLsDataModel->rowCount() == 0) {
373
            m_browserUi->editURLButton->setEnabled(false);
374
            m_browserUi->removeURLButton->setEnabled(false);
375
        }
376
        setModified(true);
377
    }
378
}
379

380
void EditEntryWidget::editCurrentURL()
381
{
382
    Q_ASSERT(!m_history);
383

384
    QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
385

386
    if (index.isValid()) {
387
        m_browserUi->additionalURLsView->edit(index);
388
        setModified(true);
389
    }
390
}
391

392
void EditEntryWidget::updateCurrentURL()
393
{
394
    QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
395

396
    if (index.isValid()) {
397
        // Don't allow editing in history view
398
        m_browserUi->editURLButton->setEnabled(!m_history);
399
        m_browserUi->removeURLButton->setEnabled(!m_history);
400
    } else {
401
        m_browserUi->editURLButton->setEnabled(false);
402
        m_browserUi->removeURLButton->setEnabled(false);
403
    }
404
}
405

406
void EditEntryWidget::entryURLEdited(const QString& url)
407
{
408
    m_additionalURLsDataModel->setEntryUrl(url);
409
}
410
#endif
411

412
void EditEntryWidget::setupProperties()
413
{
414
    addPage(tr("Properties"), icons()->icon("document-properties"), m_editWidgetProperties);
415
}
416

417
void EditEntryWidget::setupHistory()
418
{
419
    m_historyUi->setupUi(m_historyWidget);
420
    addPage(tr("History"), icons()->icon("view-history"), m_historyWidget);
421

422
    m_sortModel->setSourceModel(m_historyModel);
423
    m_sortModel->setDynamicSortFilter(true);
424
    m_sortModel->setSortLocaleAware(true);
425
    m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive);
426
    m_sortModel->setSortRole(Qt::UserRole);
427

428
    m_historyUi->historyView->setModel(m_sortModel);
429
    m_historyUi->historyView->setRootIsDecorated(false);
430

431
    // clang-format off
432
    connect(m_historyUi->historyView, SIGNAL(activated(QModelIndex)), SLOT(histEntryActivated(QModelIndex)));
433
    connect(m_historyUi->historyView->selectionModel(),
434
            SIGNAL(currentChanged(QModelIndex,QModelIndex)),
435
            SLOT(updateHistoryButtons(QModelIndex,QModelIndex)));
436

437
    connect(m_historyUi->showButton, SIGNAL(clicked()), SLOT(showHistoryEntry()));
438
    connect(m_historyUi->restoreButton, SIGNAL(clicked()), SLOT(restoreHistoryEntry()));
439
    connect(m_historyUi->deleteButton, SIGNAL(clicked()), SLOT(deleteHistoryEntry()));
440
    connect(m_historyUi->deleteAllButton, SIGNAL(clicked()), SLOT(deleteAllHistoryEntries()));
441
    // clang-format on
442
}
443

444
void EditEntryWidget::setupEntryUpdate()
445
{
446
    // Entry tab
447
    connect(m_mainUi->titleEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
448
    connect(m_mainUi->usernameComboBox->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(setModified()));
449
    connect(m_mainUi->passwordEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
450
    connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
451
#ifdef WITH_XC_NETWORKING
452
    connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString)));
453
#endif
454
    connect(m_mainUi->tagsList, SIGNAL(tagsEdited()), this, SLOT(setModified()));
455
    connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
456
    connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setModified()));
457
    connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
458

459
    // Advanced tab
460
    connect(m_advancedUi->attributesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
461
    connect(m_advancedUi->protectAttributeButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
462
    connect(m_advancedUi->excludeReportsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
463
    connect(m_advancedUi->fgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
464
    connect(m_advancedUi->bgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
465
    connect(m_advancedUi->attachmentsWidget, SIGNAL(widgetUpdated()), this, SLOT(setModified()));
466

467
    // Icon tab
468
    connect(m_iconsWidget, SIGNAL(widgetUpdated()), this, SLOT(setModified()));
469

470
    // Auto-Type tab
471
    connect(m_autoTypeUi->enableButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
472
    connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
473
    connect(m_autoTypeUi->inheritSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
474
    connect(m_autoTypeUi->customSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
475
    connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
476
    connect(m_autoTypeUi->sequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
477
    connect(m_autoTypeUi->windowTitleCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setModified()));
478
    connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)), this, SLOT(setModified()));
479

480
    // Properties and History tabs don't need extra connections
481

482
#ifdef WITH_XC_SSHAGENT
483
    // SSH Agent tab
484
    if (sshAgent()->isEnabled()) {
485
        connect(m_sshAgentUi->attachmentRadioButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
486
        connect(m_sshAgentUi->externalFileRadioButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
487
        connect(m_sshAgentUi->attachmentComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setModified()));
488
        connect(m_sshAgentUi->attachmentComboBox, SIGNAL(editTextChanged(QString)), this, SLOT(setModified()));
489
        connect(m_sshAgentUi->externalFileEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
490
        connect(m_sshAgentUi->addKeyToAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
491
        connect(m_sshAgentUi->removeKeyFromAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
492
        connect(m_sshAgentUi->requireUserConfirmationCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
493
        connect(m_sshAgentUi->lifetimeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
494
        connect(m_sshAgentUi->lifetimeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setModified()));
495
    }
496
#endif
497

498
#ifdef WITH_XC_BROWSER
499
    if (config()->get(Config::Browser_Enabled).toBool()) {
500
        connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
501
        connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
502
        connect(m_browserUi->onlyHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
503
        connect(m_browserUi->notHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
504
        connect(m_browserUi->addURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
505
        connect(m_browserUi->removeURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
506
        connect(m_browserUi->editURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
507
    }
508
#endif
509
}
510

511
void EditEntryWidget::emitHistoryEntryActivated(const QModelIndex& index)
512
{
513
    Q_ASSERT(!m_history);
514

515
    Entry* entry = m_historyModel->entryFromIndex(index);
516
    if (entry) {
517
        emit historyEntryActivated(entry);
518
    }
519
}
520

521
void EditEntryWidget::histEntryActivated(const QModelIndex& index)
522
{
523
    Q_ASSERT(!m_history);
524

525
    QModelIndex indexMapped = m_sortModel->mapToSource(index);
526
    if (indexMapped.isValid()) {
527
        emitHistoryEntryActivated(indexMapped);
528
    }
529
}
530

531
void EditEntryWidget::updateHistoryButtons(const QModelIndex& current, const QModelIndex& previous)
532
{
533
    Q_UNUSED(previous);
534

535
    if (m_historyModel->entryFromIndex(current)) {
536
        m_historyUi->showButton->setEnabled(true);
537
        m_historyUi->restoreButton->setEnabled(true);
538
        m_historyUi->deleteButton->setEnabled(true);
539
    } else {
540
        m_historyUi->showButton->setEnabled(false);
541
        m_historyUi->restoreButton->setEnabled(false);
542
        m_historyUi->deleteButton->setEnabled(false);
543
    }
544
}
545

546
#ifdef WITH_XC_SSHAGENT
547
void EditEntryWidget::setupSSHAgent()
548
{
549
    m_pendingPrivateKey = "";
550
    m_sshAgentUi->setupUi(m_sshAgentWidget);
551

552
    QFont fixedFont = Font::fixedFont();
553
    m_sshAgentUi->fingerprintTextLabel->setFont(fixedFont);
554
    m_sshAgentUi->commentTextLabel->setFont(fixedFont);
555
    m_sshAgentUi->publicKeyEdit->setFont(fixedFont);
556

557
    // clang-format off
558
    connect(m_sshAgentUi->attachmentRadioButton, &QRadioButton::clicked,
559
            this, &EditEntryWidget::updateSSHAgentKeyInfo);
560
    connect(m_sshAgentUi->attachmentComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
561
            this, &EditEntryWidget::updateSSHAgentAttachment);
562
    connect(m_sshAgentUi->externalFileRadioButton, &QRadioButton::clicked,
563
            this, &EditEntryWidget::updateSSHAgentKeyInfo);
564
    connect(m_sshAgentUi->externalFileEdit, &QLineEdit::textChanged, this, &EditEntryWidget::updateSSHAgentKeyInfo);
565
    connect(m_sshAgentUi->browseButton, &QPushButton::clicked, this, &EditEntryWidget::browsePrivateKey);
566
    connect(m_sshAgentUi->addToAgentButton, &QPushButton::clicked, this, &EditEntryWidget::addKeyToAgent);
567
    connect(m_sshAgentUi->removeFromAgentButton, &QPushButton::clicked, this, &EditEntryWidget::removeKeyFromAgent);
568
    connect(m_sshAgentUi->decryptButton, &QPushButton::clicked, this, &EditEntryWidget::decryptPrivateKey);
569
    connect(m_sshAgentUi->copyToClipboardButton, &QPushButton::clicked, this, &EditEntryWidget::copyPublicKey);
570
    connect(m_sshAgentUi->generateButton, &QPushButton::clicked, this, &EditEntryWidget::generatePrivateKey);
571

572
    connect(m_attachments.data(), &EntryAttachments::modified,
573
            this, &EditEntryWidget::updateSSHAgentAttachments);
574
    // clang-format on
575

576
    addPage(tr("SSH Agent"), icons()->icon("utilities-terminal"), m_sshAgentWidget);
577
}
578

579
void EditEntryWidget::setSSHAgentSettings()
580
{
581
    m_sshAgentUi->addKeyToAgentCheckBox->setChecked(m_sshAgentSettings.addAtDatabaseOpen());
582
    m_sshAgentUi->removeKeyFromAgentCheckBox->setChecked(m_sshAgentSettings.removeAtDatabaseClose());
583
    m_sshAgentUi->requireUserConfirmationCheckBox->setChecked(m_sshAgentSettings.useConfirmConstraintWhenAdding());
584
    m_sshAgentUi->lifetimeCheckBox->setChecked(m_sshAgentSettings.useLifetimeConstraintWhenAdding());
585
    m_sshAgentUi->lifetimeSpinBox->setValue(m_sshAgentSettings.lifetimeConstraintDuration());
586
    m_sshAgentUi->attachmentComboBox->clear();
587
    m_sshAgentUi->addToAgentButton->setEnabled(false);
588
    m_sshAgentUi->removeFromAgentButton->setEnabled(false);
589
    m_sshAgentUi->copyToClipboardButton->setEnabled(false);
590
}
591

592
void EditEntryWidget::updateSSHAgent()
593
{
594
    m_sshAgentSettings.reset();
595
    m_sshAgentSettings.fromEntry(m_entry);
596
    setSSHAgentSettings();
597

598
    if (!m_pendingPrivateKey.isEmpty()) {
599
        m_sshAgentSettings.setAttachmentName(m_pendingPrivateKey);
600
        m_sshAgentSettings.setSelectedType("attachment");
601
        m_pendingPrivateKey = "";
602
    }
603

604
    updateSSHAgentAttachments();
605
}
606

607
void EditEntryWidget::updateSSHAgentAttachment()
608
{
609
    m_sshAgentUi->attachmentRadioButton->setChecked(true);
610
    updateSSHAgentKeyInfo();
611
}
612

613
void EditEntryWidget::updateSSHAgentAttachments()
614
{
615
    // detect if KeeAgent.settings was removed by hand and reset settings
616
    if (m_entry && KeeAgentSettings::inEntryAttachments(m_entry->attachments())
617
        && !KeeAgentSettings::inEntryAttachments(m_attachments.data())) {
618
        m_sshAgentSettings.reset();
619
        setSSHAgentSettings();
620
    }
621

622
    m_sshAgentUi->attachmentComboBox->clear();
623
    m_sshAgentUi->attachmentComboBox->addItem("");
624

625
    for (const QString& fileName : m_attachments->keys()) {
626
        if (fileName == "KeeAgent.settings") {
627
            continue;
628
        }
629

630
        m_sshAgentUi->attachmentComboBox->addItem(fileName);
631
    }
632

633
    m_sshAgentUi->attachmentComboBox->setCurrentText(m_sshAgentSettings.attachmentName());
634
    m_sshAgentUi->externalFileEdit->setText(m_sshAgentSettings.fileName());
635

636
    if (m_sshAgentSettings.selectedType() == "attachment") {
637
        m_sshAgentUi->attachmentRadioButton->setChecked(true);
638
    } else {
639
        m_sshAgentUi->externalFileRadioButton->setChecked(true);
640
    }
641

642
    updateSSHAgentKeyInfo();
643
}
644

645
void EditEntryWidget::updateSSHAgentKeyInfo()
646
{
647
    m_sshAgentUi->addToAgentButton->setEnabled(false);
648
    m_sshAgentUi->removeFromAgentButton->setEnabled(false);
649
    m_sshAgentUi->copyToClipboardButton->setEnabled(false);
650
    m_sshAgentUi->fingerprintTextLabel->setText(tr("n/a"));
651
    m_sshAgentUi->commentTextLabel->setText(tr("n/a"));
652
    m_sshAgentUi->decryptButton->setEnabled(false);
653
    m_sshAgentUi->publicKeyEdit->document()->setPlainText("");
654

655
    OpenSSHKey key;
656

657
    if (!getOpenSSHKey(key)) {
658
        return;
659
    }
660

661
    if (!key.fingerprint().isEmpty()) {
662
        m_sshAgentUi->fingerprintTextLabel->setText(key.fingerprint(QCryptographicHash::Md5) + "\n"
663
                                                    + key.fingerprint(QCryptographicHash::Sha256));
664
    } else {
665
        m_sshAgentUi->fingerprintTextLabel->setText(tr("(encrypted)"));
666
    }
667

668
    if (!key.comment().isEmpty() || !key.encrypted()) {
669
        m_sshAgentUi->commentTextLabel->setText(key.comment());
670
    } else {
671
        m_sshAgentUi->commentTextLabel->setText(tr("(encrypted)"));
672
        m_sshAgentUi->decryptButton->setEnabled(true);
673
    }
674

675
    if (!key.publicKey().isEmpty()) {
676
        m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
677
        m_sshAgentUi->copyToClipboardButton->setEnabled(true);
678
    } else {
679
        m_sshAgentUi->publicKeyEdit->document()->setPlainText(tr("(encrypted)"));
680
        m_sshAgentUi->copyToClipboardButton->setDisabled(true);
681
    }
682

683
    // enable agent buttons only if we have an agent running
684
    if (sshAgent()->isAgentRunning()) {
685
        m_sshAgentUi->addToAgentButton->setEnabled(true);
686
        m_sshAgentUi->removeFromAgentButton->setEnabled(true);
687

688
        sshAgent()->setAutoRemoveOnLock(key, m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked());
689
    }
690
}
691

692
void EditEntryWidget::toKeeAgentSettings(KeeAgentSettings& settings) const
693
{
694
    settings.setAddAtDatabaseOpen(m_sshAgentUi->addKeyToAgentCheckBox->isChecked());
695
    settings.setRemoveAtDatabaseClose(m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked());
696
    settings.setUseConfirmConstraintWhenAdding(m_sshAgentUi->requireUserConfirmationCheckBox->isChecked());
697
    settings.setUseLifetimeConstraintWhenAdding(m_sshAgentUi->lifetimeCheckBox->isChecked());
698
    settings.setLifetimeConstraintDuration(m_sshAgentUi->lifetimeSpinBox->value());
699

700
    if (m_sshAgentUi->attachmentRadioButton->isChecked()) {
701
        settings.setSelectedType("attachment");
702
    } else {
703
        settings.setSelectedType("file");
704
    }
705
    settings.setAttachmentName(m_sshAgentUi->attachmentComboBox->currentText());
706
    settings.setFileName(m_sshAgentUi->externalFileEdit->text());
707

708
    // we don't use this as we don't run an agent but for compatibility we set it if necessary
709
    settings.setAllowUseOfSshKey(settings.addAtDatabaseOpen() || settings.removeAtDatabaseClose());
710

711
    // we don't use this either but we don't want it to dirty flag the config
712
    settings.setSaveAttachmentToTempFile(m_sshAgentSettings.saveAttachmentToTempFile());
713
}
714

715
void EditEntryWidget::updateTotp()
716
{
717
    if (m_entry) {
718
        m_attributesModel->setEntryAttributes(m_entry->attributes());
719
    }
720
}
721

722
void EditEntryWidget::browsePrivateKey()
723
{
724
    auto fileName = fileDialog()->getOpenFileName(this, tr("Select private key"), FileDialog::getLastDir("sshagent"));
725
    if (!fileName.isEmpty()) {
726
        FileDialog::saveLastDir("sshagent", fileName);
727
        m_sshAgentUi->externalFileEdit->setText(fileName);
728
        m_sshAgentUi->externalFileRadioButton->setChecked(true);
729
        updateSSHAgentKeyInfo();
730
    }
731
}
732

733
bool EditEntryWidget::getOpenSSHKey(OpenSSHKey& key, bool decrypt)
734
{
735
    KeeAgentSettings settings;
736
    toKeeAgentSettings(settings);
737

738
    if (!m_entry || !settings.keyConfigured()) {
739
        return false;
740
    }
741

742
    if (!settings.toOpenSSHKey(m_mainUi->usernameComboBox->lineEdit()->text(),
743
                               m_mainUi->passwordEdit->text(),
744
                               m_db->filePath(),
745
                               m_attachments.data(),
746
                               key,
747
                               decrypt)) {
748
        showMessage(settings.errorString(), MessageWidget::Error);
749
        return false;
750
    }
751

752
    return true;
753
}
754

755
void EditEntryWidget::addKeyToAgent()
756
{
757
    OpenSSHKey key;
758

759
    if (!getOpenSSHKey(key, true)) {
760
        return;
761
    }
762

763
    m_sshAgentUi->commentTextLabel->setText(key.comment());
764
    m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
765

766
    KeeAgentSettings settings;
767
    toKeeAgentSettings(settings);
768

769
    if (!sshAgent()->addIdentity(key, settings, m_db->uuid())) {
770
        showMessage(sshAgent()->errorString(), MessageWidget::Error);
771
        return;
772
    }
773
}
774

775
void EditEntryWidget::removeKeyFromAgent()
776
{
777
    OpenSSHKey key;
778

779
    if (!getOpenSSHKey(key)) {
780
        return;
781
    }
782

783
    if (!sshAgent()->removeIdentity(key)) {
784
        showMessage(sshAgent()->errorString(), MessageWidget::Error);
785
        return;
786
    }
787
}
788

789
void EditEntryWidget::decryptPrivateKey()
790
{
791
    OpenSSHKey key;
792

793
    if (!getOpenSSHKey(key, true)) {
794
        return;
795
    }
796

797
    if (!key.comment().isEmpty()) {
798
        m_sshAgentUi->commentTextLabel->setText(key.comment());
799
    } else {
800
        m_sshAgentUi->commentTextLabel->setText(tr("n/a"));
801
    }
802

803
    m_sshAgentUi->fingerprintTextLabel->setText(key.fingerprint(QCryptographicHash::Md5) + "\n"
804
                                                + key.fingerprint(QCryptographicHash::Sha256));
805
    m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
806
    m_sshAgentUi->copyToClipboardButton->setEnabled(true);
807
}
808

809
void EditEntryWidget::copyPublicKey()
810
{
811
    clipboard()->setText(m_sshAgentUi->publicKeyEdit->document()->toPlainText());
812
}
813

814
void EditEntryWidget::generatePrivateKey()
815
{
816
    auto dialog = new OpenSSHKeyGenDialog(this);
817

818
    OpenSSHKey key;
819
    dialog->setKey(&key);
820

821
    if (dialog->exec()) {
822
        // derive openssh naming from type
823
        QString keyPrefix = key.type();
824
        if (keyPrefix.startsWith("ecdsa")) {
825
            keyPrefix = "id_ecdsa";
826
        } else {
827
            keyPrefix.replace("ssh-", "id_");
828
        }
829

830
        for (int i = 0; i < 10; i++) {
831
            QString keyName = keyPrefix;
832

833
            if (i > 0) {
834
                keyName += "." + QString::number(i);
835
            }
836

837
            if (!m_entry->attachments()->hasKey(keyName)) {
838
                m_pendingPrivateKey = keyName;
839
                m_entry->attachments()->set(m_pendingPrivateKey, key.privateKey().toUtf8());
840
                break;
841
            }
842
        }
843
    }
844
}
845
#endif
846

847
void EditEntryWidget::useExpiryPreset(QAction* action)
848
{
849
    m_mainUi->expireCheck->setChecked(true);
850
    auto delta = action->data().value<TimeDelta>();
851
    QDateTime now = Clock::currentDateTime();
852
    QDateTime expiryDateTime = now + delta;
853
    m_mainUi->expireDatePicker->setDateTime(expiryDateTime);
854
}
855

856
void EditEntryWidget::toggleHideNotes(bool visible)
857
{
858
    m_mainUi->notesEdit->setVisible(visible);
859
    m_mainUi->revealNotesButton->setIcon(icons()->onOffIcon("password-show", visible));
860
}
861

862
Entry* EditEntryWidget::currentEntry() const
863
{
864
    return m_entry;
865
}
866

867
void EditEntryWidget::loadEntry(Entry* entry,
868
                                bool create,
869
                                bool history,
870
                                const QString& parentName,
871
                                QSharedPointer<Database> database)
872
{
873
    m_entry = entry;
874
    m_db = std::move(database);
875
    m_create = create;
876
    m_history = history;
877

878
    if (history) {
879
        setHeadline(QString("%1 \u2022 %2").arg(parentName, tr("Entry history")));
880
    } else {
881
        if (create) {
882
            setHeadline(QString("%1 \u2022 %2").arg(parentName, tr("Add entry")));
883
        } else {
884
            setHeadline(QString("%1 \u2022 %2 \u2022 %3").arg(parentName, entry->title(), tr("Edit entry")));
885
            // Reload entry details if changed outside of the edit dialog
886
            connect(m_entry, &Entry::modified, this, [this] { m_entryModifiedTimer.start(); });
887
        }
888
    }
889

890
    setForms(entry);
891
    setReadOnly(m_history);
892

893
    setCurrentPage(0);
894
    setPageHidden(m_historyWidget, m_history || m_entry->historyItems().count() < 1);
895
#ifdef WITH_XC_SSHAGENT
896
    setPageHidden(m_sshAgentWidget, !sshAgent()->isEnabled());
897
#endif
898

899
    // Force the user to Save/Discard new entries
900
    showApplyButton(!m_create);
901

902
    setModified(false);
903
}
904

905
void EditEntryWidget::setForms(Entry* entry, bool restore)
906
{
907
    m_attachments->copyDataFrom(entry->attachments());
908
    m_customData->copyDataFrom(entry->customData());
909

910
    m_mainUi->titleEdit->setReadOnly(m_history);
911
    m_mainUi->usernameComboBox->lineEdit()->setReadOnly(m_history);
912
    m_mainUi->urlEdit->setReadOnly(m_history);
913
    m_mainUi->passwordEdit->setReadOnly(m_history);
914
    m_mainUi->tagsList->tags(entry->tagList());
915
    m_mainUi->tagsList->completion(m_db->tagList());
916
    m_mainUi->expireCheck->setEnabled(!m_history);
917
    m_mainUi->expireDatePicker->setReadOnly(m_history);
918
    m_mainUi->revealNotesButton->setIcon(icons()->onOffIcon("password-show", false));
919
    m_mainUi->revealNotesButton->setVisible(config()->get(Config::Security_HideNotes).toBool());
920
    m_mainUi->notesEdit->setReadOnly(m_history);
921
    m_mainUi->notesEdit->setVisible(!config()->get(Config::Security_HideNotes).toBool());
922
    if (config()->get(Config::GUI_MonospaceNotes).toBool()) {
923
        m_mainUi->notesEdit->setFont(Font::fixedFont());
924
    } else {
925
        m_mainUi->notesEdit->setFont(Font::defaultFont());
926
    }
927

928
    m_advancedUi->attachmentsWidget->setReadOnly(m_history);
929
    m_advancedUi->addAttributeButton->setEnabled(!m_history);
930
    m_advancedUi->editAttributeButton->setEnabled(false);
931
    m_advancedUi->removeAttributeButton->setEnabled(false);
932
    m_advancedUi->attributesEdit->setReadOnly(m_history);
933
    QAbstractItemView::EditTriggers editTriggers;
934
    if (m_history) {
935
        editTriggers = QAbstractItemView::NoEditTriggers;
936
    } else {
937
        editTriggers = QAbstractItemView::DoubleClicked;
938
    }
939
    m_advancedUi->attributesView->setEditTriggers(editTriggers);
940
    m_advancedUi->excludeReportsCheckBox->setChecked(entry->excludeFromReports());
941
    setupColorButton(true, entry->foregroundColor());
942
    setupColorButton(false, entry->backgroundColor());
943
    m_iconsWidget->setEnabled(!m_history);
944
    m_autoTypeUi->sequenceEdit->setReadOnly(m_history);
945
    m_autoTypeUi->windowTitleCombo->lineEdit()->setReadOnly(m_history);
946
    m_autoTypeUi->windowSequenceEdit->setReadOnly(m_history);
947
    m_historyWidget->setEnabled(!m_history);
948

949
    m_mainUi->titleEdit->setText(entry->title());
950
    m_mainUi->usernameComboBox->lineEdit()->setText(entry->username());
951
    m_mainUi->urlEdit->setText(entry->url());
952
    m_mainUi->passwordEdit->setText(entry->password());
953
    m_mainUi->passwordEdit->setShowPassword(!config()->get(Config::Security_PasswordsHidden).toBool());
954
    if (!m_history) {
955
        m_mainUi->passwordEdit->enablePasswordGenerator();
956
    }
957
    m_mainUi->expireCheck->setChecked(entry->timeInfo().expires());
958
    m_mainUi->expireDatePicker->setDateTime(entry->timeInfo().expiryTime().toLocalTime());
959
    m_mainUi->expirePresets->setEnabled(!m_history);
960

961
    QList<QString> commonUsernames = m_db->commonUsernames();
962
    m_usernameCompleterModel->setStringList(commonUsernames);
963
    QString usernameToRestore = m_mainUi->usernameComboBox->lineEdit()->text();
964
    m_mainUi->usernameComboBox->clear();
965
    m_mainUi->usernameComboBox->addItems(commonUsernames);
966
    m_mainUi->usernameComboBox->lineEdit()->setText(usernameToRestore);
967

968
    m_mainUi->notesEdit->setPlainText(entry->notes());
969

970
    m_advancedUi->attachmentsWidget->linkAttachments(m_attachments.data());
971
    m_entryAttributes->copyCustomKeysFrom(entry->attributes());
972

973
    if (m_attributesModel->rowCount() != 0) {
974
        m_advancedUi->attributesView->setCurrentIndex(m_attributesModel->index(0, 0));
975
    } else {
976
        m_advancedUi->attributesEdit->setPlainText("");
977
        m_advancedUi->attributesEdit->setEnabled(false);
978
    }
979

980
    QList<int> sizes = m_advancedUi->attributesSplitter->sizes();
981
    sizes.replace(0, m_advancedUi->attributesSplitter->width() * 0.3);
982
    sizes.replace(1, m_advancedUi->attributesSplitter->width() * 0.7);
983
    m_advancedUi->attributesSplitter->setSizes(sizes);
984

985
    IconStruct iconStruct;
986
    iconStruct.uuid = entry->iconUuid();
987
    iconStruct.number = entry->iconNumber();
988
    m_iconsWidget->load(entry->uuid(), m_db, iconStruct, entry->webUrl());
989

990
    m_autoTypeUi->enableButton->setChecked(entry->autoTypeEnabled());
991
    if (entry->defaultAutoTypeSequence().isEmpty()) {
992
        m_autoTypeUi->inheritSequenceButton->setChecked(true);
993
    } else {
994
        m_autoTypeUi->customSequenceButton->setChecked(true);
995
    }
996
    m_autoTypeUi->sequenceEdit->setText(entry->effectiveAutoTypeSequence());
997
    m_autoTypeUi->windowTitleCombo->lineEdit()->clear();
998
    m_autoTypeUi->customWindowSequenceButton->setChecked(false);
999
    m_autoTypeUi->windowSequenceEdit->setText("");
1000
    m_autoTypeAssoc->copyDataFrom(entry->autoTypeAssociations());
1001
    m_autoTypeAssocModel->setEntry(entry);
1002
    if (m_autoTypeAssoc->size() != 0) {
1003
        m_autoTypeUi->assocView->setCurrentIndex(m_autoTypeAssocModel->index(0, 0));
1004
    }
1005
    if (!m_history) {
1006
        m_autoTypeUi->windowTitleCombo->refreshWindowList();
1007
    }
1008
    updateAutoTypeEnabled();
1009

1010
#ifdef WITH_XC_SSHAGENT
1011
    if (sshAgent()->isEnabled()) {
1012
        updateSSHAgent();
1013
    }
1014
#endif
1015

1016
#ifdef WITH_XC_BROWSER
1017
    if (config()->get(Config::Browser_Enabled).toBool()) {
1018
        if (!hasPage(m_browserWidget)) {
1019
            setupBrowser();
1020
        }
1021

1022
        auto hideEntriesCheckBoxEnabled = true;
1023
        auto skipAutoSubmitCheckBoxEnabled = true;
1024
        auto onlyHttpAuthCheckBoxEnabled = true;
1025
        auto notHttpAuthCheckBoxEnabled = true;
1026
        auto hideEntries = false;
1027
        auto skipAutoSubmit = false;
1028
        auto onlyHttpAuth = false;
1029
        auto notHttpAuth = false;
1030

1031
        const auto group = m_entry->group();
1032
        if (group) {
1033
            hideEntries = group->resolveCustomDataTriState(BrowserService::OPTION_HIDE_ENTRY) == Group::Enable;
1034
            skipAutoSubmit = group->resolveCustomDataTriState(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == Group::Enable;
1035
            onlyHttpAuth = group->resolveCustomDataTriState(BrowserService::OPTION_ONLY_HTTP_AUTH) == Group::Enable;
1036
            notHttpAuth = group->resolveCustomDataTriState(BrowserService::OPTION_NOT_HTTP_AUTH) == Group::Enable;
1037

1038
            hideEntriesCheckBoxEnabled =
1039
                group->resolveCustomDataTriState(BrowserService::OPTION_HIDE_ENTRY) == Group::Inherit;
1040
            skipAutoSubmitCheckBoxEnabled =
1041
                group->resolveCustomDataTriState(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == Group::Inherit;
1042
            onlyHttpAuthCheckBoxEnabled =
1043
                group->resolveCustomDataTriState(BrowserService::OPTION_ONLY_HTTP_AUTH) == Group::Inherit;
1044
            notHttpAuthCheckBoxEnabled =
1045
                group->resolveCustomDataTriState(BrowserService::OPTION_NOT_HTTP_AUTH) == Group::Inherit;
1046
        }
1047

1048
        // Show information about group level settings
1049
        if (!hideEntriesCheckBoxEnabled || !skipAutoSubmitCheckBoxEnabled || !onlyHttpAuthCheckBoxEnabled
1050
            || !notHttpAuthCheckBoxEnabled) {
1051
            m_browserUi->messageWidget->showMessage(
1052
                tr("Some Browser Integration settings are overridden by group settings."), MessageWidget::Information);
1053
            m_browserUi->messageWidget->setVisible(true);
1054
        }
1055

1056
        // Disable checkboxes based on group level settings
1057
        updateBrowserIntegrationCheckbox(
1058
            m_browserUi->hideEntryCheckbox, hideEntriesCheckBoxEnabled, hideEntries, BrowserService::OPTION_HIDE_ENTRY);
1059
        updateBrowserIntegrationCheckbox(m_browserUi->skipAutoSubmitCheckbox,
1060
                                         skipAutoSubmitCheckBoxEnabled,
1061
                                         skipAutoSubmit,
1062
                                         BrowserService::OPTION_SKIP_AUTO_SUBMIT);
1063
        updateBrowserIntegrationCheckbox(m_browserUi->onlyHttpAuthCheckbox,
1064
                                         onlyHttpAuthCheckBoxEnabled,
1065
                                         onlyHttpAuth,
1066
                                         BrowserService::OPTION_ONLY_HTTP_AUTH);
1067
        updateBrowserIntegrationCheckbox(m_browserUi->notHttpAuthCheckbox,
1068
                                         notHttpAuthCheckBoxEnabled,
1069
                                         notHttpAuth,
1070
                                         BrowserService::OPTION_NOT_HTTP_AUTH);
1071

1072
        m_browserUi->addURLButton->setEnabled(!m_history);
1073
        m_browserUi->removeURLButton->setEnabled(false);
1074
        m_browserUi->editURLButton->setEnabled(false);
1075
        m_browserUi->additionalURLsView->setEditTriggers(editTriggers);
1076

1077
        if (m_additionalURLsDataModel->rowCount() != 0) {
1078
            m_browserUi->additionalURLsView->setCurrentIndex(m_additionalURLsDataModel->index(0, 0));
1079
        }
1080
    }
1081

1082
    setPageHidden(m_browserWidget, !config()->get(Config::Browser_Enabled).toBool());
1083
#endif
1084

1085
    m_editWidgetProperties->setFields(entry->timeInfo(), entry->uuid());
1086

1087
    if (!m_history && !restore) {
1088
        m_historyModel->setEntries(entry->historyItems(), entry);
1089
        m_historyUi->historyView->sortByColumn(0, Qt::DescendingOrder);
1090
    }
1091
    if (m_historyModel->rowCount() > 0) {
1092
        m_historyUi->deleteAllButton->setEnabled(true);
1093
    } else {
1094
        m_historyUi->deleteAllButton->setEnabled(false);
1095
    }
1096

1097
    updateHistoryButtons(m_historyUi->historyView->currentIndex(), QModelIndex());
1098

1099
    m_mainUi->titleEdit->setFocus();
1100
}
1101

1102
/**
1103
 * Commit the form values to in-memory database representation
1104
 *
1105
 * @return true is commit successful, otherwise false
1106
 */
1107
bool EditEntryWidget::commitEntry()
1108
{
1109
    if (m_history) {
1110
        clear();
1111
        hideMessage();
1112
        emit editFinished(false);
1113
        return true;
1114
    }
1115

1116
    // HACK: Check that entry pointer is still valid, see https://github.com/keepassxreboot/keepassxc/issues/5722
1117
    if (!m_entry) {
1118
        QMessageBox::information(this,
1119
                                 tr("Invalid Entry"),
1120
                                 tr("An external merge operation has invalidated this entry.\n"
1121
                                    "Unfortunately, any changes made have been lost."));
1122
        return true;
1123
    }
1124

1125
    // Check Auto-Type validity early
1126
    QString error;
1127
    if (m_autoTypeUi->customSequenceButton->isChecked()
1128
        && !AutoType::verifyAutoTypeSyntax(m_autoTypeUi->sequenceEdit->text(), m_entry, error)) {
1129
        auto res = MessageBox::question(this,
1130
                                        tr("Auto-Type Validation Error"),
1131
                                        tr("An error occurred while validating the custom Auto-Type sequence:\n%1\n"
1132
                                           "Would you like to correct it?")
1133
                                            .arg(error),
1134
                                        MessageBox::Yes | MessageBox::No,
1135
                                        MessageBox::Yes);
1136
        if (res == MessageBox::Yes) {
1137
            setCurrentPage(3);
1138
            return false;
1139
        }
1140
    }
1141
    for (const auto& assoc : m_autoTypeAssoc->getAll()) {
1142
        if (!AutoType::verifyAutoTypeSyntax(assoc.sequence, m_entry, error)) {
1143
            auto res =
1144
                MessageBox::question(this,
1145
                                     tr("Auto-Type Validation Error"),
1146
                                     tr("An error occurred while validating the Auto-Type sequence for \"%1\":\n%2\n"
1147
                                        "Would you like to correct it?")
1148
                                         .arg(assoc.window.left(40), error),
1149
                                     MessageBox::Yes | MessageBox::No,
1150
                                     MessageBox::Yes);
1151
            if (res == MessageBox::Yes) {
1152
                setCurrentPage(3);
1153
                return false;
1154
            }
1155
        }
1156
    }
1157

1158
    if (m_advancedUi->attributesView->currentIndex().isValid() && m_advancedUi->attributesEdit->isEnabled()) {
1159
        QString key = m_attributesModel->keyByIndex(m_advancedUi->attributesView->currentIndex());
1160
        m_entryAttributes->set(key, m_advancedUi->attributesEdit->toPlainText(), m_entryAttributes->isProtected(key));
1161
    }
1162

1163
    m_currentAttribute = QPersistentModelIndex();
1164

1165
    // must stand before beginUpdate()
1166
    // we don't want to create a new history item, if only the history has changed
1167
    m_entry->removeHistoryItems(m_historyModel->deletedEntries());
1168
    m_historyModel->clearDeletedEntries();
1169

1170
    m_autoTypeAssoc->removeEmpty();
1171

1172
#ifdef WITH_XC_SSHAGENT
1173
    toKeeAgentSettings(m_sshAgentSettings);
1174
#endif
1175

1176
    // Begin entry update
1177
    if (!m_create) {
1178
        m_entry->beginUpdate();
1179
    }
1180

1181
#ifdef WITH_XC_BROWSER
1182
    if (config()->get(Config::Browser_Enabled).toBool()) {
1183
        updateBrowser();
1184
    }
1185
#endif
1186

1187
    updateEntryData(m_entry);
1188

1189
    if (!m_create) {
1190
        m_entry->endUpdate();
1191
    }
1192
    // End entry update
1193

1194
    m_historyModel->setEntries(m_entry->historyItems(), m_entry);
1195
    setPageHidden(m_historyWidget, m_history || m_entry->historyItems().count() < 1);
1196
    m_advancedUi->attachmentsWidget->linkAttachments(m_entry->attachments());
1197

1198
    showMessage(tr("Entry updated successfully."), MessageWidget::Positive);
1199
    setModified(false);
1200
    // Prevent a reload due to entry modified signals
1201
    m_entryModifiedTimer.stop();
1202

1203
    return true;
1204
}
1205

1206
void EditEntryWidget::acceptEntry()
1207
{
1208
    if (commitEntry()) {
1209
        clear();
1210
        emit editFinished(true);
1211
    }
1212
}
1213

1214
void EditEntryWidget::updateEntryData(Entry* entry) const
1215
{
1216
    QRegularExpression newLineRegex("(?:\r?\n|\r)");
1217

1218
    entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
1219
    entry->attachments()->copyDataFrom(m_attachments.data());
1220
    entry->customData()->copyDataFrom(m_customData.data());
1221
    entry->setTitle(m_mainUi->titleEdit->text().replace(newLineRegex, " "));
1222
    entry->setUsername(m_mainUi->usernameComboBox->lineEdit()->text().replace(newLineRegex, " "));
1223
    entry->setUrl(m_mainUi->urlEdit->text().replace(newLineRegex, " "));
1224
    entry->setPassword(m_mainUi->passwordEdit->text());
1225
    entry->setExpires(m_mainUi->expireCheck->isChecked());
1226
    entry->setExpiryTime(m_mainUi->expireDatePicker->dateTime().toUTC());
1227
    entry->setTags(m_mainUi->tagsList->tags().toSet().toList().join(";")); // remove repeated tags
1228

1229
    entry->setNotes(m_mainUi->notesEdit->toPlainText());
1230

1231
    if (entry->excludeFromReports() != m_advancedUi->excludeReportsCheckBox->isChecked()) {
1232
        entry->setExcludeFromReports(m_advancedUi->excludeReportsCheckBox->isChecked());
1233
    }
1234

1235
    if (m_advancedUi->fgColorCheckBox->isChecked() && m_advancedUi->fgColorButton->property("color").isValid()) {
1236
        entry->setForegroundColor(m_advancedUi->fgColorButton->property("color").toString());
1237
    } else {
1238
        entry->setForegroundColor(QString());
1239
    }
1240

1241
    if (m_advancedUi->bgColorCheckBox->isChecked() && m_advancedUi->bgColorButton->property("color").isValid()) {
1242
        entry->setBackgroundColor(m_advancedUi->bgColorButton->property("color").toString());
1243
    } else {
1244
        entry->setBackgroundColor(QString());
1245
    }
1246

1247
    IconStruct iconStruct = m_iconsWidget->state();
1248

1249
    if (iconStruct.number < 0) {
1250
        entry->setIcon(Entry::DefaultIconNumber);
1251
    } else if (iconStruct.uuid.isNull()) {
1252
        entry->setIcon(iconStruct.number);
1253
    } else {
1254
        entry->setIcon(iconStruct.uuid);
1255
    }
1256

1257
    entry->setAutoTypeEnabled(m_autoTypeUi->enableButton->isChecked());
1258
    if (m_autoTypeUi->inheritSequenceButton->isChecked()) {
1259
        entry->setDefaultAutoTypeSequence(QString());
1260
    } else {
1261
        entry->setDefaultAutoTypeSequence(m_autoTypeUi->sequenceEdit->text());
1262
    }
1263

1264
    entry->autoTypeAssociations()->copyDataFrom(m_autoTypeAssoc);
1265

1266
#ifdef WITH_XC_SSHAGENT
1267
    if (sshAgent()->isEnabled()) {
1268
        m_sshAgentSettings.toEntry(entry);
1269
    }
1270
#endif
1271
}
1272

1273
void EditEntryWidget::updateBrowserIntegrationCheckbox(QCheckBox* checkBox,
1274
                                                       bool enabled,
1275
                                                       bool value,
1276
                                                       const QString& option)
1277
{
1278
    auto block = checkBox->signalsBlocked();
1279
    checkBox->blockSignals(true);
1280

1281
    if (enabled) {
1282
        if (m_customData->contains(option)) {
1283
            checkBox->setChecked(m_customData->value(option) == TRUE_STR);
1284
        } else {
1285
            checkBox->setChecked(false);
1286
        }
1287
    } else {
1288
        checkBox->setChecked(value);
1289
    }
1290
    checkBox->setEnabled(enabled);
1291

1292
    checkBox->blockSignals(block);
1293
}
1294

1295
void EditEntryWidget::cancel()
1296
{
1297
    if (m_history) {
1298
        clear();
1299
        hideMessage();
1300
        emit editFinished(false);
1301
        return;
1302
    }
1303

1304
    if (!m_entry->iconUuid().isNull() && !m_db->metadata()->hasCustomIcon(m_entry->iconUuid())) {
1305
        m_entry->setIcon(Entry::DefaultIconNumber);
1306
    }
1307

1308
    bool accepted = false;
1309
    if (isModified()) {
1310
        auto result = MessageBox::question(this,
1311
                                           tr("Unsaved Changes"),
1312
                                           tr("Would you like to save changes to this entry?"),
1313
                                           MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
1314
                                           MessageBox::Cancel);
1315
        if (result == MessageBox::Cancel) {
1316
            return;
1317
        } else if (result == MessageBox::Save) {
1318
            accepted = true;
1319
            if (!commitEntry()) {
1320
                return;
1321
            }
1322
        }
1323
    }
1324

1325
    clear();
1326
    emit editFinished(accepted);
1327
}
1328

1329
void EditEntryWidget::clear()
1330
{
1331
    if (m_entry) {
1332
        m_entry->disconnect(this);
1333
    }
1334

1335
    m_entry = nullptr;
1336
    m_db.reset();
1337

1338
    m_mainUi->titleEdit->setText("");
1339
    m_mainUi->passwordEdit->setText("");
1340
    m_mainUi->urlEdit->setText("");
1341
    m_mainUi->notesEdit->clear();
1342

1343
    m_entryAttributes->clear();
1344
    m_attachments->clear();
1345
    m_customData->clear();
1346
    m_autoTypeAssoc->clear();
1347
    m_historyModel->clear();
1348
    m_iconsWidget->reset();
1349
    hideMessage();
1350
}
1351

1352
#ifdef WITH_XC_NETWORKING
1353
void EditEntryWidget::updateFaviconButtonEnable(const QString& url)
1354
{
1355
    m_mainUi->fetchFaviconButton->setDisabled(url.isEmpty());
1356
}
1357
#endif
1358

1359
void EditEntryWidget::insertAttribute()
1360
{
1361
    Q_ASSERT(!m_history);
1362

1363
    QString name = tr("New attribute");
1364
    int i = 1;
1365

1366
    while (m_entryAttributes->keys().contains(name)) {
1367
        name = tr("New attribute %1").arg(i);
1368
        i++;
1369
    }
1370

1371
    m_entryAttributes->set(name, "");
1372
    QModelIndex index = m_attributesModel->indexByKey(name);
1373

1374
    m_advancedUi->attributesView->setCurrentIndex(index);
1375
    m_advancedUi->attributesView->edit(index);
1376

1377
    setModified(true);
1378
}
1379

1380
void EditEntryWidget::editCurrentAttribute()
1381
{
1382
    Q_ASSERT(!m_history);
1383

1384
    QModelIndex index = m_advancedUi->attributesView->currentIndex();
1385

1386
    if (index.isValid()) {
1387
        m_advancedUi->attributesView->edit(index);
1388
        setModified(true);
1389
    }
1390
}
1391

1392
void EditEntryWidget::removeCurrentAttribute()
1393
{
1394
    Q_ASSERT(!m_history);
1395

1396
    QModelIndex index = m_advancedUi->attributesView->currentIndex();
1397

1398
    if (index.isValid()) {
1399

1400
        auto result = MessageBox::question(this,
1401
                                           tr("Confirm Removal"),
1402
                                           tr("Are you sure you want to remove this attribute?"),
1403
                                           MessageBox::Remove | MessageBox::Cancel,
1404
                                           MessageBox::Cancel);
1405

1406
        if (result == MessageBox::Remove) {
1407
            m_entryAttributes->remove(m_attributesModel->keyByIndex(index));
1408
            setModified(true);
1409
        }
1410
    }
1411
}
1412

1413
void EditEntryWidget::updateCurrentAttribute()
1414
{
1415
    QModelIndex newIndex = m_advancedUi->attributesView->currentIndex();
1416
    QString newKey = m_attributesModel->keyByIndex(newIndex);
1417

1418
    if (!m_history && m_currentAttribute != newIndex) {
1419
        // Save changes to the currently selected attribute if editing is enabled
1420
        if (m_currentAttribute.isValid() && m_advancedUi->attributesEdit->isEnabled()) {
1421
            QString currKey = m_attributesModel->keyByIndex(m_currentAttribute);
1422
            m_entryAttributes->set(
1423
                currKey, m_advancedUi->attributesEdit->toPlainText(), m_entryAttributes->isProtected(currKey));
1424
        }
1425
    }
1426

1427
    displayAttribute(newIndex, m_entryAttributes->isProtected(newKey));
1428

1429
    m_currentAttribute = newIndex;
1430
}
1431

1432
void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
1433
{
1434
    // Block signals to prevent modified being set
1435
    m_advancedUi->protectAttributeButton->blockSignals(true);
1436
    m_advancedUi->attributesEdit->blockSignals(true);
1437
    m_advancedUi->revealAttributeButton->setText(tr("Reveal"));
1438

1439
    if (index.isValid()) {
1440
        QString key = m_attributesModel->keyByIndex(index);
1441
        if (showProtected) {
1442
            m_advancedUi->attributesEdit->setPlainText(tr("[PROTECTED] Press Reveal to view or edit"));
1443
            m_advancedUi->attributesEdit->setEnabled(false);
1444
            m_advancedUi->revealAttributeButton->setEnabled(true);
1445
            m_advancedUi->protectAttributeButton->setChecked(true);
1446
        } else {
1447
            m_advancedUi->attributesEdit->setPlainText(m_entryAttributes->value(key));
1448
            m_advancedUi->attributesEdit->setEnabled(true);
1449
            m_advancedUi->revealAttributeButton->setEnabled(false);
1450
            m_advancedUi->protectAttributeButton->setChecked(false);
1451
        }
1452

1453
        // Don't allow editing in history view
1454
        m_advancedUi->protectAttributeButton->setEnabled(!m_history);
1455
        m_advancedUi->editAttributeButton->setEnabled(!m_history);
1456
        m_advancedUi->removeAttributeButton->setEnabled(!m_history);
1457
    } else {
1458
        m_advancedUi->attributesEdit->setPlainText("");
1459
        m_advancedUi->attributesEdit->setEnabled(false);
1460
        m_advancedUi->revealAttributeButton->setEnabled(false);
1461
        m_advancedUi->protectAttributeButton->setChecked(false);
1462
        m_advancedUi->protectAttributeButton->setEnabled(false);
1463
        m_advancedUi->editAttributeButton->setEnabled(false);
1464
        m_advancedUi->removeAttributeButton->setEnabled(false);
1465
    }
1466

1467
    m_advancedUi->protectAttributeButton->blockSignals(false);
1468
    m_advancedUi->attributesEdit->blockSignals(false);
1469
}
1470

1471
void EditEntryWidget::protectCurrentAttribute(bool state)
1472
{
1473
    QModelIndex index = m_advancedUi->attributesView->currentIndex();
1474
    if (!m_history && index.isValid()) {
1475
        QString key = m_attributesModel->keyByIndex(index);
1476
        if (state) {
1477
            // Save the current text and protect the attribute
1478
            m_entryAttributes->set(key, m_advancedUi->attributesEdit->toPlainText(), true);
1479
        } else {
1480
            // Unprotect the current attribute value (don't save text as it is obscured)
1481
            m_entryAttributes->set(key, m_entryAttributes->value(key), false);
1482
        }
1483

1484
        // Display the attribute
1485
        displayAttribute(index, state);
1486
    }
1487
}
1488

1489
void EditEntryWidget::toggleCurrentAttributeVisibility()
1490
{
1491
    if (!m_advancedUi->attributesEdit->isEnabled()) {
1492
        QModelIndex index = m_advancedUi->attributesView->currentIndex();
1493
        if (index.isValid()) {
1494
            bool oldBlockSignals = m_advancedUi->attributesEdit->blockSignals(true);
1495
            QString key = m_attributesModel->keyByIndex(index);
1496
            m_advancedUi->attributesEdit->setPlainText(m_entryAttributes->value(key));
1497
            m_advancedUi->attributesEdit->setEnabled(true);
1498
            m_advancedUi->attributesEdit->blockSignals(oldBlockSignals);
1499
        }
1500
        m_advancedUi->revealAttributeButton->setText(tr("Hide"));
1501
    } else {
1502
        protectCurrentAttribute(true);
1503
        m_advancedUi->revealAttributeButton->setText(tr("Reveal"));
1504
    }
1505
}
1506

1507
void EditEntryWidget::updateAutoTypeEnabled()
1508
{
1509
    bool autoTypeEnabled = m_autoTypeUi->enableButton->isChecked();
1510
    bool validIndex = m_autoTypeUi->assocView->currentIndex().isValid() && m_autoTypeAssoc->size() != 0;
1511

1512
    m_autoTypeUi->enableButton->setEnabled(!m_history);
1513
    m_autoTypeUi->inheritSequenceButton->setEnabled(!m_history && autoTypeEnabled);
1514
    m_autoTypeUi->customSequenceButton->setEnabled(!m_history && autoTypeEnabled);
1515
    m_autoTypeUi->sequenceEdit->setEnabled(autoTypeEnabled && m_autoTypeUi->customSequenceButton->isChecked());
1516
    m_autoTypeUi->openHelpButton->setEnabled(autoTypeEnabled && m_autoTypeUi->customSequenceButton->isChecked());
1517

1518
    m_autoTypeUi->assocView->setEnabled(autoTypeEnabled);
1519
    m_autoTypeUi->assocAddButton->setEnabled(!m_history);
1520
    m_autoTypeUi->assocRemoveButton->setEnabled(!m_history && validIndex);
1521

1522
    m_autoTypeUi->windowTitleLabel->setEnabled(autoTypeEnabled && validIndex);
1523
    m_autoTypeUi->windowTitleCombo->setEnabled(autoTypeEnabled && validIndex);
1524
    m_autoTypeUi->customWindowSequenceButton->setEnabled(!m_history && autoTypeEnabled && validIndex);
1525
    m_autoTypeUi->windowSequenceEdit->setEnabled(autoTypeEnabled && validIndex
1526
                                                 && m_autoTypeUi->customWindowSequenceButton->isChecked());
1527
}
1528

1529
void EditEntryWidget::insertAutoTypeAssoc()
1530
{
1531
    AutoTypeAssociations::Association assoc;
1532
    m_autoTypeAssoc->add(assoc);
1533
    QModelIndex newIndex = m_autoTypeAssocModel->index(m_autoTypeAssoc->size() - 1, 0);
1534
    m_autoTypeUi->assocView->setCurrentIndex(newIndex);
1535
    loadCurrentAssoc(newIndex);
1536
    m_autoTypeUi->windowTitleCombo->setFocus();
1537
    setModified(true);
1538
}
1539

1540
void EditEntryWidget::removeAutoTypeAssoc()
1541
{
1542
    QModelIndex currentIndex = m_autoTypeUi->assocView->currentIndex();
1543

1544
    if (currentIndex.isValid()) {
1545
        m_autoTypeAssoc->remove(currentIndex.row());
1546
        setModified(true);
1547
    }
1548
}
1549

1550
void EditEntryWidget::loadCurrentAssoc(const QModelIndex& current)
1551
{
1552
    bool modified = isModified();
1553
    if (current.isValid() && current.row() < m_autoTypeAssoc->size()) {
1554
        AutoTypeAssociations::Association assoc = m_autoTypeAssoc->get(current.row());
1555
        m_autoTypeUi->windowTitleCombo->setEditText(assoc.window);
1556
        if (assoc.sequence.isEmpty()) {
1557
            m_autoTypeUi->customWindowSequenceButton->setChecked(false);
1558
            m_autoTypeUi->windowSequenceEdit->setText(m_entry->effectiveAutoTypeSequence());
1559
        } else {
1560
            m_autoTypeUi->customWindowSequenceButton->setChecked(true);
1561
            m_autoTypeUi->windowSequenceEdit->setText(assoc.sequence);
1562
        }
1563

1564
        updateAutoTypeEnabled();
1565
    } else {
1566
        clearCurrentAssoc();
1567
    }
1568
    setModified(modified);
1569
}
1570

1571
void EditEntryWidget::clearCurrentAssoc()
1572
{
1573
    m_autoTypeUi->windowTitleCombo->setEditText("");
1574

1575
    m_autoTypeUi->customWindowSequenceButton->setChecked(false);
1576
    m_autoTypeUi->windowSequenceEdit->setText("");
1577

1578
    updateAutoTypeEnabled();
1579
}
1580

1581
void EditEntryWidget::applyCurrentAssoc()
1582
{
1583
    QModelIndex index = m_autoTypeUi->assocView->currentIndex();
1584

1585
    if (!index.isValid() || m_autoTypeAssoc->size() == 0 || m_history) {
1586
        return;
1587
    }
1588

1589
    AutoTypeAssociations::Association assoc;
1590
    assoc.window = m_autoTypeUi->windowTitleCombo->currentText();
1591
    if (m_autoTypeUi->customWindowSequenceButton->isChecked()) {
1592
        assoc.sequence = m_autoTypeUi->windowSequenceEdit->text();
1593
    }
1594

1595
    m_autoTypeAssoc->update(index.row(), assoc);
1596
}
1597

1598
void EditEntryWidget::showHistoryEntry()
1599
{
1600
    QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex());
1601
    if (index.isValid()) {
1602
        emitHistoryEntryActivated(index);
1603
    }
1604
}
1605

1606
void EditEntryWidget::restoreHistoryEntry()
1607
{
1608
    QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex());
1609
    auto entry = m_historyModel->entryFromIndex(index);
1610
    if (entry) {
1611
        setForms(entry, true);
1612
        setModified(true);
1613
    }
1614
}
1615

1616
void EditEntryWidget::deleteHistoryEntry()
1617
{
1618
    QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex());
1619
    if (m_historyModel->entryFromIndex(index)) {
1620
        m_historyModel->deleteIndex(index);
1621
        if (m_historyModel->rowCount() > 0) {
1622
            m_historyUi->deleteAllButton->setEnabled(true);
1623
        } else {
1624
            m_historyUi->deleteAllButton->setEnabled(false);
1625
        }
1626
        setModified(true);
1627
    }
1628
}
1629

1630
void EditEntryWidget::deleteAllHistoryEntries()
1631
{
1632
    m_historyModel->deleteAll();
1633
    m_historyUi->deleteAllButton->setEnabled(m_historyModel->rowCount() > 0);
1634
    setModified(true);
1635
}
1636

1637
QMenu* EditEntryWidget::createPresetsMenu()
1638
{
1639
    auto* expirePresetsMenu = new QMenu(this);
1640
    expirePresetsMenu->addAction(tr("%n hour(s)", nullptr, 12))->setData(QVariant::fromValue(TimeDelta::fromHours(12)));
1641
    expirePresetsMenu->addAction(tr("%n hour(s)", nullptr, 24))->setData(QVariant::fromValue(TimeDelta::fromHours(24)));
1642
    expirePresetsMenu->addSeparator();
1643
    expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromDays(7)));
1644
    expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 2))->setData(QVariant::fromValue(TimeDelta::fromDays(14)));
1645
    expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromDays(21)));
1646
    expirePresetsMenu->addSeparator();
1647
    expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromMonths(1)));
1648
    expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 2))->setData(QVariant::fromValue(TimeDelta::fromMonths(2)));
1649
    expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromMonths(3)));
1650
    expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 6))->setData(QVariant::fromValue(TimeDelta::fromMonths(6)));
1651
    expirePresetsMenu->addSeparator();
1652
    expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromYears(1)));
1653
    expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 2))->setData(QVariant::fromValue(TimeDelta::fromYears(2)));
1654
    expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromYears(3)));
1655
    return expirePresetsMenu;
1656
}
1657

1658
void EditEntryWidget::setupColorButton(bool foreground, const QColor& color)
1659
{
1660
    QWidget* button = m_advancedUi->fgColorButton;
1661
    QCheckBox* checkBox = m_advancedUi->fgColorCheckBox;
1662
    if (!foreground) {
1663
        button = m_advancedUi->bgColorButton;
1664
        checkBox = m_advancedUi->bgColorCheckBox;
1665
    }
1666

1667
    if (color.isValid()) {
1668
        button->setStyleSheet(QString("background-color:%1").arg(color.name()));
1669
        button->setProperty("color", color.name());
1670
        checkBox->setChecked(true);
1671
    } else {
1672
        button->setStyleSheet("");
1673
        button->setProperty("color", QVariant());
1674
        checkBox->setChecked(false);
1675
    }
1676
}
1677

1678
void EditEntryWidget::pickColor()
1679
{
1680
    bool isForeground = (sender() == m_advancedUi->fgColorButton);
1681
    QColor oldColor = QColor(m_advancedUi->fgColorButton->property("color").toString());
1682
    if (!isForeground) {
1683
        oldColor = QColor(m_advancedUi->bgColorButton->property("color").toString());
1684
    }
1685

1686
    QColor newColor = QColorDialog::getColor(oldColor);
1687
    if (newColor.isValid()) {
1688
        setupColorButton(isForeground, newColor);
1689
        setModified(true);
1690
    }
1691
}
1692

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.