Add custom light and dark UI themes

This commit is contained in:
Janek Bevendorff 2020-01-06 03:00:25 +01:00
parent 6d2ca74878
commit 557736ea5e
39 changed files with 6452 additions and 401 deletions

View File

@ -87,6 +87,11 @@ set(keepassx_SOURCES
format/OpVaultReaderAttachments.cpp format/OpVaultReaderAttachments.cpp
format/OpVaultReaderBandEntry.cpp format/OpVaultReaderBandEntry.cpp
format/OpVaultReaderSections.cpp format/OpVaultReaderSections.cpp
gui/styles/styles.qrc
gui/styles/base/phantomcolor.cpp
gui/styles/base/BaseStyle.cpp
gui/styles/dark/DarkStyle.cpp
gui/styles/light/LightStyle.cpp
gui/AboutDialog.cpp gui/AboutDialog.cpp
gui/Application.cpp gui/Application.cpp
gui/CategoryListWidget.cpp gui/CategoryListWidget.cpp

View File

@ -232,6 +232,7 @@ void Config::init(const QString& fileName)
m_defaults.insert("GUI/HidePasswords", true); m_defaults.insert("GUI/HidePasswords", true);
m_defaults.insert("GUI/AdvancedSettings", false); m_defaults.insert("GUI/AdvancedSettings", false);
m_defaults.insert("GUI/MonospaceNotes", false); m_defaults.insert("GUI/MonospaceNotes", false);
m_defaults.insert("GUI/ApplicationTheme", "auto");
} }
Config* Config::instance() Config* Config::instance()

View File

@ -18,13 +18,15 @@
#include "FilePath.h" #include "FilePath.h"
#include <QCoreApplication> #include <QBitmap>
#include <QDir> #include <QDir>
#include <QLibrary> #include <QLibrary>
#include <QStyle>
#include "config-keepassx.h" #include "config-keepassx.h"
#include "core/Config.h" #include "core/Config.h"
#include "core/Global.h" #include "core/Global.h"
#include "gui/MainWindow.h"
FilePath* FilePath::m_instance(nullptr); FilePath* FilePath::m_instance(nullptr);
@ -98,7 +100,7 @@ QIcon FilePath::applicationIcon()
#ifdef KEEPASSXC_DIST_SNAP #ifdef KEEPASSXC_DIST_SNAP
return icon("apps", "keepassxc", false); return icon("apps", "keepassxc", false);
#else #else
return icon("apps", "keepassxc"); return icon("apps", "keepassxc", false);
#endif #endif
} }
@ -109,7 +111,7 @@ QIcon FilePath::trayIcon()
#ifdef KEEPASSXC_DIST_SNAP #ifdef KEEPASSXC_DIST_SNAP
return (darkIcon) ? icon("apps", "keepassxc-dark", false) : icon("apps", "keepassxc", false); return (darkIcon) ? icon("apps", "keepassxc-dark", false) : icon("apps", "keepassxc", false);
#else #else
return (darkIcon) ? icon("apps", "keepassxc-dark") : icon("apps", "keepassxc"); return (darkIcon) ? icon("apps", "keepassxc-dark", false) : icon("apps", "keepassxc", false);
#endif #endif
} }
@ -118,7 +120,7 @@ QIcon FilePath::trayIconLocked()
#ifdef KEEPASSXC_DIST_SNAP #ifdef KEEPASSXC_DIST_SNAP
return icon("apps", "keepassxc-locked", false); return icon("apps", "keepassxc-locked", false);
#else #else
return icon("apps", "keepassxc-locked"); return icon("apps", "keepassxc-locked", false);
#endif #endif
} }
@ -129,14 +131,13 @@ QIcon FilePath::trayIconUnlocked()
#ifdef KEEPASSXC_DIST_SNAP #ifdef KEEPASSXC_DIST_SNAP
return darkIcon ? icon("apps", "keepassxc-dark", false) : icon("apps", "keepassxc-unlocked", false); return darkIcon ? icon("apps", "keepassxc-dark", false) : icon("apps", "keepassxc-unlocked", false);
#else #else
return darkIcon ? icon("apps", "keepassxc-dark") : icon("apps", "keepassxc-unlocked"); return darkIcon ? icon("apps", "keepassxc-dark", false) : icon("apps", "keepassxc-unlocked", false);
#endif #endif
} }
QIcon FilePath::icon(const QString& category, const QString& name) QIcon FilePath::icon(const QString& category, const QString& name, bool recolor)
{ {
QString combinedName = category + "/" + name; QString combinedName = category + "/" + name;
QIcon icon = m_iconCache.value(combinedName); QIcon icon = m_iconCache.value(combinedName);
if (!icon.isNull()) { if (!icon.isNull()) {
@ -154,7 +155,30 @@ QIcon FilePath::icon(const QString& category, const QString& name)
} }
} }
filename = QString("%1/icons/application/scalable/%2.svg").arg(m_dataPath, combinedName); filename = QString("%1/icons/application/scalable/%2.svg").arg(m_dataPath, combinedName);
if (QFile::exists(filename)) { if (QFile::exists(filename) && getMainWindow() && recolor) {
QPalette palette = getMainWindow()->palette();
QFile f(filename);
QIcon scalable(filename);
QPixmap pixmap = scalable.pixmap({128, 128});
auto mask = QBitmap::fromImage(pixmap.toImage().createAlphaMask());
pixmap.fill(palette.color(QPalette::WindowText));
pixmap.setMask(mask);
icon.addPixmap(pixmap, QIcon::Mode::Normal);
pixmap.fill(palette.color(QPalette::HighlightedText));
pixmap.setMask(mask);
icon.addPixmap(pixmap, QIcon::Mode::Selected);
pixmap.fill(palette.color(QPalette::Disabled, QPalette::WindowText));
pixmap.setMask(mask);
icon.addPixmap(pixmap, QIcon::Mode::Disabled);
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
icon.setIsMask(true);
#endif
} else if (QFile::exists(filename)) {
icon.addFile(filename); icon.addFile(filename);
} }
} }
@ -164,7 +188,7 @@ QIcon FilePath::icon(const QString& category, const QString& name)
return icon; return icon;
} }
QIcon FilePath::onOffIcon(const QString& category, const QString& name) QIcon FilePath::onOffIcon(const QString& category, const QString& name, bool recolor)
{ {
QString combinedName = category + "/" + name; QString combinedName = category + "/" + name;
QString cacheName = "onoff/" + combinedName; QString cacheName = "onoff/" + combinedName;
@ -175,31 +199,17 @@ QIcon FilePath::onOffIcon(const QString& category, const QString& name)
return icon; return icon;
} }
for (int i = 0; i < 2; i++) { QIcon on = FilePath::icon(category, name + "-on", recolor);
QIcon::State state; for (const auto& size : on.availableSizes()) {
QString stateName; icon.addPixmap(on.pixmap(size, QIcon::Mode::Normal), QIcon::Mode::Normal, QIcon::On);
icon.addPixmap(on.pixmap(size, QIcon::Mode::Selected), QIcon::Mode::Selected, QIcon::On);
if (i == 0) { icon.addPixmap(on.pixmap(size, QIcon::Mode::Disabled), QIcon::Mode::Disabled, QIcon::On);
state = QIcon::Off;
stateName = "off";
} else {
state = QIcon::On;
stateName = "on";
}
const QList<int> pngSizes = {16, 22, 24, 32, 48, 64, 128};
QString filename;
for (int size : pngSizes) {
filename = QString("%1/icons/application/%2x%2/%3-%4.png")
.arg(m_dataPath, QString::number(size), combinedName, stateName);
if (QFile::exists(filename)) {
icon.addFile(filename, QSize(size, size), QIcon::Normal, state);
}
}
filename = QString("%1/icons/application/scalable/%2-%3.svg").arg(m_dataPath, combinedName, stateName);
if (QFile::exists(filename)) {
icon.addFile(filename, QSize(), QIcon::Normal, state);
} }
QIcon off = FilePath::icon(category, name + "-off", recolor);
for (const auto& size : off.availableSizes()) {
icon.addPixmap(off.pixmap(size, QIcon::Mode::Normal), QIcon::Mode::Normal, QIcon::Off);
icon.addPixmap(off.pixmap(size, QIcon::Mode::Selected), QIcon::Mode::Selected, QIcon::Off);
icon.addPixmap(off.pixmap(size, QIcon::Mode::Disabled), QIcon::Mode::Disabled, QIcon::Off);
} }
m_iconCache.insert(cacheName, icon); m_iconCache.insert(cacheName, icon);

View File

@ -32,8 +32,8 @@ public:
QIcon trayIcon(); QIcon trayIcon();
QIcon trayIconLocked(); QIcon trayIconLocked();
QIcon trayIconUnlocked(); QIcon trayIconUnlocked();
QIcon icon(const QString& category, const QString& name); QIcon icon(const QString& category, const QString& name, bool recolor = true);
QIcon onOffIcon(const QString& category, const QString& name); QIcon onOffIcon(const QString& category, const QString& name, bool recolor = true);
static FilePath* instance(); static FilePath* instance();

View File

