keepassxc

Форк
0
/
DatabaseTabWidget.cpp 
888 строк · 26.7 Кб
1
/*
2
 *  Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
3
 *
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.
8
 *
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.
13
 *
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/>.
16
 */
17

18
#include "DatabaseTabWidget.h"
19

20
#include <QFileInfo>
21
#include <QTabBar>
22

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"
34
#ifdef Q_OS_MACOS
35
#include "gui/osutils/macutils/MacUtils.h"
36
#endif
37
#include "gui/wizard/NewDatabaseWizard.h"
38
#include "wizard/ImportWizard.h"
39

40
DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
41
    : QTabWidget(parent)
42
    , m_dbWidgetStateSync(new DatabaseWidgetStateSync(this))
43
    , m_dbWidgetPendingLock(nullptr)
44
    , m_databaseOpenDialog(new DatabaseOpenDialog(this))
45
    , m_databaseOpenInProgress(false)
46
{
47
    auto* tabBar = new QTabBar(this);
48
    tabBar->setAcceptDrops(true);
49
    tabBar->setChangeCurrentOnDrag(true);
50
    setTabBar(tabBar);
51
    setDocumentMode(true);
52

53
    // clang-format off
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);
63
    // clang-format on
64

65
#ifdef Q_OS_MACOS
66
    connect(macUtils(), SIGNAL(lockDatabases()), SLOT(lockDatabases()));
67
#endif
68

69
    m_lockDelayTimer.setSingleShot(true);
70
    connect(&m_lockDelayTimer, &QTimer::timeout, this, [this] { lockDatabases(); });
71
}
72

73
DatabaseTabWidget::~DatabaseTabWidget() = default;
74

75
void DatabaseTabWidget::toggleTabbar()
76
{
77
    if (count() > 1) {
78
        tabBar()->show();
79
        setFocusPolicy(Qt::StrongFocus);
80
        emit tabVisibilityChanged(true);
81
    } else {
82
        tabBar()->hide();
83
        setFocusPolicy(Qt::NoFocus);
84
        emit tabVisibilityChanged(false);
85
    }
86
}
87

88
/**
89
 * Helper method for invoking the new database wizard.
90
 * The user of this method MUST take ownership of the returned pointer.
91
 *
92
 * @return pointer to the configured new database, nullptr on failure
93
 */
94
QSharedPointer<Database> DatabaseTabWidget::execNewDatabaseWizard()
95
{
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()) {
100
        return {};
101
    }
102

103
    auto db = wizard->takeDatabase();
104
    if (!db) {
105
        return {};
106
    }
107
    Q_ASSERT(db->key());
108
    Q_ASSERT(db->kdf());
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."),
114
                             MessageBox::Ok,
115
                             MessageBox::Ok);
116
        return {};
117
    }
118

119
    return db;
120
}
121

122
DatabaseWidget* DatabaseTabWidget::newDatabase()
123
{
124
    auto db = execNewDatabaseWizard();
125
    if (!db) {
126
        return nullptr;
127
    }
128

129
    auto dbWidget = new DatabaseWidget(db, this);
130
    addDatabaseTab(dbWidget);
131
    db->markAsModified();
132
    return dbWidget;
133
}
134

135
void DatabaseTabWidget::openDatabase()
136
{
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);
142
    }
143
}
144

145
/**
146
 * Add a new database tab or switch to an existing one if the
147
 * database has been opened already.
148
 *
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
153
 *
154
 */
155
void DatabaseTabWidget::addDatabaseTab(const QString& filePath,
156
                                       bool inBackground,
157
                                       const QString& password,
158
                                       const QString& keyfile)
159
{
160
    QString cleanFilePath = QDir::toNativeSeparators(filePath);
161
    QFileInfo fileInfo(cleanFilePath);
162
    QString canonicalFilePath = fileInfo.canonicalFilePath();
163

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);
167
        return;
168
    }
169

