2017-08-17 15:02:21 -04:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
|
|
|
* Copyright (C) 2017 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 "DetailsWidget.h"
|
|
|
|
#include "ui_DetailsWidget.h"
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
|
|
#include "core/Config.h"
|
|
|
|
#include "core/FilePath.h"
|
|
|
|
#include "core/TimeInfo.h"
|
2017-08-30 18:52:05 -04:00
|
|
|
#include "gui/Clipboard.h"
|
2017-09-27 13:18:13 -04:00
|
|
|
#include "gui/DatabaseWidget.h"
|
2017-08-17 15:02:21 -04:00
|
|
|
|
|
|
|
DetailsWidget::DetailsWidget(QWidget* parent)
|
|
|
|
: QWidget(parent)
|
|
|
|
, m_ui(new Ui::DetailsWidget())
|
2017-09-27 13:18:13 -04:00
|
|
|
, m_locked(false)
|
2017-08-17 15:02:21 -04:00
|
|
|
, m_currentEntry(nullptr)
|
|
|
|
, m_currentGroup(nullptr)
|
2017-09-29 14:37:37 -04:00
|
|
|
, m_attributesWidget(nullptr)
|
|
|
|
, m_autotypeWidget(nullptr)
|
2017-09-29 16:02:07 -04:00
|
|
|
, m_selectedTabEntry(0)
|
|
|
|
, m_selectedTabGroup(0)
|
2017-08-17 15:02:21 -04:00
|
|
|
{
|
|
|
|
m_ui->setupUi(this);
|
|
|
|
|
|
|
|
connect(parent, SIGNAL(pressedEntry(Entry*)), SLOT(getSelectedEntry(Entry*)));
|
|
|
|
connect(parent, SIGNAL(pressedGroup(Group*)), SLOT(getSelectedGroup(Group*)));
|
2017-09-27 13:18:13 -04:00
|
|
|
connect(parent, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), SLOT(setDatabaseMode(DatabaseWidget::Mode)));
|
2017-08-17 15:02:21 -04:00
|
|
|
|
|
|
|
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()));
|
2017-09-29 16:02:07 -04:00
|
|
|
connect(m_ui->tabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndex(int)));
|
2017-08-17 15:02:21 -04:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2017-09-29 14:37:37 -04:00
|
|
|
if (m_ui->tabWidget->count() < 4) {
|
|
|
|
m_ui->tabWidget->insertTab(static_cast<int>(AttributesTab), m_attributesWidget, "Attributes");
|
|
|
|
m_ui->tabWidget->insertTab(static_cast<int>(AutotypeTab), m_autotypeWidget, "Autotype");
|
|
|
|
}
|
|
|
|
|
2017-09-27 13:18:13 -04:00
|
|
|
m_ui->tabWidget->setTabEnabled(AttributesTab, false);
|
|
|
|
m_ui->tabWidget->setTabEnabled(NotesTab, false);
|
2017-09-29 14:37:37 -04:00
|
|
|
m_ui->tabWidget->setTabEnabled(AutotypeTab, false);
|
2017-09-27 13:18:13 -04:00
|
|
|
|
2017-08-17 15:02:21 -04:00
|
|
|
m_ui->totpButton->hide();
|
|
|
|
m_ui->totpWidget->hide();
|
|
|
|
m_ui->totpButton->setChecked(false);
|
|
|
|
|
2017-09-28 14:48:49 -04:00
|
|
|
auto icon = m_currentEntry->iconPixmap();
|
|
|
|
if (icon.width() > 16 || icon.height() > 16) {
|
|
|
|
icon = icon.scaled(16, 16);
|
|
|
|
}
|
|
|
|
m_ui->entryIcon->setPixmap(icon);
|
2017-08-30 18:52:05 -04:00
|
|
|
|
2017-09-28 14:48:49 -04:00
|
|
|
QString title = QString(" / ");
|
2017-08-30 18:52:05 -04:00
|
|
|
Group* entry_group = m_currentEntry->group();
|
|
|
|
if (entry_group) {
|
|
|
|
QStringList hierarchy = entry_group->hierarchy();
|
2017-09-28 14:48:49 -04:00
|
|
|
hierarchy.removeFirst();
|
|
|
|
title += hierarchy.join(" / ");
|
|
|
|
if (hierarchy.size() > 0) {
|
|
|
|
title += " / ";
|
2017-08-30 18:52:05 -04:00
|
|
|
}
|
2017-08-17 15:02:21 -04:00
|
|
|
}
|
2017-08-30 18:52:05 -04:00
|
|
|
title.append(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->title()));
|
2017-08-17 15:02:21 -04:00
|
|
|
m_ui->titleLabel->setText(title);
|
|
|
|
|
2017-08-30 18:52:05 -04:00
|
|
|
m_ui->usernameLabel->setText(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->username()));
|
2017-08-17 15:02:21 -04:00
|
|
|
|
|
|
|
if (!config()->get("security/hidepassworddetails").toBool()) {
|
2017-08-30 18:52:05 -04:00
|
|
|
m_ui->passwordLabel->setText(shortPassword(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password())));
|
|
|
|
m_ui->passwordLabel->setToolTip(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password()));
|
2017-08-17 15:02:21 -04:00
|
|
|
} else {
|
2017-08-30 18:52:05 -04:00
|
|
|
m_ui->passwordLabel->setText("****");
|
2017-08-17 15:02:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
QString url = m_currentEntry->webUrl();
|
2017-10-25 00:01:29 -04:00
|
|
|
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
|
2017-10-25 23:29:01 -04:00
|
|
|
url = QString("<a href=\"%1\">%2</a>").arg(url).arg(shortUrl(m_currentEntry->displayUrl()));
|
2017-08-17 15:02:21 -04:00
|
|
|
m_ui->urlLabel->setOpenExternalLinks(true);
|
|
|
|
} else {
|
2017-10-25 00:01:29 -04:00
|
|
|
// Fallback to the raw url string
|
2017-08-17 15:02:21 -04:00
|
|
|
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);
|
|
|
|
}
|
2017-09-27 13:18:13 -04:00
|
|
|
|
|
|
|
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)) {
|
2017-10-24 08:08:22 -04:00
|
|
|
value = "<i>" + tr("[PROTECTED]") + "</i>";
|
2017-09-27 13:18:13 -04:00
|
|
|
}
|
|
|
|
attributesText.append(QString("<b>%1</b>: %2<br/>").arg(key, value));
|
|
|
|
}
|
|
|
|
m_ui->attributesEdit->setText(attributesText);
|
|
|
|
}
|
2017-09-29 14:37:37 -04:00
|
|
|
|
|
|
|
m_ui->autotypeTree->clear();
|
|
|
|
AutoTypeAssociations* autotypeAssociations = m_currentEntry->autoTypeAssociations();
|
2017-10-24 08:08:22 -04:00
|
|
|
QList<QTreeWidgetItem*> items;
|
2017-09-29 14:37:37 -04:00
|
|
|
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);
|
|
|
|
}
|
2017-09-29 16:02:07 -04:00
|
|
|
|
|
|
|
if (m_ui->tabWidget->isTabEnabled(m_selectedTabEntry)) {
|
|
|
|
m_ui->tabWidget->setCurrentIndex(m_selectedTabEntry);
|
|
|
|
}
|
2017-08-17 15:02:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void DetailsWidget::getSelectedGroup(Group* selectedGroup)
|
|
|
|
{
|
|
|
|
m_currentGroup = selectedGroup;
|
|
|
|
|
|
|
|
if (!config()->get("GUI/HideDetailsView").toBool()) {
|
|
|
|
this->show();
|
|
|
|
}
|
|
|
|
|
|
|
|
m_ui->stackedWidget->setCurrentIndex(GroupPreview);
|
|
|
|
|
2017-09-29 14:37:37 -04:00
|
|
|
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);
|
|
|
|
|
2017-09-27 13:18:13 -04:00
|
|
|
|
2017-08-17 15:02:21 -04:00
|
|
|
m_ui->totpButton->hide();
|
|
|
|
m_ui->totpWidget->hide();
|
|
|
|
|
2017-09-28 14:48:49 -04:00
|
|
|
auto icon = m_currentGroup->iconPixmap();
|
|
|
|
if (icon.width() > 32 || icon.height() > 32) {
|
|
|
|
icon = icon.scaled(32, 32);
|
|
|
|
}
|
|
|
|
m_ui->entryIcon->setPixmap(icon);
|
2017-08-17 15:02:21 -04:00
|
|
|
|
|
|
|
QString title = " / ";
|
2017-09-27 13:18:13 -04:00
|
|
|
QStringList hierarchy = m_currentGroup->hierarchy();
|
2017-09-28 14:48:49 -04:00
|
|
|
hierarchy.removeFirst();
|
|
|
|
title += hierarchy.join(" / ");
|
|
|
|
if (hierarchy.size() > 0) {
|
|
|
|
title += " / ";
|
2017-08-17 15:02:21 -04:00
|
|
|
}
|
|
|
|
m_ui->titleLabel->setText(title);
|
|
|
|
|
2017-09-29 14:37:37 -04:00
|
|
|
QString notes = m_currentGroup->notes();
|
|
|
|
if (!notes.isEmpty()) {
|
|
|
|
m_ui->tabWidget->setTabEnabled(GroupNotesTab, true);
|
|
|
|
m_ui->notesEdit->setText(notes);
|
|
|
|
}
|
2017-08-17 15:02:21 -04:00
|
|
|
|
|
|
|
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"));
|
|
|
|
}
|
2017-09-29 16:02:07 -04:00
|
|
|
|
|
|
|
if (m_ui->tabWidget->isTabEnabled(m_selectedTabGroup)) {
|
|
|
|
m_ui->tabWidget->setCurrentIndex(m_selectedTabGroup);
|
|
|
|
}
|
2017-08-17 15:02:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void DetailsWidget::updateTotp()
|
|
|
|
{
|
2017-09-27 13:18:13 -04:00
|
|
|
if (m_locked) {
|
|
|
|
m_timer->stop();
|
|
|
|
return;
|
|
|
|
}
|
2017-08-17 15:02:21 -04:00
|
|
|
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));
|
2017-10-24 08:08:22 -04:00
|
|
|
newurl.append("…");
|
2017-08-17 15:02:21 -04:00
|
|
|
newurl.append(url.right(20));
|
|
|
|
return newurl;
|
|
|
|
}
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
2017-08-30 18:52:05 -04:00
|
|
|
QString DetailsWidget::shortPassword(QString password)
|
2017-08-17 15:02:21 -04:00
|
|
|
{
|
2017-08-30 18:52:05 -04:00
|
|
|
QString newpassword = "";
|
|
|
|
if (password.length() > 60) {
|
|
|
|
newpassword.append(password.left(50));
|
2017-10-24 08:08:22 -04:00
|
|
|
newpassword.append("…");
|
2017-08-30 18:52:05 -04:00
|
|
|
return newpassword;
|
|
|
|
}
|
|
|
|
return password;
|
2017-08-17 15:02:21 -04:00
|
|
|
}
|
|
|
|
|
2017-08-30 18:52:05 -04:00
|
|
|
void DetailsWidget::hideDetails()
|
2017-08-17 15:02:21 -04:00
|
|
|
{
|
2017-08-30 18:52:05 -04:00
|
|
|
this->hide();
|
|
|
|
}
|
2017-09-27 13:18:13 -04:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-29 16:02:07 -04:00
|
|
|
void DetailsWidget::updateTabIndex(int index) {
|
|
|
|
if (m_ui->stackedWidget->currentIndex() == GroupPreview) {
|
|
|
|
m_selectedTabGroup = index;
|
|
|
|
} else {
|
|
|
|
m_selectedTabEntry = index;
|
|
|
|
}
|
2017-10-25 23:29:01 -04:00
|
|
|
}
|