@ -213,6 +213,14 @@ void ApplicationSettingsWidget::loadSettings()
m_generalUi->toolbarMovableCheckBox->setChecked(config()->get("GUI/MovableToolbar").toBool()); m_generalUi->toolbarMovableCheckBox->setChecked(config()->get("GUI/MovableToolbar").toBool());
m_generalUi->monospaceNotesCheckBox->setChecked(config()->get("GUI/MonospaceNotes").toBool()); m_generalUi->monospaceNotesCheckBox->setChecked(config()->get("GUI/MonospaceNotes").toBool());
m_generalUi->appThemeSelection->clear();
m_generalUi->appThemeSelection->addItem(tr("Automatic"), QStringLiteral("auto"));
m_generalUi->appThemeSelection->addItem(tr("Light"), QStringLiteral("light"));
m_generalUi->appThemeSelection->addItem(tr("Dark"), QStringLiteral("dark"));
m_generalUi->appThemeSelection->addItem(tr("Classic (Platform-native)"), QStringLiteral("classic"));
m_generalUi->appThemeSelection->setCurrentIndex(
m_generalUi->appThemeSelection->findData(config()->get("GUI/ApplicationTheme").toString()));
m_generalUi->toolButtonStyleComboBox->clear(); m_generalUi->toolButtonStyleComboBox->clear();
m_generalUi->toolButtonStyleComboBox->addItem(tr("Icon only"), Qt::ToolButtonIconOnly); m_generalUi->toolButtonStyleComboBox->addItem(tr("Icon only"), Qt::ToolButtonIconOnly);
m_generalUi->toolButtonStyleComboBox->addItem(tr("Text only"), Qt::ToolButtonTextOnly); m_generalUi->toolButtonStyleComboBox->addItem(tr("Text only"), Qt::ToolButtonTextOnly);
@ -303,19 +311,18 @@ void ApplicationSettingsWidget::saveSettings()
config()->set("IgnoreGroupExpansion", m_generalUi->ignoreGroupExpansionCheckBox->isChecked()); config()->set("IgnoreGroupExpansion", m_generalUi->ignoreGroupExpansionCheckBox->isChecked());
config()->set("AutoTypeEntryTitleMatch", m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked()); config()->set("AutoTypeEntryTitleMatch", m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked());
config()->set("AutoTypeEntryURLMatch", m_generalUi->autoTypeEntryURLMatchCheckBox->isChecked()); config()->set("AutoTypeEntryURLMatch", m_generalUi->autoTypeEntryURLMatchCheckBox->isChecked());
int currentLangIndex = m_generalUi->languageComboBox->currentIndex();
config()->set("FaviconDownloadTimeout", m_generalUi->faviconTimeoutSpinBox->value()); config()->set("FaviconDownloadTimeout", m_generalUi->faviconTimeoutSpinBox->value());
config()->set("GUI/Language", m_generalUi->languageComboBox->itemData(currentLangIndex).toString()); config()->set("GUI/Language", m_generalUi->languageComboBox->currentData().toString());
config()->set("GUI/HidePreviewPanel", m_generalUi->previewHideCheckBox->isChecked()); config()->set("GUI/HidePreviewPanel", m_generalUi->previewHideCheckBox->isChecked());
config()->set("GUI/HideToolbar", m_generalUi->toolbarHideCheckBox->isChecked()); config()->set("GUI/HideToolbar", m_generalUi->toolbarHideCheckBox->isChecked());
config()->set("GUI/MovableToolbar", m_generalUi->toolbarMovableCheckBox->isChecked()); config()->set("GUI/MovableToolbar", m_generalUi->toolbarMovableCheckBox->isChecked());
config()->set("GUI/MonospaceNotes", m_generalUi->monospaceNotesCheckBox->isChecked()); config()->set("GUI/MonospaceNotes", m_generalUi->monospaceNotesCheckBox->isChecked());
int currentToolButtonStyleIndex = m_generalUi->toolButtonStyleComboBox->currentIndex(); QString theme = m_generalUi->appThemeSelection->currentData().toString();
config()->set("GUI/ToolButtonStyle", config()->set("GUI/ApplicationTheme", theme);
m_generalUi->toolButtonStyleComboBox->itemData(currentToolButtonStyleIndex).toString());
config()->set("GUI/ToolButtonStyle", m_generalUi->toolButtonStyleComboBox->currentData().toString());
config()->set("GUI/ShowTrayIcon", m_generalUi->systrayShowCheckBox->isChecked()); config()->set("GUI/ShowTrayIcon", m_generalUi->systrayShowCheckBox->isChecked());
config()->set("GUI/DarkTrayIcon", m_generalUi->systrayDarkIconCheckBox->isChecked()); config()->set("GUI/DarkTrayIcon", m_generalUi->systrayDarkIconCheckBox->isChecked());

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>684</width> <width>499</width>
<height>951</height> <height>1174</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
@ -337,7 +337,7 @@
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,1"> <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0">
<item> <item>
<widget class="QLabel" name="faviconTimeoutLabel"> <widget class="QLabel" name="faviconTimeoutLabel">
<property name="text"> <property name="text">
@ -350,12 +350,6 @@
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::StrongFocus</enum> <enum>Qt::StrongFocus</enum>
</property> </property>
@ -400,6 +394,50 @@
<string>General</string> <string>General</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_7"> <layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="appThemeLabel">
<property name="text">
<string>Application Theme:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="appThemeSelection">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="accessibleName">
<string>Application Theme Selection</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>(restart program to activate)</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item> <item>
<widget class="QCheckBox" name="toolbarHideCheckBox"> <widget class="QCheckBox" name="toolbarHideCheckBox">
<property name="text"> <property name="text">
@ -491,9 +529,6 @@
</item> </item>
<item> <item>
<widget class="QComboBox" name="toolButtonStyleComboBox"> <widget class="QComboBox" name="toolButtonStyleComboBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -726,7 +761,7 @@
<item> <item>
<widget class="QPushButton" name="resetSettingsButton"> <widget class="QPushButton" name="resetSettingsButton">
<property name="text"> <property name="text">
<string>Reset Settings to Default</string> <string>Reset settings to default…</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -20,6 +20,7 @@
#include <QListWidget> #include <QListWidget>
#include <QPainter> #include <QPainter>
#include <QProxyStyle>
#include <QScrollBar> #include <QScrollBar>
#include <QSize> #include <QSize>
#include <QStyledItemDelegate> #include <QStyledItemDelegate>
@ -158,9 +159,7 @@ CategoryListWidgetDelegate::CategoryListWidgetDelegate(QListWidget* parent)
} }
} }
#ifdef Q_OS_WIN class IconSelectionCorrectedStyle : public QProxyStyle
#include <QProxyStyle>
class WindowsCorrectedStyle : public QProxyStyle
{ {
public: public:
void drawPrimitive(PrimitiveElement element, void drawPrimitive(PrimitiveElement element,
@ -171,8 +170,8 @@ public:
painter->save(); painter->save();
if (PE_PanelItemViewItem == element) { if (PE_PanelItemViewItem == element) {
// Qt on Windows draws selection backgrounds only for the actual text/icon // Qt on Windows and the Fusion/Phantom base styles draw selection backgrounds only for
// bounding box, not over the full width of a list item. // the actual text/icon bounding box, not over the full width of a list item.
// We therefore need to translate and stretch the painter before we can // We therefore need to translate and stretch the painter before we can
// tell Qt to draw its native styles. // tell Qt to draw its native styles.
// Since we are scaling horizontally, we also need to move the right and left // Since we are scaling horizontally, we also need to move the right and left
@ -186,7 +185,6 @@ public:
painter->restore(); painter->restore();
} }
}; };
#endif
void CategoryListWidgetDelegate::paint(QPainter* painter, void CategoryListWidgetDelegate::paint(QPainter* painter,
const QStyleOptionViewItem& option, const QStyleOptionViewItem& option,
@ -203,12 +201,7 @@ void CategoryListWidgetDelegate::paint(QPainter* painter,
opt.decorationAlignment = Qt::AlignHCenter | Qt::AlignVCenter; opt.decorationAlignment = Qt::AlignHCenter | Qt::AlignVCenter;
opt.decorationPosition = QStyleOptionViewItem::Top; opt.decorationPosition = QStyleOptionViewItem::Top;
#ifdef Q_OS_WIN QScopedPointer<QStyle> style(new IconSelectionCorrectedStyle());
QScopedPointer<QStyle> style(new WindowsCorrectedStyle());
#else
QStyle* style = opt.widget ? opt.widget->style() : QApplication::style();
#endif
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
QRect fontRect = painter->fontMetrics().boundingRect( QRect fontRect = painter->fontMetrics().boundingRect(

View File

@ -90,12 +90,6 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
m_ui->yubikeyProgress->setVisible(false); m_ui->yubikeyProgress->setVisible(false);
#endif #endif
#ifdef Q_OS_MACOS
// add random padding to layouts to align widgets properly
m_ui->dialogButtonsLayout->setContentsMargins(10, 0, 15, 0);
m_ui->gridLayout->setContentsMargins(10, 0, 0, 0);
#endif
#ifndef WITH_XC_TOUCHID #ifndef WITH_XC_TOUCHID
m_ui->touchIDContainer->setVisible(false); m_ui->touchIDContainer->setVisible(false);
#else #else

View File

@ -99,7 +99,7 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QFrame" name="horizontalFrame"> <widget class="QFrame" name="loginFrame">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>550</width> <width>550</width>
@ -235,142 +235,6 @@
<property name="topMargin"> <property name="topMargin">
<number>3</number> <number>3</number>
</property> </property>
<item row="1" column="3">
<layout class="QGridLayout" name="gridLayout">
<property name="spacing">
<number>0</number>
</property>
<item row="1" column="2">
<widget class="QProgressBar" name="yubikeyProgress">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>2</height>
</size>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>0</number>
</property>
<property name="value">
<number>-1</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="comboChallengeResponse">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="accessibleName">
<string>Hardware key slot selection</string>
</property>
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="4">
<widget class="QPushButton" name="buttonBrowseFile">
<property name="toolTip">
<string>Browse for key file</string>
</property>
<property name="accessibleName">
<string>Browse for key file</string>
</property>
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
<item row="0" column="3">
<layout class="QGridLayout" name="gridLayout_2">
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="1">
<widget class="QComboBox" name="comboKeyFile">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="accessibleName">
<string>Key file selection</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="hardwareKeyLabel">
<property name="text">
<string>Hardware Key:</string>
</property>
<property name="buddy">
<cstring>comboChallengeResponse</cstring>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="hardwareKeyLabelHelp">
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="toolTip">
<string>&lt;p&gt;You can use a hardware security key such as a &lt;strong&gt;YubiKey&lt;/strong&gt; or &lt;strong&gt;OnlyKey&lt;/strong&gt; with slots configured for HMAC-SHA1.&lt;/p&gt;
&lt;p&gt;Click for more information...&lt;/p&gt;</string>
</property>
<property name="accessibleName">
<string>Hardware key help</string>
</property>
<property name="styleSheet">
<string notr="true">QToolButton {
border: none;
background: none;
}</string>
</property>
<property name="text">
<string notr="true">?</string>
</property>
<property name="iconSize">
<size>
<width>12</width>
<height>12</height>
</size>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0"> <item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="spacing"> <property name="spacing">
@ -419,7 +283,171 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="1" column="3">
<layout class="QGridLayout" name="gridLayout">
<property name="spacing">
<number>0</number>
</property>
<item row="1" column="2">
<widget class="QProgressBar" name="yubikeyProgress">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>2</height>
</size>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>0</number>
</property>
<property name="value">
<number>-1</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="comboChallengeResponse">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="accessibleName">
<string>Hardware key slot selection</string>
</property>
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="spacing">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="hardwareKeyLabel">
<property name="text">
<string>Hardware Key:</string>
</property>
<property name="buddy">
<cstring>comboChallengeResponse</cstring>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="hardwareKeyLabelHelp">
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="toolTip">
<string>&lt;p&gt;You can use a hardware security key such as a &lt;strong&gt;YubiKey&lt;/strong&gt; or &lt;strong&gt;OnlyKey&lt;/strong&gt; with slots configured for HMAC-SHA1.&lt;/p&gt;
&lt;p&gt;Click for more information...&lt;/p&gt;</string>
</property>
<property name="accessibleName">
<string>Hardware key help</string>
</property>
<property name="styleSheet">
<string notr="true">QToolButton {
border: none;
background: none;
}</string>
</property>
<property name="text">
<string notr="true">?</string>
</property>
<property name="iconSize">
<size>
<width>12</width>
<height>12</height>
</size>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>2</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="3">
<layout class="QGridLayout" name="gridLayout_2">
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="1">
<widget class="QComboBox" name="comboKeyFile">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="accessibleName">
<string>Key file selection</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="4">
<widget class="QPushButton" name="buttonBrowseFile">
<property name="toolTip">
<string>Browse for key file</string>
</property>
<property name="accessibleName">
<string>Browse for key file</string>
</property>
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
<item row="1" column="4"> <item row="1" column="4">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="buttonRedetectYubikey"> <widget class="QPushButton" name="buttonRedetectYubikey">
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
@ -435,6 +463,24 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>2</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>
@ -572,7 +618,6 @@
<tabstop>buttonBrowseFile</tabstop> <tabstop>buttonBrowseFile</tabstop>
<tabstop>hardwareKeyLabelHelp</tabstop> <tabstop>hardwareKeyLabelHelp</tabstop>
<tabstop>comboChallengeResponse</tabstop> <tabstop>comboChallengeResponse</tabstop>
<tabstop>buttonRedetectYubikey</tabstop>
<tabstop>checkTouchID</tabstop> <tabstop>checkTouchID</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>

View File

@ -80,8 +80,10 @@ void DatabaseTabWidget::toggleTabbar()
{ {
if (count() > 1) { if (count() > 1) {
tabBar()->show(); tabBar()->show();
emit tabVisibilityChanged(true);
} else { } else {
tabBar()->hide(); tabBar()->hide();
emit tabVisibilityChanged(false);
} }
} }

View File

@ -89,6 +89,7 @@ signals:
void databaseLocked(DatabaseWidget* dbWidget); void databaseLocked(DatabaseWidget* dbWidget);
void activateDatabaseChanged(DatabaseWidget* dbWidget); void activateDatabaseChanged(DatabaseWidget* dbWidget);
void tabNameChanged(); void tabNameChanged();
void tabVisibilityChanged(bool tabsVisible);
void messageGlobal(const QString&, MessageWidget::MessageType type); void messageGlobal(const QString&, MessageWidget::MessageType type);
void messageDismissGlobal(); void messageDismissGlobal();
void databaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget); void databaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget);

View File

@ -101,9 +101,7 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget)
* *
* NOTE: * NOTE:
* If m_listViewState is empty, the list view has been activated for the first * If m_listViewState is empty, the list view has been activated for the first
* time after starting with a clean (or invalid) config. Thus, save the current * time after starting with a clean (or invalid) config.
* state. Without this, m_listViewState would remain empty until there is an
* actual view state change (e.g. column is resized)
*/ */
void DatabaseWidgetStateSync::restoreListView() void DatabaseWidgetStateSync::restoreListView()
{ {
@ -112,8 +110,6 @@ void DatabaseWidgetStateSync::restoreListView()
if (!m_listViewState.isEmpty()) { if (!m_listViewState.isEmpty()) {
m_activeDbWidget->setEntryViewState(m_listViewState); m_activeDbWidget->setEntryViewState(m_listViewState);
} else {
m_listViewState = m_activeDbWidget->entryViewState();
} }
m_blockUpdates = false; m_blockUpdates = false;

View File

@ -24,6 +24,7 @@
DialogyWidget::DialogyWidget(QWidget* parent) DialogyWidget::DialogyWidget(QWidget* parent)
: QWidget(parent) : QWidget(parent)
{ {
setAutoFillBackground(true);
} }
void DialogyWidget::keyPressEvent(QKeyEvent* e) void DialogyWidget::keyPressEvent(QKeyEvent* e)

View File

@ -102,12 +102,6 @@ void KMessageWidgetPrivate::init(KMessageWidget *q_ptr)
closeButton->setAutoRaise(true); closeButton->setAutoRaise(true);
closeButton->setDefaultAction(closeAction); closeButton->setDefaultAction(closeAction);
closeButtonPixmap = QPixmap(closeButton->icon().pixmap(closeButton->icon().actualSize(QSize(16, 16)))); closeButtonPixmap = QPixmap(closeButton->icon().pixmap(closeButton->icon().actualSize(QSize(16, 16))));
#ifdef Q_OS_MACOS
closeButton->setStyleSheet("QToolButton { background: transparent;"
"border-radius: 2px; padding: 3px; }"
"QToolButton::hover, QToolButton::focus {"
"border: 1px solid rgb(90, 200, 250); }");
#endif
q->setMessageType(KMessageWidget::Information); q->setMessageType(KMessageWidget::Information);
} }
@ -263,7 +257,7 @@ void KMessageWidget::setMessageType(KMessageWidget::MessageType type)
{ {
d->messageType = type; d->messageType = type;
QColor bg0, bg1, bg2, border; QColor bg0, bg1, bg2, border;
QColor fg = palette().light().color(); QColor fg = QColor(238, 238, 238);
switch (type) { switch (type) {
case Positive: case Positive:
bg1.setRgb(37, 163, 83); bg1.setRgb(37, 163, 83);
@ -273,7 +267,7 @@ void KMessageWidget::setMessageType(KMessageWidget::MessageType type)
break; break;
case Warning: case Warning:
bg1.setRgb(252, 193, 57); bg1.setRgb(252, 193, 57);
fg = palette().windowText().color(); fg = QColor(48, 48, 48);
break; break;
case Error: case Error:
bg1.setRgb(198, 69, 21); bg1.setRgb(198, 69, 21);
@ -294,9 +288,15 @@ void KMessageWidget::setMessageType(KMessageWidget::MessageType type)
painter.fillRect(QRect(0, 0, 16, 16), fg); painter.fillRect(QRect(0, 0, 16, 16), fg);
painter.end(); painter.end();
d->closeButton->setIcon(closeButtonPixmap); d->closeButton->setIcon(closeButtonPixmap);
d->closeButton->setStyleSheet(QStringLiteral("QToolButton {"
" background: transparent;"
" border-radius: 2px;"
" border: none; }"
"QToolButton:hover, QToolButton:focus {"
" border: 1px solid %1; }").arg(fg.name()));
d->content->setStyleSheet( d->content->setStyleSheet(
QString(QLatin1String(".QFrame {" QStringLiteral(".QFrame {"
"background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1," "background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,"
" stop: 0 %1," " stop: 0 %1,"
" stop: 0.1 %2," " stop: 0.1 %2,"
@ -307,7 +307,7 @@ void KMessageWidget::setMessageType(KMessageWidget::MessageType type)
" padding: 5px;" " padding: 5px;"
"}" "}"
".QLabel { color: %6; }" ".QLabel { color: %6; }"
)) )
.arg(bg0.name(), .arg(bg0.name(),
bg1.name(), bg1.name(),
bg2.name(), bg2.name(),

View File

@ -236,6 +236,9 @@ MainWindow::MainWindow()
autoType()->registerGlobalShortcut(globalAutoTypeKey, globalAutoTypeModifiers); autoType()->registerGlobalShortcut(globalAutoTypeKey, globalAutoTypeModifiers);
} }
m_ui->toolbarSeparator->setVisible(false);
m_showToolbarSeparator = config()->get("GUI/ApplicationTheme").toString() != "classic";
m_ui->actionEntryAutoType->setVisible(autoType()->isAvailable()); m_ui->actionEntryAutoType->setVisible(autoType()->isAvailable());
m_inactivityTimer = new InactivityTimer(this); m_inactivityTimer = new InactivityTimer(this);
@ -389,6 +392,7 @@ MainWindow::MainWindow()
connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), m_searchWidget, SLOT(databaseChanged())); connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), m_searchWidget, SLOT(databaseChanged()));
connect(m_ui->tabWidget, SIGNAL(tabNameChanged()), SLOT(updateWindowTitle())); connect(m_ui->tabWidget, SIGNAL(tabNameChanged()), SLOT(updateWindowTitle()));
connect(m_ui->tabWidget, SIGNAL(tabVisibilityChanged(bool)), SLOT(adjustToTabVisibilityChange(bool)));
connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(updateWindowTitle())); connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(updateWindowTitle()));
connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(databaseTabChanged(int))); connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(databaseTabChanged(int)));
connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(setMenuActionState())); connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(setMenuActionState()));
@ -618,14 +622,18 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
bool inDatabaseTabWidgetOrWelcomeWidget = inDatabaseTabWidget || inWelcomeWidget; bool inDatabaseTabWidgetOrWelcomeWidget = inDatabaseTabWidget || inWelcomeWidget;
m_ui->actionDatabaseMerge->setEnabled(inDatabaseTabWidget); m_ui->actionDatabaseMerge->setEnabled(inDatabaseTabWidget);
m_ui->actionDatabaseNew->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); m_ui->actionDatabaseNew->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
m_ui->actionDatabaseOpen->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); m_ui->actionDatabaseOpen->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
m_ui->menuRecentDatabases->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); m_ui->menuRecentDatabases->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
m_ui->menuImport->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); m_ui->menuImport->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
m_ui->actionLockDatabases->setEnabled(m_ui->tabWidget->hasLockableDatabases()); m_ui->actionLockDatabases->setEnabled(m_ui->tabWidget->hasLockableDatabases());
if (m_showToolbarSeparator) {
m_ui->toolbarSeparator->setVisible(
(!inWelcomeWidget && inDatabaseTabWidget && !m_ui->tabWidget->tabBar()->isVisible())
|| currentIndex == SettingsScreen);
}
if (inDatabaseTabWidget && m_ui->tabWidget->currentIndex() != -1) { if (inDatabaseTabWidget && m_ui->tabWidget->currentIndex() != -1) {
DatabaseWidget* dbWidget = m_ui->tabWidget->currentDatabaseWidget(); DatabaseWidget* dbWidget = m_ui->tabWidget->currentDatabaseWidget();
Q_ASSERT(dbWidget); Q_ASSERT(dbWidget);
@ -776,6 +784,13 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
} }
} }
void MainWindow::adjustToTabVisibilityChange(bool tabsVisible)
{
if (m_showToolbarSeparator) {
m_ui->toolbarSeparator->setVisible(!tabsVisible && m_ui->stackedWidget->currentIndex() == DatabaseTabScreen);
}
}
void MainWindow::updateWindowTitle() void MainWindow::updateWindowTitle()
{ {
QString customWindowTitlePart; QString customWindowTitlePart;
@ -1125,7 +1140,7 @@ void MainWindow::updateTrayIcon()
QAction* actionToggle = new QAction(tr("Toggle window"), menu); QAction* actionToggle = new QAction(tr("Toggle window"), menu);
menu->addAction(actionToggle); menu->addAction(actionToggle);
actionToggle->setIcon(filePath()->icon("apps", "keepassxc-dark")); actionToggle->setIcon(filePath()->icon("apps", "keepassxc-dark", false));
menu->addAction(m_ui->actionLockDatabases); menu->addAction(m_ui->actionLockDatabases);

View File

@ -86,6 +86,7 @@ protected:
private slots: private slots:
void setMenuActionState(DatabaseWidget::Mode mode = DatabaseWidget::Mode::None); void setMenuActionState(DatabaseWidget::Mode mode = DatabaseWidget::Mode::None);
void adjustToTabVisibilityChange(bool tabsVisible);
void updateWindowTitle(); void updateWindowTitle();
void showAboutDialog(); void showAboutDialog();
void showUpdateCheckStartup(); void showUpdateCheckStartup();
@ -167,6 +168,7 @@ private:
bool m_appExitCalled = false; bool m_appExitCalled = false;
bool m_appExiting = false; bool m_appExiting = false;
bool m_contextMenuFocusLock = false; bool m_contextMenuFocusLock = false;
bool m_showToolbarSeparator = false;
qint64 m_lastFocusOutTime = 0; qint64 m_lastFocusOutTime = 0;
qint64 m_lastShowTime = 0; qint64 m_lastShowTime = 0;
QTimer m_trayIconTriggerTimer; QTimer m_trayIconTriggerTimer;

View File

@ -61,6 +61,16 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="Line" name="toolbarSeparator">
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item> <item>
<widget class="QStackedWidget" name="stackedWidget"> <widget class="QStackedWidget" name="stackedWidget">
<property name="sizePolicy"> <property name="sizePolicy">
@ -195,7 +205,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>800</width>
<height>21</height> <height>24</height>
</rect> </rect>
</property> </property>
<property name="focusPolicy"> <property name="focusPolicy">
@ -210,7 +220,7 @@
</property> </property>
<widget class="QMenu" name="menuRecentDatabases"> <widget class="QMenu" name="menuRecentDatabases">
<property name="title"> <property name="title">
<string>&amp;Recent databases</string> <string>&amp;Recent Databases</string>
</property> </property>
</widget> </widget>
<widget class="QMenu" name="menuImport"> <widget class="QMenu" name="menuImport">
@ -249,7 +259,6 @@
<property name="title"> <property name="title">
<string>&amp;Help</string> <string>&amp;Help</string>
</property> </property>
<addaction name="actionAbout"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionGettingStarted"/> <addaction name="actionGettingStarted"/>
<addaction name="actionUserGuide"/> <addaction name="actionUserGuide"/>
@ -259,10 +268,11 @@
<addaction name="actionCheckForUpdates"/> <addaction name="actionCheckForUpdates"/>
<addaction name="actionDonate"/> <addaction name="actionDonate"/>
<addaction name="actionBugReport"/> <addaction name="actionBugReport"/>
<addaction name="actionAbout"/>
</widget> </widget>
<widget class="QMenu" name="menuEntries"> <widget class="QMenu" name="menuEntries">
<property name="title"> <property name="title">
<string>E&amp;ntries</string> <string>&amp;Entries</string>
</property> </property>
<widget class="QMenu" name="menuEntryCopyAttribute"> <widget class="QMenu" name="menuEntryCopyAttribute">
<property name="enabled"> <property name="enabled">
@ -272,7 +282,7 @@
<string/> <string/>
</property> </property>
<property name="title"> <property name="title">
<string>Copy att&amp;ribute...</string> <string>Copy Att&amp;ribute</string>
</property> </property>
<addaction name="actionEntryCopyTitle"/> <addaction name="actionEntryCopyTitle"/>
<addaction name="actionEntryCopyURL"/> <addaction name="actionEntryCopyURL"/>
@ -284,7 +294,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="title"> <property name="title">
<string>TOTP...</string> <string>TOTP</string>
</property> </property>
<addaction name="actionEntryCopyTotp"/> <addaction name="actionEntryCopyTotp"/>
<addaction name="actionEntryTotp"/> <addaction name="actionEntryTotp"/>
@ -311,7 +321,6 @@
<string>&amp;Groups</string> <string>&amp;Groups</string>
</property> </property>
<addaction name="actionGroupNew"/> <addaction name="actionGroupNew"/>
<addaction name="separator"/>
<addaction name="actionGroupEdit"/> <addaction name="actionGroupEdit"/>
<addaction name="actionGroupDelete"/> <addaction name="actionGroupDelete"/>
<addaction name="actionGroupEmptyRecycleBin"/> <addaction name="actionGroupEmptyRecycleBin"/>
@ -347,8 +356,8 @@
</property> </property>
<property name="iconSize"> <property name="iconSize">
<size> <size>
<width>32</width> <width>26</width>
<height>32</height> <height>26</height>
</size> </size>
</property> </property>
<attribute name="toolBarArea"> <attribute name="toolBarArea">
@ -393,7 +402,7 @@
</action> </action>
<action name="actionCheckForUpdates"> <action name="actionCheckForUpdates">
<property name="text"> <property name="text">
<string>&amp;Check for Updates...</string> <string>&amp;Check for Updates</string>
</property> </property>
<property name="menuRole"> <property name="menuRole">
<enum>QAction::ApplicationSpecificRole</enum> <enum>QAction::ApplicationSpecificRole</enum>
@ -401,7 +410,7 @@
</action> </action>
<action name="actionDatabaseOpen"> <action name="actionDatabaseOpen">
<property name="text"> <property name="text">
<string>&amp;Open database...</string> <string>&amp;Open Database…</string>
</property> </property>
</action> </action>
<action name="actionDatabaseSave"> <action name="actionDatabaseSave">
@ -409,7 +418,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Save database</string> <string>&amp;Save Database</string>
</property> </property>
</action> </action>
<action name="actionDatabaseClose"> <action name="actionDatabaseClose">
@ -417,12 +426,12 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Close database</string> <string>&amp;Close Database</string>
</property> </property>
</action> </action>
<action name="actionDatabaseNew"> <action name="actionDatabaseNew">
<property name="text"> <property name="text">
<string>&amp;New database...</string> <string>&amp;New Database…</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Create a new database</string> <string>Create a new database</string>
@ -430,7 +439,7 @@
</action> </action>
<action name="actionDatabaseMerge"> <action name="actionDatabaseMerge">
<property name="text"> <property name="text">
<string>&amp;Merge from database...</string> <string>&amp;Merge From Database…</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Merge from another KDBX database</string> <string>Merge from another KDBX database</string>
@ -441,7 +450,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;New entry</string> <string>&amp;New Entry…</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Add a new entry</string> <string>Add a new entry</string>
@ -452,7 +461,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Edit entry</string> <string>&amp;Edit Entry…</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>View or edit entry</string> <string>View or edit entry</string>
@ -463,7 +472,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Delete entry</string> <string>&amp;Delete Entry…</string>
</property> </property>
</action> </action>
<action name="actionGroupNew"> <action name="actionGroupNew">
@ -471,7 +480,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;New group</string> <string>&amp;New Group…</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Add a new group</string> <string>Add a new group</string>
@ -482,7 +491,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Edit group</string> <string>&amp;Edit Group…</string>
</property> </property>
</action> </action>
<action name="actionGroupDelete"> <action name="actionGroupDelete">
@ -490,7 +499,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Delete group</string> <string>&amp;Delete Group…</string>
</property> </property>
</action> </action>
<action name="actionGroupDownloadFavicons"> <action name="actionGroupDownloadFavicons">
@ -498,7 +507,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Downlo&amp;ad all favicons</string> <string>Download All &amp;Favicons…</string>
</property> </property>
</action> </action>
<action name="actionGroupSortAsc"> <action name="actionGroupSortAsc">
@ -522,7 +531,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Sa&amp;ve database as...</string> <string>Sa&amp;ve Database As…</string>
</property> </property>
</action> </action>
<action name="actionChangeMasterKey"> <action name="actionChangeMasterKey">
@ -530,7 +539,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Change master &amp;key...</string> <string>Change Master &amp;Key…</string>
</property> </property>
</action> </action>
<action name="actionReports"> <action name="actionReports">
@ -552,7 +561,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Database settings...</string> <string>&amp;Database Settings…</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Database settings</string> <string>Database settings</string>
@ -566,7 +575,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Clone entry</string> <string>&amp;Clone Entry…</string>
</property> </property>
</action> </action>
<action name="actionEntryCopyUsername"> <action name="actionEntryCopyUsername">
@ -574,7 +583,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Copy &amp;username</string> <string>Copy &amp;Username</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Copy username to clipboard</string> <string>Copy username to clipboard</string>
@ -585,7 +594,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Copy &amp;password</string> <string>Copy &amp;Password</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Copy password to clipboard</string> <string>Copy password to clipboard</string>
@ -620,7 +629,7 @@
</action> </action>
<action name="actionEntryDownloadIcon"> <action name="actionEntryDownloadIcon">
<property name="text"> <property name="text">
<string>Download favicon</string> <string>Download &amp;Favicon</string>
</property> </property>
</action> </action>
<action name="actionEntryOpenUrl"> <action name="actionEntryOpenUrl">
@ -636,7 +645,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Lock databases</string> <string>&amp;Lock Databases</string>
</property> </property>
</action> </action>
<action name="actionEntryCopyTitle"> <action name="actionEntryCopyTitle">
@ -677,7 +686,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Export to CSV file...</string> <string>&amp;Export to CSV File…</string>
</property> </property>
</action> </action>
<action name="actionExportHtml"> <action name="actionExportHtml">
@ -685,12 +694,12 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Export to HTML file...</string> <string>&amp;Export to HTML File…</string>
</property> </property>
</action> </action>
<action name="actionImportKeePass1"> <action name="actionImportKeePass1">
<property name="text"> <property name="text">
<string>KeePass 1 database...</string> <string>KeePass 1 Database…</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Import a KeePass 1 database</string> <string>Import a KeePass 1 database</string>
@ -698,7 +707,7 @@
</action> </action>
<action name="actionImportOpVault"> <action name="actionImportOpVault">
<property name="text"> <property name="text">
<string>1Password Vault...</string> <string>1Password Vault</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Import a 1Password Vault</string> <string>Import a 1Password Vault</string>
@ -706,7 +715,7 @@
</action> </action>
<action name="actionImportCsv"> <action name="actionImportCsv">
<property name="text"> <property name="text">
<string>CSV file...</string> <string>CSV File…</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Import a CSV file</string> <string>Import a CSV file</string>
@ -714,17 +723,17 @@
</action> </action>
<action name="actionEntryTotp"> <action name="actionEntryTotp">
<property name="text"> <property name="text">
<string>Show TOTP...</string> <string>Show TOTP</string>
</property> </property>
</action> </action>
<action name="actionEntryTotpQRCode"> <action name="actionEntryTotpQRCode">
<property name="text"> <property name="text">
<string>Show TOTP QR Code...</string> <string>Show QR Code</string>
</property> </property>
</action> </action>
<action name="actionEntrySetupTotp"> <action name="actionEntrySetupTotp">
<property name="text"> <property name="text">
<string>Set up TOTP...</string> <string>Set up TOTP</string>
</property> </property>
</action> </action>
<action name="actionEntryCopyTotp"> <action name="actionEntryCopyTotp">
@ -747,7 +756,7 @@
</action> </action>
<action name="actionBugReport"> <action name="actionBugReport">
<property name="text"> <property name="text">
<string>Report a &amp;bug</string> <string>Report a &amp;Bug</string>
</property> </property>
</action> </action>
<action name="actionGettingStarted"> <action name="actionGettingStarted">
@ -760,7 +769,7 @@
</action> </action>
<action name="actionOnlineHelp"> <action name="actionOnlineHelp">
<property name="text"> <property name="text">
<string>&amp;Online Help...</string> <string>&amp;Online Help</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Go to online documentation (opens browser)</string> <string>Go to online documentation (opens browser)</string>

View File

@ -28,6 +28,7 @@
#include "core/PasswordGenerator.h" #include "core/PasswordGenerator.h"
#include "core/PasswordHealth.h" #include "core/PasswordHealth.h"
#include "gui/Clipboard.h" #include "gui/Clipboard.h"
#include "gui/osutils/OSUtils.h"
PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
: QWidget(parent) : QWidget(parent)
@ -390,27 +391,33 @@ void PasswordGeneratorWidget::colorStrengthIndicator(const PasswordHealth& healt
style.replace(re, "\\1 %1;"); style.replace(re, "\\1 %1;");
// Set the color and background based on entropy // Set the color and background based on entropy
// colors are taking from the KDE breeze palette QList<QString> qualityColors;
// <https://community.kde.org/KDE_Visual_Design_Group/HIG/Color> if (osUtils->isDarkMode()) {
qualityColors << QStringLiteral("#C43F31") << QStringLiteral("#DB9837") << QStringLiteral("#608A22")
<< QStringLiteral("#1F8023");
} else {
qualityColors << QStringLiteral("#C43F31") << QStringLiteral("#E09932") << QStringLiteral("#5EA10E")
<< QStringLiteral("#118f17");
}
switch (health.quality()) { switch (health.quality()) {
case PasswordHealth::Quality::Bad: case PasswordHealth::Quality::Bad:
case PasswordHealth::Quality::Poor: case PasswordHealth::Quality::Poor:
m_ui->entropyProgressBar->setStyleSheet(style.arg("#c0392b")); m_ui->entropyProgressBar->setStyleSheet(style.arg(qualityColors[0]));
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Poor", "Password quality"))); m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Poor", "Password quality")));
break; break;
case PasswordHealth::Quality::Weak: case PasswordHealth::Quality::Weak:
m_ui->entropyProgressBar->setStyleSheet(style.arg("#f39c1f")); m_ui->entropyProgressBar->setStyleSheet(style.arg(qualityColors[1]));
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Weak", "Password quality"))); m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Weak", "Password quality")));
break; break;
case PasswordHealth::Quality::Good: case PasswordHealth::Quality::Good:
m_ui->entropyProgressBar->setStyleSheet(style.arg("#11d116")); m_ui->entropyProgressBar->setStyleSheet(style.arg(qualityColors[2]));
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Good", "Password quality"))); m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Good", "Password quality")));
break; break;
case PasswordHealth::Quality::Excellent: case PasswordHealth::Quality::Excellent:
m_ui->entropyProgressBar->setStyleSheet(style.arg("#27ae60")); m_ui->entropyProgressBar->setStyleSheet(style.arg(qualityColors[3]));
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Excellent", "Password quality"))); m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Excellent", "Password quality")));
break; break;
} }

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>334</width> <width>487</width>
<height>249</height> <height>326</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -17,10 +17,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::Box</enum> <enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing"> <property name="spacing">
@ -58,6 +55,9 @@
<property name="text"> <property name="text">
<string>Search terms are as follows: [modifiers][field:][&quot;]term[&quot;]</string> <string>Search terms are as follows: [modifiers][field:][&quot;]term[&quot;]</string>
</property> </property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -77,6 +77,9 @@
<property name="text"> <property name="text">
<string>Every search term must match (ie, logical AND)</string> <string>Every search term must match (ie, logical AND)</string>
</property> </property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget> </widget>
</item> </item>
<item> <item>