170
    for (int i = 0, c = count(); i < c; ++i) {
171
        auto* dbWidget = databaseWidgetFromIndex(i);
172
        Q_ASSERT(dbWidget);
173
        if (dbWidget
174
            && dbWidget->database()->canonicalFilePath().compare(canonicalFilePath, FILE_CASE_SENSITIVE) == 0) {
175
            dbWidget->performUnlockDatabase(password, keyfile);
176
            if (!inBackground) {
177
                // switch to existing tab if file is already open
178
                setCurrentIndex(indexOf(dbWidget));
179
            }
180
            return;
181
        }
182
    }
183

184
    auto* dbWidget = new DatabaseWidget(QSharedPointer<Database>::create(cleanFilePath), this);
185
    addDatabaseTab(dbWidget, inBackground);
186
    dbWidget->performUnlockDatabase(password, keyfile);
187
    updateLastDatabases(cleanFilePath);
188
}
189

190
/**
191
 * Tries to lock the database at the given index and if
192
 * it succeeds proceed to switch to the first unlocked database tab
193
 */
194
void DatabaseTabWidget::lockAndSwitchToFirstUnlockedDatabase(int index)
195
{
196
    if (index == -1) {
197
        index = currentIndex();
198
    }
199
    auto dbWidget = databaseWidgetFromIndex(index);
200
    if (!dbWidget) {
201
        return;
202
    }
203

204
    if (dbWidget->isLocked()) {
205
        // Database is already locked, act like lock all databases instead
206
        lockDatabases();
207
    } else if (dbWidget->lock()) {
208
        for (int i = 0, c = count(); i < c; ++i) {
209
            if (!databaseWidgetFromIndex(i)->isLocked()) {
210
                setCurrentIndex(i);
211
                emitActiveDatabaseChanged();
212
                return;
213
            }
214
        }
215
    }
216
}
217

218
/**
219
 * Add a new database tab containing the given DatabaseWidget
220
 * @param filePath
221
 * @param inBackground optional, don't focus tab after opening
222
 */
223
void DatabaseTabWidget::addDatabaseTab(DatabaseWidget* dbWidget, bool inBackground)
224
{
225
    Q_ASSERT(dbWidget->database());
226

227
    // emit before index change
228
    emit databaseOpened(dbWidget);
229

230
    int index = addTab(dbWidget, "");
231
    updateTabName(index);
232
    toggleTabbar();
233
    if (!inBackground) {
234
        setCurrentIndex(index);
235
    }
236

237
    connect(dbWidget,
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()));
242
    connect(dbWidget,
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()));
252
}
253

254
DatabaseWidget* DatabaseTabWidget::importFile()
255
{
256
    // Show the import wizard
257
    QScopedPointer wizard(new ImportWizard(this));
258
    if (!wizard->exec()) {
259
        return nullptr;
260
    }
261

262
    auto db = wizard->database();
263
    if (!db) {
264
        // Import wizard was cancelled
265
        return nullptr;
266
    }
267

268
    auto importInto = wizard->importInto();
269
    if (importInto.first.isNull()) {
270
        // Start the new database wizard with the imported database
271
        auto newDb = execNewDatabaseWizard();
272
        if (newDb) {
273
            // Merge the imported db into the new one
274
            Merger merger(db.data(), newDb.data());
275
            merger.setSkipDatabaseCustomData(true);
276
            merger.merge();
277
            // Show the new database
278
            auto dbWidget = new DatabaseWidget(newDb, this);
279
            addDatabaseTab(dbWidget);
280
            newDb->markAsModified();
281
            return dbWidget;
282
        }
283
    } else {
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);
289
                if (group) {
290
                    // Extract the root group from the import database
291
                    auto importGroup = db->setRootGroup(new Group());
292
                    importGroup->setParent(group);
293
                    setCurrentIndex(i);
294
                    return dbWidget;
295
                }
296
            }
297
        }
298
    }
299

300
    return nullptr;
301
}
302

303
void DatabaseTabWidget::mergeDatabase()
304
{
305
    auto dbWidget = currentDatabaseWidget();
306
    if (dbWidget && !dbWidget->isLocked()) {
307
        auto filter = QString("%1 (*.kdbx);;%2 (*)").arg(tr("KeePass 2 Database"), tr("All files"));
308
        auto fileName =
309
            fileDialog()->getOpenFileName(this, tr("Merge database"), FileDialog::getLastDir("merge"), filter);
310
        if (!fileName.isEmpty()) {
311
            FileDialog::saveLastDir("merge", fileName, true);
312
            mergeDatabase(fileName);
313
        }
314
    }
315
}
316

