diff --git a/COPYING b/COPYING index 174848e46..aa11b14fa 100644 --- a/COPYING +++ b/COPYING @@ -140,7 +140,9 @@ Files: share/icons/badges/2_Expired.svg Copyright: 2020 KeePassXC Team License: MIT -Files: share/icons/application/scalable/actions/document-close.svg +Files: share/icons/application/scalable/actions/chevron-double-down.svg + share/icons/application/scalable/actions/chevron-double-right.svg + share/icons/application/scalable/actions/document-close.svg share/icons/application/scalable/actions/document-edit.svg share/icons/application/scalable/actions/document-export.svg share/icons/application/scalable/actions/document-import.svg diff --git a/share/icons/application/scalable/actions/chevron-double-down.svg b/share/icons/application/scalable/actions/chevron-double-down.svg new file mode 100644 index 000000000..edcf11814 --- /dev/null +++ b/share/icons/application/scalable/actions/chevron-double-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/share/icons/application/scalable/actions/chevron-double-right.svg b/share/icons/application/scalable/actions/chevron-double-right.svg new file mode 100644 index 000000000..3f2a06b4f --- /dev/null +++ b/share/icons/application/scalable/actions/chevron-double-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/share/icons/icons.qrc b/share/icons/icons.qrc index cb11f9068..cf73b4958 100644 --- a/share/icons/icons.qrc +++ b/share/icons/icons.qrc @@ -8,6 +8,8 @@ application/scalable/actions/application-exit.svg application/scalable/actions/auto-type.svg application/scalable/actions/bugreport.svg + application/scalable/actions/chevron-double-down.svg + application/scalable/actions/chevron-double-right.svg application/scalable/actions/chronometer.svg application/scalable/actions/clipboard-text.svg application/scalable/actions/configure.svg diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8c6729386..2044784b6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -118,7 +118,6 @@ set(keepassx_SOURCES gui/IconModels.cpp gui/KeePass1OpenWidget.cpp gui/KMessageWidget.cpp - gui/LineEdit.cpp gui/MainWindow.cpp gui/MessageBox.cpp gui/MessageWidget.cpp diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 382e04344..ce5754428 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -101,6 +101,7 @@ static const QHash configStrings = { {Config::GUI_MinimizeOnStartup, {QS("GUI/MinimizeOnStartup"), Roaming, false}}, {Config::GUI_MinimizeOnClose, {QS("GUI/MinimizeOnClose"), Roaming, false}}, {Config::GUI_HideUsernames, {QS("GUI/HideUsernames"), Roaming, false}}, + {Config::GUI_HidePasswords, {QS("GUI/HidePasswords"), Roaming, true}}, {Config::GUI_AdvancedSettings, {QS("GUI/AdvancedSettings"), Roaming, false}}, {Config::GUI_MonospaceNotes, {QS("GUI/MonospaceNotes"), Roaming, false}}, {Config::GUI_ApplicationTheme, {QS("GUI/ApplicationTheme"), Roaming, QS("auto")}}, diff --git a/src/core/Config.h b/src/core/Config.h index 63bfb00c5..553804699 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -83,6 +83,7 @@ public: GUI_MinimizeOnStartup, GUI_MinimizeOnClose, GUI_HideUsernames, + GUI_HidePasswords, GUI_AdvancedSettings, GUI_MonospaceNotes, GUI_ApplicationTheme, diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 72b48283a..82c9e7bc3 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -36,6 +36,8 @@ const int Entry::ResolveMaximumDepth = 10; const QString Entry::AutoTypeSequenceUsername = "{USERNAME}{ENTER}"; const QString Entry::AutoTypeSequencePassword = "{PASSWORD}{ENTER}"; +Entry::CloneFlags Entry::DefaultCloneFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo; + Entry::Entry() : m_attributes(new EntryAttributes(this)) , m_attachments(new EntryAttachments(this)) diff --git a/src/core/Entry.h b/src/core/Entry.h index 6fef11940..a0dbbf7d4 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -125,11 +125,6 @@ public: CustomData* customData(); const CustomData* customData() const; - static const int DefaultIconNumber; - static const int ResolveMaximumDepth; - static const QString AutoTypeSequenceUsername; - static const QString AutoTypeSequencePassword; - void setUuid(const QUuid& uuid); void setIcon(int iconNumber); void setIcon(const QUuid& uuid); @@ -210,13 +205,19 @@ public: DbDir }; + static const int DefaultIconNumber; + static const int ResolveMaximumDepth; + static const QString AutoTypeSequenceUsername; + static const QString AutoTypeSequencePassword; + static CloneFlags DefaultCloneFlags; + /** * Creates a duplicate of this entry except that the returned entry isn't * part of any group. * Note that you need to copy the custom icons manually when inserting the * new entry into another database. */ - Entry* clone(CloneFlags flags) const; + Entry* clone(CloneFlags flags = DefaultCloneFlags) const; void copyDataFrom(const Entry* other); QString maskPasswordPlaceholders(const QString& str) const; Entry* resolveReference(const QString& str) const; diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 8be8b5f95..56e545bf6 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -37,9 +37,7 @@ const int Group::RecycleBinIconNumber = 43; const QString Group::RootAutoTypeSequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; Group::CloneFlags Group::DefaultCloneFlags = - static_cast(Group::CloneNewUuid | Group::CloneResetTimeInfo | Group::CloneIncludeEntries); -Entry::CloneFlags Group::DefaultEntryCloneFlags = - static_cast(Entry::CloneNewUuid | Entry::CloneResetTimeInfo); + Group::CloneNewUuid | Group::CloneResetTimeInfo | Group::CloneIncludeEntries; Group::Group() : m_customData(new CustomData(this)) diff --git a/src/core/Group.h b/src/core/Group.h index 8ff33d8d0..4bea8e4eb 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -109,7 +109,6 @@ public: static const int DefaultIconNumber; static const int RecycleBinIconNumber; static CloneFlags DefaultCloneFlags; - static Entry::CloneFlags DefaultEntryCloneFlags; static const QString RootAutoTypeSequence; Group* findChildByName(const QString& name); @@ -158,7 +157,7 @@ public: QSet customIconsRecursive() const; QList usernamesRecursive(int topN = -1) const; - Group* clone(Entry::CloneFlags entryFlags = DefaultEntryCloneFlags, + Group* clone(Entry::CloneFlags entryFlags = Entry::DefaultCloneFlags, CloneFlags groupFlags = DefaultCloneFlags) const; void copyDataFrom(const Group* other); diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index a3ff32305..b9f77823c 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -76,12 +76,6 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) m_ui->keyFileLabelHelp->setIcon(icons()->icon("system-help").pixmap(QSize(12, 12))); connect(m_ui->keyFileLabelHelp, SIGNAL(clicked(bool)), SLOT(openKeyFileHelp())); - connect(m_ui->keyFileLineEdit, SIGNAL(textChanged(QString)), SLOT(keyFileTextChanged())); - m_ui->keyFileLineEdit->addAction(m_ui->keyFileClearIcon, QLineEdit::TrailingPosition); - m_ui->keyFileClearIcon->setIcon(icons()->icon("edit-clear-locationbar-rtl")); - m_ui->keyFileClearIcon->setVisible(false); - connect(m_ui->keyFileClearIcon, SIGNAL(triggered(bool)), SLOT(clearKeyFileText())); - #ifdef WITH_XC_YUBIKEY m_ui->hardwareKeyProgress->setVisible(false); QSizePolicy sp = m_ui->hardwareKeyProgress->sizePolicy(); @@ -145,8 +139,6 @@ void DatabaseOpenWidget::load(const QString& filename) m_filename = filename; m_ui->fileNameLabel->setRawText(m_filename); - m_ui->keyFileClearIcon->setVisible(false); - if (config()->get(Config::RememberLastKeyFiles).toBool()) { auto lastKeyFiles = config()->get(Config::LastKeyFiles).toHash(); if (lastKeyFiles.contains(m_filename)) { @@ -388,11 +380,6 @@ void DatabaseOpenWidget::clearKeyFileText() m_ui->keyFileLineEdit->clear(); } -void DatabaseOpenWidget::keyFileTextChanged() -{ - m_ui->keyFileClearIcon->setVisible(!m_ui->keyFileLineEdit->text().isEmpty()); -} - void DatabaseOpenWidget::pollHardwareKey() { if (m_pollingHardwareKey) { diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h index 6d2b688ca..fffdf8a7a 100644 --- a/src/gui/DatabaseOpenWidget.h +++ b/src/gui/DatabaseOpenWidget.h @@ -66,7 +66,6 @@ protected slots: private slots: void browseKeyFile(); void clearKeyFileText(); - void keyFileTextChanged(); void pollHardwareKey(); void hardwareKeyResponse(bool found); void openHardwareKeyHelp(); diff --git a/src/gui/DatabaseOpenWidget.ui b/src/gui/DatabaseOpenWidget.ui index a7510baba..eef4a2ed2 100644 --- a/src/gui/DatabaseOpenWidget.ui +++ b/src/gui/DatabaseOpenWidget.ui @@ -416,6 +416,9 @@ Key file to unlock the database + + true + @@ -575,16 +578,14 @@ - - - Clear - - - Clear Key File - - + + PasswordEdit + QLineEdit +
gui/PasswordEdit.h
+ 1 +
MessageWidget QWidget @@ -596,11 +597,6 @@ QLabel
gui/widgets/ElidedLabel.h
- - PasswordEdit - QLineEdit -
gui/PasswordEdit.h
-
editPassword diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 5cfe26e03..9a1bd0590 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -329,38 +329,6 @@ void DatabaseWidget::setPreviewSplitterSizes(const QList& sizes) m_previewSplitter->setSizes(sizes); } -/** - * Get current state of entry view 'Hide Usernames' setting - */ -bool DatabaseWidget::isUsernamesHidden() const -{ - return m_entryView->isUsernamesHidden(); -} - -/** - * Set state of entry view 'Hide Usernames' setting - */ -void DatabaseWidget::setUsernamesHidden(bool hide) -{ - m_entryView->setUsernamesHidden(hide); -} - -/** - * Get current state of entry view 'Hide Passwords' setting - */ -bool DatabaseWidget::isPasswordsHidden() const -{ - return m_entryView->isPasswordsHidden(); -} - -/** - * Set state of entry view 'Hide Passwords' setting - */ -void DatabaseWidget::setPasswordsHidden(bool hide) -{ - m_entryView->setPasswordsHidden(hide); -} - /** * Get current view state of entry view */ @@ -890,7 +858,8 @@ void DatabaseWidget::openUrlForEntry(Entry* entry) // otherwise ask user if (!launch && cmdString.length() > 6) { - QString cmdTruncated = cmdString.mid(6); + QString cmdTruncated = entry->resolveMultiplePlaceholders(entry->maskPasswordPlaceholders(entry->url())); + cmdTruncated = cmdTruncated.mid(6); if (cmdTruncated.length() > 400) { cmdTruncated = cmdTruncated.left(400) + " […]"; } diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 71fceadf5..efe602121 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -104,10 +104,6 @@ public: QStringList customEntryAttributes() const; bool isEditWidgetModified() const; - bool isUsernamesHidden() const; - void setUsernamesHidden(bool hide); - bool isPasswordsHidden() const; - void setPasswordsHidden(bool hide); void clearAllWidgets(); bool currentEntryHasTitle(); bool currentEntryHasUsername(); diff --git a/src/gui/DatabaseWidgetStateSync.cpp b/src/gui/DatabaseWidgetStateSync.cpp index e984e3d5f..0d8607374 100644 --- a/src/gui/DatabaseWidgetStateSync.cpp +++ b/src/gui/DatabaseWidgetStateSync.cpp @@ -29,8 +29,6 @@ DatabaseWidgetStateSync::DatabaseWidgetStateSync(QObject* parent) { m_mainSplitterSizes = variantToIntList(config()->get(Config::GUI_SplitterState)); m_previewSplitterSizes = variantToIntList(config()->get(Config::GUI_PreviewSplitterState)); - m_hideUsernames = config()->get(Config::GUI_HideUsernames).toBool(); - m_hidePasswords = true; m_listViewState = config()->get(Config::GUI_ListViewState).toByteArray(); m_searchViewState = config()->get(Config::GUI_SearchViewState).toByteArray(); @@ -48,7 +46,6 @@ void DatabaseWidgetStateSync::sync() { config()->set(Config::GUI_SplitterState, intListToVariant(m_mainSplitterSizes)); config()->set(Config::GUI_PreviewSplitterState, intListToVariant(m_previewSplitterSizes)); - config()->set(Config::GUI_HideUsernames, m_hideUsernames); config()->set(Config::GUI_ListViewState, m_listViewState); config()->set(Config::GUI_SearchViewState, m_searchViewState); config()->sync(); @@ -104,9 +101,6 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget) */ void DatabaseWidgetStateSync::restoreListView() { - m_activeDbWidget->setUsernamesHidden(m_hideUsernames); - m_activeDbWidget->setPasswordsHidden(m_hidePasswords); - if (!m_listViewState.isEmpty()) { m_activeDbWidget->setEntryViewState(m_listViewState); } @@ -129,9 +123,6 @@ void DatabaseWidgetStateSync::restoreListView() */ void DatabaseWidgetStateSync::restoreSearchView() { - m_activeDbWidget->setUsernamesHidden(m_hideUsernames); - m_activeDbWidget->setPasswordsHidden(m_hidePasswords); - if (!m_searchViewState.isEmpty()) { m_activeDbWidget->setEntryViewState(m_searchViewState); } else { @@ -169,9 +160,6 @@ void DatabaseWidgetStateSync::updateViewState() return; } - m_hideUsernames = m_activeDbWidget->isUsernamesHidden(); - m_hidePasswords = m_activeDbWidget->isPasswordsHidden(); - if (m_activeDbWidget->isSearchActive()) { m_searchViewState = m_activeDbWidget->entryViewState(); } else { diff --git a/src/gui/DatabaseWidgetStateSync.h b/src/gui/DatabaseWidgetStateSync.h index bf254e1f5..4e28b0a43 100644 --- a/src/gui/DatabaseWidgetStateSync.h +++ b/src/gui/DatabaseWidgetStateSync.h @@ -51,9 +51,6 @@ private: QList m_mainSplitterSizes; QList m_previewSplitterSizes; - bool m_hideUsernames; - bool m_hidePasswords; - QByteArray m_listViewState; QByteArray m_searchViewState; }; diff --git a/src/gui/LineEdit.cpp b/src/gui/LineEdit.cpp deleted file mode 100644 index a64338b7f..000000000 --- a/src/gui/LineEdit.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2007 Trolltech ASA - * Copyright (C) 2012 Felix Geyer - * Copyright (C) 2012 Florian Geyer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 or (at your option) - * version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "LineEdit.h" - -#include -#include - -#include "gui/Icons.h" - -LineEdit::LineEdit(QWidget* parent) - : QLineEdit(parent) - , m_clearButton(new QToolButton(this)) -{ - m_clearButton->setObjectName("clearButton"); - - QString iconNameDirected = - QString("edit-clear-locationbar-").append((layoutDirection() == Qt::LeftToRight) ? "rtl" : "ltr"); - - const auto icon = icons()->icon(iconNameDirected); - - m_clearButton->setIcon(icon); - m_clearButton->setCursor(Qt::ArrowCursor); - m_clearButton->setStyleSheet("QToolButton { border: none; padding: 0px; }"); - m_clearButton->hide(); - connect(m_clearButton, SIGNAL(clicked()), this, SLOT(clear())); - connect(this, SIGNAL(textChanged(QString)), this, SLOT(updateCloseButton(QString))); - int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - setStyleSheet( - QString("QLineEdit { padding-right: %1px; } ").arg(m_clearButton->sizeHint().width() + frameWidth + 1)); - QSize msz = minimumSizeHint(); - setMinimumSize(qMax(msz.width(), m_clearButton->sizeHint().height() + frameWidth * 2 + 2), - qMax(msz.height(), m_clearButton->sizeHint().height() + frameWidth * 2 + 2)); -} - -void LineEdit::resizeEvent(QResizeEvent* event) -{ - QSize sz = m_clearButton->sizeHint(); - int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - int y = (rect().bottom() + 1 - sz.height()) / 2; - - if (layoutDirection() == Qt::LeftToRight) { - m_clearButton->move(rect().right() - frameWidth - sz.width(), y); - } else { - m_clearButton->move(rect().left() + frameWidth, y); - } - - QLineEdit::resizeEvent(event); -} - -void LineEdit::updateCloseButton(const QString& text) -{ - m_clearButton->setVisible(!text.isEmpty()); -} diff --git a/src/gui/LineEdit.h b/src/gui/LineEdit.h deleted file mode 100644 index 1695e8551..000000000 --- a/src/gui/LineEdit.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2007 Trolltech ASA - * Copyright (C) 2012 Felix Geyer - * Copyright (C) 2012 Florian Geyer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 or (at your option) - * version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef KEEPASSX_LINEEDIT_H -#define KEEPASSX_LINEEDIT_H - -#include - -class QToolButton; - -class LineEdit : public QLineEdit -{ - Q_OBJECT - -public: - explicit LineEdit(QWidget* parent = nullptr); - -protected: - void resizeEvent(QResizeEvent* event) override; - -private slots: - void updateCloseButton(const QString& text); - -private: - QToolButton* const m_clearButton; -}; - -#endif // KEEPASSX_LINEEDIT_H diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index f9d3cf406..663f0a5e8 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -338,10 +338,6 @@ MainWindow::MainWindow() shortcut = new QShortcut(dbTabModifier + Qt::Key_9, this); connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(m_ui->tabWidget->count() - 1); }); - // Toggle password and username visibility in entry view - new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C, this, SLOT(togglePasswordsHidden())); - new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_B, this, SLOT(toggleUsernamesHidden())); - m_ui->actionDatabaseNew->setIcon(icons()->icon("document-new")); m_ui->actionDatabaseOpen->setIcon(icons()->icon("document-open")); m_ui->menuRecentDatabases->setIcon(icons()->icon("document-open-recent")); @@ -509,9 +505,6 @@ MainWindow::MainWindow() #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); - if (macUtils()->isDarkMode()) { - setStyleSheet("QToolButton {color:white;}"); - } #endif #ifdef WITH_XC_UPDATECHECK @@ -1165,22 +1158,6 @@ void MainWindow::databaseTabChanged(int tabIndex) m_actionMultiplexer.setCurrentObject(m_ui->tabWidget->currentDatabaseWidget()); } -void MainWindow::togglePasswordsHidden() -{ - auto dbWidget = m_ui->tabWidget->currentDatabaseWidget(); - if (dbWidget) { - dbWidget->setPasswordsHidden(!dbWidget->isPasswordsHidden()); - } -} - -void MainWindow::toggleUsernamesHidden() -{ - auto dbWidget = m_ui->tabWidget->currentDatabaseWidget(); - if (dbWidget) { - dbWidget->setUsernamesHidden(!dbWidget->isUsernamesHidden()); - } -} - void MainWindow::closeEvent(QCloseEvent* event) { if (m_appExiting) { @@ -1801,4 +1778,23 @@ void MainWindow::initViewMenu() connect(m_ui->actionShowPreviewPanel, &QAction::toggled, this, [](bool checked) { config()->set(Config::GUI_HidePreviewPanel, !checked); }); + + connect(m_ui->actionAlwaysOnTop, &QAction::toggled, this, [this](bool checked) { + if (checked) { + setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); + } else { + setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); + } + show(); + }); + + m_ui->actionHideUsernames->setChecked(config()->get(Config::GUI_HideUsernames).toBool()); + connect(m_ui->actionHideUsernames, &QAction::toggled, this, [](bool checked) { + config()->set(Config::GUI_HideUsernames, checked); + }); + + m_ui->actionHidePasswords->setChecked(config()->get(Config::GUI_HidePasswords).toBool()); + connect(m_ui->actionHidePasswords, &QAction::toggled, this, [](bool checked) { + config()->set(Config::GUI_HidePasswords, checked); + }); } diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index f09b0ea80..187b36d55 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -132,8 +132,6 @@ private slots: void selectNextDatabaseTab(); void selectPreviousDatabaseTab(); void selectDatabaseTab(int tabIndex, bool wrap = false); - void togglePasswordsHidden(); - void toggleUsernamesHidden(); void obtainContextFocusLock(); void releaseContextFocusLock(); void agentEnabled(bool enabled); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 40efd3fa8..b141c8cc3 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -381,9 +381,12 @@ + + + @@ -984,6 +987,42 @@ Show Preview Panel + + + true + + + Always on Top + + + Ctrl+Shift+A + + + + + true + + + Hide Usernames + + + Ctrl+Shift+B + + + + + true + + + true + + + Hide Passwords + + + Ctrl+Shift+C + + diff --git a/src/gui/PasswordEdit.cpp b/src/gui/PasswordEdit.cpp index e67a94fb9..1773a4365 100644 --- a/src/gui/PasswordEdit.cpp +++ b/src/gui/PasswordEdit.cpp @@ -180,7 +180,9 @@ void PasswordEdit::autocompletePassword(const QString& password) bool PasswordEdit::event(QEvent* event) { - if (isVisible()) { + if (isVisible() + && (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease + || event->type() == QEvent::FocusIn)) { checkCapslockState(); } return QLineEdit::event(event); @@ -204,7 +206,9 @@ void PasswordEdit::checkCapslockState() if (newCapslockState) { QTimer::singleShot( - 150, [this]() { QToolTip::showText(mapToGlobal(rect().bottomLeft()), m_capslockAction->text()); }); + 150, [this] { QToolTip::showText(mapToGlobal(rect().bottomLeft()), m_capslockAction->text()); }); + } else if (QToolTip::isVisible()) { + QToolTip::hideText(); } } } diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 3168b3e7c..74c443734 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -46,14 +46,6 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) m_ui->buttonCopy->setIcon(icons()->icon("clipboard-text")); m_ui->buttonClose->setShortcut(Qt::Key_Escape); - m_ui->clearInclude->setIcon(icons()->icon("edit-clear-locationbar-rtl")); - m_ui->editAdditionalChars->addAction(m_ui->clearInclude, QLineEdit::TrailingPosition); - m_ui->clearInclude->setVisible(false); - - m_ui->clearExclude->setIcon(icons()->icon("edit-clear-locationbar-rtl")); - m_ui->editExcludedChars->addAction(m_ui->clearExclude, QLineEdit::TrailingPosition); - m_ui->clearExclude->setVisible(false); - connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updateButtonsEnabled(QString))); connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updatePasswordStrength(QString))); connect(m_ui->buttonAdvancedMode, SIGNAL(toggled(bool)), SLOT(setAdvancedMode(bool))); @@ -64,8 +56,6 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) connect(m_ui->buttonCopy, SIGNAL(clicked()), SLOT(copyPassword())); connect(m_ui->buttonGenerate, SIGNAL(clicked()), SLOT(regeneratePassword())); connect(m_ui->buttonClose, SIGNAL(clicked()), SIGNAL(closed())); - connect(m_ui->clearInclude, SIGNAL(triggered(bool)), m_ui->editAdditionalChars, SLOT(clear())); - connect(m_ui->clearExclude, SIGNAL(triggered(bool)), m_ui->editExcludedChars, SLOT(clear())); connect(m_ui->sliderLength, SIGNAL(valueChanged(int)), SLOT(passwordLengthChanged(int))); connect(m_ui->spinBoxLength, SIGNAL(valueChanged(int)), SLOT(passwordLengthChanged(int))); @@ -533,9 +523,6 @@ void PasswordGeneratorWidget::updateGenerator() } else { m_ui->buttonGenerate->setEnabled(false); } - - m_ui->clearInclude->setVisible(!m_ui->editAdditionalChars->text().isEmpty()); - m_ui->clearExclude->setVisible(!m_ui->editExcludedChars->text().isEmpty()); } else { m_dicewareGenerator->setWordCase( static_cast(m_ui->wordCaseComboBox->currentData().toInt())); diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index 00d7fde0c..5652f5cb1 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -608,6 +608,9 @@ QProgressBar::chunk { Additional characters + + true + @@ -646,6 +649,9 @@ QProgressBar::chunk { Excluded characters + + true + @@ -954,22 +960,6 @@ QProgressBar::chunk { - - - Clear - - - Clear - - - - - Clear - - - Clear - - diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index 464272c3c..31ee7e9b4 100644 --- a/src/gui/SearchWidget.cpp +++ b/src/gui/SearchWidget.cpp @@ -45,7 +45,6 @@ SearchWidget::SearchWidget(QWidget* parent) m_clearSearchTimer->setSingleShot(true); connect(m_ui->searchEdit, SIGNAL(textChanged(QString)), SLOT(startSearchTimer())); - connect(m_ui->clearIcon, SIGNAL(triggered(bool)), m_ui->searchEdit, SLOT(clear())); connect(m_ui->helpIcon, SIGNAL(triggered()), SLOT(toggleHelp())); connect(m_ui->searchIcon, SIGNAL(triggered()), SLOT(showSearchMenu())); connect(m_searchTimer, SIGNAL(timeout()), SLOT(startSearch())); @@ -75,10 +74,6 @@ SearchWidget::SearchWidget(QWidget* parent) m_ui->helpIcon->setIcon(icons()->icon("system-help")); m_ui->searchEdit->addAction(m_ui->helpIcon, QLineEdit::TrailingPosition); - m_ui->clearIcon->setIcon(icons()->icon("edit-clear-locationbar-rtl")); - m_ui->clearIcon->setVisible(false); - m_ui->searchEdit->addAction(m_ui->clearIcon, QLineEdit::TrailingPosition); - // Fix initial visibility of actions (bug in Qt) for (QToolButton* toolButton : m_ui->searchEdit->findChildren()) { toolButton->setVisible(toolButton->defaultAction()->isVisible()); @@ -172,9 +167,6 @@ void SearchWidget::startSearch() m_searchTimer->stop(); } - bool hasText = m_ui->searchEdit->text().length() > 0; - m_ui->clearIcon->setVisible(hasText); - search(m_ui->searchEdit->text()); } diff --git a/src/gui/SearchWidget.ui b/src/gui/SearchWidget.ui index 74cc468cf..c924b4076 100644 --- a/src/gui/SearchWidget.ui +++ b/src/gui/SearchWidget.ui @@ -41,7 +41,7 @@ - false + true @@ -51,11 +51,6 @@ Search - - - Clear - - Search Help diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index c3dc21ec8..28a969d0f 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -809,12 +809,12 @@ void EditEntryWidget::loadEntry(Entry* entry, connect(m_entry, &Entry::entryModified, this, [this] { m_entryModifiedTimer.start(); }); if (history) { - setHeadline(QString("%1 \u2B29 %2").arg(parentName, tr("Entry history"))); + setHeadline(QString("%1 \u2022 %2").arg(parentName, tr("Entry history"))); } else { if (create) { - setHeadline(QString("%1 \u2B29 %2").arg(parentName, tr("Add entry"))); + setHeadline(QString("%1 \u2022 %2").arg(parentName, tr("Add entry"))); } else { - setHeadline(QString("%1 \u2B29 %2 \u2B29 %3").arg(parentName, entry->title(), tr("Edit entry"))); + setHeadline(QString("%1 \u2022 %2 \u2022 %3").arg(parentName, entry->title(), tr("Edit entry"))); } } @@ -1013,6 +1013,11 @@ bool EditEntryWidget::commitEntry() return true; } + // Check Auto-Type validity early + if (!AutoType::verifyAutoTypeSyntax(m_autoTypeUi->sequenceEdit->text())) { + return false; + } + if (m_advancedUi->attributesView->currentIndex().isValid() && m_advancedUi->attributesEdit->isEnabled()) { QString key = m_attributesModel->keyByIndex(m_advancedUi->attributesView->currentIndex()); m_entryAttributes->set(key, m_advancedUi->attributesEdit->toPlainText(), m_entryAttributes->isProtected(key)); @@ -1111,7 +1116,7 @@ void EditEntryWidget::updateEntryData(Entry* entry) const entry->setAutoTypeEnabled(m_autoTypeUi->enableButton->isChecked()); if (m_autoTypeUi->inheritSequenceButton->isChecked()) { entry->setDefaultAutoTypeSequence(QString()); - } else if (AutoType::verifyAutoTypeSyntax(m_autoTypeUi->sequenceEdit->text())) { + } else { entry->setDefaultAutoTypeSequence(m_autoTypeUi->sequenceEdit->text()); } @@ -1380,6 +1385,7 @@ void EditEntryWidget::removeAutoTypeAssoc() void EditEntryWidget::loadCurrentAssoc(const QModelIndex& current) { + bool modified = isModified(); if (current.isValid() && current.row() < m_autoTypeAssoc->size()) { AutoTypeAssociations::Association assoc = m_autoTypeAssoc->get(current.row()); m_autoTypeUi->windowTitleCombo->setEditText(assoc.window); @@ -1395,6 +1401,7 @@ void EditEntryWidget::loadCurrentAssoc(const QModelIndex& current) } else { clearCurrentAssoc(); } + setModified(modified); } void EditEntryWidget::clearCurrentAssoc() diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index 6b3608be2..ccd9b95fb 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -37,11 +37,10 @@ EntryModel::EntryModel(QObject* parent) : QAbstractTableModel(parent) , m_group(nullptr) - , m_hideUsernames(false) - , m_hidePasswords(true) , HiddenContentDisplay(QString("\u25cf").repeated(6)) , DateFormat(Qt::DefaultLocaleShortDate) { + connect(config(), &Config::changed, this, &EntryModel::onConfigChanged); } Entry* EntryModel::entryFromIndex(const QModelIndex& index) const @@ -156,7 +155,7 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const } return result; case Username: - if (m_hideUsernames) { + if (config()->get(Config::GUI_HideUsernames).toBool()) { result = EntryModel::HiddenContentDisplay; } else { result = entry->resolveMultiplePlaceholders(entry->username()); @@ -164,9 +163,12 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const if (attr->isReference(EntryAttributes::UserNameKey)) { result.prepend(tr("Ref: ", "Reference abbreviation")); } + if (entry->username().isEmpty() && !config()->get(Config::Security_PasswordEmptyPlaceholder).toBool()) { + result = ""; + } return result; case Password: - if (m_hidePasswords) { + if (config()->get(Config::GUI_HidePasswords).toBool()) { result = EntryModel::HiddenContentDisplay; } else { result = entry->resolveMultiplePlaceholders(entry->password()); @@ -537,6 +539,20 @@ void EntryModel::entryDataChanged(Entry* entry) emit dataChanged(index(row, 0), index(row, columnCount() - 1)); } +void EntryModel::onConfigChanged(Config::ConfigKey key) +{ + switch (key) { + case Config::GUI_HideUsernames: + emit dataChanged(index(0, Username), index(rowCount() - 1, Username), {Qt::DisplayRole}); + break; + case Config::GUI_HidePasswords: + emit dataChanged(index(0, Password), index(rowCount() - 1, Password), {Qt::DisplayRole}); + break; + default: + break; + } +} + void EntryModel::severConnections() { if (m_group) { @@ -560,39 +576,3 @@ void EntryModel::makeConnections(const Group* group) connect(group, SIGNAL(entryMovedDown()), SLOT(entryMovedDown())); connect(group, SIGNAL(entryDataChanged(Entry*)), SLOT(entryDataChanged(Entry*))); } - -/** - * Get current state of 'Hide Usernames' setting - */ -bool EntryModel::isUsernamesHidden() const -{ - return m_hideUsernames; -} - -/** - * Set state of 'Hide Usernames' setting and signal change - */ -void EntryModel::setUsernamesHidden(bool hide) -{ - m_hideUsernames = hide; - emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); - emit usernamesHiddenChanged(); -} - -/** - * Get current state of 'Hide Passwords' setting - */ -bool EntryModel::isPasswordsHidden() const -{ - return m_hidePasswords; -} - -/** - * Set state of 'Hide Passwords' setting and signal change - */ -void EntryModel::setPasswordsHidden(bool hide) -{ - m_hidePasswords = hide; - emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); - emit passwordsHiddenChanged(); -} diff --git a/src/gui/entry/EntryModel.h b/src/gui/entry/EntryModel.h index 78da7194c..01c2483a9 100644 --- a/src/gui/entry/EntryModel.h +++ b/src/gui/entry/EntryModel.h @@ -21,6 +21,8 @@ #include #include +#include "core/Config.h" + class Entry; class Group; @@ -64,15 +66,6 @@ public: void setGroup(Group* group); void setEntries(const QList& entries); - bool isUsernamesHidden() const; - void setUsernamesHidden(bool hide); - bool isPasswordsHidden() const; - void setPasswordsHidden(bool hide); - -signals: - void usernamesHiddenChanged(); - void passwordsHiddenChanged(); - private slots: void entryAboutToAdd(Entry* entry); void entryAdded(Entry* entry); @@ -84,6 +77,8 @@ private slots: void entryMovedDown(); void entryDataChanged(Entry* entry); + void onConfigChanged(Config::ConfigKey key); + private: void severConnections(); void makeConnections(const Group* group); @@ -93,9 +88,6 @@ private: QList m_orgEntries; QList m_allGroups; - bool m_hideUsernames; - bool m_hidePasswords; - const QString HiddenContentDisplay; const Qt::DateFormat DateFormat; }; diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp index 53f03b989..54c1547c7 100644 --- a/src/gui/entry/EntryView.cpp +++ b/src/gui/entry/EntryView.cpp @@ -32,7 +32,7 @@ EntryView::EntryView(QWidget* parent) , m_sortModel(new SortFilterHideProxyModel(this)) , m_lastIndex(-1) , m_lastOrder(Qt::AscendingOrder) - , m_inSearchMode(false) + , m_headerMenu(new QMenu(this)) { m_sortModel->setSourceModel(m_model); m_sortModel->setDynamicSortFilter(true); @@ -55,22 +55,10 @@ EntryView::EntryView(QWidget* parent) // clang-format off connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex))); connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(emitEntrySelectionChanged())); - connect(m_model, SIGNAL(usernamesHiddenChanged()), SIGNAL(viewStateChanged())); - connect(m_model, SIGNAL(passwordsHiddenChanged()), SIGNAL(viewStateChanged())); // clang-format on new QShortcut(Qt::CTRL + Qt::Key_F10, this, SLOT(contextMenuShortcutPressed()), nullptr, Qt::WidgetShortcut); - m_headerMenu = new QMenu(this); - m_headerMenu->setTitle(tr("Customize View")); - m_headerMenu->addSection(tr("Customize View")); - - m_hideUsernamesAction = m_headerMenu->addAction(tr("Hide Usernames"), this, SLOT(setUsernamesHidden(bool))); - m_hideUsernamesAction->setCheckable(true); - m_hidePasswordsAction = m_headerMenu->addAction(tr("Hide Passwords"), this, SLOT(setPasswordsHidden(bool))); - m_hidePasswordsAction->setCheckable(true); - m_headerMenu->addSeparator(); - resetViewToDefaults(); // Actions to toggle column visibility, each carrying the corresponding @@ -292,50 +280,6 @@ int EntryView::currentEntryIndex() } } -/** - * Get current state of 'Hide Usernames' setting (NOTE: just pass-through for - * m_model) - */ -bool EntryView::isUsernamesHidden() const -{ - return m_model->isUsernamesHidden(); -} - -/** - * Set state of 'Hide Usernames' setting (NOTE: just pass-through for m_model) - */ -void EntryView::setUsernamesHidden(bool hide) -{ - bool block = m_hideUsernamesAction->signalsBlocked(); - m_hideUsernamesAction->blockSignals(true); - m_hideUsernamesAction->setChecked(hide); - m_hideUsernamesAction->blockSignals(block); - - m_model->setUsernamesHidden(hide); -} - -/** - * Get current state of 'Hide Passwords' setting (NOTE: just pass-through for - * m_model) - */ -bool EntryView::isPasswordsHidden() const -{ - return m_model->isPasswordsHidden(); -} - -/** - * Set state of 'Hide Passwords' setting (NOTE: just pass-through for m_model) - */ -void EntryView::setPasswordsHidden(bool hide) -{ - bool block = m_hidePasswordsAction->signalsBlocked(); - m_hidePasswordsAction->blockSignals(true); - m_hidePasswordsAction->setChecked(hide); - m_hidePasswordsAction->blockSignals(block); - - m_model->setPasswordsHidden(hide); -} - /** * Get current view state */ @@ -363,8 +307,6 @@ bool EntryView::setViewState(const QByteArray& state) */ void EntryView::showHeaderMenu(const QPoint& position) { - m_hideUsernamesAction->setChecked(m_model->isUsernamesHidden()); - m_hidePasswordsAction->setChecked(m_model->isPasswordsHidden()); const QList actions = m_columnActions->actions(); for (auto& action : actions) { Q_ASSERT(static_cast(action->data().type()) == QMetaType::Int); @@ -469,9 +411,6 @@ void EntryView::resetFixedColumns() */ void EntryView::resetViewToDefaults() { - m_model->setUsernamesHidden(false); - m_model->setPasswordsHidden(true); - // Reduce number of columns that are shown by default if (m_inSearchMode) { header()->showSection(EntryModel::ParentGroup); diff --git a/src/gui/entry/EntryView.h b/src/gui/entry/EntryView.h index 65cbf104a..ad2fd037c 100644 --- a/src/gui/entry/EntryView.h +++ b/src/gui/entry/EntryView.h @@ -44,8 +44,6 @@ public: bool isSorted(); int numberOfSelectedEntries(); void setFirstEntryActive(); - bool isUsernamesHidden() const; - bool isPasswordsHidden() const; QByteArray viewState() const; bool setViewState(const QByteArray& state); @@ -57,10 +55,6 @@ signals: void entrySelectionChanged(Entry* entry); void viewStateChanged(); -public slots: - void setUsernamesHidden(bool hide); - void setPasswordsHidden(bool hide); - protected: void keyPressEvent(QKeyEvent* event) override; void focusInEvent(QFocusEvent* event) override; @@ -86,12 +80,10 @@ private: SortFilterHideProxyModel* const m_sortModel; int m_lastIndex; Qt::SortOrder m_lastOrder; - bool m_inSearchMode; + bool m_inSearchMode = false; bool m_columnsNeedRelayout = true; QMenu* m_headerMenu; - QAction* m_hideUsernamesAction; - QAction* m_hidePasswordsAction; QActionGroup* m_columnActions; }; diff --git a/src/gui/group/GroupModel.cpp b/src/gui/group/GroupModel.cpp index beba13774..e61410334 100644 --- a/src/gui/group/GroupModel.cpp +++ b/src/gui/group/GroupModel.cpp @@ -252,19 +252,23 @@ bool GroupModel::dropMimeData(const QMimeData* data, row--; } - Group* group; - if (action == Qt::MoveAction) { - group = dragGroup; - } else { - group = dragGroup->clone(); - } - Database* sourceDb = dragGroup->database(); Database* targetDb = parentGroup->database(); + Group* group = dragGroup; + if (sourceDb != targetDb) { QSet customIcons = group->customIconsRecursive(); targetDb->metadata()->copyCustomIcons(customIcons, sourceDb->metadata()); + + // Always clone the group across db's to reset UUIDs + group = dragGroup->clone(); + if (action == Qt::MoveAction) { + // Remove the original group from the sourceDb + delete dragGroup; + } + } else if (action == Qt::CopyAction) { + group = dragGroup->clone(); } group->setParent(parentGroup, row); @@ -288,19 +292,24 @@ bool GroupModel::dropMimeData(const QMimeData* data, continue; } - Entry* entry; - if (action == Qt::MoveAction) { - entry = dragEntry; - } else { - entry = dragEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo); - } - Database* sourceDb = dragEntry->group()->database(); Database* targetDb = parentGroup->database(); - QUuid customIcon = entry->iconUuid(); - if (sourceDb != targetDb && !customIcon.isNull() && !targetDb->metadata()->hasCustomIcon(customIcon)) { - targetDb->metadata()->addCustomIcon(customIcon, sourceDb->metadata()->customIcon(customIcon)); + Entry* entry = dragEntry; + + if (sourceDb != targetDb) { + QUuid customIcon = entry->iconUuid(); + if (!customIcon.isNull() && !targetDb->metadata()->hasCustomIcon(customIcon)) { + targetDb->metadata()->addCustomIcon(customIcon, sourceDb->metadata()->customIcon(customIcon)); + } + + // Always clone the entry across db's to reset the UUID + entry = dragEntry->clone(); + if (action == Qt::MoveAction) { + delete dragEntry; + } + } else if (action == Qt::CopyAction) { + entry = dragEntry->clone(); } entry->setGroup(parentGroup); diff --git a/src/gui/styles/base/BaseStyle.cpp b/src/gui/styles/base/BaseStyle.cpp index aac9daf1c..10207ce16 100644 --- a/src/gui/styles/base/BaseStyle.cpp +++ b/src/gui/styles/base/BaseStyle.cpp @@ -54,6 +54,8 @@ #include +#include "gui/Icons.h" + QT_BEGIN_NAMESPACE Q_GUI_EXPORT int qt_defaultDpiX(); QT_END_NAMESPACE @@ -282,6 +284,17 @@ namespace Phantom ? highlightedOutlineOf(pal) : Grad(pal.color(QPalette::WindowText), pal.color(QPalette::Window)).sample(0.5); } + +#ifdef Q_OS_MACOS + QColor tabBarBase(const QPalette& pal) + { + return hack_isLightPalette(pal) ? QRgb(0xD1D1D1) : QRgb(0x252525); + } + QColor tabBarBaseInactive(const QPalette& pal) + { + return hack_isLightPalette(pal) ? QRgb(0xF4F4F4) : QRgb(0x282828); + } +#endif } // namespace DeriveColors namespace SwatchColors @@ -328,6 +341,9 @@ namespace Phantom S_itemView_headerOnLine, S_scrollbarGutter_disabled, + S_tabBarBase, + S_tabBarBase_inactive, + // Aliases S_progressBar = S_highlight, S_progressBar_specular = S_highlight_specular, @@ -340,7 +356,7 @@ namespace Phantom enum { - Num_SwatchColors = SwatchColors::S_scrollbarGutter_disabled + 1, + Num_SwatchColors = SwatchColors::S_tabBarBase_inactive + 1, Num_ShadowSteps = 3, }; @@ -443,6 +459,14 @@ namespace Phantom colors[S_itemView_headerOnLine] = Dc::itemViewHeaderOnLineColorOf(pal); colors[S_scrollbarGutter_disabled] = colors[S_window]; +#ifdef Q_OS_MACOS + colors[S_tabBarBase] = Dc::tabBarBase(pal); + colors[S_tabBarBase_inactive] = Dc::tabBarBaseInactive(pal); +#else + colors[S_tabBarBase] = pal.color(QPalette::Active, QPalette::Window); + colors[S_tabBarBase_inactive] = pal.color(QPalette::Inactive, QPalette::Window); +#endif + brushes[S_none] = Qt::NoBrush; for (int i = S_none + 1; i < Num_SwatchColors; ++i) { // todo try to reuse @@ -1551,6 +1575,12 @@ void BaseStyle::drawPrimitive(PrimitiveElement elem, auto tbb = qstyleoption_cast(option); if (!tbb) break; + +#ifdef Q_OS_MACOS + painter->fillRect(widget->rect(), + swatch.color(option->state & QStyle::State_Active ? S_tabBarBase : S_tabBarBase_inactive)); +#endif + Qt::Edge edge = Qt::TopEdge; switch (tbb->shape) { case QTabBar::RoundedNorth: @@ -2253,6 +2283,21 @@ void BaseStyle::drawControl(ControlElement element, auto toolBar = qstyleoption_cast(option); if (!toolBar) break; + +#ifdef Q_OS_MACOS + if (auto* mainWindow = qobject_cast(widget->window())) { + // Fill toolbar background with transparent pixels to reveal the + // gradient background drawn by the Cocoa platform plugin. + // Inspired by qmacstyle_mac.mm. + if (m_drawNativeMacOsToolBar && toolBar && toolBar->toolBarArea == Qt::TopToolBarArea + && mainWindow->unifiedTitleAndToolBarOnMac()) { + painter->setCompositionMode(QPainter::CompositionMode_Source); + painter->fillRect(option->rect, Qt::transparent); + break; + } + } +#endif + painter->fillRect(option->rect, option->palette.window().color()); bool isFloating = false; if (auto tb = qobject_cast(widget)) { @@ -3036,6 +3081,21 @@ QPalette BaseStyle::standardPalette() const return QCommonStyle::standardPalette(); } +QIcon BaseStyle::standardIcon(StandardPixmap sp, const QStyleOption* opt, const QWidget* widget) const +{ + switch (sp) { + case SP_ToolBarHorizontalExtensionButton: + return icons()->icon("chevron-double-down"); + case SP_ToolBarVerticalExtensionButton: + return icons()->icon("chevron-double-right"); + case SP_LineEditClearButton: + return icons()->icon( + QString("edit-clear-locationbar-").append((opt->direction == Qt::LeftToRight) ? "rtl" : "ltr")); + default: + return QCommonStyle::standardIcon(sp, opt, widget); + } +} + void BaseStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex* option, QPainter* painter, diff --git a/src/gui/styles/base/BaseStyle.h b/src/gui/styles/base/BaseStyle.h index d6269fad7..d3c20915c 100644 --- a/src/gui/styles/base/BaseStyle.h +++ b/src/gui/styles/base/BaseStyle.h @@ -42,6 +42,8 @@ public: }; QPalette standardPalette() const override; + QIcon + standardIcon(StandardPixmap sp, const QStyleOption* opt = nullptr, const QWidget* widget = nullptr) const override; void drawPrimitive(PrimitiveElement elem, const QStyleOption* option, QPainter* painter, @@ -95,6 +97,15 @@ protected: return {}; } +#ifdef Q_OS_MACOS + /** + * Whether to draw a native macOS toolbar or fill it with a solid color instead. + * Can be set to false to avoid mixed themes if the OS theme isn't the same as + * the KeePassXC application theme. + */ + bool m_drawNativeMacOsToolBar = true; +#endif + BaseStylePrivate* d; }; diff --git a/src/gui/styles/dark/DarkStyle.cpp b/src/gui/styles/dark/DarkStyle.cpp index b8e548228..25f75e5ab 100644 --- a/src/gui/styles/dark/DarkStyle.cpp +++ b/src/gui/styles/dark/DarkStyle.cpp @@ -23,6 +23,14 @@ #include #include +DarkStyle::DarkStyle() + : BaseStyle() +{ +#ifdef Q_OS_MACOS + m_drawNativeMacOsToolBar = osUtils->isDarkMode(); +#endif +} + QPalette DarkStyle::standardPalette() const { auto palette = BaseStyle::standardPalette(); @@ -105,13 +113,10 @@ void DarkStyle::polish(QWidget* widget) || qobject_cast(widget)) { auto palette = widget->palette(); #if defined(Q_OS_MACOS) - if (osUtils->isDarkMode()) { - // Let the Cocoa platform plugin draw its own background - palette.setColor(QPalette::All, QPalette::Window, Qt::transparent); - } else { - palette.setColor(QPalette::Active, QPalette::Window, QRgb(0x2A2A2A)); - palette.setColor(QPalette::Inactive, QPalette::Window, QRgb(0x2D2D2D)); - palette.setColor(QPalette::Disabled, QPalette::Window, QRgb(0x2A2A2A)); + if (!osUtils->isDarkMode()) { + palette.setColor(QPalette::Active, QPalette::Window, QRgb(0x252525)); + palette.setColor(QPalette::Inactive, QPalette::Window, QRgb(0x282828)); + palette.setColor(QPalette::Disabled, QPalette::Window, QRgb(0x252525)); } #elif defined(Q_OS_WIN) // Register event filter for better dark mode support diff --git a/src/gui/styles/dark/DarkStyle.h b/src/gui/styles/dark/DarkStyle.h index 9b955d3a5..3de05c065 100644 --- a/src/gui/styles/dark/DarkStyle.h +++ b/src/gui/styles/dark/DarkStyle.h @@ -26,6 +26,7 @@ class DarkStyle : public BaseStyle Q_OBJECT public: + DarkStyle(); QPalette standardPalette() const override; using BaseStyle::polish; diff --git a/src/gui/styles/light/LightStyle.cpp b/src/gui/styles/light/LightStyle.cpp index 8dd9d6c53..f1f0cb997 100644 --- a/src/gui/styles/light/LightStyle.cpp +++ b/src/gui/styles/light/LightStyle.cpp @@ -24,6 +24,14 @@ #include #include +LightStyle::LightStyle() + : BaseStyle() +{ +#ifdef Q_OS_MACOS + m_drawNativeMacOsToolBar = !osUtils->isDarkMode(); +#endif +} + QPalette LightStyle::standardPalette() const { auto palette = BaseStyle::standardPalette(); @@ -106,13 +114,10 @@ void LightStyle::polish(QWidget* widget) || qobject_cast(widget)) { auto palette = widget->palette(); #if defined(Q_OS_MACOS) - if (!osUtils->isDarkMode()) { - // Let the Cocoa platform plugin draw its own background - palette.setColor(QPalette::All, QPalette::Window, Qt::transparent); - } else { - palette.setColor(QPalette::Active, QPalette::Window, QRgb(0xD6D6D6)); - palette.setColor(QPalette::Inactive, QPalette::Window, QRgb(0xF6F6F6)); - palette.setColor(QPalette::Disabled, QPalette::Window, QRgb(0xD4D4D4)); + if (osUtils->isDarkMode()) { + palette.setColor(QPalette::Active, QPalette::Window, QRgb(0xD1D1D1)); + palette.setColor(QPalette::Inactive, QPalette::Window, QRgb(0xF4F4F4)); + palette.setColor(QPalette::Disabled, QPalette::Window, QRgb(0xD1D1D1)); } #elif defined(Q_OS_WIN) palette.setColor(QPalette::All, QPalette::Window, QRgb(0xFFFFFF)); diff --git a/src/gui/styles/light/LightStyle.h b/src/gui/styles/light/LightStyle.h index d2d4f48a3..87e202502 100644 --- a/src/gui/styles/light/LightStyle.h +++ b/src/gui/styles/light/LightStyle.h @@ -26,6 +26,7 @@ class LightStyle : public BaseStyle Q_OBJECT public: + LightStyle(); QPalette standardPalette() const override; using BaseStyle::polish; diff --git a/src/keeshare/ShareObserver.cpp b/src/keeshare/ShareObserver.cpp index 6dc7a748d..6fd629b4c 100644 --- a/src/keeshare/ShareObserver.cpp +++ b/src/keeshare/ShareObserver.cpp @@ -66,7 +66,7 @@ void ShareObserver::deinitialize() void ShareObserver::reinitialize() { - QList> shares; + QList, KeeShareSettings::Reference>> shares; for (Group* group : m_db->rootGroup()->groupsRecursive(true)) { auto oldReference = m_groupToReference.value(group); auto newReference = KeeShare::referenceOf(group); @@ -97,6 +97,10 @@ void ShareObserver::reinitialize() for (const auto& share : shares) { auto group = share.first; auto& reference = share.second; + // Check group validity, it may have been deleted by a merge action + if (!group) { + continue; + } if (!reference.path.isEmpty() && reference.type != KeeShareSettings::Inactive) { const auto newResolvedPath = resolvePath(reference.path, m_db); diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index b4c47cc14..bc346966d 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -574,7 +574,7 @@ void TestGui::testSearchEditEntry() // Check the path in header is "parent-group > entry" QCOMPARE(m_dbWidget->findChild("editEntryWidget")->findChild("headerLabel")->text(), - QStringLiteral("Good \u2B29 Doggy \u2B29 Edit entry")); + QStringLiteral("Good \u2022 Doggy \u2022 Edit entry")); } void TestGui::testAddEntry() @@ -854,8 +854,7 @@ void TestGui::testSearch() auto* entryView = m_dbWidget->findChild("entryView"); QVERIFY(entryView->isVisible()); - auto* clearButton = searchWidget->findChild("clearIcon"); - QVERIFY(!clearButton->isVisible()); + QVERIFY(searchTextEdit->isClearButtonEnabled()); auto* helpButton = searchWidget->findChild("helpIcon"); auto* helpPanel = searchWidget->findChild("SearchHelpWidget"); @@ -865,7 +864,6 @@ void TestGui::testSearch() // Enter search QTest::mouseClick(searchTextEdit, Qt::LeftButton); QTRY_VERIFY(searchTextEdit->hasFocus()); - QTRY_VERIFY(!clearButton->isVisible()); // Show/Hide search help helpButton->trigger(); QTRY_VERIFY(helpPanel->isVisible()); @@ -876,14 +874,12 @@ void TestGui::testSearch() // Search for "ZZZ" QTest::keyClicks(searchTextEdit, "ZZZ"); QTRY_COMPARE(searchTextEdit->text(), QString("ZZZ")); - QTRY_VERIFY(clearButton->isVisible()); QTRY_VERIFY(m_dbWidget->isSearchActive()); QTRY_COMPARE(entryView->model()->rowCount(), 0); // Press the search clear button - clearButton->trigger(); + searchTextEdit->clear(); QTRY_VERIFY(searchTextEdit->text().isEmpty()); QTRY_VERIFY(searchTextEdit->hasFocus()); - QTRY_VERIFY(!clearButton->isVisible()); // Escape clears searchedit and retains focus QTest::keyClicks(searchTextEdit, "ZZZ"); QTest::keyClick(searchTextEdit, Qt::Key_Escape); diff --git a/utils/keepassxc-snap-helper.sh b/utils/keepassxc-snap-helper.sh index ecb290daa..61f966a0b 100755 --- a/utils/keepassxc-snap-helper.sh +++ b/utils/keepassxc-snap-helper.sh @@ -18,13 +18,24 @@ set -e -DEBUG=false +JSON_OUT="" +BASE_DIR="." +INSTALL_DIR="" +INSTALL_FILE="org.keepassxc.keepassxc_browser.json" + +# Early out if the keepassxc.proxy executable cannot be found +if ! command -v keepassxc.proxy; then + echo "Could not find keepassxc.proxy! Ensure the keepassxc snap is installed properly." + exit 0 +fi + +PROXY_PATH=$(command -v keepassxc.proxy) JSON_FIREFOX=$(cat << EOF { "name": "org.keepassxc.keepassxc_browser", "description": "KeePassXC integration with native messaging support", - "path": "/snap/bin/keepassxc.proxy", + "path": "${PROXY_PATH}", "type": "stdio", "allowed_extensions": [ "keepassxc-browser@keepassxc.org" @@ -37,7 +48,7 @@ JSON_CHROME=$(cat << EOF { "name": "org.keepassxc.keepassxc_browser", "description": "KeePassXC integration with native messaging support", - "path": "/snap/bin/keepassxc.proxy", + "path": "${PROXY_PATH}", "type": "stdio", "allowed_origins": [ "chrome-extension://iopaggbpplllidnfmcghoonnokmjoicf/", @@ -47,21 +58,6 @@ JSON_CHROME=$(cat << EOF EOF ) -JSON_OUT="" -BASE_DIR="." -INSTALL_DIR="" -INSTALL_FILE="org.keepassxc.keepassxc_browser.json" - -buildJson() { - if [ -n "$1" ]; then - # Insert Firefox data - JSON_OUT=$JSON_FIREFOX - else - # Insert Chrome data - JSON_OUT=$JSON_CHROME - fi -} - askBrowserSnap() { if (whiptail --title "Snap Choice" --defaultno \ --yesno "Is this browser installed as a snap (usually NO)?" 8 60); then @@ -73,33 +69,33 @@ askBrowserSnap() { setupFirefox() { askBrowserSnap "./snap/firefox/common" - buildJson "firefox" + JSON_OUT=${JSON_FIREFOX} INSTALL_DIR="${BASE_DIR}/.mozilla/native-messaging-hosts" } setupChrome() { - buildJson + JSON_OUT=${JSON_CHROME} INSTALL_DIR="${BASE_DIR}/.config/google-chrome/NativeMessagingHosts" } setupChromium() { askBrowserSnap "./snap/chromium/current" - buildJson + JSON_OUT=${JSON_CHROME} INSTALL_DIR="${BASE_DIR}/.config/chromium/NativeMessagingHosts" } setupVivaldi() { - buildJson + JSON_OUT=${JSON_CHROME} INSTALL_DIR="${BASE_DIR}/.config/vivaldi/NativeMessagingHosts" } setupBrave() { - buildJson + JSON_OUT=${JSON_CHROME} INSTALL_DIR="${BASE_DIR}/.config/BraveSoftware/Brave-Browser/NativeMessagingHosts" } setupTorBrowser() { - buildJson "firefox" + JSON_OUT=${JSON_FIREFOX} INSTALL_DIR="${BASE_DIR}/.tor-browser/app/Browser/TorBrowser/Data/Browser/.mozilla/native-messaging-hosts" } @@ -138,8 +134,6 @@ if [ $exitstatus = 0 ]; then mkdir -p "$INSTALL_DIR" echo "$JSON_OUT" > ${INSTALL_DIR}/${INSTALL_FILE} - $DEBUG && echo "Installed to: ${INSTALL_DIR}/${INSTALL_FILE}" - whiptail \ --title "Installation Complete" \ --msgbox "You will need to restart your browser in order to connect to KeePassXC" \