View File

@ -16,7 +16,7 @@
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,4"> <layout class="QHBoxLayout" name="horizontalLayout" stretch="4">
<property name="leftMargin"> <property name="leftMargin">
<number>3</number> <number>3</number>
</property> </property>
@ -29,22 +29,6 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>30</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<widget class="QLineEdit" name="searchEdit"> <widget class="QLineEdit" name="searchEdit">
<property name="sizePolicy"> <property name="sizePolicy">

View File

@ -19,7 +19,6 @@
#include <QDateTime> #include <QDateTime>
#include <QFont> #include <QFont>
#include <QFontMetrics>
#include <QMimeData> #include <QMimeData>
#include <QPainter> #include <QPainter>
#include <QPalette> #include <QPalette>
@ -27,6 +26,7 @@
#include "core/Config.h" #include "core/Config.h"
#include "core/DatabaseIcons.h" #include "core/DatabaseIcons.h"
#include "core/Entry.h" #include "core/Entry.h"
#include "core/FilePath.h"
#include "core/Global.h" #include "core/Global.h"
#include "core/Group.h" #include "core/Group.h"
#include "core/Metadata.h" #include "core/Metadata.h"
@ -218,9 +218,6 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
} }
return result; return result;
} }
case Totp:
result = entry->hasTotp() ? tr("Yes") : "";
return result;
} }
} else if (role == Qt::UserRole) { // Qt::UserRole is used as sort role, see EntryView::EntryView() } else if (role == Qt::UserRole) { // Qt::UserRole is used as sort role, see EntryView::EntryView()
switch (index.column()) { switch (index.column()) {
@ -240,7 +237,9 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
case Paperclip: case Paperclip:
// Display entries with attachments above those without when // Display entries with attachments above those without when
// sorting ascendingly (and vice versa when sorting descendingly) // sorting ascendingly (and vice versa when sorting descendingly)
return entry->attachments()->isEmpty() ? 1 : 0; return !entry->attachments()->isEmpty();
case Totp:
return entry->hasTotp();
default: default:
// For all other columns, simply use data provided by Qt::Display- // For all other columns, simply use data provided by Qt::Display-
// Role for sorting // Role for sorting
@ -260,7 +259,12 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
return entry->iconScaledPixmap(); return entry->iconScaledPixmap();
case Paperclip: case Paperclip:
if (!entry->attachments()->isEmpty()) { if (!entry->attachments()->isEmpty()) {
return m_paperClipPixmap; return filePath()->icon("actions", "paperclip");
}
break;
case Totp:
if (entry->hasTotp()) {
return filePath()->icon("actions", "chronometer");
} }
break; break;
} }
@ -327,16 +331,47 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro
return tr("Accessed"); return tr("Accessed");
case Attachments: case Attachments:
return tr("Attachments"); return tr("Attachments");
case Totp:
return tr("TOTP");
} }
} else if (role == Qt::DecorationRole) { } else if (role == Qt::DecorationRole) {
if (section == Paperclip) { switch (section) {
return m_paperClipPixmap; case Paperclip:
return filePath()->icon("actions", "paperclip");
case Totp:
return filePath()->icon("actions", "chronometer");
}
} else if (role == Qt::ToolTipRole) {
switch (section) {
case ParentGroup:
return tr("Group name");
case Title:
return tr("Entry title");
case Username:
return tr("Username");
case Password:
return tr("Password");
case Url:
return tr("URL");
case Notes:
return tr("Entry notes");
case Expires:
return tr("Entry expires at");
case Created:
return tr("Creation date");
case Modified:
return tr("Last modification date");
case Accessed:
return tr("Last access date");
case Attachments:
return tr("Attached files");
case Paperclip:
return tr("Has attachments");
case Totp:
return tr("Has TOTP one-time password");
} }
} }
return QVariant(); return {};
} }
Qt::DropActions EntryModel::supportedDropActions() const Qt::DropActions EntryModel::supportedDropActions() const
@ -502,8 +537,3 @@ void EntryModel::setPasswordsHidden(bool hide)
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
emit passwordsHiddenChanged(); emit passwordsHiddenChanged();
} }
void EntryModel::setPaperClipPixmap(const QPixmap& paperclip)
{
m_paperClipPixmap = paperclip;
}