317
void DatabaseTabWidget::mergeDatabase(const QString& filePath)
318
{
319
    unlockDatabaseInDialog(currentDatabaseWidget(), DatabaseOpenDialog::Intent::Merge, filePath);
320
}
321

322
/**
323
 * Attempt to close the current database and remove its tab afterwards.
324
 *
325
 * @param index index of the database tab to close
326
 * @return true if database was closed successfully
327
 */
328
bool DatabaseTabWidget::closeCurrentDatabaseTab()
329
{
330
    return closeDatabaseTab(currentIndex());
331
}
332

333
/**
334
 * Attempt to close the database tab that sent the close request.
335
 *
336
 * @param index index of the database tab to close
337
 * @return true if database was closed successfully
338
 */
339
bool DatabaseTabWidget::closeDatabaseTabFromSender()
340
{
341
    return closeDatabaseTab(qobject_cast<DatabaseWidget*>(sender()));
342
}
343

344
/**
345
 * Attempt to close a database and remove its tab afterwards.
346
 *
347
 * @param index index of the database tab to close
348
 * @return true if database was closed successfully
349
 */
350
bool DatabaseTabWidget::closeDatabaseTab(int index)
351
{
352
    return closeDatabaseTab(qobject_cast<DatabaseWidget*>(widget(index)));
353
}
354

355
/**
356
 * Attempt to close a database and remove its tab afterwards.
357
 *
358
 * @param dbWidget \link DatabaseWidget to close
359
 * @return true if database was closed successfully
360
 */
361
bool DatabaseTabWidget::closeDatabaseTab(DatabaseWidget* dbWidget)
362
{
363
    int tabIndex = indexOf(dbWidget);
364
    if (!dbWidget || tabIndex < 0) {
365
        return false;
366
    }
367

368
    QString filePath = dbWidget->database()->filePath();
369
    if (!dbWidget->close()) {
370
        return false;
371
    }
372

373
    removeTab(tabIndex);
374
    dbWidget->deleteLater();
375
    toggleTabbar();
376
    emit databaseClosed(filePath);
377
    return true;
378
}
379

380
/**
381
 * Attempt to close all opened databases.
382
 * The attempt will be aborted with the first database that cannot be closed.
383
 *
384
 * @return true if all databases could be closed.
385
 */
386
bool DatabaseTabWidget::closeAllDatabaseTabs()
387
{
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)) {
392
                return false;
393
            }
394
        }
395
        return true;
396
    }
397

398
    return false;
399
}
400

401
bool DatabaseTabWidget::saveDatabase(int index)
402
{
403
    if (index == -1) {
404
        index = currentIndex();
405
    }
406

407
    return databaseWidgetFromIndex(index)->save();
408
}
409

410
bool DatabaseTabWidget::saveDatabaseAs(int index)
411
{
412
    if (index == -1) {
413
        index = currentIndex();
414
    }
415

416
    auto* dbWidget = databaseWidgetFromIndex(index);
417
    bool ok = dbWidget->saveAs();
418
    if (ok) {
419
        updateLastDatabases(dbWidget->database()->filePath());
420
    }
421
    return ok;
422
}
423

424
bool DatabaseTabWidget::saveDatabaseBackup(int index)
425
{
426
    if (index == -1) {
427
        index = currentIndex();
428
    }
429

430
    auto* dbWidget = databaseWidgetFromIndex(index);
431
    bool ok = dbWidget->saveBackup();
432
    if (ok) {
433
        updateLastDatabases(dbWidget->database()->filePath());
434
    }
435
    return ok;
436
}
437

438
void DatabaseTabWidget::closeDatabaseFromSender()
439
{
440
    auto* dbWidget = qobject_cast<DatabaseWidget*>(sender());
441
    Q_ASSERT(dbWidget);
442
    closeDatabaseTab(dbWidget);
443
}
444

