2
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation, either version 2 or (at your option)
7
* version 3 of the License.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18
#include "DatabaseTabWidget.h"
23
#include "autotype/AutoType.h"
24
#include "core/Merger.h"
25
#include "core/Tools.h"
26
#include "format/CsvExporter.h"
27
#include "gui/Clipboard.h"
28
#include "gui/DatabaseOpenDialog.h"
29
#include "gui/DatabaseWidget.h"
30
#include "gui/DatabaseWidgetStateSync.h"
31
#include "gui/FileDialog.h"
32
#include "gui/MessageBox.h"
33
#include "gui/export/ExportDialog.h"
35
#include "gui/osutils/macutils/MacUtils.h"
37
#include "gui/wizard/NewDatabaseWizard.h"
38
#include "wizard/ImportWizard.h"
40
DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
42
, m_dbWidgetStateSync(new DatabaseWidgetStateSync(this))
43
, m_dbWidgetPendingLock(nullptr)
44
, m_databaseOpenDialog(new DatabaseOpenDialog(this))
45
, m_databaseOpenInProgress(false)
47
auto* tabBar = new QTabBar(this);
48
tabBar->setAcceptDrops(true);
49
tabBar->setChangeCurrentOnDrag(true);
51
setDocumentMode(true);
54
connect(this, SIGNAL(tabCloseRequested(int)), SLOT(closeDatabaseTab(int)));
55
connect(this, SIGNAL(currentChanged(int)), SLOT(emitActiveDatabaseChanged()));
56
connect(this, SIGNAL(activeDatabaseChanged(DatabaseWidget*)),
57
m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*)));
58
connect(autoType(), SIGNAL(globalAutoTypeTriggered(const QString&)), SLOT(performGlobalAutoType(const QString&)));
59
connect(autoType(), SIGNAL(autotypeRetypeTimeout()), SLOT(relockPendingDatabase()));
60
connect(autoType(), SIGNAL(autotypeFinished()), SLOT(relockPendingDatabase()));
61
connect(m_databaseOpenDialog.data(), &DatabaseOpenDialog::dialogFinished,
62
this, &DatabaseTabWidget::handleDatabaseUnlockDialogFinished);
66
connect(macUtils(), SIGNAL(lockDatabases()), SLOT(lockDatabases()));
69
m_lockDelayTimer.setSingleShot(true);
70
connect(&m_lockDelayTimer, &QTimer::timeout, this, [this] { lockDatabases(); });
73
DatabaseTabWidget::~DatabaseTabWidget() = default;
75
void DatabaseTabWidget::toggleTabbar()
79
setFocusPolicy(Qt::StrongFocus);
80
emit tabVisibilityChanged(true);
83
setFocusPolicy(Qt::NoFocus);
84
emit tabVisibilityChanged(false);
89
* Helper method for invoking the new database wizard.
90
* The user of this method MUST take ownership of the returned pointer.
92
* @return pointer to the configured new database, nullptr on failure
94
QSharedPointer<Database> DatabaseTabWidget::execNewDatabaseWizard()
96
// use QScopedPointer to ensure deletion after scope ends, but still parent
97
// it to this to make it modal and allow easier access in unit tests
98
QScopedPointer<NewDatabaseWizard> wizard(new NewDatabaseWizard(this));
99
if (!wizard->exec()) {
103
auto db = wizard->takeDatabase();
109
if (!db->key() || !db->kdf()) {
110
MessageBox::critical(this,
111
tr("Database creation error"),
112
tr("The created database has no key or KDF, refusing to save it.\n"
113
"This is definitely a bug, please report it to the developers."),
122
DatabaseWidget* DatabaseTabWidget::newDatabase()
124
auto db = execNewDatabaseWizard();
129
auto dbWidget = new DatabaseWidget(db, this);
130
addDatabaseTab(dbWidget);
131
db->markAsModified();
135
void DatabaseTabWidget::openDatabase()
137
auto filter = QString("%1 (*.kdbx);;%2 (*)").arg(tr("KeePass 2 Database"), tr("All files"));
138
auto fileName = fileDialog()->getOpenFileName(this, tr("Open database"), FileDialog::getLastDir("db"), filter);
139
if (!fileName.isEmpty()) {
140
FileDialog::saveLastDir("db", fileName, true);
141
addDatabaseTab(fileName);
146
* Add a new database tab or switch to an existing one if the
147
* database has been opened already.
149
* @param filePath database file path
150
* @param inBackground optional, don't focus tab after opening
151
* @param password optional, password to unlock database
152
* @param keyfile optional, path to keyfile to unlock database
155
void DatabaseTabWidget::addDatabaseTab(const QString& filePath,
157
const QString& password,
158
const QString& keyfile)
160
QString cleanFilePath = QDir::toNativeSeparators(filePath);
161
QFileInfo fileInfo(cleanFilePath);
162
QString canonicalFilePath = fileInfo.canonicalFilePath();
164
if (canonicalFilePath.isEmpty()) {
165
emit messageGlobal(tr("Failed to open %1. It either does not exist or is not accessible.").arg(cleanFilePath),
166
MessageWidget::Error);
170
for (int i = 0, c = count(); i < c; ++i) {
171
auto* dbWidget = databaseWidgetFromIndex(i);
174
&& dbWidget->database()->canonicalFilePath().compare(canonicalFilePath, FILE_CASE_SENSITIVE) == 0) {
175
dbWidget->performUnlockDatabase(password, keyfile);
177
// switch to existing tab if file is already open
178
setCurrentIndex(indexOf(dbWidget));
184
auto* dbWidget = new DatabaseWidget(QSharedPointer<Database>::create(cleanFilePath), this);
185
addDatabaseTab(dbWidget, inBackground);
186
dbWidget->performUnlockDatabase(password, keyfile);
187
updateLastDatabases(cleanFilePath);
191
* Tries to lock the database at the given index and if
192
* it succeeds proceed to switch to the first unlocked database tab
194
void DatabaseTabWidget::lockAndSwitchToFirstUnlockedDatabase(int index)
197
index = currentIndex();
199
auto dbWidget = databaseWidgetFromIndex(index);
204
if (dbWidget->isLocked()) {
205
// Database is already locked, act like lock all databases instead
207
} else if (dbWidget->lock()) {
208
for (int i = 0, c = count(); i < c; ++i) {
209
if (!databaseWidgetFromIndex(i)->isLocked()) {
211
emitActiveDatabaseChanged();
219
* Add a new database tab containing the given DatabaseWidget
221
* @param inBackground optional, don't focus tab after opening
223
void DatabaseTabWidget::addDatabaseTab(DatabaseWidget* dbWidget, bool inBackground)
225
Q_ASSERT(dbWidget->database());
227
// emit before index change
228
emit databaseOpened(dbWidget);
230
int index = addTab(dbWidget, "");
231
updateTabName(index);
234
setCurrentIndex(index);
238
SIGNAL(requestOpenDatabase(QString, bool, QString, QString)),
239
SLOT(addDatabaseTab(QString, bool, QString, QString)));
240
connect(dbWidget, SIGNAL(databaseFilePathChanged(QString, QString)), SLOT(updateTabName()));
241
connect(dbWidget, SIGNAL(closeRequest()), SLOT(closeDatabaseTabFromSender()));
243
SIGNAL(databaseReplaced(const QSharedPointer<Database>&, const QSharedPointer<Database>&)),
244
SLOT(updateTabName()));
245
connect(dbWidget, SIGNAL(databaseModified()), SLOT(updateTabName()));
246
connect(dbWidget, SIGNAL(databaseSaved()), SLOT(updateTabName()));
247
connect(dbWidget, SIGNAL(databaseSaved()), SLOT(updateLastDatabases()));
248
connect(dbWidget, SIGNAL(databaseUnlocked()), SLOT(updateTabName()));
249
connect(dbWidget, SIGNAL(databaseUnlocked()), SLOT(emitDatabaseLockChanged()));
250
connect(dbWidget, SIGNAL(databaseLocked()), SLOT(updateTabName()));
251
connect(dbWidget, SIGNAL(databaseLocked()), SLOT(emitDatabaseLockChanged()));
254
DatabaseWidget* DatabaseTabWidget::importFile()
256
// Show the import wizard
257
QScopedPointer wizard(new ImportWizard(this));
258
if (!wizard->exec()) {
262
auto db = wizard->database();
264
// Import wizard was cancelled
268
auto importInto = wizard->importInto();
269
if (importInto.first.isNull()) {
270
// Start the new database wizard with the imported database
271
auto newDb = execNewDatabaseWizard();
273
// Merge the imported db into the new one
274
Merger merger(db.data(), newDb.data());
275
merger.setSkipDatabaseCustomData(true);
277
// Show the new database
278
auto dbWidget = new DatabaseWidget(newDb, this);
279
addDatabaseTab(dbWidget);
280
newDb->markAsModified();
284
for (int i = 0, c = count(); i < c; ++i) {
285
// Find the database and group to import into based on import wizard choice
286
auto dbWidget = databaseWidgetFromIndex(i);
287
if (!dbWidget->isLocked() && dbWidget->database()->uuid() == importInto.first) {
288
auto group = dbWidget->database()->rootGroup()->findGroupByUuid(importInto.second);
290
// Extract the root group from the import database
291
auto importGroup = db->setRootGroup(new Group());
292
importGroup->setParent(group);
303
void DatabaseTabWidget::mergeDatabase()
305
auto dbWidget = currentDatabaseWidget();
306
if (dbWidget && !dbWidget->isLocked()) {
307
auto filter = QString("%1 (*.kdbx);;%2 (*)").arg(tr("KeePass 2 Database"), tr("All files"));
309
fileDialog()->getOpenFileName(this, tr("Merge database"), FileDialog::getLastDir("merge"), filter);
310
if (!fileName.isEmpty()) {
311
FileDialog::saveLastDir("merge", fileName, true);
312
mergeDatabase(fileName);
317
void DatabaseTabWidget::mergeDatabase(const QString& filePath)
319
unlockDatabaseInDialog(currentDatabaseWidget(), DatabaseOpenDialog::Intent::Merge, filePath);
323
* Attempt to close the current database and remove its tab afterwards.
325
* @param index index of the database tab to close
326
* @return true if database was closed successfully
328
bool DatabaseTabWidget::closeCurrentDatabaseTab()
330
return closeDatabaseTab(currentIndex());
334
* Attempt to close the database tab that sent the close request.
336
* @param index index of the database tab to close
337
* @return true if database was closed successfully
339
bool DatabaseTabWidget::closeDatabaseTabFromSender()
341
return closeDatabaseTab(qobject_cast<DatabaseWidget*>(sender()));
345
* Attempt to close a database and remove its tab afterwards.
347
* @param index index of the database tab to close
348
* @return true if database was closed successfully
350
bool DatabaseTabWidget::closeDatabaseTab(int index)
352
return closeDatabaseTab(qobject_cast<DatabaseWidget*>(widget(index)));
356
* Attempt to close a database and remove its tab afterwards.
358
* @param dbWidget \link DatabaseWidget to close
359
* @return true if database was closed successfully
361
bool DatabaseTabWidget::closeDatabaseTab(DatabaseWidget* dbWidget)
363
int tabIndex = indexOf(dbWidget);
364
if (!dbWidget || tabIndex < 0) {
368
QString filePath = dbWidget->database()->filePath();
369
if (!dbWidget->close()) {
374
dbWidget->deleteLater();
376
emit databaseClosed(filePath);
381
* Attempt to close all opened databases.
382
* The attempt will be aborted with the first database that cannot be closed.
384
* @return true if all databases could be closed.
386
bool DatabaseTabWidget::closeAllDatabaseTabs()
388
// Attempt to lock all databases first to prevent closing only a portion of tabs
389
if (lockDatabases()) {
390
while (count() > 0) {
391
if (!closeDatabaseTab(0)) {
401
bool DatabaseTabWidget::saveDatabase(int index)
404
index = currentIndex();
407
return databaseWidgetFromIndex(index)->save();
410
bool DatabaseTabWidget::saveDatabaseAs(int index)
413
index = currentIndex();
416
auto* dbWidget = databaseWidgetFromIndex(index);
417
bool ok = dbWidget->saveAs();
419
updateLastDatabases(dbWidget->database()->filePath());
424
bool DatabaseTabWidget::saveDatabaseBackup(int index)
427
index = currentIndex();
430
auto* dbWidget = databaseWidgetFromIndex(index);
431
bool ok = dbWidget->saveBackup();
433
updateLastDatabases(dbWidget->database()->filePath());
438
void DatabaseTabWidget::closeDatabaseFromSender()
440
auto* dbWidget = qobject_cast<DatabaseWidget*>(sender());
442
closeDatabaseTab(dbWidget);
445
void DatabaseTabWidget::exportToCsv()
447
auto db = databaseWidgetFromIndex(currentIndex())->database();
453
if (!warnOnExport()) {
457
auto fileName = fileDialog()->getSaveFileName(
458
this, tr("Export database to CSV file"), FileDialog::getLastDir("csv"), tr("CSV file").append(" (*.csv)"));
459
if (fileName.isEmpty()) {
463
FileDialog::saveLastDir("csv", fileName, true);
465
CsvExporter csvExporter;
466
if (!csvExporter.exportDatabase(fileName, db)) {
467
emit messageGlobal(tr("Writing the CSV file failed.").append("\n").append(csvExporter.errorString()),
468
MessageWidget::Error);
472
void DatabaseTabWidget::handleExportError(const QString& reason)
474
emit messageGlobal(tr("Writing the HTML file failed.").append("\n").append(reason), MessageWidget::Error);
477
void DatabaseTabWidget::exportToHtml()
479
auto db = databaseWidgetFromIndex(currentIndex())->database();
485
auto exportDialog = new ExportDialog(db, this);
486
connect(exportDialog, SIGNAL(exportFailed(QString)), SLOT(handleExportError(const QString&)));
487
exportDialog->exec();
490
void DatabaseTabWidget::exportToXML()
492
auto db = databaseWidgetFromIndex(currentIndex())->database();
498
if (!warnOnExport()) {
502
auto fileName = fileDialog()->getSaveFileName(
503
this, tr("Export database to XML file"), FileDialog::getLastDir("xml"), tr("XML file").append(" (*.xml)"));
504
if (fileName.isEmpty()) {
508
FileDialog::saveLastDir("xml", fileName, true);
512
if (!db->extract(xmlData, &err)) {
513
emit messageGlobal(tr("Writing the XML file failed").append("\n").append(err), MessageWidget::Error);
516
QFile file(fileName);
517
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
518
emit messageGlobal(tr("Writing the XML file failed").append("\n").append(file.errorString()),
519
MessageWidget::Error);
524
bool DatabaseTabWidget::warnOnExport()
527
MessageBox::question(this,
528
tr("Export Confirmation"),
529
tr("You are about to export your database to an unencrypted file. This will leave your "
530
"passwords and sensitive information vulnerable! Are you sure you want to continue?"),
531
MessageBox::Yes | MessageBox::No,
533
return ans == MessageBox::Yes;
536
void DatabaseTabWidget::showDatabaseSecurity()
538
currentDatabaseWidget()->switchToDatabaseSecurity();
541
void DatabaseTabWidget::showDatabaseReports()
543
currentDatabaseWidget()->switchToDatabaseReports();
546
void DatabaseTabWidget::showDatabaseSettings()
548
currentDatabaseWidget()->switchToDatabaseSettings();
551
#ifdef WITH_XC_BROWSER_PASSKEYS
552
void DatabaseTabWidget::showPasskeys()
554
currentDatabaseWidget()->switchToPasskeys();
557
void DatabaseTabWidget::importPasskey()
559
currentDatabaseWidget()->showImportPasskeyDialog();
562
void DatabaseTabWidget::importPasskeyToEntry()
564
currentDatabaseWidget()->showImportPasskeyDialog(true);
568
bool DatabaseTabWidget::isModified(int index) const
575
index = currentIndex();
578
auto db = databaseWidgetFromIndex(index)->database();
579
return db && db->isModified();
582
bool DatabaseTabWidget::canSave(int index) const
584
return isModified(index);
587
bool DatabaseTabWidget::hasLockableDatabases() const
589
for (int i = 0, c = count(); i < c; ++i) {
590
if (!databaseWidgetFromIndex(i)->isLocked()) {
598
* Get the tab's (original) display name without platform-specific
599
* mangling that may occur when reading back the actual widget's \link tabText()
601
* @param index tab index
604
QString DatabaseTabWidget::tabName(int index)
606
auto dbWidget = databaseWidgetFromIndex(index);
611
auto tabName = dbWidget->displayName();
613
if (dbWidget->isLocked()) {
614
tabName = tr("%1 [Locked]", "Database tab name modifier").arg(tabName);
617
if (dbWidget->database()->isModified()) {
625
* Update of the given tab index or of the sending
626
* DatabaseWidget if `index` == -1.
628
void DatabaseTabWidget::updateTabName(int index)
630
auto* dbWidget = databaseWidgetFromIndex(index);
632
dbWidget = qobject_cast<DatabaseWidget*>(sender());
638
index = indexOf(dbWidget);
639
setTabText(index, tabName(index));
640
setTabToolTip(index, dbWidget->displayFilePath());
641
emit tabNameChanged();
644
DatabaseWidget* DatabaseTabWidget::databaseWidgetFromIndex(int index) const
646
return qobject_cast<DatabaseWidget*>(widget(index));
649
DatabaseWidget* DatabaseTabWidget::currentDatabaseWidget()
651
return qobject_cast<DatabaseWidget*>(currentWidget());
655
* Attempt to lock all open databases
657
* @return return true if all databases are locked
659
bool DatabaseTabWidget::lockDatabases()
663
for (int i = 0; i < c; ++i) {
664
auto dbWidget = databaseWidgetFromIndex(i);
665
if (dbWidget->lock()) {
667
if (dbWidget->database()->filePath().isEmpty()) {
668
// If we locked a database without a file close the tab
669
closeDatabaseTab(dbWidget);
674
return numLocked == c;
677
void DatabaseTabWidget::lockDatabasesDelayed()
679
// Delay at least 1 second and up to 20 seconds depending on clipboard state.
680
// This allows for Auto-Type, Browser Extension, and clipboard to function
681
// even with "Lock on Minimize" setting enabled.
682
int lockDelay = qBound(1, clipboard()->secondsToClear(), 20);
683
m_lockDelayTimer.setInterval(lockDelay * 1000);
684
if (!m_lockDelayTimer.isActive()) {
685
m_lockDelayTimer.start();
690
* Unlock a database with an unlock popup dialog.
692
* @param dbWidget DatabaseWidget which to connect signals to
693
* @param intent intent for unlocking
695
void DatabaseTabWidget::unlockDatabaseInDialog(DatabaseWidget* dbWidget, DatabaseOpenDialog::Intent intent)
697
unlockDatabaseInDialog(dbWidget, intent, dbWidget->database()->filePath());
701
* Unlock a database with an unlock popup dialog.
703
* @param dbWidget DatabaseWidget which to connect signals to
704
* @param intent intent for unlocking
705
* @param file path of the database to be unlocked
707
void DatabaseTabWidget::unlockDatabaseInDialog(DatabaseWidget* dbWidget,
708
DatabaseOpenDialog::Intent intent,
709
const QString& filePath)
711
m_databaseOpenDialog->clearForms();
712
m_databaseOpenDialog->setIntent(intent);
713
m_databaseOpenDialog->setTarget(dbWidget, filePath);
714
displayUnlockDialog();
718
* Unlock a database with an unlock popup dialog.
719
* The dialog allows the user to select any open & locked database.
721
* @param intent intent for unlocking
723
void DatabaseTabWidget::unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent intent)
725
m_databaseOpenDialog->clearForms();
726
m_databaseOpenDialog->setIntent(intent);
728
// add a tab to the dialog for each open unlocked database
729
for (int i = 0, c = count(); i < c; ++i) {
730
auto* dbWidget = databaseWidgetFromIndex(i);
731
if (dbWidget && dbWidget->isLocked()) {
732
m_databaseOpenDialog->addDatabaseTab(dbWidget);
735
// default to the current tab
736
m_databaseOpenDialog->setActiveDatabaseTab(currentDatabaseWidget());
737
displayUnlockDialog();
741
* Display the unlock dialog after it's been initialized.
742
* This is an internal method, it should only be called by unlockDatabaseInDialog or unlockAnyDatabaseInDialog.
744
void DatabaseTabWidget::displayUnlockDialog()
747
auto intent = m_databaseOpenDialog->intent();
748
if (intent == DatabaseOpenDialog::Intent::AutoType || intent == DatabaseOpenDialog::Intent::Browser) {
749
macUtils()->raiseOwnWindow();
754
m_databaseOpenDialog->show();
755
m_databaseOpenDialog->raise();
756
m_databaseOpenDialog->activateWindow();
760
* Actions to take when the unlock dialog has completed.
762
void DatabaseTabWidget::handleDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget)
764
// change the active tab to the database that was just unlocked in the dialog
765
auto intent = m_databaseOpenDialog->intent();
766
if (accepted && intent != DatabaseOpenDialog::Intent::Merge) {
767
int index = indexOf(dbWidget);
769
setCurrentIndex(index);
773
// if unlocked for AutoType, set pending lock flag if needed
774
if (intent == DatabaseOpenDialog::Intent::AutoType && config()->get(Config::Security_RelockAutoType).toBool()) {
775
m_dbWidgetPendingLock = dbWidget;
778
// signal other objects that the dialog finished
779
emit databaseUnlockDialogFinished(accepted, dbWidget);
783
* This function relock the pending database when autotype has been performed successfully
784
* A database is marked as pending when it's unlocked after a global Auto-Type invocation
786
void DatabaseTabWidget::relockPendingDatabase()
788
if (!m_dbWidgetPendingLock || !config()->get(Config::Security_RelockAutoType).toBool()) {
792
if (m_dbWidgetPendingLock->isLocked() || !m_dbWidgetPendingLock->database()->isInitialized()) {
793
m_dbWidgetPendingLock = nullptr;
797
m_dbWidgetPendingLock->lock();
798
m_dbWidgetPendingLock = nullptr;
801
void DatabaseTabWidget::updateLastDatabases(const QString& filename)
803
if (!config()->get(Config::RememberLastDatabases).toBool()) {
804
config()->remove(Config::LastDatabases);
806
QStringList lastDatabases = config()->get(Config::LastDatabases).toStringList();
807
lastDatabases.prepend(QDir::toNativeSeparators(filename));
808
lastDatabases.removeDuplicates();
810
while (lastDatabases.count() > config()->get(Config::NumberOfRememberedLastDatabases).toInt()) {
811
lastDatabases.removeLast();
813
config()->set(Config::LastDatabases, lastDatabases);
817
void DatabaseTabWidget::updateLastDatabases()
819
auto dbWidget = currentDatabaseWidget();
822
auto filePath = dbWidget->database()->filePath();
823
if (!filePath.isEmpty()) {
824
updateLastDatabases(filePath);
829
void DatabaseTabWidget::emitActiveDatabaseChanged()
831
emit activeDatabaseChanged(currentDatabaseWidget());
834
void DatabaseTabWidget::emitDatabaseLockChanged()
836
auto* dbWidget = qobject_cast<DatabaseWidget*>(sender());
842
if (dbWidget->isLocked()) {
843
emit databaseLocked(dbWidget);
845
emit databaseUnlocked(dbWidget);
846
m_databaseOpenInProgress = false;
850
void DatabaseTabWidget::performGlobalAutoType(const QString& search)
852
auto currentDbWidget = currentDatabaseWidget();
853
if (!currentDbWidget) {
854
// No open databases, nothing to do
856
} else if (currentDbWidget->isLocked()) {
857
// Current database tab is locked, match behavior of browser unlock - prompt with
858
// the unlock dialog even if there are additional unlocked open database tabs.
859
currentDbWidget->setSearchStringForAutoType(search);
860
unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent::AutoType);
862
// Current database is unlocked, use it for AutoType along with any other unlocked databases
863
QList<QSharedPointer<Database>> unlockedDatabases;
864
for (int i = 0, c = count(); i < c; ++i) {
865
auto* dbWidget = databaseWidgetFromIndex(i);
866
if (!dbWidget->isLocked()) {
867
dbWidget->setSearchStringForAutoType(search);
868
unlockedDatabases.append(dbWidget->database());
872
Q_ASSERT(!unlockedDatabases.isEmpty());
873
autoType()->performGlobalAutoType(unlockedDatabases, search);
877
void DatabaseTabWidget::performBrowserUnlock()
879
if (m_databaseOpenInProgress) {
883
m_databaseOpenInProgress = true;
884
auto dbWidget = currentDatabaseWidget();
885
if (dbWidget && dbWidget->isLocked()) {
886
unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent::Browser);