View File

@ -68,8 +68,6 @@ public:
bool isPasswordsHidden() const; bool isPasswordsHidden() const;
void setPasswordsHidden(bool hide); void setPasswordsHidden(bool hide);
void setPaperClipPixmap(const QPixmap& paperclip);
signals: signals:
void usernamesHiddenChanged(); void usernamesHiddenChanged();
void passwordsHiddenChanged(); void passwordsHiddenChanged();
@ -93,8 +91,6 @@ private:
bool m_hideUsernames; bool m_hideUsernames;
bool m_hidePasswords; bool m_hidePasswords;
QPixmap m_paperClipPixmap;
const QString HiddenContentDisplay; const QString HiddenContentDisplay;
const Qt::DateFormat DateFormat; const Qt::DateFormat DateFormat;
}; };

View File

@ -24,7 +24,6 @@
#include <QMenu> #include <QMenu>
#include <QShortcut> #include <QShortcut>
#include "core/FilePath.h"
#include "gui/SortFilterHideProxyModel.h" #include "gui/SortFilterHideProxyModel.h"
EntryView::EntryView(QWidget* parent) EntryView::EntryView(QWidget* parent)
@ -70,22 +69,31 @@ EntryView::EntryView(QWidget* parent)
m_hidePasswordsAction->setCheckable(true); m_hidePasswordsAction->setCheckable(true);
m_headerMenu->addSeparator(); m_headerMenu->addSeparator();
resetViewToDefaults();
// Actions to toggle column visibility, each carrying the corresponding // Actions to toggle column visibility, each carrying the corresponding
// colummn index as data // colummn index as data
m_columnActions = new QActionGroup(this); m_columnActions = new QActionGroup(this);
m_columnActions->setExclusive(false); m_columnActions->setExclusive(false);
for (int columnIndex = 1; columnIndex < header()->count(); ++columnIndex) { for (int visualIndex = 1; visualIndex < header()->count(); ++visualIndex) {
QString caption = m_model->headerData(columnIndex, Qt::Horizontal, Qt::DisplayRole).toString(); int logicalIndex = header()->logicalIndex(visualIndex);
if (columnIndex == EntryModel::Paperclip) { QString caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole).toString();
caption = tr("Attachments (icon)"); if (logicalIndex == EntryModel::Paperclip) {
caption = tr("Has attachments", "Entry attachment icon toggle");
} else if (logicalIndex == EntryModel::Totp) {
caption = tr("Has TOTP", "Entry TOTP icon toggle");
} }
QAction* action = m_headerMenu->addAction(caption); QAction* action = m_headerMenu->addAction(caption);
action->setCheckable(true); action->setCheckable(true);
action->setData(columnIndex); action->setData(logicalIndex);
m_columnActions->addAction(action); m_columnActions->addAction(action);
} }
connect(m_columnActions, SIGNAL(triggered(QAction*)), this, SLOT(toggleColumnVisibility(QAction*))); connect(m_columnActions, SIGNAL(triggered(QAction*)), this, SLOT(toggleColumnVisibility(QAction*)));
connect(header(), &QHeaderView::sortIndicatorChanged, [this](int index, Qt::SortOrder order) {
Q_UNUSED(order)
header()->setSortIndicatorShown(index != EntryModel::Paperclip && index != EntryModel::Totp);
});
m_headerMenu->addSeparator(); m_headerMenu->addSeparator();
m_headerMenu->addAction(tr("Fit to window"), this, SLOT(fitColumnsToWindow())); m_headerMenu->addAction(tr("Fit to window"), this, SLOT(fitColumnsToWindow()));
@ -114,22 +122,6 @@ EntryView::EntryView(QWidget* parent)
// clang-format off // clang-format off
connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), SIGNAL(viewStateChanged())); connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), SIGNAL(viewStateChanged()));
// clang-format on // clang-format on
resetFixedColumns();
// Configure default search view state and save for later use
header()->showSection(EntryModel::ParentGroup);
m_sortModel->sort(EntryModel::ParentGroup, Qt::AscendingOrder);
sortByColumn(EntryModel::ParentGroup, Qt::AscendingOrder);
m_defaultSearchViewState = header()->saveState();
// Configure default list view state and save for later use
header()->hideSection(EntryModel::ParentGroup);
m_sortModel->sort(EntryModel::Title, Qt::AscendingOrder);
sortByColumn(EntryModel::Title, Qt::AscendingOrder);
m_defaultListViewState = header()->saveState();
m_model->setPaperClipPixmap(filePath()->icon("actions", "paperclip").pixmap(16));
} }
void EntryView::contextMenuShortcutPressed() void EntryView::contextMenuShortcutPressed()
@ -325,6 +317,7 @@ bool EntryView::setViewState(const QByteArray& state)
{ {
bool status = header()->restoreState(state); bool status = header()->restoreState(state);
resetFixedColumns(); resetFixedColumns();
m_columnsNeedRelayout = state.isEmpty();
return status; return status;
} }
@ -397,9 +390,11 @@ void EntryView::toggleColumnVisibility(QAction* action)
*/ */
void EntryView::fitColumnsToWindow() void EntryView::fitColumnsToWindow()
{ {
header()->resizeSections(QHeaderView::Stretch); header()->setSectionResizeMode(QHeaderView::Stretch);
resetFixedColumns();
QCoreApplication::processEvents();
header()->setSectionResizeMode(QHeaderView::Interactive);
resetFixedColumns(); resetFixedColumns();
fillRemainingWidth(true);
emit viewStateChanged(); emit viewStateChanged();
} }
@ -409,69 +404,88 @@ void EntryView::fitColumnsToWindow()
*/ */
void EntryView::fitColumnsToContents() void EntryView::fitColumnsToContents()
{ {
// Resize columns to fit contents header()->setSectionResizeMode(QHeaderView::ResizeToContents);
header()->resizeSections(QHeaderView::ResizeToContents); resetFixedColumns();
QCoreApplication::processEvents();
header()->setSectionResizeMode(QHeaderView::Interactive);
resetFixedColumns(); resetFixedColumns();
fillRemainingWidth(false);
emit viewStateChanged(); emit viewStateChanged();
} }
/** /**
* Reset view to defaults * Mark icon-only columns as fixed and resize them to their minimum section size.
*/
void EntryView::resetFixedColumns()
{
header()->setSectionResizeMode(EntryModel::Paperclip, QHeaderView::Fixed);
header()->resizeSection(EntryModel::Paperclip, header()->minimumSectionSize());
header()->setSectionResizeMode(EntryModel::Totp, QHeaderView::Fixed);
header()->resizeSection(EntryModel::Totp, header()->minimumSectionSize());
}
/**
* Reset item view to defaults.
*/ */
void EntryView::resetViewToDefaults() void EntryView::resetViewToDefaults()
{ {
m_model->setUsernamesHidden(false); m_model->setUsernamesHidden(false);
m_model->setPasswordsHidden(true); m_model->setPasswordsHidden(true);
// Reduce number of columns that are shown by default
if (m_inSearchMode) { if (m_inSearchMode) {
header()->restoreState(m_defaultSearchViewState); header()->showSection(EntryModel::ParentGroup);
} else { } else {
header()->restoreState(m_defaultListViewState); header()->hideSection(EntryModel::ParentGroup);
}
header()->showSection(EntryModel::Title);
header()->showSection(EntryModel::Username);
header()->showSection(EntryModel::Url);
header()->showSection(EntryModel::Notes);
header()->showSection(EntryModel::Modified);
header()->showSection(EntryModel::Paperclip);
header()->showSection(EntryModel::Totp);
header()->hideSection(EntryModel::Password);
header()->hideSection(EntryModel::Expires);
header()->hideSection(EntryModel::Created);
header()->hideSection(EntryModel::Accessed);
header()->hideSection(EntryModel::Attachments);
// Reset column order to logical indices
for (int i = 0; i < header()->count(); ++i) {
header()->moveSection(header()->visualIndex(i), i);
} }
// Reorder some columns
header()->moveSection(header()->visualIndex(EntryModel::Paperclip), 1);
header()->moveSection(header()->visualIndex(EntryModel::Totp), 2);
// Sort by title or group (depending on the mode)
m_sortModel->sort(EntryModel::Title, Qt::AscendingOrder);
sortByColumn(EntryModel::Title, Qt::AscendingOrder);
if (m_inSearchMode) {
m_sortModel->sort(EntryModel::ParentGroup, Qt::AscendingOrder);
sortByColumn(EntryModel::ParentGroup, Qt::AscendingOrder);
}
// The following call only relayouts reliably if the widget has been shown
// already, so only do it if the widget is visible and let showEvent() handle
// the initial default layout.
if (isVisible()) {
fitColumnsToWindow(); fitColumnsToWindow();
} }
}
void EntryView::fillRemainingWidth(bool lastColumnOnly) void EntryView::showEvent(QShowEvent* event)
{ {
// Determine total width of currently visible columns QTreeView::showEvent(event);
int width = 0;
int lastColumnIndex = 0;
for (int columnIndex = 0; columnIndex < header()->count(); ++columnIndex) {
if (!header()->isSectionHidden(columnIndex)) {
width += header()->sectionSize(columnIndex);
}
if (header()->visualIndex(columnIndex) > lastColumnIndex) {
lastColumnIndex = header()->visualIndex(columnIndex);
}
}
int numColumns = header()->count() - header()->hiddenSectionCount(); // Check if header columns need to be resized to sensible defaults.
int availWidth = header()->width() - width; // This is only needed if no previous view state has been loaded.
if ((numColumns <= 0) || (availWidth <= 0)) { if (m_columnsNeedRelayout) {
return; fitColumnsToWindow();
} m_columnsNeedRelayout = false;
if (!lastColumnOnly) {
// Equally distribute remaining width to visible columns
int add = availWidth / numColumns;
width = 0;
for (int columnIndex = 0; columnIndex < header()->count(); ++columnIndex) {
if (!header()->isSectionHidden(columnIndex)) {
header()->resizeSection(columnIndex, header()->sectionSize(columnIndex) + add);
width += header()->sectionSize(columnIndex);
} }
} }
}
// Add remaining width to last column
header()->resizeSection(header()->logicalIndex(lastColumnIndex),
header()->sectionSize(lastColumnIndex) + (header()->width() - width));
}
void EntryView::resetFixedColumns()
{
header()->setSectionResizeMode(EntryModel::Paperclip, QHeaderView::Fixed);
header()->resizeSection(EntryModel::Paperclip, header()->minimumSectionSize());
}