445
void DatabaseTabWidget::exportToCsv()
446
{
447
    auto db = databaseWidgetFromIndex(currentIndex())->database();
448
    if (!db) {
449
        Q_ASSERT(false);
450
        return;
451
    }
452

453
    if (!warnOnExport()) {
454
        return;
455
    }
456

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()) {
460
        return;
461
    }
462

463
    FileDialog::saveLastDir("csv", fileName, true);
464

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);
469
    }
470
}
471

472
void DatabaseTabWidget::handleExportError(const QString& reason)
473
{
474
    emit messageGlobal(tr("Writing the HTML file failed.").append("\n").append(reason), MessageWidget::Error);
475
}
476

477
void DatabaseTabWidget::exportToHtml()
478
{
479
    auto db = databaseWidgetFromIndex(currentIndex())->database();
480
    if (!db) {
481
        Q_ASSERT(false);
482
        return;
483
    }
484

485
    auto exportDialog = new ExportDialog(db, this);
486
    connect(exportDialog, SIGNAL(exportFailed(QString)), SLOT(handleExportError(const QString&)));
487
    exportDialog->exec();
488
}
489

490
void DatabaseTabWidget::exportToXML()
491
{
492
    auto db = databaseWidgetFromIndex(currentIndex())->database();
493
    if (!db) {
494
        Q_ASSERT(false);
495
        return;
496
    }
497

498
    if (!warnOnExport()) {
499
        return;
500
    }
501

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()) {
505
        return;
506
    }
507

508
    FileDialog::saveLastDir("xml", fileName, true);
509

510
    QByteArray xmlData;
511
    QString err;
512
    if (!db->extract(xmlData, &err)) {
513
        emit messageGlobal(tr("Writing the XML file failed").append("\n").append(err), MessageWidget::Error);
514
    }
515

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);
520
    }
521
    file.write(xmlData);
522
}
523

524
bool DatabaseTabWidget::warnOnExport()
525
{
526
    auto ans =
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,
532
                             MessageBox::No);
533
    return ans == MessageBox::Yes;
534
}
535

536
void DatabaseTabWidget::showDatabaseSecurity()
537
{
538
    currentDatabaseWidget()->switchToDatabaseSecurity();
539
}
540

541
void DatabaseTabWidget::showDatabaseReports()
542
{
543
    currentDatabaseWidget()->switchToDatabaseReports();
544
}
545

546
void DatabaseTabWidget::showDatabaseSettings()
547
{
548
    currentDatabaseWidget()->switchToDatabaseSettings();
549
}
550

551
#ifdef WITH_XC_BROWSER_PASSKEYS
552
void DatabaseTabWidget::showPasskeys()
553
{
554
    currentDatabaseWidget()->switchToPasskeys();
555
}
556

557
void DatabaseTabWidget::importPasskey()
558
{
559
    currentDatabaseWidget()->showImportPasskeyDialog();
560
}
561

562
void DatabaseTabWidget::importPasskeyToEntry()
563
{
564
    currentDatabaseWidget()->showImportPasskeyDialog(true);
565
}
566
#endif
567

568
bool DatabaseTabWidget::isModified(int index) const
569
{
570
    if (count() == 0) {
571
        return false;
572
    }
573

574
    if (index == -1) {
575
        index = currentIndex();
576
    }
577

578
    auto db = databaseWidgetFromIndex(index)->database();
579
    return db && db->isModified();
580
}
581

582
bool DatabaseTabWidget::canSave(int index) const
583
{
584
    return isModified(index);
585
}
586

587
bool DatabaseTabWidget::hasLockableDatabases() const
588
{
589
    for (int i = 0, c = count(); i < c; ++i) {
590
        if (!databaseWidgetFromIndex(i)->isLocked()) {
591
            return true;
592
        }
593
    }
594
    return false;
595
}
596

597
/**
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()
600
 *
601
 * @param index tab index
602
 * @return tab name
603
 */
