diff --git a/COPYING b/COPYING index 403e4564..e12f4534 100644 --- a/COPYING +++ b/COPYING @@ -156,6 +156,7 @@ License: LGPL-2.1 Comment: based on Nuvola icon theme Files: share/icons/application/*/actions/application-exit.png + share/icons/application/*/actions/chronometer.png share/icons/application/*/actions/configure.png share/icons/application/*/actions/dialog-close.png share/icons/application/*/actions/dialog-ok.png diff --git a/share/icons/application/22x22/actions/chronometer.png b/share/icons/application/22x22/actions/chronometer.png new file mode 100644 index 00000000..71d6eabe Binary files /dev/null and b/share/icons/application/22x22/actions/chronometer.png differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3f0ba782..97ff5edf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -98,6 +98,7 @@ set(keepassx_SOURCES gui/DatabaseTabWidget.cpp gui/DatabaseWidget.cpp gui/DatabaseWidgetStateSync.cpp + gui/DetailsWidget.cpp gui/DialogyWidget.cpp gui/DragTabBar.cpp gui/EditWidget.cpp diff --git a/src/core/Config.cpp b/src/core/Config.cpp index a432b3c9..6899015a 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -131,6 +131,7 @@ void Config::init(const QString& fileName) m_defaults.insert("security/lockdatabasescreenlock", true); m_defaults.insert("security/passwordsrepeat", false); m_defaults.insert("security/passwordscleartext", false); + m_defaults.insert("security/hidepassworddetails", true); m_defaults.insert("security/autotypeask", true); m_defaults.insert("security/IconDownloadFallbackToGoogle", false); m_defaults.insert("GUI/Language", "system"); diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index d5afec04..91d757a9 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -243,7 +243,15 @@ QString Entry::url() const QString Entry::webUrl() const { - return resolveUrl(m_attributes->value(EntryAttributes::URLKey)); + QString url = resolveMultiplePlaceholders(m_attributes->value(EntryAttributes::URLKey)); + return resolveUrl(url); +} + +QString Entry::displayUrl() const +{ + QString url = maskPasswordPlaceholders(m_attributes->value(EntryAttributes::URLKey)); + url = resolveMultiplePlaceholders(url); + return resolveUrl(url); } QString Entry::username() const diff --git a/src/core/Entry.h b/src/core/Entry.h index 212c8668..d4d2b903 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -79,6 +79,7 @@ public: QString title() const; QString url() const; QString webUrl() const; + QString displayUrl() const; QString username() const; QString password() const; QString notes() const; diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 644d2ff6..6f70db34 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -435,6 +435,23 @@ void Group::setParent(Database* db) QObject::setParent(db); } +QStringList Group::hierarchy() +{ + QStringList hierarchy; + Group* group = this; + Group* parent = m_parent; + hierarchy.prepend(group->name()); + + while (parent) { + group = group->parentGroup(); + parent = group->parentGroup(); + + hierarchy.prepend(group->name()); + } + + return hierarchy; +} + Database* Group::database() { return m_db; diff --git a/src/core/Group.h b/src/core/Group.h index 27400703..2a9d2b18 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -106,6 +106,7 @@ public: Group* parentGroup(); const Group* parentGroup() const; void setParent(Group* parent, int index = -1); + QStringList hierarchy(); Database* database(); const Database* database() const; diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 6a425b5d..6571668c 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -47,6 +47,7 @@ #include "gui/TotpDialog.h" #include "gui/DatabaseOpenWidget.h" #include "gui/DatabaseSettingsWidget.h" +#include "gui/DetailsWidget.h" #include "gui/KeePass1OpenWidget.h" #include "gui/MessageBox.h" #include "gui/UnlockDatabaseWidget.h" @@ -74,6 +75,9 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) mainLayout->addLayout(layout); m_splitter = new QSplitter(m_mainWidget); m_splitter->setChildrenCollapsible(false); + m_detailSplitter = new QSplitter(m_mainWidget); + m_detailSplitter->setOrientation(Qt::Vertical); + m_detailSplitter->setChildrenCollapsible(true); QWidget* rightHandSideWidget = new QWidget(m_splitter); @@ -99,10 +103,18 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) "border: 2px solid rgb(190, 190, 190);" "border-radius: 5px;"); + m_detailsView = new DetailsWidget(this); + QVBoxLayout* vLayout = new QVBoxLayout(rightHandSideWidget); vLayout->setMargin(0); vLayout->addWidget(m_searchingLabel); - vLayout->addWidget(m_entryView); + vLayout->addWidget(m_detailSplitter); + + m_detailSplitter->addWidget(m_entryView); + m_detailSplitter->addWidget(m_detailsView); + + m_detailSplitter->setStretchFactor(0, 80); + m_detailSplitter->setStretchFactor(1, 20); m_searchingLabel->setVisible(false); @@ -180,6 +192,12 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(&m_fileWatchUnblockTimer, SIGNAL(timeout()), this, SLOT(unblockAutoReload())); connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged())); + connect(m_groupView, SIGNAL(groupPressed(Group*)), SLOT(emitPressedGroup(Group*))); + connect(m_groupView, SIGNAL(groupChanged(Group*)), SLOT(emitPressedGroup(Group*))); + connect(m_entryView, SIGNAL(entryPressed(Entry*)), SLOT(emitPressedEntry(Entry*))); + connect(m_entryView, SIGNAL(entrySelectionChanged()), SLOT(emitPressedEntry())); + connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(emitPressedEntry())); + m_databaseModified = false; m_fileWatchTimer.setSingleShot(true); @@ -1041,6 +1059,32 @@ void DatabaseWidget::emitEntryContextMenuRequested(const QPoint& pos) emit entryContextMenuRequested(m_entryView->viewport()->mapToGlobal(pos)); } +void DatabaseWidget::emitPressedEntry() +{ + Entry* currentEntry = m_entryView->currentEntry(); + emitPressedEntry(currentEntry); +} + +void DatabaseWidget::emitPressedEntry(Entry* currentEntry) +{ + if (!currentEntry) { + // if no entry is pressed, leave in details the last entry + return; + } + + emit pressedEntry(currentEntry); +} + +void DatabaseWidget::emitPressedGroup(Group* currentGroup) +{ + if (!currentGroup) { + // if no group is pressed, leave in details the last group + return; + } + + emit pressedGroup(currentGroup); +} + bool DatabaseWidget::dbHasKey() const { return m_db->hasKey(); diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 93482000..404adb52 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -47,6 +47,7 @@ class QSplitter; class QLabel; class UnlockDatabaseWidget; class MessageWidget; +class DetailsWidget; class UnlockDatabaseDialog; class QFileSystemWatcher; @@ -115,6 +116,8 @@ signals: void databaseMerged(Database* mergedDb); void groupContextMenuRequested(const QPoint& globalPos); void entryContextMenuRequested(const QPoint& globalPos); + void pressedEntry(Entry* selectedEntry); + void pressedGroup(Group* selectedGroup); void unlockedDatabase(); void listModeAboutToActivate(); void listModeActivated(); @@ -179,6 +182,9 @@ private slots: void switchToGroupEdit(Group* entry, bool create); void emitGroupContextMenuRequested(const QPoint& pos); void emitEntryContextMenuRequested(const QPoint& pos); + void emitPressedEntry(); + void emitPressedEntry(Entry* currentEntry); + void emitPressedGroup(Group* currentGroup); void updateMasterKey(bool accepted); void openDatabase(bool accepted); void mergeDatabase(bool accepted); @@ -209,6 +215,7 @@ private: UnlockDatabaseWidget* m_unlockDatabaseWidget; UnlockDatabaseDialog* m_unlockDatabaseDialog; QSplitter* m_splitter; + QSplitter* m_detailSplitter; GroupView* m_groupView; EntryView* m_entryView; QLabel* m_searchingLabel; @@ -219,6 +226,7 @@ private: Uuid m_groupBeforeLock; Uuid m_entryBeforeLock; MessageWidget* m_messageWidget; + DetailsWidget* m_detailsView; // Search state QString m_lastSearchText; diff --git a/src/gui/DetailsWidget.cpp b/src/gui/DetailsWidget.cpp new file mode 100644 index 00000000..b805f9b1 --- /dev/null +++ b/src/gui/DetailsWidget.cpp @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "DetailsWidget.h" +#include "ui_DetailsWidget.h" + +#include + +#include "core/Config.h" +#include "core/FilePath.h" +#include "core/TimeInfo.h" +#include "gui/Clipboard.h" +#include "gui/DatabaseWidget.h" + +DetailsWidget::DetailsWidget(QWidget* parent) + : QWidget(parent) + , m_ui(new Ui::DetailsWidget()) + , m_locked(false) + , m_currentEntry(nullptr) + , m_currentGroup(nullptr) + , m_attributesWidget(nullptr) + , m_autotypeWidget(nullptr) + , m_selectedTabEntry(0) + , m_selectedTabGroup(0) +{ + m_ui->setupUi(this); + + connect(parent, SIGNAL(pressedEntry(Entry*)), SLOT(getSelectedEntry(Entry*))); + connect(parent, SIGNAL(pressedGroup(Group*)), SLOT(getSelectedGroup(Group*))); + connect(parent, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), SLOT(setDatabaseMode(DatabaseWidget::Mode))); + + m_ui->totpButton->setIcon(filePath()->icon("actions", "chronometer")); + m_ui->closeButton->setIcon(filePath()->icon("actions", "dialog-close")); + + connect(m_ui->totpButton, SIGNAL(toggled(bool)), SLOT(showTotp(bool))); + connect(m_ui->closeButton, SIGNAL(toggled(bool)), SLOT(hideDetails())); + connect(m_ui->tabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndex(int))); + + this->hide(); +} + +DetailsWidget::~DetailsWidget() +{ +} + +void DetailsWidget::getSelectedEntry(Entry* selectedEntry) +{ + m_currentEntry = selectedEntry; + + if (!config()->get("GUI/HideDetailsView").toBool()) { + this->show(); + } + + m_ui->stackedWidget->setCurrentIndex(EntryPreview); + + if (m_ui->tabWidget->count() < 4) { + m_ui->tabWidget->insertTab(static_cast(AttributesTab), m_attributesWidget, "Attributes"); + m_ui->tabWidget->insertTab(static_cast(AutotypeTab), m_autotypeWidget, "Autotype"); + } + + m_ui->tabWidget->setTabEnabled(AttributesTab, false); + m_ui->tabWidget->setTabEnabled(NotesTab, false); + m_ui->tabWidget->setTabEnabled(AutotypeTab, false); + + m_ui->totpButton->hide(); + m_ui->totpWidget->hide(); + m_ui->totpButton->setChecked(false); + + auto icon = m_currentEntry->iconPixmap(); + if (icon.width() > 16 || icon.height() > 16) { + icon = icon.scaled(16, 16); + } + m_ui->entryIcon->setPixmap(icon); + + QString title = QString(" / "); + Group* entry_group = m_currentEntry->group(); + if (entry_group) { + QStringList hierarchy = entry_group->hierarchy(); + hierarchy.removeFirst(); + title += hierarchy.join(" / "); + if (hierarchy.size() > 0) { + title += " / "; + } + } + title.append(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->title())); + m_ui->titleLabel->setText(title); + + m_ui->usernameLabel->setText(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->username())); + + if (!config()->get("security/hidepassworddetails").toBool()) { + m_ui->passwordLabel->setText(shortPassword(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password()))); + m_ui->passwordLabel->setToolTip(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password())); + } else { + m_ui->passwordLabel->setText("****"); + } + + QString url = m_currentEntry->webUrl(); + if (!url.isEmpty()) { + // URL is well formed and can be opened in a browser + // create a new display url that masks password placeholders + // the actual link will use the password + url = QString("%2").arg(url).arg(shortUrl(m_currentEntry->displayUrl())); + m_ui->urlLabel->setOpenExternalLinks(true); + } else { + // Fallback to the raw url string + url = shortUrl(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->url())); + m_ui->urlLabel->setOpenExternalLinks(false); + } + m_ui->urlLabel->setText(url); + + TimeInfo entryTime = m_currentEntry->timeInfo(); + if (entryTime.expires()) { + m_ui->expirationLabel->setText(entryTime.expiryTime().toString(Qt::DefaultLocaleShortDate)); + } else { + m_ui->expirationLabel->setText(tr("Never")); + } + + if (m_currentEntry->hasTotp()) { + m_ui->totpButton->show(); + updateTotp(); + + m_step = m_currentEntry->totpStep(); + + m_timer = new QTimer(this); + connect(m_timer, SIGNAL(timeout()), this, SLOT(updateTotp())); + m_timer->start(m_step * 10); + } + + QString notes = m_currentEntry->notes(); + if (!notes.isEmpty()) { + m_ui->tabWidget->setTabEnabled(NotesTab, true); + m_ui->notesEdit->setText(m_currentEntry->resolveMultiplePlaceholders(notes)); + } + + QStringList customAttributes = m_currentEntry->attributes()->customKeys(); + if (customAttributes.size() > 0) { + m_ui->tabWidget->setTabEnabled(AttributesTab, true); + m_ui->attributesEdit->clear(); + + QString attributesText = QString(); + for (const QString& key : customAttributes) { + QString value = m_currentEntry->attributes()->value(key); + if (m_currentEntry->attributes()->isProtected(key)) { + value = "" + tr("[PROTECTED]") + ""; + } + attributesText.append(QString("%1: %2
").arg(key, value)); + } + m_ui->attributesEdit->setText(attributesText); + } + + m_ui->autotypeTree->clear(); + AutoTypeAssociations* autotypeAssociations = m_currentEntry->autoTypeAssociations(); + QList items; + for (auto assoc : autotypeAssociations->getAll()) { + QStringList association = QStringList() << assoc.window << assoc.sequence; + if (association.at(1).isEmpty()) { + association.replace(1, m_currentEntry->effectiveAutoTypeSequence()); + } + items.append(new QTreeWidgetItem(m_ui->autotypeTree, association)); + } + if (items.count() > 0) { + m_ui->autotypeTree->addTopLevelItems(items); + m_ui->tabWidget->setTabEnabled(AutotypeTab, true); + } + + if (m_ui->tabWidget->isTabEnabled(m_selectedTabEntry)) { + m_ui->tabWidget->setCurrentIndex(m_selectedTabEntry); + } +} + +void DetailsWidget::getSelectedGroup(Group* selectedGroup) +{ + m_currentGroup = selectedGroup; + + if (!config()->get("GUI/HideDetailsView").toBool()) { + this->show(); + } + + m_ui->stackedWidget->setCurrentIndex(GroupPreview); + + if (m_ui->tabWidget->count() > 2) { + m_autotypeWidget = m_ui->tabWidget->widget(AutotypeTab); + m_attributesWidget = m_ui->tabWidget->widget(AttributesTab); + m_ui->tabWidget->removeTab(AutotypeTab); + m_ui->tabWidget->removeTab(AttributesTab); + } + + m_ui->tabWidget->setTabEnabled(GroupNotesTab, false); + + + m_ui->totpButton->hide(); + m_ui->totpWidget->hide(); + + auto icon = m_currentGroup->iconPixmap(); + if (icon.width() > 32 || icon.height() > 32) { + icon = icon.scaled(32, 32); + } + m_ui->entryIcon->setPixmap(icon); + + QString title = " / "; + QStringList hierarchy = m_currentGroup->hierarchy(); + hierarchy.removeFirst(); + title += hierarchy.join(" / "); + if (hierarchy.size() > 0) { + title += " / "; + } + m_ui->titleLabel->setText(title); + + QString notes = m_currentGroup->notes(); + if (!notes.isEmpty()) { + m_ui->tabWidget->setTabEnabled(GroupNotesTab, true); + m_ui->notesEdit->setText(notes); + } + + QString searching = tr("Disabled"); + if (m_currentGroup->resolveSearchingEnabled()) { + searching = tr("Enabled"); + } + m_ui->searchingLabel->setText(searching); + + QString autotype = tr("Disabled"); + if (m_currentGroup->resolveAutoTypeEnabled()) { + autotype = tr("Enabled"); + } + m_ui->autotypeLabel->setText(autotype); + + TimeInfo groupTime = m_currentGroup->timeInfo(); + if (groupTime.expires()) { + m_ui->groupExpirationLabel->setText(groupTime.expiryTime().toString(Qt::DefaultLocaleShortDate)); + } else { + m_ui->groupExpirationLabel->setText(tr("Never")); + } + + if (m_ui->tabWidget->isTabEnabled(m_selectedTabGroup)) { + m_ui->tabWidget->setCurrentIndex(m_selectedTabGroup); + } +} + +void DetailsWidget::updateTotp() +{ + if (m_locked) { + m_timer->stop(); + return; + } + QString totpCode = m_currentEntry->totp(); + QString firstHalf = totpCode.left(totpCode.size()/2); + QString secondHalf = totpCode.right(totpCode.size()/2); + m_ui->totpLabel->setText(firstHalf + " " + secondHalf); +} + +void DetailsWidget::showTotp(bool visible) +{ + if (visible){ + m_ui->totpWidget->show(); + } else { + m_ui->totpWidget->hide(); + } +} + +QString DetailsWidget::shortUrl(QString url) +{ + QString newurl = ""; + if (url.length() > 60) { + newurl.append(url.left(20)); + newurl.append("…"); + newurl.append(url.right(20)); + return newurl; + } + return url; +} + +QString DetailsWidget::shortPassword(QString password) +{ + QString newpassword = ""; + if (password.length() > 60) { + newpassword.append(password.left(50)); + newpassword.append("…"); + return newpassword; + } + return password; +} + +void DetailsWidget::hideDetails() +{ + this->hide(); +} + +void DetailsWidget::setDatabaseMode(DatabaseWidget::Mode mode) +{ + m_locked = false; + if (mode == DatabaseWidget::LockedMode) { + m_locked = true; + return; + } + if (mode == DatabaseWidget::ViewMode) { + if (m_ui->stackedWidget->currentIndex() == GroupPreview) { + getSelectedGroup(m_currentGroup); + } else { + getSelectedEntry(m_currentEntry); + } + } +} + +void DetailsWidget::updateTabIndex(int index) { + if (m_ui->stackedWidget->currentIndex() == GroupPreview) { + m_selectedTabGroup = index; + } else { + m_selectedTabEntry = index; + } +} diff --git a/src/gui/DetailsWidget.h b/src/gui/DetailsWidget.h new file mode 100644 index 00000000..5bc02600 --- /dev/null +++ b/src/gui/DetailsWidget.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_DETAILSWIDGET_H +#define KEEPASSX_DETAILSWIDGET_H + +#include + +#include "gui/DatabaseWidget.h" + +namespace Ui { + class DetailsWidget; +} + +class DetailsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit DetailsWidget(QWidget* parent = nullptr); + ~DetailsWidget(); + + enum StackedWidgetIndex + { + EntryPreview = 0, + GroupPreview = 1, + }; + + enum TabWidgetIndex + { + GeneralTab = 0, + AttributesTab = 1, + GroupNotesTab = 1, + NotesTab = 2, + AutotypeTab = 3, + }; + +private slots: + void getSelectedEntry(Entry* selectedEntry); + void getSelectedGroup(Group* selectedGroup); + void showTotp(bool visible); + void updateTotp(); + void hideDetails(); + void setDatabaseMode(DatabaseWidget::Mode mode); + void updateTabIndex(int index); + +private: + const QScopedPointer m_ui; + bool m_locked; + Entry* m_currentEntry; + Group* m_currentGroup; + quint8 m_step; + QTimer* m_timer; + QWidget* m_attributesWidget; + QWidget* m_autotypeWidget; + quint8 m_selectedTabEntry; + quint8 m_selectedTabGroup; + QString shortUrl(QString url); + QString shortPassword(QString password); +}; + +#endif // KEEPASSX_DETAILSWIDGET_H diff --git a/src/gui/DetailsWidget.ui b/src/gui/DetailsWidget.ui new file mode 100644 index 00000000..fb31409f --- /dev/null +++ b/src/gui/DetailsWidget.ui @@ -0,0 +1,567 @@ + + + DetailsWidget + + + + 0 + 0 + 630 + 200 + + + + + 0 + 0 + + + + + 16777215 + 200 + + + + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + + 20 + 16777215 + + + + + + + + + + + + 0 + 0 + + + + + 12 + + + + Qt::AutoText + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + + + + 10 + 75 + true + + + + + + + + + + + + + + Generate TOTP Token + + + + + + true + + + + + + + Generate TOTP Token + + + + + + true + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + 0 + + + false + + + false + + + false + + + + General + + + + + + + 0 + 0 + + + + 0 + + + + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Expiration + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 9 + 75 + true + + + + URL + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Password + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Qt::LeftToRight + + + Username + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Autotype + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Searching + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Expiration + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Attributes + + + + + + + 0 + 1 + + + + + 0 + 80 + + + + + 16777215 + 80 + + + + Qt::ClickFocus + + + true + + + + + + + + Notes + + + + + + + 0 + 1 + + + + + 0 + 80 + + + + + 16777215 + 80 + + + + Qt::ClickFocus + + + true + + + + + + + + Autotype + + + + + + QFrame::Sunken + + + true + + + true + + + false + + + 2 + + + false + + + 250 + + + 50 + + + true + + + + Window + + + + + Sequence + + + + + + + + + + + + Search + + + + + Clear + + + + + + diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 8385bf26..aa0e5664 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -134,6 +134,7 @@ void SettingsWidget::loadSettings() m_generalUi->languageComboBox->setCurrentIndex(defaultIndex); } + m_generalUi->detailsHideCheckBox->setChecked(config()->get("GUI/HideDetailsView").toBool()); m_generalUi->systrayShowCheckBox->setChecked(config()->get("GUI/ShowTrayIcon").toBool()); m_generalUi->systrayMinimizeToTrayCheckBox->setChecked(config()->get("GUI/MinimizeToTray").toBool()); m_generalUi->systrayMinimizeOnCloseCheckBox->setChecked(config()->get("GUI/MinimizeOnClose").toBool()); @@ -160,6 +161,7 @@ void SettingsWidget::loadSettings() m_secUi->fallbackToGoogle->setChecked(config()->get("security/IconDownloadFallbackToGoogle").toBool()); m_secUi->passwordCleartextCheckBox->setChecked(config()->get("security/passwordscleartext").toBool()); + m_secUi->passwordDetailsCleartextCheckBox->setChecked(config()->get("security/hidepassworddetails").toBool()); m_secUi->passwordRepeatCheckBox->setChecked(config()->get("security/passwordsrepeat").toBool()); @@ -203,6 +205,7 @@ void SettingsWidget::saveSettings() config()->set("GUI/Language", m_generalUi->languageComboBox->itemData(currentLangIndex).toString()); + config()->set("GUI/HideDetailsView", m_generalUi->detailsHideCheckBox->isChecked()); config()->set("GUI/ShowTrayIcon", m_generalUi->systrayShowCheckBox->isChecked()); config()->set("GUI/MinimizeToTray", m_generalUi->systrayMinimizeToTrayCheckBox->isChecked()); config()->set("GUI/MinimizeOnClose", m_generalUi->systrayMinimizeOnCloseCheckBox->isChecked()); @@ -226,6 +229,7 @@ void SettingsWidget::saveSettings() config()->set("security/IconDownloadFallbackToGoogle", m_secUi->fallbackToGoogle->isChecked()); config()->set("security/passwordscleartext", m_secUi->passwordCleartextCheckBox->isChecked()); + config()->set("security/hidepassworddetails", m_secUi->passwordDetailsCleartextCheckBox->isChecked()); config()->set("security/passwordsrepeat", m_secUi->passwordRepeatCheckBox->isChecked()); // Security: clear storage if related settings are disabled diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index 1e62104c..7dc4487c 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -153,6 +153,13 @@ + + + + Hide the Details view + + + diff --git a/src/gui/SettingsWidgetSecurity.ui b/src/gui/SettingsWidgetSecurity.ui index 4233fdbd..53c69db6 100644 --- a/src/gui/SettingsWidgetSecurity.ui +++ b/src/gui/SettingsWidgetSecurity.ui @@ -136,6 +136,13 @@ + + + + Hide passwords in the preview panel + + + diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 463096b3..2d17c345 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -632,7 +632,7 @@ void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected) if (index.isValid()) { QString key = m_attributesModel->keyByIndex(index); if (showProtected) { - m_advancedUi->attributesEdit->setPlainText(tr("[PROTECTED] Press reveal to view or edit")); + m_advancedUi->attributesEdit->setPlainText(tr("[PROTECTED]") + " " + tr("Press reveal to view or edit")); m_advancedUi->attributesEdit->setEnabled(false); m_advancedUi->revealAttributeButton->setEnabled(true); m_advancedUi->protectAttributeButton->setChecked(true); diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index 2f79f02d..4d357806 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -151,8 +151,7 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const } return result; case Url: - result = entry->maskPasswordPlaceholders(entry->url()); - result = entry->resolveMultiplePlaceholders(result); + result = entry->displayUrl(); if (attr->isReference(EntryAttributes::URLKey)) { result.prepend(tr("Ref: ","Reference abbreviation")); } diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp index 1bdd4fbc..ac8e6678 100644 --- a/src/gui/entry/EntryView.cpp +++ b/src/gui/entry/EntryView.cpp @@ -49,6 +49,8 @@ EntryView::EntryView(QWidget* parent) connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SIGNAL(entrySelectionChanged())); connect(m_model, SIGNAL(switchedToEntryListMode()), SLOT(switchToEntryListMode())); connect(m_model, SIGNAL(switchedToGroupMode()), SLOT(switchToGroupMode())); + + connect(this, SIGNAL(clicked(QModelIndex)), SLOT(emitEntryPressed(QModelIndex))); } void EntryView::keyPressEvent(QKeyEvent* event) @@ -99,6 +101,11 @@ void EntryView::emitEntryActivated(const QModelIndex& index) emit entryActivated(entry, static_cast(m_sortModel->mapToSource(index).column())); } +void EntryView::emitEntryPressed(const QModelIndex& index) +{ + emit entryPressed(entryFromIndex(index)); +} + void EntryView::setModel(QAbstractItemModel* model) { Q_UNUSED(model); diff --git a/src/gui/entry/EntryView.h b/src/gui/entry/EntryView.h index 6a545f62..14c6b7cc 100644 --- a/src/gui/entry/EntryView.h +++ b/src/gui/entry/EntryView.h @@ -47,6 +47,7 @@ public slots: signals: void entryActivated(Entry* entry, EntryModel::ModelColumn column); + void entryPressed(Entry* entry); void entrySelectionChanged(); protected: @@ -54,6 +55,7 @@ protected: private slots: void emitEntryActivated(const QModelIndex& index); + void emitEntryPressed(const QModelIndex& index); void switchToEntryListMode(); void switchToGroupMode(); diff --git a/src/gui/group/GroupView.cpp b/src/gui/group/GroupView.cpp index e9649e44..82d01e31 100644 --- a/src/gui/group/GroupView.cpp +++ b/src/gui/group/GroupView.cpp @@ -41,6 +41,8 @@ GroupView::GroupView(Database* db, QWidget* parent) connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(emitGroupChanged())); + connect(this, SIGNAL(clicked(QModelIndex)), SLOT(emitGroupPressed(QModelIndex))); + modelReset(); setDragEnabled(true); @@ -126,6 +128,11 @@ void GroupView::emitGroupChanged() emit groupChanged(currentGroup()); } +void GroupView::emitGroupPressed(const QModelIndex& index) +{ + emit groupPressed(m_model->groupFromIndex(index)); +} + void GroupView::syncExpandedState(const QModelIndex& parent, int start, int end) { for (int row = start; row <= end; row++) { diff --git a/src/gui/group/GroupView.h b/src/gui/group/GroupView.h index eaa29072..0a084425 100644 --- a/src/gui/group/GroupView.h +++ b/src/gui/group/GroupView.h @@ -38,11 +38,13 @@ public: signals: void groupChanged(Group* group); + void groupPressed(Group* group); private slots: void expandedChanged(const QModelIndex& index); void emitGroupChanged(const QModelIndex& index); void emitGroupChanged(); + void emitGroupPressed(const QModelIndex& index); void syncExpandedState(const QModelIndex& parent, int start, int end); void modelReset();