View File

@ -63,6 +63,7 @@ protected:
void keyPressEvent(QKeyEvent* event) override; void keyPressEvent(QKeyEvent* event) override;
void focusInEvent(QFocusEvent* event) override; void focusInEvent(QFocusEvent* event) override;
void focusOutEvent(QFocusEvent* event) override; void focusOutEvent(QFocusEvent* event) override;
void showEvent(QShowEvent* event) override;
private slots: private slots:
void emitEntryActivated(const QModelIndex& index); void emitEntryActivated(const QModelIndex& index);
@ -75,15 +76,12 @@ private slots:
void contextMenuShortcutPressed(); void contextMenuShortcutPressed();
private: private:
void fillRemainingWidth(bool lastColumnOnly);
void resetFixedColumns(); void resetFixedColumns();
EntryModel* const m_model; EntryModel* const m_model;
SortFilterHideProxyModel* const m_sortModel; SortFilterHideProxyModel* const m_sortModel;
bool m_inSearchMode; bool m_inSearchMode;
bool m_columnsNeedRelayout = true;
QByteArray m_defaultListViewState;
QByteArray m_defaultSearchViewState;
QMenu* m_headerMenu; QMenu* m_headerMenu;
QAction* m_hideUsernamesAction; QAction* m_hideUsernamesAction;