604
QString DatabaseTabWidget::tabName(int index)
605
{
606
    auto dbWidget = databaseWidgetFromIndex(index);
607
    if (!dbWidget) {
608
        return {};
609
    }
610

611
    auto tabName = dbWidget->displayName();
612

613
    if (dbWidget->isLocked()) {
614
        tabName = tr("%1 [Locked]", "Database tab name modifier").arg(tabName);
615
    }
616

617
    if (dbWidget->database()->isModified()) {
618
        tabName.append("*");
619
    }
620

621
    return tabName;
622
}
623

624
/**
625
 * Update of the given tab index or of the sending
626
 * DatabaseWidget if `index` == -1.
627
 */
628
void DatabaseTabWidget::updateTabName(int index)
629
{
630
    auto* dbWidget = databaseWidgetFromIndex(index);
631
    if (!dbWidget) {
632
        dbWidget = qobject_cast<DatabaseWidget*>(sender());
633
    }
634
    Q_ASSERT(dbWidget);
635
    if (!dbWidget) {
636
        return;
637
    }
638
    index = indexOf(dbWidget);
639
    setTabText(index, tabName(index));
640
    setTabToolTip(index, dbWidget->displayFilePath());
641
    emit tabNameChanged();
642
}
643

644
DatabaseWidget* DatabaseTabWidget::databaseWidgetFromIndex(int index) const
645
{
646
    return qobject_cast<DatabaseWidget*>(widget(index));
647
}
648

649
DatabaseWidget* DatabaseTabWidget::currentDatabaseWidget()
650
{
651
    return qobject_cast<DatabaseWidget*>(currentWidget());
652
}
653

654
/**
655
 * Attempt to lock all open databases
656
 *
657
 * @return return true if all databases are locked
658
 */
659
bool DatabaseTabWidget::lockDatabases()
660
{
661
    int numLocked = 0;
662
    int c = count();
663
    for (int i = 0; i < c; ++i) {
664
        auto dbWidget = databaseWidgetFromIndex(i);
665
        if (dbWidget->lock()) {
666
            ++numLocked;
667
            if (dbWidget->database()->filePath().isEmpty()) {
668
                // If we locked a database without a file close the tab
669
                closeDatabaseTab(dbWidget);
670
            }
671
        }
672
    }
673

674
    return numLocked == c;
675
}
676

677
void DatabaseTabWidget::lockDatabasesDelayed()
678
{
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();
686
    }
687
}
688

689
/**
690
 * Unlock a database with an unlock popup dialog.
691
 *
692
 * @param dbWidget DatabaseWidget which to connect signals to
693
 * @param intent intent for unlocking
694
 */
695
void DatabaseTabWidget::unlockDatabaseInDialog(DatabaseWidget* dbWidget, DatabaseOpenDialog::Intent intent)
696
{
697
    unlockDatabaseInDialog(dbWidget, intent, dbWidget->database()->filePath());
698
}
699

700
/**
701
 * Unlock a database with an unlock popup dialog.
702
 *
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
706
 */
707
void DatabaseTabWidget::unlockDatabaseInDialog(DatabaseWidget* dbWidget,
708
                                               DatabaseOpenDialog::Intent intent,
709
                                               const QString& filePath)
710
{
711
    m_databaseOpenDialog->clearForms();
712
    m_databaseOpenDialog->setIntent(intent);
713
    m_databaseOpenDialog->setTarget(dbWidget, filePath);
714
    displayUnlockDialog();
715
}
716

717
/**
718
 * Unlock a database with an unlock popup dialog.
719
 * The dialog allows the user to select any open & locked database.
720
 *
721
 * @param intent intent for unlocking
722
 */
723
void DatabaseTabWidget::unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent intent)
724
{
725
    m_databaseOpenDialog->clearForms();
726
    m_databaseOpenDialog->setIntent(intent);
727

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);
733
        }
734
    }
735
    // default to the current tab
736
    m_databaseOpenDialog->setActiveDatabaseTab(currentDatabaseWidget());
737
    displayUnlockDialog();
738
}
739

740
/**
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.
743
 */