View File

@ -22,7 +22,7 @@
QPointer<MacUtils> MacUtils::m_instance = nullptr; QPointer<MacUtils> MacUtils::m_instance = nullptr;
MacUtils::MacUtils(QObject* parent) MacUtils::MacUtils(QObject* parent)
: OSUtils(parent) : OSUtilsBase(parent)
, m_appkit(new AppKit()) , m_appkit(new AppKit())
{ {
connect(m_appkit.data(), SIGNAL(lockDatabases()), SIGNAL(lockDatabases())); connect(m_appkit.data(), SIGNAL(lockDatabases()), SIGNAL(lockDatabases()));

View File

@ -21,9 +21,12 @@
#include "gui/osutils/OSUtilsBase.h" #include "gui/osutils/OSUtilsBase.h"
#include "AppKit.h" #include "AppKit.h"
#include <QPointer>
class MacUtils : public OSUtils #include <QPointer>
#include <QScopedPointer>
#include <qwindowdefs.h>
class MacUtils : public OSUtilsBase
{ {
Q_OBJECT Q_OBJECT

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 Andrew Richards
*
* Derived from Phantomstyle and relicensed under the GPLv2 or v3.
* https://github.com/randrew/phantomstyle
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_BASESTYLE_H
#define KEEPASSXC_BASESTYLE_H
#include <QCommonStyle>
class BaseStylePrivate;
class BaseStyle : public QCommonStyle
{
Q_OBJECT
public:
BaseStyle();
~BaseStyle() override;
enum PhantomPrimitiveElement
{
Phantom_PE_IndicatorTabNew = PE_CustomBase + 1,
Phantom_PE_ScrollBarSliderVertical,
Phantom_PE_WindowFrameColor,
};
QPalette standardPalette() const override;
void drawPrimitive(PrimitiveElement elem,
const QStyleOption* option,
QPainter* painter,
const QWidget* widget = nullptr) const override;
void
drawControl(ControlElement ce, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override;
int pixelMetric(PixelMetric metric,
const QStyleOption* option = nullptr,
const QWidget* widget = nullptr) const override;
void drawComplexControl(ComplexControl control,
const QStyleOptionComplex* option,
QPainter* painter,
const QWidget* widget) const override;
QRect subElementRect(SubElement r, const QStyleOption* opt, const QWidget* widget = nullptr) const override;
QSize sizeFromContents(ContentsType type,
const QStyleOption* option,
const QSize& size,
const QWidget* widget) const override;
SubControl hitTestComplexControl(ComplexControl cc,
const QStyleOptionComplex* opt,
const QPoint& pt,
const QWidget* w = nullptr) const override;
QRect subControlRect(ComplexControl cc,
const QStyleOptionComplex* opt,
SubControl sc,
const QWidget* widget) const override;
QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap, const QStyleOption* opt) const override;
int styleHint(StyleHint hint,
const QStyleOption* option = nullptr,
const QWidget* widget = nullptr,
QStyleHintReturn* returnData = nullptr) const override;
QRect itemPixmapRect(const QRect& r, int flags, const QPixmap& pixmap) const override;
void drawItemPixmap(QPainter* painter, const QRect& rect, int alignment, const QPixmap& pixmap) const override;
void drawItemText(QPainter* painter,
const QRect& rect,
int flags,
const QPalette& pal,
bool enabled,
const QString& text,
QPalette::ColorRole textRole = QPalette::NoRole) const override;
using QCommonStyle::polish;
void polish(QApplication* app) override;
protected:
/**
* @return Paths to application stylesheets
*/
virtual QString getAppStyleSheet() const
{
return {};
}
BaseStylePrivate* d;
};
#endif

View File

@ -0,0 +1,48 @@
QPushButton:default {
background: palette(highlight);
color: palette(highlighted-text);
}
QSpinBox {
min-width: 90px;
}
QDialogButtonBox QPushButton {
min-width: 55px;
}
QCheckBox, QRadioButton {
spacing: 10px;
}
DatabaseWidget, GroupView {
background-color: palette(window);
border: none;
}
EntryPreviewWidget QLineEdit, EntryPreviewWidget QTextEdit {
background-color: palette(window);
border: none;
}
DatabaseOpenWidget #loginFrame {
border: 2px groove palette(mid);
background: palette(light);
}
QGroupBox {
margin-top: 1.4em;
margin-bottom: 1.4em;
font-weight: bold;
}
QGroupBox::title {
margin-top: -3.4em;
margin-left: -.4em;
subcontrol-origin: padding;
}
QToolTip {
border: none;
padding: 3px;
}

View File

@ -0,0 +1,423 @@
/*
* HSLuv-C: Human-friendly HSL
* <http://github.com/hsluv/hsluv-c>
* <http://www.hsluv.org/>
*
* Copyright (c) 2015 Alexei Boronine (original idea, JavaScript implementation)
* Copyright (c) 2015 Roger Tallada (Obj-C implementation)
* Copyright (c) 2017 Martin Mitas (C implementation, based on Obj-C implementation)
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "phantomcolor.h"
#include <cfloat>
#include <cmath>
namespace Phantom
{
namespace
{
// Th`ese declarations originate from hsluv.h, from the hsluv-c library. The
// hpluv functions have been removed, as they are unnecessary for Phantom.
/**
* Convert HSLuv to RGB.
*
* @param h Hue. Between 0.0 and 360.0.
* @param s Saturation. Between 0.0 and 100.0.
* @param l Lightness. Between 0.0 and 100.0.
* @param[out] pr Red component. Between 0.0 and 1.0.
* @param[out] pr Green component. Between 0.0 and 1.0.
* @param[out] pr Blue component. Between 0.0 and 1.0.
*/
void hsluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb);
/**
* Convert RGB to HSLuv.
*
* @param r Red component. Between 0.0 and 1.0.
* @param g Green component. Between 0.0 and 1.0.
* @param b Blue component. Between 0.0 and 1.0.
* @param[out] ph Hue. Between 0.0 and 360.0.
* @param[out] ps Saturation. Between 0.0 and 100.0.
* @param[out] pl Lightness. Between 0.0 and 100.0.
*/
void rgb2hsluv(double r, double g, double b, double* ph, double* ps, double* pl);
// Contents below originate from hsluv.c from the hsluv-c library. They have
// been wrapped in a C++ namespace to avoid collisions and to reduce the
// translation unit count, and hsluv's own sRGB conversion code has been
// stripped out (sRGB conversion is now performed in the Phantom color code
// when going to/from the Rgb type.)
//
// If you need to update the hsluv-c code, be mindful of the removed sRGB
// conversions -- you will need to make similar modifications to the upstream
// hsluv-c code. Also note that that the hpluv (pastel) functions have been
// removed, as they are not used in Phantom.
typedef struct Triplet_tag Triplet;
struct Triplet_tag
{
double a;
double b;
double c;
};
/* for RGB */
const Triplet m[3] = {{3.24096994190452134377, -1.53738317757009345794, -0.49861076029300328366},
{-0.96924363628087982613, 1.87596750150772066772, 0.04155505740717561247},
{0.05563007969699360846, -0.20397695888897656435, 1.05697151424287856072}};
/* for XYZ */
const Triplet m_inv[3] = {{0.41239079926595948129, 0.35758433938387796373, 0.18048078840183428751},
{0.21263900587151035754, 0.71516867876775592746, 0.07219231536073371500},
{0.01933081871559185069, 0.11919477979462598791, 0.95053215224966058086}};
const double ref_u = 0.19783000664283680764;
const double ref_v = 0.46831999493879100370;
const double kappa = 903.29629629629629629630;
const double epsilon = 0.00885645167903563082;
typedef struct Bounds_tag Bounds;
struct Bounds_tag
{
double a;
double b;
};
void get_bounds(double l, Bounds bounds[6])
{
double tl = l + 16.0;
double sub1 = (tl * tl * tl) / 1560896.0;
double sub2 = (sub1 > epsilon ? sub1 : (l / kappa));
int channel;
int t;
for (channel = 0; channel < 3; channel++) {
double m1 = m[channel].a;
double m2 = m[channel].b;
double m3 = m[channel].c;
for (t = 0; t < 2; t++) {
double top1 = (284517.0 * m1 - 94839.0 * m3) * sub2;
double top2 = (838422.0 * m3 + 769860.0 * m2 + 731718.0 * m1) * l * sub2 - 769860.0 * t * l;
double bottom = (632260.0 * m3 - 126452.0 * m2) * sub2 + 126452.0 * t;
bounds[channel * 2 + t].a = top1 / bottom;
bounds[channel * 2 + t].b = top2 / bottom;
}
}
}
double ray_length_until_intersect(double theta, const Bounds* line)
{
return line->b / (sin(theta) - line->a * cos(theta));
}
double max_chroma_for_lh(double l, double h)
{
double min_len = DBL_MAX;
double hrad = h * 0.01745329251994329577; /* (2 * pi / 360) */
Bounds bounds[6];
int i;
get_bounds(l, bounds);
for (i = 0; i < 6; i++) {
double len = ray_length_until_intersect(hrad, &bounds[i]);
if (len >= 0 && len < min_len)
min_len = len;
}
return min_len;
}
double dot_product(const Triplet* t1, const Triplet* t2)
{
return (t1->a * t2->a + t1->b * t2->b + t1->c * t2->c);
}
void xyz2rgb(Triplet* in_out)
{
double r = dot_product(&m[0], in_out);
double g = dot_product(&m[1], in_out);
double b = dot_product(&m[2], in_out);
in_out->a = r;
in_out->b = g;
in_out->c = b;
}
void rgb2xyz(Triplet* in_out)
{
Triplet rgbl = {in_out->a, in_out->b, in_out->c};
double x = dot_product(&m_inv[0], &rgbl);
double y = dot_product(&m_inv[1], &rgbl);
double z = dot_product(&m_inv[2], &rgbl);
in_out->a = x;
in_out->b = y;
in_out->c = z;
}
/* http://en.wikipedia.org/wiki/CIELUV
* In these formulas, Yn refers to the reference white point. We are using
* illuminant D65, so Yn (see refY in Maxima file) equals 1. The formula is
* simplified accordingly.
*/
double y2l(double y)
{
if (y <= epsilon) {
return y * kappa;
} else {
return 116.0 * cbrt(y) - 16.0;
}
}
double l2y(double l)
{
if (l <= 8.0) {
return l / kappa;
} else {
double x = (l + 16.0) / 116.0;
return (x * x * x);
}
}
void xyz2luv(Triplet* in_out)
{
double divisor = in_out->a + (15.0 * in_out->b) + (3.0 * in_out->c);
if (divisor <= 0.00000001) {
in_out->a = 0.0;
in_out->b = 0.0;
in_out->c = 0.0;
return;
}
double var_u = (4.0 * in_out->a) / divisor;
double var_v = (9.0 * in_out->b) / divisor;
double l = y2l(in_out->b);
double u = 13.0 * l * (var_u - ref_u);
double v = 13.0 * l * (var_v - ref_v);
in_out->a = l;
if (l < 0.00000001) {
in_out->b = 0.0;
in_out->c = 0.0;
} else {
in_out->b = u;
in_out->c = v;
}
}
void luv2xyz(Triplet* in_out)
{
if (in_out->a <= 0.00000001) {
/* Black will create a divide-by-zero error. */
in_out->a = 0.0;
in_out->b = 0.0;
in_out->c = 0.0;
return;
}
double var_u = in_out->b / (13.0 * in_out->a) + ref_u;
double var_v = in_out->c / (13.0 * in_out->a) + ref_v;
double y = l2y(in_out->a);
double x = -(9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v);
double z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v);
in_out->a = x;
in_out->b = y;
in_out->c = z;
}
void luv2lch(Triplet* in_out)
{
double l = in_out->a;
double u = in_out->b;
double v = in_out->c;
double h;
double c = sqrt(u * u + v * v);
/* Grays: disambiguate hue */
if (c < 0.00000001) {
h = 0;
} else {
h = atan2(v, u) * 57.29577951308232087680; /* (180 / pi) */
if (h < 0.0)
h += 360.0;
}
in_out->a = l;
in_out->b = c;
in_out->c = h;
}
void lch2luv(Triplet* in_out)
{
double hrad = in_out->c * 0.01745329251994329577; /* (pi / 180.0) */
double u = cos(hrad) * in_out->b;
double v = sin(hrad) * in_out->b;
in_out->b = u;
in_out->c = v;
}
void hsluv2lch(Triplet* in_out)
{
double h = in_out->a;
double s = in_out->b;
double l = in_out->c;
double c;
/* White and black: disambiguate chroma */
if (l > 99.9999999 || l < 0.00000001) {
c = 0.0;
} else {
c = max_chroma_for_lh(l, h) / 100.0 * s;
}
/* Grays: disambiguate hue */
if (s < 0.00000001)
h = 0.0;
in_out->a = l;
in_out->b = c;
in_out->c = h;
}
void lch2hsluv(Triplet* in_out)
{
double l = in_out->a;
double c = in_out->b;
double h = in_out->c;
double s;
/* White and black: disambiguate saturation */
if (l > 99.9999999 || l < 0.00000001) {
s = 0.0;
} else {
s = c / max_chroma_for_lh(l, h) * 100.0;
}
/* Grays: disambiguate hue */
if (c < 0.00000001)
h = 0.0;
in_out->a = h;
in_out->b = s;
in_out->c = l;
}
void hsluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb)
{
Triplet tmp = {h, s, l};
hsluv2lch(&tmp);
lch2luv(&tmp);
luv2xyz(&tmp);
xyz2rgb(&tmp);
*pr = tmp.a;
*pg = tmp.b;
*pb = tmp.c;
}
void rgb2hsluv(double r, double g, double b, double* ph, double* ps, double* pl)
{
Triplet tmp = {r, g, b};
rgb2xyz(&tmp);
xyz2luv(&tmp);
luv2lch(&tmp);
lch2hsluv(&tmp);
*ph = tmp.a;
*ps = tmp.b;
*pl = tmp.c;
}
} // namespace
} // namespace Phantom
// The code below is for Phantom, and is used for the Rgb/Hsl-based interface
// for color operations.
namespace Phantom
{
namespace
{
// Note: these constants might be out of range when qreal is defined as float
// instead of double.
inline qreal linear_of_srgb(qreal x)
{
return x < 0.0404482362771082 ? x / 12.92 : std::pow((x + 0.055) / 1.055, 2.4f);
}
inline qreal srgb_of_linear(qreal x)
{
return x < 0.00313066844250063 ? x * 12.92 : std::pow(x, 1.0 / 2.4) * 1.055 - 0.055;
}
} // namespace
Rgb rgb_of_qcolor(const QColor& color)
{
Rgb a;
a.r = linear_of_srgb(color.red() / 255.0);
a.g = linear_of_srgb(color.green() / 255.0);
a.b = linear_of_srgb(color.blue() / 255.0);
return a;
}
Hsl hsl_of_rgb(qreal r, qreal g, qreal b)
{
double h, s, l;
rgb2hsluv(r, g, b, &h, &s, &l);
s /= 100.0;
l /= 100.0;
return {h, s, l};
}
Rgb rgb_of_hsl(qreal h, qreal s, qreal l)
{
double r, g, b;
hsluv2rgb(h, s * 100.0, l * 100.0, &r, &g, &b);
return {r, g, b};
}
QColor qcolor_of_rgb(qreal r, qreal g, qreal b)
{
int r_ = static_cast<int>(std::lround(srgb_of_linear(r) * 255.0));
int g_ = static_cast<int>(std::lround(srgb_of_linear(g) * 255.0));
int b_ = static_cast<int>(std::lround(srgb_of_linear(b) * 255.0));
return {r_, g_, b_};
}
QColor lerpQColor(const QColor& x, const QColor& y, qreal a)
{
Rgb x_ = rgb_of_qcolor(x);
Rgb y_ = rgb_of_qcolor(y);
Rgb z = Rgb::lerp(x_, y_, a);
return qcolor_of_rgb(z.r, z.g, z.b);
}
Rgb Rgb::lerp(const Rgb& x, const Rgb& y, qreal a)
{
Rgb z;
z.r = (1.0 - a) * x.r + a * y.r;
z.g = (1.0 - a) * x.g + a * y.g;
z.b = (1.0 - a) * x.b + a * y.b;
return z;
}
} // namespace Phantom

View File

@ -0,0 +1,165 @@
/*
* HSLuv-C: Human-friendly HSL
* <http://github.com/hsluv/hsluv-c>
* <http://www.hsluv.org/>
*
* Copyright (c) 2015 Alexei Boronine (original idea, JavaScript implementation)
* Copyright (c) 2015 Roger Tallada (Obj-C implementation)
* Copyright (c) 2017 Martin Mitas (C implementation, based on Obj-C implementation)
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef PHANTOMCOLOR_H
#define PHANTOMCOLOR_H
#include <QColor>
namespace Phantom
{
struct Rgb;
struct Hsl;
// A color presumed to be in linear space, represented as RGB. Values are in
// the range 0.0 - 1.0. Conversions to and from QColor will assume the QColor
// is in sRGB space, and sRGB conversion will be performed.
struct Rgb
{
qreal r, g, b;
Rgb()
{
}
Rgb(qreal r, qreal g, qreal b)
: r(r)
, g(g)
, b(b)
{
}
inline Hsl toHsl() const;
inline QColor toQColor() const;
static inline Rgb ofHsl(const Hsl&);
static inline Rgb ofQColor(const QColor&);
static Rgb lerp(const Rgb& x, const Rgb& y, qreal a);
};
// A color represented as pseudo-CIE hue, saturation, and lightness. Hue is in
// the range 0.0 - 360.0 (degrees). Lightness and saturation are in the range
// 0.0 - 1.0. Using this and making adjustments to the L value will produce
// more consistent and predictable results than QColor's .darker()/.lighter().
// Note that this is not strictly CIE -- some of the colorspace is distorted so
// that it can represented as a continuous coordinate space. Therefore not all
// adjustments to the parameters will produce perfectly linear results with
// regards to saturation and lightness. But it's still useful, and better than
// QColor's .darker()/.lighter(). Additionally, the L value is more useful for
// performing comparisons between two colors to measure relative and absolute
// brightness.
//
// See the documentation for the hsluv library for more information. (Note that
// for consistency we treat the S and L values in the range 0.0 - 1.0 instead
// of 0.0 - 100.0 like hsluv-c on its own does.)
struct Hsl
{
qreal h, s, l;
Hsl()
{
}
Hsl(qreal h, qreal s, qreal l)
: h(h)
, s(s)
, l(l)
{
}
inline Rgb toRgb() const;
inline QColor toQColor() const;
static inline Hsl ofRgb(const Rgb&);
static inline Hsl ofQColor(const QColor&);
};
Rgb rgb_of_qcolor(const QColor& color);
QColor qcolor_of_rgb(qreal r, qreal g, qreal b);
Hsl hsl_of_rgb(qreal r, qreal g, qreal b);
Rgb rgb_of_hsl(qreal h, qreal s, qreal l);
// Clip a floating point value to the range 0.0 - 1.0.
inline qreal saturate(qreal x)
{
if (x < 0.0)
return 0.0;
if (x > 1.0)
return 1.0;
return x;
}
inline qreal lerp(qreal x, qreal y, qreal a)
{
return (1.0 - a) * x + a * y;
}
// Linearly interpolate two QColors after trasnforming them to linear color
// space, treating the QColor values as if they were in sRGB space. The
// returned QColor is converted back to sRGB space.
QColor lerpQColor(const QColor& x, const QColor& y, qreal a);
Hsl Rgb::toHsl() const
{
return hsl_of_rgb(r, g, b);
}
QColor Rgb::toQColor() const
{
return qcolor_of_rgb(r, g, b);
}
Rgb Rgb::ofHsl(const Hsl& hsl)
{
return rgb_of_hsl(hsl.h, hsl.s, hsl.l);
}
Rgb Rgb::ofQColor(const QColor& color)
{
return rgb_of_qcolor(color);
}
Rgb Hsl::toRgb() const
{
return rgb_of_hsl(h, s, l);
}
QColor Hsl::toQColor() const
{
Rgb rgb = rgb_of_hsl(h, s, l);
return qcolor_of_rgb(rgb.r, rgb.g, rgb.b);
}
Hsl Hsl::ofRgb(const Rgb& rgb)
{
return hsl_of_rgb(rgb.r, rgb.g, rgb.b);
}
Hsl Hsl::ofQColor(const QColor& color)
{
Rgb rgb = rgb_of_qcolor(color);
return hsl_of_rgb(rgb.r, rgb.g, rgb.b);
}
} // namespace Phantom
#endif

View File

@ -0,0 +1,125 @@
/*
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DarkStyle.h"
#include "gui/osutils/OSUtils.h"
#include <QDialog>
#include <QMainWindow>
#include <QMenuBar>
#include <QToolBar>
void DarkStyle::polish(QPalette& palette)
{
palette.setColor(QPalette::Active, QPalette::Window, QStringLiteral("#3B3B3D"));
palette.setColor(QPalette::Inactive, QPalette::Window, QStringLiteral("#404042"));
palette.setColor(QPalette::Disabled, QPalette::Window, QStringLiteral("#424242"));
palette.setColor(QPalette::Active, QPalette::WindowText, QStringLiteral("#CACBCE"));
palette.setColor(QPalette::Inactive, QPalette::WindowText, QStringLiteral("#C8C8C6"));
palette.setColor(QPalette::Disabled, QPalette::WindowText, QStringLiteral("#707070"));
palette.setColor(QPalette::Active, QPalette::Text, QStringLiteral("#CACBCE"));
palette.setColor(QPalette::Inactive, QPalette::Text, QStringLiteral("#C8C8C6"));
palette.setColor(QPalette::Disabled, QPalette::Text, QStringLiteral("#707070"));
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
palette.setColor(QPalette::Active, QPalette::PlaceholderText, QStringLiteral("#7D7D82"));
palette.setColor(QPalette::Inactive, QPalette::PlaceholderText, QStringLiteral("#87888C"));
palette.setColor(QPalette::Disabled, QPalette::PlaceholderText, QStringLiteral("#737373"));
#endif
palette.setColor(QPalette::Active, QPalette::BrightText, QStringLiteral("#252627"));
palette.setColor(QPalette::Inactive, QPalette::BrightText, QStringLiteral("#2D2D2F"));
palette.setColor(QPalette::Disabled, QPalette::BrightText, QStringLiteral("#333333"));
palette.setColor(QPalette::Active, QPalette::Base, QStringLiteral("#27272A"));
palette.setColor(QPalette::Inactive, QPalette::Base, QStringLiteral("#2A2A2D"));
palette.setColor(QPalette::Disabled, QPalette::Base, QStringLiteral("#343437"));
palette.setColor(QPalette::Active, QPalette::AlternateBase, QStringLiteral("#303036"));
palette.setColor(QPalette::Inactive, QPalette::AlternateBase, QStringLiteral("#333338"));
palette.setColor(QPalette::Disabled, QPalette::AlternateBase, QStringLiteral("#36363A"));
palette.setColor(QPalette::All, QPalette::ToolTipBase, QStringLiteral("#2D532D"));
palette.setColor(QPalette::All, QPalette::ToolTipText, QStringLiteral("#BFBFBF"));
palette.setColor(QPalette::Active, QPalette::Button, QStringLiteral("#28282B"));
palette.setColor(QPalette::Inactive, QPalette::Button, QStringLiteral("#2B2B2E"));
palette.setColor(QPalette::Disabled, QPalette::Button, QStringLiteral("#2B2A2A"));
palette.setColor(QPalette::Active, QPalette::ButtonText, QStringLiteral("#B9B9BE"));
palette.setColor(QPalette::Inactive, QPalette::ButtonText, QStringLiteral("#9E9FA5"));
palette.setColor(QPalette::Disabled, QPalette::ButtonText, QStringLiteral("#73747E"));
palette.setColor(QPalette::Active, QPalette::Highlight, QStringLiteral("#2D532D"));
palette.setColor(QPalette::Inactive, QPalette::Highlight, QStringLiteral("#294C29"));
palette.setColor(QPalette::Disabled, QPalette::Highlight, QStringLiteral("#293D29"));
palette.setColor(QPalette::Active, QPalette::HighlightedText, QStringLiteral("#CCCCCC"));
palette.setColor(QPalette::Inactive, QPalette::HighlightedText, QStringLiteral("#C7C7C7"));
palette.setColor(QPalette::Disabled, QPalette::HighlightedText, QStringLiteral("#707070"));
palette.setColor(QPalette::All, QPalette::Light, QStringLiteral("#414145"));
palette.setColor(QPalette::All, QPalette::Midlight, QStringLiteral("#39393C"));
palette.setColor(QPalette::All, QPalette::Mid, QStringLiteral("#2F2F32"));
palette.setColor(QPalette::All, QPalette::Dark, QStringLiteral("#202022"));
palette.setColor(QPalette::All, QPalette::Shadow, QStringLiteral("#19191A"));
palette.setColor(QPalette::All, QPalette::Link, QStringLiteral("#6BAE6B"));
palette.setColor(QPalette::Disabled, QPalette::Link, QStringLiteral("#9DE9D"));
palette.setColor(QPalette::All, QPalette::LinkVisited, QStringLiteral("#70A970"));
palette.setColor(QPalette::Disabled, QPalette::LinkVisited, QStringLiteral("#98A998"));
}
QString DarkStyle::getAppStyleSheet() const
{
QFile extStylesheetFile(QStringLiteral(":/styles/dark/darkstyle.qss"));
if (extStylesheetFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
return extStylesheetFile.readAll();
}
qWarning("Failed to load dark theme stylesheet.");
return {};
}
void DarkStyle::polish(QWidget* widget)
{
if (qobject_cast<QMainWindow*>(widget) || qobject_cast<QDialog*>(widget) || qobject_cast<QMenuBar*>(widget)
|| qobject_cast<QToolBar*>(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, QStringLiteral("#2A2A2A"));
palette.setColor(QPalette::Inactive, QPalette::Window, QStringLiteral("#2D2D2D"));
palette.setColor(QPalette::Disabled, QPalette::Window, QStringLiteral("#2A2A2A"));
}
#elif defined(Q_OS_WIN)
// Register event filter for better dark mode support
WinUtils::registerEventFilters();
palette.setColor(QPalette::All, QPalette::Window, QStringLiteral("#2F2F30"));
#else
palette.setColor(QPalette::Active, QPalette::Window, QStringLiteral("#2F2F30"));
palette.setColor(QPalette::Inactive, QPalette::Window, QStringLiteral("#313133"));
palette.setColor(QPalette::Disabled, QPalette::Window, QStringLiteral("#3A3A3B"));
#endif
widget->setPalette(palette);
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_DARKSTYLE_H
#define KEEPASSXC_DARKSTYLE_H
#include "gui/styles/base/BaseStyle.h"
#include <QApplication>
class DarkStyle : public BaseStyle
{
Q_OBJECT
public:
using BaseStyle::polish;
void polish(QPalette& palette) override;
void polish(QWidget* widget) override;
protected:
QString getAppStyleSheet() const override;
};
#endif // KEEPASSXC_DARKSTYLE_H

View File

@ -0,0 +1,18 @@
DatabaseWidget:!active, GroupView:!active,
EntryPreviewWidget QLineEdit:!active, EntryPreviewWidget QTextEdit:!active {
background-color: #404042;
}
DatabaseWidget:disabled, GroupView:disabled,
EntryPreviewWidget QLineEdit:disabled, EntryPreviewWidget QTextEdit:disabled {
background-color: #424242;
}
QToolTip {
color: #BFBFBF;
background-color: #2D532D;
}
QGroupBox {
background-color: palette(light);
}

View File

@ -0,0 +1,124 @@
/*
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LightStyle.h"
#include "gui/ApplicationSettingsWidget.h"
#include "gui/osutils/OSUtils.h"
#include <QDialog>
#include <QMainWindow>
#include <QMenuBar>
#include <QToolBar>
void LightStyle::polish(QPalette& palette)
{
palette.setColor(QPalette::Active, QPalette::Window, QStringLiteral("#F7F7F7"));
palette.setColor(QPalette::Inactive, QPalette::Window, QStringLiteral("#FCFCFC"));
palette.setColor(QPalette::Disabled, QPalette::Window, QStringLiteral("#EDEDED"));
palette.setColor(QPalette::Active, QPalette::WindowText, QStringLiteral("#1D1D20"));
palette.setColor(QPalette::Inactive, QPalette::WindowText, QStringLiteral("#252528"));
palette.setColor(QPalette::Disabled, QPalette::WindowText, QStringLiteral("#8C8C92"));
palette.setColor(QPalette::Active, QPalette::Text, QStringLiteral("#1D1D20"));
palette.setColor(QPalette::Inactive, QPalette::Text, QStringLiteral("#252528"));
palette.setColor(QPalette::Disabled, QPalette::Text, QStringLiteral("#8C8C92"));
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
palette.setColor(QPalette::Active, QPalette::PlaceholderText, QStringLiteral("#71727D"));
palette.setColor(QPalette::Inactive, QPalette::PlaceholderText, QStringLiteral("#878893"));
palette.setColor(QPalette::Disabled, QPalette::PlaceholderText, QStringLiteral("#A3A4AC"));
#endif
palette.setColor(QPalette::Active, QPalette::BrightText, QStringLiteral("#F3F3F4"));
palette.setColor(QPalette::Inactive, QPalette::BrightText, QStringLiteral("#EAEAEB"));
palette.setColor(QPalette::Disabled, QPalette::BrightText, QStringLiteral("#E4E5E7"));
palette.setColor(QPalette::Active, QPalette::Base, QStringLiteral("#F9F9F9"));
palette.setColor(QPalette::Inactive, QPalette::Base, QStringLiteral("#F5F5F4"));
palette.setColor(QPalette::Disabled, QPalette::Base, QStringLiteral("#EFEFF2"));
palette.setColor(QPalette::Active, QPalette::AlternateBase, QStringLiteral("#ECF3E8"));
palette.setColor(QPalette::Inactive, QPalette::AlternateBase, QStringLiteral("#EAF2E6"));
palette.setColor(QPalette::Disabled, QPalette::AlternateBase, QStringLiteral("#E1E9DD"));
palette.setColor(QPalette::All, QPalette::ToolTipBase, QStringLiteral("#548C1D"));
palette.setColor(QPalette::All, QPalette::ToolTipText, QStringLiteral("#F7F7F7"));
palette.setColor(QPalette::Active, QPalette::Button, QStringLiteral("#D4D5DD"));
palette.setColor(QPalette::Inactive, QPalette::Button, QStringLiteral("#DCDCE0"));
palette.setColor(QPalette::Disabled, QPalette::Button, QStringLiteral("#E5E5E6"));
palette.setColor(QPalette::Active, QPalette::ButtonText, QStringLiteral("#181A18"));
palette.setColor(QPalette::Inactive, QPalette::ButtonText, QStringLiteral("#5F6671"));
palette.setColor(QPalette::Disabled, QPalette::ButtonText, QStringLiteral("#97979B"));
palette.setColor(QPalette::Active, QPalette::Highlight, QStringLiteral("#549712"));
palette.setColor(QPalette::Inactive, QPalette::Highlight, QStringLiteral("#528D16"));
palette.setColor(QPalette::Disabled, QPalette::Highlight, QStringLiteral("#6F9847"));
palette.setColor(QPalette::Active, QPalette::HighlightedText, QStringLiteral("#FCFCFC"));
palette.setColor(QPalette::Inactive, QPalette::HighlightedText, QStringLiteral("#F2F2F2"));
palette.setColor(QPalette::Disabled, QPalette::HighlightedText, QStringLiteral("#D9D9D9"));
palette.setColor(QPalette::All, QPalette::Light, QStringLiteral("#F9F9F9"));
palette.setColor(QPalette::All, QPalette::Midlight, QStringLiteral("#E9E9EB"));
palette.setColor(QPalette::All, QPalette::Mid, QStringLiteral("#C9C9CF"));
palette.setColor(QPalette::All, QPalette::Dark, QStringLiteral("#BBBBC2"));
palette.setColor(QPalette::All, QPalette::Shadow, QStringLiteral("#6C6D79"));
palette.setColor(QPalette::All, QPalette::Link, QStringLiteral("#429F14"));
palette.setColor(QPalette::Disabled, QPalette::Link, QStringLiteral("#949F8F"));
palette.setColor(QPalette::All, QPalette::LinkVisited, QStringLiteral("#3F8C17"));
palette.setColor(QPalette::Disabled, QPalette::LinkVisited, QStringLiteral("#838C7E"));
}
QString LightStyle::getAppStyleSheet() const
{
QFile extStylesheetFile(QStringLiteral(":/styles/light/lightstyle.qss"));
if (extStylesheetFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
return extStylesheetFile.readAll();
}
qWarning("Failed to load light theme stylesheet.");
return {};
}
void LightStyle::polish(QWidget* widget)
{
if (qobject_cast<QMainWindow*>(widget) || qobject_cast<QDialog*>(widget) || qobject_cast<QMenuBar*>(widget)
|| qobject_cast<QToolBar*>(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, QStringLiteral("#D6D6D6"));
palette.setColor(QPalette::Inactive, QPalette::Window, QStringLiteral("#F6F6F6"));
palette.setColor(QPalette::Disabled, QPalette::Window, QStringLiteral("#D4D4D4"));
}
#elif defined(Q_OS_WIN)
palette.setColor(QPalette::All, QPalette::Window, QStringLiteral("#FFFFFF"));
#else
palette.setColor(QPalette::Active, QPalette::Window, QStringLiteral("#EFF0F1"));
palette.setColor(QPalette::Inactive, QPalette::Window, QStringLiteral("#EFF0F1"));
palette.setColor(QPalette::Disabled, QPalette::Window, QStringLiteral("#E1E2E4"));
#endif
widget->setPalette(palette);
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_LIGHTSTYLE_H
#define KEEPASSXC_LIGHTSTYLE_H
#include "gui/styles/base/BaseStyle.h"
#include <QApplication>
class LightStyle : public BaseStyle
{
Q_OBJECT
public:
using BaseStyle::polish;
void polish(QPalette& palette) override;
void polish(QWidget* widget) override;
protected:
QString getAppStyleSheet() const override;
};
#endif // KEEPASSXC_LIGHTSTYLE_H

View File

@ -0,0 +1,18 @@
DatabaseWidget:!active, GroupView:!active,
EntryPreviewWidget QLineEdit:!active, EntryPreviewWidget QTextEdit:!active {
background-color: #FCFCFC;
}
DatabaseWidget:disabled, GroupView:disabled,
EntryPreviewWidget QLineEdit:disabled, EntryPreviewWidget QTextEdit:disabled {
background-color: #EDEDED;
}
QGroupBox::title {
color: palette(highlight);
}
QToolTip {
color: #F7F7F7;
background-color: #548C1D;
}

View File

@ -0,0 +1,8 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/styles">
<file>base/basestyle.qss</file>
<file>dark/darkstyle.qss</file>
<file>light/lightstyle.qss</file>
</qresource>
</RCC>

View File

@ -29,6 +29,9 @@
#include "gui/Application.h" #include "gui/Application.h"
#include "gui/MainWindow.h" #include "gui/MainWindow.h"
#include "gui/MessageBox.h" #include "gui/MessageBox.h"
#include "gui/osutils/OSUtils.h"
#include "gui/styles/dark/DarkStyle.h"
#include "gui/styles/light/LightStyle.h"
#if defined(WITH_ASAN) && defined(WITH_LSAN) #if defined(WITH_ASAN) && defined(WITH_LSAN)
#include <sanitizer/lsan_interface.h> #include <sanitizer/lsan_interface.h>
@ -60,6 +63,20 @@ int main(int argc, char** argv)
Application app(argc, argv); Application app(argc, argv);
Application::setApplicationName("KeePassXC"); Application::setApplicationName("KeePassXC");
Application::setApplicationVersion(KEEPASSXC_VERSION); Application::setApplicationVersion(KEEPASSXC_VERSION);
QString appTheme = config()->get("GUI/ApplicationTheme").toString();
if (appTheme == "auto") {
if (osUtils->isDarkMode()) {
QApplication::setStyle(new DarkStyle);
} else {
QApplication::setStyle(new LightStyle);
}
} else if (appTheme == "light") {
QApplication::setStyle(new LightStyle);
} else if (appTheme == "dark") {
QApplication::setStyle(new DarkStyle);
}
// don't set organizationName as that changes the return value of // don't set organizationName as that changes the return value of
// QStandardPaths::writableLocation(QDesktopServices::DataLocation) // QStandardPaths::writableLocation(QDesktopServices::DataLocation)
Bootstrap::bootstrapApplication(); Bootstrap::bootstrapApplication();