744
void DatabaseTabWidget::displayUnlockDialog()
745
{
746
#ifdef Q_OS_MACOS
747
    auto intent = m_databaseOpenDialog->intent();
748
    if (intent == DatabaseOpenDialog::Intent::AutoType || intent == DatabaseOpenDialog::Intent::Browser) {
749
        macUtils()->raiseOwnWindow();
750
        Tools::wait(200);
751
    }
752
#endif
753

754
    m_databaseOpenDialog->show();
755
    m_databaseOpenDialog->raise();
756
    m_databaseOpenDialog->activateWindow();
757
}
758

759
/**
760
 * Actions to take when the unlock dialog has completed.
761
 */
762
void DatabaseTabWidget::handleDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget)
763
{
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);
768
        if (index != -1) {
769
            setCurrentIndex(index);
770
        }
771
    }
772

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;
776
    }
777

778
    // signal other objects that the dialog finished
779
    emit databaseUnlockDialogFinished(accepted, dbWidget);
780
}
781

782
/**
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
785
 */
786
void DatabaseTabWidget::relockPendingDatabase()
787
{
788
    if (!m_dbWidgetPendingLock || !config()->get(Config::Security_RelockAutoType).toBool()) {
789
        return;
790
    }
791

792
    if (m_dbWidgetPendingLock->isLocked() || !m_dbWidgetPendingLock->database()->isInitialized()) {
793
        m_dbWidgetPendingLock = nullptr;
794
        return;
795
    }
796

797
    m_dbWidgetPendingLock->lock();
798
    m_dbWidgetPendingLock = nullptr;
799
}
800

801
void DatabaseTabWidget::updateLastDatabases(const QString& filename)
802
{
803
    if (!config()->get(Config::RememberLastDatabases).toBool()) {
804
        config()->remove(Config::LastDatabases);
805
    } else {
806
        QStringList lastDatabases = config()->get(Config::LastDatabases).toStringList();
807
        lastDatabases.prepend(QDir::toNativeSeparators(filename));
808
        lastDatabases.removeDuplicates();
809

810
        while (lastDatabases.count() > config()->get(Config::NumberOfRememberedLastDatabases).toInt()) {
811
            lastDatabases.removeLast();
812
        }
813
        config()->set(Config::LastDatabases, lastDatabases);
814
    }
815
}
816

817
void DatabaseTabWidget::updateLastDatabases()
818
{
819
    auto dbWidget = currentDatabaseWidget();
820

821
    if (dbWidget) {
822
        auto filePath = dbWidget->database()->filePath();
823
        if (!filePath.isEmpty()) {
824
            updateLastDatabases(filePath);
825
        }
826
    }
827
}
828

829
void DatabaseTabWidget::emitActiveDatabaseChanged()
830
{
831
    emit activeDatabaseChanged(currentDatabaseWidget());
832
}
833

834
void DatabaseTabWidget::emitDatabaseLockChanged()
835
{
836
    auto* dbWidget = qobject_cast<DatabaseWidget*>(sender());
837
    Q_ASSERT(dbWidget);
838
    if (!dbWidget) {
839
        return;
840
    }
841

842
    if (dbWidget->isLocked()) {
843
        emit databaseLocked(dbWidget);
844
    } else {
845
        emit databaseUnlocked(dbWidget);
846
        m_databaseOpenInProgress = false;
847
    }
848
}
849

850
void DatabaseTabWidget::performGlobalAutoType(const QString& search)
851
{
852
    auto currentDbWidget = currentDatabaseWidget();
853
    if (!currentDbWidget) {
854
        // No open databases, nothing to do
855
        return;
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);
861
    } else {
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());
869
            }
870
        }
871

872
        Q_ASSERT(!unlockedDatabases.isEmpty());
873
        autoType()->performGlobalAutoType(unlockedDatabases, search);
874
    }
875
}
876

877
void DatabaseTabWidget::performBrowserUnlock()
878
{
879
    if (m_databaseOpenInProgress) {
880
        return;
881
    }
882

883
    m_databaseOpenInProgress = true;
884
    auto dbWidget = currentDatabaseWidget();
885
    if (dbWidget && dbWidget->isLocked()) {
886
        unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent::Browser);
887
    }
888
}
889

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

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

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

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