From df95ad6543393d277fd9d74d58f3c3005f679f7e Mon Sep 17 00:00:00 2001 From: w15dev Date: Tue, 7 Jan 2025 23:13:10 +0300 Subject: [PATCH 01/10] Add New Entry Attachments dialog and functionality Fixes #11506 --- share/translations/keepassxc_en.ts | 31 ++++++++ src/CMakeLists.txt | 1 + src/gui/entry/EntryAttachmentsWidget.cpp | 17 +++++ src/gui/entry/EntryAttachmentsWidget.h | 1 + src/gui/entry/EntryAttachmentsWidget.ui | 10 +++ src/gui/entry/NewEntryAttachmentsDialog.cpp | 79 +++++++++++++++++++++ src/gui/entry/NewEntryAttachmentsDialog.h | 54 ++++++++++++++ src/gui/entry/NewEntryAttachmentsDialog.ui | 55 ++++++++++++++ 8 files changed, 248 insertions(+) create mode 100644 src/gui/entry/NewEntryAttachmentsDialog.cpp create mode 100644 src/gui/entry/NewEntryAttachmentsDialog.h create mode 100644 src/gui/entry/NewEntryAttachmentsDialog.ui diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index 5d52cc5e69..b999f2679f 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -3980,6 +3980,10 @@ Error: %1 Would you like to overwrite the existing attachment? + + New + + EntryAttributesModel @@ -6349,6 +6353,33 @@ Expect some bugs and minor issues, this version is meant for testing purposes. + + NewEntryAttachmentsDialog + + New entry attachments + + + + File name + + + + File contents... + + + + Attachment name cannot be empty + + + + Attachment with the same name already exists + + + + Save attachment + + + NixUtils diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5c7326b5c5..59c4587f3f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -157,6 +157,7 @@ set(gui_SOURCES gui/entry/EntryAttachmentsModel.cpp gui/entry/EntryAttachmentsWidget.cpp gui/entry/EntryAttributesModel.cpp + gui/entry/NewEntryAttachmentsDialog.cpp gui/entry/EntryHistoryModel.cpp gui/entry/EntryModel.cpp gui/entry/EntryView.cpp diff --git a/src/gui/entry/EntryAttachmentsWidget.cpp b/src/gui/entry/EntryAttachmentsWidget.cpp index d514804f8e..a649844980 100644 --- a/src/gui/entry/EntryAttachmentsWidget.cpp +++ b/src/gui/entry/EntryAttachmentsWidget.cpp @@ -16,6 +16,7 @@ */ #include "EntryAttachmentsWidget.h" +#include "NewEntryAttachmentsDialog.h" #include "ui_EntryAttachmentsWidget.h" #include @@ -68,6 +69,7 @@ EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) connect(m_ui->saveAttachmentButton, SIGNAL(clicked()), SLOT(saveSelectedAttachments())); connect(m_ui->openAttachmentButton, SIGNAL(clicked()), SLOT(openSelectedAttachments())); connect(m_ui->addAttachmentButton, SIGNAL(clicked()), SLOT(insertAttachments())); + connect(m_ui->newAttachmentButton, SIGNAL(clicked()), SLOT(newAttachments())); connect(m_ui->removeAttachmentButton, SIGNAL(clicked()), SLOT(removeSelectedAttachments())); connect(m_ui->renameAttachmentButton, SIGNAL(clicked()), SLOT(renameSelectedAttachments())); @@ -163,6 +165,20 @@ void EntryAttachmentsWidget::insertAttachments() emit widgetUpdated(); } +void EntryAttachmentsWidget::newAttachments() +{ + Q_ASSERT(m_entryAttachments); + Q_ASSERT(!isReadOnly()); + if (isReadOnly()) { + return; + } + + auto newWidnow = new NewEntryAttachmentsDialog(m_entryAttachments, this); + if (newWidnow->exec() == QDialog::Accepted) { + emit widgetUpdated(); + } +} + void EntryAttachmentsWidget::removeSelectedAttachments() { Q_ASSERT(m_entryAttachments); @@ -300,6 +316,7 @@ void EntryAttachmentsWidget::updateButtonsEnabled() const bool hasSelection = m_ui->attachmentsView->selectionModel()->hasSelection(); m_ui->addAttachmentButton->setEnabled(!m_readOnly); + m_ui->newAttachmentButton->setEnabled(!m_readOnly); m_ui->removeAttachmentButton->setEnabled(hasSelection && !m_readOnly); m_ui->renameAttachmentButton->setEnabled(hasSelection && !m_readOnly); diff --git a/src/gui/entry/EntryAttachmentsWidget.h b/src/gui/entry/EntryAttachmentsWidget.h index 0f104a82a6..5424dfa64d 100644 --- a/src/gui/entry/EntryAttachmentsWidget.h +++ b/src/gui/entry/EntryAttachmentsWidget.h @@ -57,6 +57,7 @@ public slots: private slots: void insertAttachments(); + void newAttachments(); void removeSelectedAttachments(); void renameSelectedAttachments(); void saveSelectedAttachments(); diff --git a/src/gui/entry/EntryAttachmentsWidget.ui b/src/gui/entry/EntryAttachmentsWidget.ui index e685813b3d..a0c9f37830 100644 --- a/src/gui/entry/EntryAttachmentsWidget.ui +++ b/src/gui/entry/EntryAttachmentsWidget.ui @@ -51,6 +51,16 @@ 0 + + + + false + + + New + + + diff --git a/src/gui/entry/NewEntryAttachmentsDialog.cpp b/src/gui/entry/NewEntryAttachmentsDialog.cpp new file mode 100644 index 0000000000..d7c4ee8e80 --- /dev/null +++ b/src/gui/entry/NewEntryAttachmentsDialog.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 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 "NewEntryAttachmentsDialog.h" +#include "core/EntryAttachments.h" +#include "ui_NewEntryAttachmentsDialog.h" + +#include +#include + +NewEntryAttachmentsDialog::NewEntryAttachmentsDialog(QPointer attachments, QWidget* parent) + : QDialog(parent) + , m_attachments(std::move(attachments)) + , m_ui(new Ui::NewEntryAttachmentsDialog) +{ + m_ui->setupUi(this); + + connect(m_ui->dialogButtons, SIGNAL(accepted()), this, SLOT(saveAttachment())); + connect(m_ui->dialogButtons, SIGNAL(rejected()), this, SLOT(reject())); + connect(m_ui->titleEdit, SIGNAL(textChanged(const QString&)), this, SLOT(fileNameTextChanged(const QString&))); + + fileNameTextChanged(m_ui->titleEdit->text()); +} + +NewEntryAttachmentsDialog::~NewEntryAttachmentsDialog() = default; + +std::optional NewEntryAttachmentsDialog::ValidateFileName(const QString& fileName) const +{ + if (fileName.isEmpty()) { + return tr("Attachment name cannot be empty"); + } + + if (m_attachments->hasKey(fileName)) { + return tr("Attachment with the same name already exists"); + } + + return std::nullopt; +} + +void NewEntryAttachmentsDialog::saveAttachment() +{ + auto fileName = m_ui->titleEdit->text(); + auto text = m_ui->attachmentTextEdit->toPlainText().toUtf8(); + + if (auto error = ValidateFileName(fileName); error) { + QMessageBox::warning(this, tr("Save attachment"), error.value()); + return; + } + + m_attachments->set(fileName, text); + + accept(); +} + +void NewEntryAttachmentsDialog::fileNameTextChanged(const QString& fileName) +{ + const auto error = ValidateFileName(fileName); + + m_ui->errorLabel->setText(error.value_or(QString{})); + m_ui->errorLabel->setVisible(error.has_value()); + + if (auto okButton = m_ui->dialogButtons->button(QDialogButtonBox::Ok); okButton) { + okButton->setDisabled(error.has_value()); + } +} diff --git a/src/gui/entry/NewEntryAttachmentsDialog.h b/src/gui/entry/NewEntryAttachmentsDialog.h new file mode 100644 index 0000000000..ff861fd23e --- /dev/null +++ b/src/gui/entry/NewEntryAttachmentsDialog.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 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 NEWENTRYATTACHMENTSWIDGET_H +#define NEWENTRYATTACHMENTSWIDGET_H + +#include +#include + +#include + +namespace Ui +{ + class NewEntryAttachmentsDialog; +} + +class QByteArray; +class EntryAttachments; + +class NewEntryAttachmentsDialog : public QDialog +{ + Q_OBJECT +public: + explicit NewEntryAttachmentsDialog(QPointer attachments, QWidget* parent = nullptr); + ~NewEntryAttachmentsDialog() override; + +private slots: + void saveAttachment(); + void fileNameTextChanged(const QString& fileName); + +private: + std::optional ValidateFileName(const QString& fileName) const; + +private: + QPointer m_attachments; + + QScopedPointer m_ui; +}; + +#endif // NEWENTRYATTACHMENTSWIDGET_H diff --git a/src/gui/entry/NewEntryAttachmentsDialog.ui b/src/gui/entry/NewEntryAttachmentsDialog.ui new file mode 100644 index 0000000000..7c7397bb8f --- /dev/null +++ b/src/gui/entry/NewEntryAttachmentsDialog.ui @@ -0,0 +1,55 @@ + + + NewEntryAttachmentsDialog + + + + 0 + 0 + 402 + 300 + + + + New entry attachments + + + + + + File name + + + + + + + true + + + color: #FF9696 + + + + + + + + + + File contents... + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + From e00aec9034eb3b6b00e37b93d08fc15c582d09c7 Mon Sep 17 00:00:00 2001 From: w15dev Date: Wed, 8 Jan 2025 21:17:25 +0300 Subject: [PATCH 02/10] Add preview functionality for entry attachments Supported types: - text - image Fixes #11506 --- share/translations/keepassxc_en.ts | 50 ++++++-- src/CMakeLists.txt | 1 + ...ntsDialog.ui => EntryAttachmentsDialog.ui} | 6 +- src/gui/entry/EntryAttachmentsWidget.cpp | 26 ++++- src/gui/entry/EntryAttachmentsWidget.h | 1 + src/gui/entry/EntryAttachmentsWidget.ui | 10 ++ src/gui/entry/NewEntryAttachmentsDialog.cpp | 12 +- src/gui/entry/NewEntryAttachmentsDialog.h | 4 +- .../entry/PreviewEntryAttachmentsDialog.cpp | 109 ++++++++++++++++++ src/gui/entry/PreviewEntryAttachmentsDialog.h | 66 +++++++++++ 10 files changed, 263 insertions(+), 22 deletions(-) rename src/gui/entry/{NewEntryAttachmentsDialog.ui => EntryAttachmentsDialog.ui} (89%) create mode 100644 src/gui/entry/PreviewEntryAttachmentsDialog.cpp create mode 100644 src/gui/entry/PreviewEntryAttachmentsDialog.h diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index b999f2679f..209397128d 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -3826,6 +3826,21 @@ This may cause the affected plugins to malfunction. + + EntryAttachmentsDialog + + Form + + + + File name + + + + File contents... + + + EntryAttachmentsModel @@ -3984,6 +3999,14 @@ Would you like to overwrite the existing attachment? New + + Preview + + + + Failed to preview an attachment: Attachment not found + + EntryAttributesModel @@ -6355,18 +6378,6 @@ Expect some bugs and minor issues, this version is meant for testing purposes. NewEntryAttachmentsDialog - - New entry attachments - - - - File name - - - - File contents... - - Attachment name cannot be empty @@ -6379,6 +6390,10 @@ Expect some bugs and minor issues, this version is meant for testing purposes.Save attachment + + New entry attachment + + NixUtils @@ -7145,6 +7160,17 @@ Do you want to overwrite it? + + PreviewEntryAttachmentsDialog + + Preview entry attachment + + + + No preview available + + + QMessageBox diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 59c4587f3f..84c6090ba5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -158,6 +158,7 @@ set(gui_SOURCES gui/entry/EntryAttachmentsWidget.cpp gui/entry/EntryAttributesModel.cpp gui/entry/NewEntryAttachmentsDialog.cpp + gui/entry/PreviewEntryAttachmentsDialog.cpp gui/entry/EntryHistoryModel.cpp gui/entry/EntryModel.cpp gui/entry/EntryView.cpp diff --git a/src/gui/entry/NewEntryAttachmentsDialog.ui b/src/gui/entry/EntryAttachmentsDialog.ui similarity index 89% rename from src/gui/entry/NewEntryAttachmentsDialog.ui rename to src/gui/entry/EntryAttachmentsDialog.ui index 7c7397bb8f..2b13ea0be1 100644 --- a/src/gui/entry/NewEntryAttachmentsDialog.ui +++ b/src/gui/entry/EntryAttachmentsDialog.ui @@ -1,7 +1,7 @@ - NewEntryAttachmentsDialog - + EntryAttachmentsDialog + 0 @@ -11,7 +11,7 @@ - New entry attachments + Form diff --git a/src/gui/entry/EntryAttachmentsWidget.cpp b/src/gui/entry/EntryAttachmentsWidget.cpp index a649844980..c1688153a5 100644 --- a/src/gui/entry/EntryAttachmentsWidget.cpp +++ b/src/gui/entry/EntryAttachmentsWidget.cpp @@ -17,6 +17,7 @@ #include "EntryAttachmentsWidget.h" #include "NewEntryAttachmentsDialog.h" +#include "PreviewEntryAttachmentsDialog.h" #include "ui_EntryAttachmentsWidget.h" #include @@ -26,11 +27,11 @@ #include #include "EntryAttachmentsModel.h" -#include "core/Config.h" #include "core/EntryAttachments.h" #include "core/Tools.h" #include "gui/FileDialog.h" #include "gui/MessageBox.h" +#include EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) : QWidget(parent) @@ -70,6 +71,7 @@ EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) connect(m_ui->openAttachmentButton, SIGNAL(clicked()), SLOT(openSelectedAttachments())); connect(m_ui->addAttachmentButton, SIGNAL(clicked()), SLOT(insertAttachments())); connect(m_ui->newAttachmentButton, SIGNAL(clicked()), SLOT(newAttachments())); + connect(m_ui->previewButton, SIGNAL(clicked()), SLOT(previewAttachments())); connect(m_ui->removeAttachmentButton, SIGNAL(clicked()), SLOT(removeSelectedAttachments())); connect(m_ui->renameAttachmentButton, SIGNAL(clicked()), SLOT(renameSelectedAttachments())); @@ -173,12 +175,28 @@ void EntryAttachmentsWidget::newAttachments() return; } - auto newWidnow = new NewEntryAttachmentsDialog(m_entryAttachments, this); - if (newWidnow->exec() == QDialog::Accepted) { + NewEntryAttachmentsDialog newEntryDialog{m_entryAttachments, this}; + if (newEntryDialog.exec() == QDialog::Accepted) { emit widgetUpdated(); } } +void EntryAttachmentsWidget::previewAttachments() +{ + Q_ASSERT(m_entryAttachments); + + const auto index = m_ui->attachmentsView->selectionModel()->selectedIndexes().first(); + if (!index.isValid()) { + qWarning() << tr("Failed to preview an attachment: Attachment not found"); + return; + } + + PreviewEntryAttachmentsDialog previewDialog{m_entryAttachments, this}; + previewDialog.setAttachment(m_attachmentsModel->keyByIndex(index)); + + previewDialog.exec(); +} + void EntryAttachmentsWidget::removeSelectedAttachments() { Q_ASSERT(m_entryAttachments); @@ -321,12 +339,14 @@ void EntryAttachmentsWidget::updateButtonsEnabled() m_ui->renameAttachmentButton->setEnabled(hasSelection && !m_readOnly); m_ui->saveAttachmentButton->setEnabled(hasSelection); + m_ui->previewButton->setEnabled(hasSelection); m_ui->openAttachmentButton->setEnabled(hasSelection); } void EntryAttachmentsWidget::updateButtonsVisible() { m_ui->addAttachmentButton->setVisible(m_buttonsVisible && !m_readOnly); + m_ui->newAttachmentButton->setVisible(m_buttonsVisible && !m_readOnly); m_ui->removeAttachmentButton->setVisible(m_buttonsVisible && !m_readOnly); m_ui->renameAttachmentButton->setVisible(m_buttonsVisible && !m_readOnly); } diff --git a/src/gui/entry/EntryAttachmentsWidget.h b/src/gui/entry/EntryAttachmentsWidget.h index 5424dfa64d..3dfdf0ef6f 100644 --- a/src/gui/entry/EntryAttachmentsWidget.h +++ b/src/gui/entry/EntryAttachmentsWidget.h @@ -58,6 +58,7 @@ public slots: private slots: void insertAttachments(); void newAttachments(); + void previewAttachments(); void removeSelectedAttachments(); void renameSelectedAttachments(); void saveSelectedAttachments(); diff --git a/src/gui/entry/EntryAttachmentsWidget.ui b/src/gui/entry/EntryAttachmentsWidget.ui index a0c9f37830..16e578613c 100644 --- a/src/gui/entry/EntryAttachmentsWidget.ui +++ b/src/gui/entry/EntryAttachmentsWidget.ui @@ -100,6 +100,16 @@ + + + + false + + + Preview + + + diff --git a/src/gui/entry/NewEntryAttachmentsDialog.cpp b/src/gui/entry/NewEntryAttachmentsDialog.cpp index d7c4ee8e80..9bff623c38 100644 --- a/src/gui/entry/NewEntryAttachmentsDialog.cpp +++ b/src/gui/entry/NewEntryAttachmentsDialog.cpp @@ -17,7 +17,7 @@ #include "NewEntryAttachmentsDialog.h" #include "core/EntryAttachments.h" -#include "ui_NewEntryAttachmentsDialog.h" +#include "ui_EntryAttachmentsDialog.h" #include #include @@ -25,10 +25,18 @@ NewEntryAttachmentsDialog::NewEntryAttachmentsDialog(QPointer attachments, QWidget* parent) : QDialog(parent) , m_attachments(std::move(attachments)) - , m_ui(new Ui::NewEntryAttachmentsDialog) + , m_ui(new Ui::EntryAttachmentsDialog) { + Q_ASSERT(m_attachments); + m_ui->setupUi(this); + setWindowTitle(tr("New entry attachment")); + + m_ui->dialogButtons->clear(); + m_ui->dialogButtons->addButton(QDialogButtonBox::Ok); + m_ui->dialogButtons->addButton(QDialogButtonBox::Cancel); + connect(m_ui->dialogButtons, SIGNAL(accepted()), this, SLOT(saveAttachment())); connect(m_ui->dialogButtons, SIGNAL(rejected()), this, SLOT(reject())); connect(m_ui->titleEdit, SIGNAL(textChanged(const QString&)), this, SLOT(fileNameTextChanged(const QString&))); diff --git a/src/gui/entry/NewEntryAttachmentsDialog.h b/src/gui/entry/NewEntryAttachmentsDialog.h index ff861fd23e..09dfdaed6d 100644 --- a/src/gui/entry/NewEntryAttachmentsDialog.h +++ b/src/gui/entry/NewEntryAttachmentsDialog.h @@ -25,7 +25,7 @@ namespace Ui { - class NewEntryAttachmentsDialog; + class EntryAttachmentsDialog; } class QByteArray; @@ -48,7 +48,7 @@ private slots: private: QPointer m_attachments; - QScopedPointer m_ui; + QScopedPointer m_ui; }; #endif // NEWENTRYATTACHMENTSWIDGET_H diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp new file mode 100644 index 0000000000..51d1180611 --- /dev/null +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2021 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 "PreviewEntryAttachmentsDialog.h" +#include "core/EntryAttachments.h" +#include "ui_EntryAttachmentsDialog.h" + +#include +#include +#include +#include + +PreviewEntryAttachmentsDialog::PreviewEntryAttachmentsDialog(QPointer attachments, QWidget* parent) + : QDialog(parent) + , m_attachments(std::move(attachments)) + , m_ui(new Ui::EntryAttachmentsDialog) +{ + Q_ASSERT(m_attachments); + + m_ui->setupUi(this); + + setWindowTitle(tr("Preview entry attachment")); + + m_ui->titleEdit->setReadOnly(true); + m_ui->attachmentTextEdit->setReadOnly(true); + + m_ui->dialogButtons->clear(); + m_ui->dialogButtons->addButton(QDialogButtonBox::Close); + + connect(m_ui->dialogButtons, SIGNAL(rejected()), this, SLOT(reject())); +} + +PreviewEntryAttachmentsDialog::~PreviewEntryAttachmentsDialog() = default; + +void PreviewEntryAttachmentsDialog::setAttachment(const QString& name) +{ + m_name = name; + m_type = attachmentType(m_name); + + m_ui->titleEdit->setText(m_name); + + update(); +} + +void PreviewEntryAttachmentsDialog::update() +{ + if (m_type == AttachmentType::Unknown) { + updateTextAttachment(tr("No preview available").toUtf8()); + } else if (const auto data = m_attachments->value(m_name); m_type == AttachmentType::Image) { + updateImageAttachment(data); + } else if (m_type == AttachmentType::PlantText) { + updateTextAttachment(data); + } +} + +void PreviewEntryAttachmentsDialog::updateTextAttachment(const QByteArray& data) +{ + m_ui->attachmentTextEdit->setPlainText(QString::fromUtf8(data)); +} + +void PreviewEntryAttachmentsDialog::updateImageAttachment(const QByteArray& data) +{ + QImage image{}; + image.loadFromData(data); + + m_ui->attachmentTextEdit->clear(); + auto cursor = m_ui->attachmentTextEdit->textCursor(); + + cursor.insertImage(image.scaled(m_ui->attachmentTextEdit->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); +} + +PreviewEntryAttachmentsDialog::AttachmentType PreviewEntryAttachmentsDialog::attachmentType(const QString& name) const +{ + const auto data = m_attachments->value(name); + + QMimeDatabase mimeDb{}; + const auto mime = mimeDb.mimeTypeForData(data); + + if (auto mimeName = mime.name(); mimeName.startsWith("image/")) { + return AttachmentType::Image; + } else if (mimeName.startsWith("text/")) { + return AttachmentType::PlantText; + } + + return AttachmentType::Unknown; +} + +void PreviewEntryAttachmentsDialog::resizeEvent(QResizeEvent* event) +{ + QDialog::resizeEvent(event); + + if (m_type == AttachmentType::Image) { + update(); + } +} diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.h b/src/gui/entry/PreviewEntryAttachmentsDialog.h new file mode 100644 index 0000000000..22221d72bc --- /dev/null +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 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 PREVIEWENTRYATTACHMENTSWIDGET_H +#define PREVIEWENTRYATTACHMENTSWIDGET_H + +#include +#include + +namespace Ui +{ + class EntryAttachmentsDialog; +} + +class QByteArray; +class EntryAttachments; + +class PreviewEntryAttachmentsDialog : public QDialog +{ + Q_OBJECT +public: + explicit PreviewEntryAttachmentsDialog(QPointer attachments, QWidget* parent = nullptr); + ~PreviewEntryAttachmentsDialog() override; + + void setAttachment(const QString& name); + +private: + enum class AttachmentType + { + Image, + PlantText, + Unknown + }; + + void resizeEvent(QResizeEvent* event) override; + + AttachmentType attachmentType(const QString& name) const; + + void update(); + void updateTextAttachment(const QByteArray& data); + void updateImageAttachment(const QByteArray& data); + +private: + QPointer m_attachments; + + QScopedPointer m_ui; + + QString m_name; + AttachmentType m_type{AttachmentType::Unknown}; +}; + +#endif // PREVIEWENTRYATTACHMENTSWIDGET_H From 3f91ffea333cbcc9c427e336b9ef1788fb422191 Mon Sep 17 00:00:00 2001 From: w15dev Date: Thu, 9 Jan 2025 19:33:40 +0300 Subject: [PATCH 03/10] Fix read-only preview Refactor EntryAttachmentsWidget and PreviewEntryAttachmentsDialog to remove unnecessary parent references Fixes #11506 --- src/gui/entry/EntryAttachmentsWidget.cpp | 8 ++++---- src/gui/entry/EntryAttachmentsWidget.ui | 4 ++-- src/gui/entry/PreviewEntryAttachmentsDialog.cpp | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/gui/entry/EntryAttachmentsWidget.cpp b/src/gui/entry/EntryAttachmentsWidget.cpp index c1688153a5..6b888a4d54 100644 --- a/src/gui/entry/EntryAttachmentsWidget.cpp +++ b/src/gui/entry/EntryAttachmentsWidget.cpp @@ -71,7 +71,7 @@ EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) connect(m_ui->openAttachmentButton, SIGNAL(clicked()), SLOT(openSelectedAttachments())); connect(m_ui->addAttachmentButton, SIGNAL(clicked()), SLOT(insertAttachments())); connect(m_ui->newAttachmentButton, SIGNAL(clicked()), SLOT(newAttachments())); - connect(m_ui->previewButton, SIGNAL(clicked()), SLOT(previewAttachments())); + connect(m_ui->previewAttachmentButton, SIGNAL(clicked()), SLOT(previewAttachments())); connect(m_ui->removeAttachmentButton, SIGNAL(clicked()), SLOT(removeSelectedAttachments())); connect(m_ui->renameAttachmentButton, SIGNAL(clicked()), SLOT(renameSelectedAttachments())); @@ -175,7 +175,7 @@ void EntryAttachmentsWidget::newAttachments() return; } - NewEntryAttachmentsDialog newEntryDialog{m_entryAttachments, this}; + NewEntryAttachmentsDialog newEntryDialog{m_entryAttachments}; if (newEntryDialog.exec() == QDialog::Accepted) { emit widgetUpdated(); } @@ -191,7 +191,7 @@ void EntryAttachmentsWidget::previewAttachments() return; } - PreviewEntryAttachmentsDialog previewDialog{m_entryAttachments, this}; + PreviewEntryAttachmentsDialog previewDialog{m_entryAttachments}; previewDialog.setAttachment(m_attachmentsModel->keyByIndex(index)); previewDialog.exec(); @@ -339,7 +339,7 @@ void EntryAttachmentsWidget::updateButtonsEnabled() m_ui->renameAttachmentButton->setEnabled(hasSelection && !m_readOnly); m_ui->saveAttachmentButton->setEnabled(hasSelection); - m_ui->previewButton->setEnabled(hasSelection); + m_ui->previewAttachmentButton->setEnabled(hasSelection); m_ui->openAttachmentButton->setEnabled(hasSelection); } diff --git a/src/gui/entry/EntryAttachmentsWidget.ui b/src/gui/entry/EntryAttachmentsWidget.ui index 16e578613c..99a16ca4e9 100644 --- a/src/gui/entry/EntryAttachmentsWidget.ui +++ b/src/gui/entry/EntryAttachmentsWidget.ui @@ -7,7 +7,7 @@ 0 0 337 - 289 + 294 @@ -101,7 +101,7 @@ - + false diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp index 51d1180611..91ddb16737 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp @@ -37,6 +37,7 @@ PreviewEntryAttachmentsDialog::PreviewEntryAttachmentsDialog(QPointertitleEdit->setReadOnly(true); m_ui->attachmentTextEdit->setReadOnly(true); + m_ui->errorLabel->setVisible(false); m_ui->dialogButtons->clear(); m_ui->dialogButtons->addButton(QDialogButtonBox::Close); From 2153105689d275f4b68b9b8c78991f0b8a9fffda Mon Sep 17 00:00:00 2001 From: w15dev Date: Fri, 10 Jan 2025 01:28:33 +0300 Subject: [PATCH 04/10] Enhance attachment type detection with format compatibility checks Fixes #11506 --- .../entry/PreviewEntryAttachmentsDialog.cpp | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp index 91ddb16737..cdd1786b52 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp @@ -22,7 +22,39 @@ #include #include #include -#include +#include + +#include +#include + +namespace +{ + bool isTextCompatibleFormat(const QString& format) noexcept + { + constexpr static std::array Formats = { + "text/", + "application/json", + "application/xml", + "application/soap+xml", + "application/x-yaml", + "application/protobuf", + }; + + return std::any_of( + std::cbegin(Formats), std::cend(Formats), [&format](const auto& f) { return format.startsWith(f); }); + } + + bool isImageCompatibleFormat(const QString& format) noexcept + { + constexpr static std::array Formats = { + "image/", + }; + + return std::any_of( + std::cbegin(Formats), std::cend(Formats), [&format](const auto& f) { return format.startsWith(f); }); + } + +} // namespace PreviewEntryAttachmentsDialog::PreviewEntryAttachmentsDialog(QPointer attachments, QWidget* parent) : QDialog(parent) @@ -91,12 +123,16 @@ PreviewEntryAttachmentsDialog::AttachmentType PreviewEntryAttachmentsDialog::att QMimeDatabase mimeDb{}; const auto mime = mimeDb.mimeTypeForData(data); - if (auto mimeName = mime.name(); mimeName.startsWith("image/")) { + auto mimeName = mime.name(); + + if (isImageCompatibleFormat(mimeName)) { return AttachmentType::Image; - } else if (mimeName.startsWith("text/")) { + } else if (isTextCompatibleFormat(mimeName)) { return AttachmentType::PlantText; } + qWarning() << tr("Unknown attachment type: %1").arg(mimeName); + return AttachmentType::Unknown; } From deed1789e5a6b498cf10a3597874dc8ce0912cff Mon Sep 17 00:00:00 2001 From: w15dev Date: Sat, 11 Jan 2025 00:59:14 +0300 Subject: [PATCH 05/10] Add MimeTypes handling and related tests for attachment previews --- src/CMakeLists.txt | 1 + src/core/MimeTypes.cpp | 46 +++++++++++ src/core/MimeTypes.h | 33 ++++++++ src/gui/entry/NewEntryAttachmentsDialog.cpp | 2 +- src/gui/entry/NewEntryAttachmentsDialog.h | 7 +- .../entry/PreviewEntryAttachmentsDialog.cpp | 53 ++---------- src/gui/entry/PreviewEntryAttachmentsDialog.h | 18 ++--- tests/CMakeLists.txt | 3 + tests/TestMimeTypes.cpp | 80 +++++++++++++++++++ tests/TestMimeTypes.h | 29 +++++++ 10 files changed, 206 insertions(+), 66 deletions(-) create mode 100644 src/core/MimeTypes.cpp create mode 100644 src/core/MimeTypes.h create mode 100644 tests/TestMimeTypes.cpp create mode 100644 tests/TestMimeTypes.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 84c6090ba5..2d665c575b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -59,6 +59,7 @@ set(core_SOURCES core/Tools.cpp core/Totp.cpp core/Translator.cpp + core/MimeTypes.cpp cli/Utils.cpp cli/TextStream.cpp crypto/Crypto.cpp diff --git a/src/core/MimeTypes.cpp b/src/core/MimeTypes.cpp new file mode 100644 index 0000000000..d4ccae75eb --- /dev/null +++ b/src/core/MimeTypes.cpp @@ -0,0 +1,46 @@ +#include "MimeTypes.h" + +#include + +namespace +{ + + bool isTextCompatibleFormat(const QString& format) + { + constexpr static std::array Formats = { + "text/", + "application/json", + "application/xml", + "application/soap+xml", + "application/x-yaml", + "application/protobuf", + }; + + return std::any_of( + std::cbegin(Formats), std::cend(Formats), [&format](const auto& f) { return format.startsWith(f); }); + } + + bool isImageCompatibleFormat(const QString& format) + { + constexpr static std::array Formats = {"image/"}; + + return std::any_of( + std::cbegin(Formats), std::cend(Formats), [&format](const auto& f) { return format.startsWith(f); }); + } + +} // namespace + +namespace core +{ + MimeType toMimeType(const QString& mimeName) + { + if (isImageCompatibleFormat(mimeName)) { + return MimeType::Image; + } else if (isTextCompatibleFormat(mimeName)) { + return MimeType::PlantText; + } + + return MimeType::Unknown; + } + +} // namespace core diff --git a/src/core/MimeTypes.h b/src/core/MimeTypes.h new file mode 100644 index 0000000000..a748055a21 --- /dev/null +++ b/src/core/MimeTypes.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2025 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 . + */ + +#pragma once + +#include + +namespace core +{ + enum class MimeType : uint8_t + { + Image, + PlantText, + Unknown + }; + + MimeType toMimeType(const QString& mimeName); + +} // namespace core diff --git a/src/gui/entry/NewEntryAttachmentsDialog.cpp b/src/gui/entry/NewEntryAttachmentsDialog.cpp index 9bff623c38..84f290de66 100644 --- a/src/gui/entry/NewEntryAttachmentsDialog.cpp +++ b/src/gui/entry/NewEntryAttachmentsDialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 KeePassXC Team + * Copyright (C) 2025 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 diff --git a/src/gui/entry/NewEntryAttachmentsDialog.h b/src/gui/entry/NewEntryAttachmentsDialog.h index 09dfdaed6d..467a0f9867 100644 --- a/src/gui/entry/NewEntryAttachmentsDialog.h +++ b/src/gui/entry/NewEntryAttachmentsDialog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 KeePassXC Team + * Copyright (C) 2025 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 @@ -15,8 +15,7 @@ * along with this program. If not, see . */ -#ifndef NEWENTRYATTACHMENTSWIDGET_H -#define NEWENTRYATTACHMENTSWIDGET_H +#pragma once #include #include @@ -50,5 +49,3 @@ private slots: QScopedPointer m_ui; }; - -#endif // NEWENTRYATTACHMENTSWIDGET_H diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp index cdd1786b52..eeddbd0acb 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp @@ -24,37 +24,6 @@ #include #include -#include -#include - -namespace -{ - bool isTextCompatibleFormat(const QString& format) noexcept - { - constexpr static std::array Formats = { - "text/", - "application/json", - "application/xml", - "application/soap+xml", - "application/x-yaml", - "application/protobuf", - }; - - return std::any_of( - std::cbegin(Formats), std::cend(Formats), [&format](const auto& f) { return format.startsWith(f); }); - } - - bool isImageCompatibleFormat(const QString& format) noexcept - { - constexpr static std::array Formats = { - "image/", - }; - - return std::any_of( - std::cbegin(Formats), std::cend(Formats), [&format](const auto& f) { return format.startsWith(f); }); - } - -} // namespace PreviewEntryAttachmentsDialog::PreviewEntryAttachmentsDialog(QPointer attachments, QWidget* parent) : QDialog(parent) @@ -91,11 +60,11 @@ void PreviewEntryAttachmentsDialog::setAttachment(const QString& name) void PreviewEntryAttachmentsDialog::update() { - if (m_type == AttachmentType::Unknown) { + if (m_type == core::MimeType::Unknown) { updateTextAttachment(tr("No preview available").toUtf8()); - } else if (const auto data = m_attachments->value(m_name); m_type == AttachmentType::Image) { + } else if (const auto data = m_attachments->value(m_name); m_type == core::MimeType::Image) { updateImageAttachment(data); - } else if (m_type == AttachmentType::PlantText) { + } else if (m_type == core::MimeType::PlantText) { updateTextAttachment(data); } } @@ -116,31 +85,21 @@ void PreviewEntryAttachmentsDialog::updateImageAttachment(const QByteArray& data cursor.insertImage(image.scaled(m_ui->attachmentTextEdit->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } -PreviewEntryAttachmentsDialog::AttachmentType PreviewEntryAttachmentsDialog::attachmentType(const QString& name) const +core::MimeType PreviewEntryAttachmentsDialog::attachmentType(const QString& name) const { const auto data = m_attachments->value(name); QMimeDatabase mimeDb{}; const auto mime = mimeDb.mimeTypeForData(data); - auto mimeName = mime.name(); - - if (isImageCompatibleFormat(mimeName)) { - return AttachmentType::Image; - } else if (isTextCompatibleFormat(mimeName)) { - return AttachmentType::PlantText; - } - - qWarning() << tr("Unknown attachment type: %1").arg(mimeName); - - return AttachmentType::Unknown; + return core::toMimeType(mime.name()); } void PreviewEntryAttachmentsDialog::resizeEvent(QResizeEvent* event) { QDialog::resizeEvent(event); - if (m_type == AttachmentType::Image) { + if (m_type == core::MimeType::Image) { update(); } } diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.h b/src/gui/entry/PreviewEntryAttachmentsDialog.h index 22221d72bc..5191fdcf74 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.h +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.h @@ -15,8 +15,9 @@ * along with this program. If not, see . */ -#ifndef PREVIEWENTRYATTACHMENTSWIDGET_H -#define PREVIEWENTRYATTACHMENTSWIDGET_H +#pragma once + +#include "core/MimeTypes.h" #include #include @@ -39,16 +40,9 @@ class PreviewEntryAttachmentsDialog : public QDialog void setAttachment(const QString& name); private: - enum class AttachmentType - { - Image, - PlantText, - Unknown - }; - void resizeEvent(QResizeEvent* event) override; - AttachmentType attachmentType(const QString& name) const; + core::MimeType attachmentType(const QString& name) const; void update(); void updateTextAttachment(const QByteArray& data); @@ -60,7 +54,5 @@ class PreviewEntryAttachmentsDialog : public QDialog QScopedPointer m_ui; QString m_name; - AttachmentType m_type{AttachmentType::Unknown}; + core::MimeType m_type{core::MimeType::Unknown}; }; - -#endif // PREVIEWENTRYATTACHMENTSWIDGET_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8ed6868df0..4be77cd579 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -217,6 +217,9 @@ endif() add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp LIBS testsupport ${TEST_LIBRARIES}) +add_unit_test(NAME testmimetypes SOURCES TestMimeTypes.cpp + LIBS testsupport ${TEST_LIBRARIES}) + add_unit_test(NAME testtools SOURCES TestTools.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/TestMimeTypes.cpp b/tests/TestMimeTypes.cpp new file mode 100644 index 0000000000..6fee3fce98 --- /dev/null +++ b/tests/TestMimeTypes.cpp @@ -0,0 +1,80 @@ +#include "TestMimeTypes.h" + +#include + +#include + +QTEST_GUILESS_MAIN(TestMimeType) + +void TestMimeType::testMimePlantText() +{ + const std::array TextMimeTypes = { + "text/plain", // Plain text + "text/html", // HTML documents + "text/css", // CSS stylesheets + "text/javascript", // JavaScript files + "text/markdown", // Markdown documents + "text/xml", // XML documents + "text/rtf", // Rich Text Format + "text/vcard", // vCard files + "text/tab-separated-values", // Tab-separated values + "application/json", // JSON data + "application/xml", // XML data + "application/soap+xml", // SOAP messages + "application/x-yaml", // YAML data + "application/protobuf", // Protocol Buffers + }; + + for (const auto& mime : TextMimeTypes) { + QCOMPARE(core::toMimeType(mime), core::MimeType::PlantText); + } +} + +void TestMimeType::testMimeImage() +{ + constexpr static std::array ImageMimeTypes = { + "image/jpeg", // JPEG images + "image/png", // PNG images + "image/gif", // GIF images + "image/bmp", // BMP images + "image/webp", // WEBP images + "image/svg+xml" // SVG images + }; + + for (const auto& mime : ImageMimeTypes) { + QCOMPARE(core::toMimeType(mime), core::MimeType::Image); + } +} + +void TestMimeType::testMimeUnknown() +{ + constexpr static std::array UnknownMimeTypes = { + "audio/mpeg", // MPEG audio files + "video/mp4", // MP4 video files + "application/pdf", // PDF documents + "application/zip", // ZIP archives + "application/x-tar", // TAR archives + "application/x-rar-compressed", // RAR archives + "application/x-7z-compressed", // 7z archives + "application/x-shockwave-flash", // Adobe Flash files + "application/vnd.ms-excel", // Microsoft Excel files + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // Microsoft Excel (OpenXML) files + "application/vnd.ms-powerpoint", // Microsoft PowerPoint files + "application/vnd.openxmlformats-officedocument.presentationml.presentation", // Microsoft PowerPoint (OpenXML) + // files + "application/msword", // Microsoft Word files + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", // Microsoft Word (OpenXML) files + "application/vnd.oasis.opendocument.text", // OpenDocument Text + "application/vnd.oasis.opendocument.spreadsheet", // OpenDocument Spreadsheet + "application/vnd.oasis.opendocument.presentation", // OpenDocument Presentation + "application/x-httpd-php", // PHP files + "application/x-perl", // Perl scripts + "application/x-python", // Python scripts + "application/x-ruby", // Ruby scripts + "application/x-shellscript", // Shell scripts + }; + + for (const auto& mime : UnknownMimeTypes) { + QCOMPARE(core::toMimeType(mime), core::MimeType::Unknown); + } +} diff --git a/tests/TestMimeTypes.h b/tests/TestMimeTypes.h new file mode 100644 index 0000000000..b2583db5c5 --- /dev/null +++ b/tests/TestMimeTypes.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2025 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 . + */ + +#pragma once + +#include + +class TestMimeType : public QObject +{ + Q_OBJECT +private slots: + void testMimePlantText(); + void testMimeImage(); + void testMimeUnknown(); +}; From c941cda3c694db16aba7755e247986d1f7af474d Mon Sep 17 00:00:00 2001 From: w15dev Date: Sat, 11 Jan 2025 03:41:30 +0300 Subject: [PATCH 06/10] Add Open and Save functionality to attachment preview dialog --- share/translations/keepassxc_en.ts | 4 ++ src/gui/entry/EntryAttachmentsWidget.cpp | 6 +++ .../entry/PreviewEntryAttachmentsDialog.cpp | 40 ++++++++++++++++--- src/gui/entry/PreviewEntryAttachmentsDialog.h | 7 ++++ tests/TestMimeTypes.cpp | 6 +-- 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index 209397128d..8445fc9e92 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -7170,6 +7170,10 @@ Do you want to overwrite it? No preview available + + Open + + QMessageBox diff --git a/src/gui/entry/EntryAttachmentsWidget.cpp b/src/gui/entry/EntryAttachmentsWidget.cpp index 6b888a4d54..9d035e1f7c 100644 --- a/src/gui/entry/EntryAttachmentsWidget.cpp +++ b/src/gui/entry/EntryAttachmentsWidget.cpp @@ -194,7 +194,13 @@ void EntryAttachmentsWidget::previewAttachments() PreviewEntryAttachmentsDialog previewDialog{m_entryAttachments}; previewDialog.setAttachment(m_attachmentsModel->keyByIndex(index)); + connect(&previewDialog, SIGNAL(openAttachment(QString)), SLOT(openSelectedAttachments())); + connect(&previewDialog, SIGNAL(saveAttachment(QString)), SLOT(saveSelectedAttachments())); + previewDialog.exec(); + + // Set focus back to the widget to allow keyboard navigation + setFocus(); } void EntryAttachmentsWidget::removeSelectedAttachments() diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp index eeddbd0acb..877e155416 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp @@ -21,10 +21,10 @@ #include #include +#include #include #include - PreviewEntryAttachmentsDialog::PreviewEntryAttachmentsDialog(QPointer attachments, QWidget* parent) : QDialog(parent) , m_attachments(std::move(attachments)) @@ -35,18 +35,48 @@ PreviewEntryAttachmentsDialog::PreviewEntryAttachmentsDialog(QPointersetupUi(this); setWindowTitle(tr("Preview entry attachment")); + // Disable the help button in the title bar + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - m_ui->titleEdit->setReadOnly(true); - m_ui->attachmentTextEdit->setReadOnly(true); - m_ui->errorLabel->setVisible(false); + connect(m_attachments, &EntryAttachments::keyModified, [this](const QString& name) { + if (m_name == name) { + update(); + } + }); + enableReadOnlyMode(); + initDialogButtons(); +} + +PreviewEntryAttachmentsDialog::~PreviewEntryAttachmentsDialog() = default; + +void PreviewEntryAttachmentsDialog::initDialogButtons() +{ m_ui->dialogButtons->clear(); m_ui->dialogButtons->addButton(QDialogButtonBox::Close); + m_ui->dialogButtons->addButton(QDialogButtonBox::Save); + + if (auto openButton = m_ui->dialogButtons->addButton(QDialogButtonBox::Open); openButton) { + openButton->setText(tr("Open")); + } connect(m_ui->dialogButtons, SIGNAL(rejected()), this, SLOT(reject())); + connect(m_ui->dialogButtons, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { + if (auto standartButton = m_ui->dialogButtons->standardButton(button); + standartButton == QDialogButtonBox::Open) { + emit openAttachment(m_name); + } else if (standartButton == QDialogButtonBox::Save) { + emit saveAttachment(m_name); + } + }); } -PreviewEntryAttachmentsDialog::~PreviewEntryAttachmentsDialog() = default; +void PreviewEntryAttachmentsDialog::enableReadOnlyMode() +{ + m_ui->titleEdit->setReadOnly(true); + m_ui->attachmentTextEdit->setReadOnly(true); + m_ui->errorLabel->setVisible(false); +} void PreviewEntryAttachmentsDialog::setAttachment(const QString& name) { diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.h b/src/gui/entry/PreviewEntryAttachmentsDialog.h index 5191fdcf74..a2ef0caa07 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.h +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.h @@ -39,7 +39,14 @@ class PreviewEntryAttachmentsDialog : public QDialog void setAttachment(const QString& name); +Q_SIGNALS: + void openAttachment(const QString& name); + void saveAttachment(const QString& name); + private: + void initDialogButtons(); + void enableReadOnlyMode(); + void resizeEvent(QResizeEvent* event) override; core::MimeType attachmentType(const QString& name) const; diff --git a/tests/TestMimeTypes.cpp b/tests/TestMimeTypes.cpp index 6fee3fce98..43158d9520 100644 --- a/tests/TestMimeTypes.cpp +++ b/tests/TestMimeTypes.cpp @@ -8,7 +8,7 @@ QTEST_GUILESS_MAIN(TestMimeType) void TestMimeType::testMimePlantText() { - const std::array TextMimeTypes = { + const std::array TextMimeTypes = { "text/plain", // Plain text "text/html", // HTML documents "text/css", // CSS stylesheets @@ -32,7 +32,7 @@ void TestMimeType::testMimePlantText() void TestMimeType::testMimeImage() { - constexpr static std::array ImageMimeTypes = { + const std::array ImageMimeTypes = { "image/jpeg", // JPEG images "image/png", // PNG images "image/gif", // GIF images @@ -48,7 +48,7 @@ void TestMimeType::testMimeImage() void TestMimeType::testMimeUnknown() { - constexpr static std::array UnknownMimeTypes = { + const std::array UnknownMimeTypes = { "audio/mpeg", // MPEG audio files "video/mp4", // MP4 video files "application/pdf", // PDF documents From 14db3d5902d5559afd3d6ee68ceadb2c68f284c9 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 11 Jan 2025 08:18:09 -0500 Subject: [PATCH 07/10] Merge mimetype code into core/Tools.cpp --- src/CMakeLists.txt | 1 - src/core/MimeTypes.cpp | 46 ----------- src/core/MimeTypes.h | 33 -------- src/core/Tools.cpp | 28 +++++++ src/core/Tools.h | 9 +++ .../entry/PreviewEntryAttachmentsDialog.cpp | 12 +-- src/gui/entry/PreviewEntryAttachmentsDialog.h | 11 +-- tests/CMakeLists.txt | 3 - tests/TestMimeTypes.cpp | 80 ------------------- tests/TestMimeTypes.h | 29 ------- tests/TestTools.cpp | 67 ++++++++++++++++ tests/TestTools.h | 1 + 12 files changed, 115 insertions(+), 205 deletions(-) delete mode 100644 src/core/MimeTypes.cpp delete mode 100644 src/core/MimeTypes.h delete mode 100644 tests/TestMimeTypes.cpp delete mode 100644 tests/TestMimeTypes.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2d665c575b..84c6090ba5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -59,7 +59,6 @@ set(core_SOURCES core/Tools.cpp core/Totp.cpp core/Translator.cpp - core/MimeTypes.cpp cli/Utils.cpp cli/TextStream.cpp crypto/Crypto.cpp diff --git a/src/core/MimeTypes.cpp b/src/core/MimeTypes.cpp deleted file mode 100644 index d4ccae75eb..0000000000 --- a/src/core/MimeTypes.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "MimeTypes.h" - -#include - -namespace -{ - - bool isTextCompatibleFormat(const QString& format) - { - constexpr static std::array Formats = { - "text/", - "application/json", - "application/xml", - "application/soap+xml", - "application/x-yaml", - "application/protobuf", - }; - - return std::any_of( - std::cbegin(Formats), std::cend(Formats), [&format](const auto& f) { return format.startsWith(f); }); - } - - bool isImageCompatibleFormat(const QString& format) - { - constexpr static std::array Formats = {"image/"}; - - return std::any_of( - std::cbegin(Formats), std::cend(Formats), [&format](const auto& f) { return format.startsWith(f); }); - } - -} // namespace - -namespace core -{ - MimeType toMimeType(const QString& mimeName) - { - if (isImageCompatibleFormat(mimeName)) { - return MimeType::Image; - } else if (isTextCompatibleFormat(mimeName)) { - return MimeType::PlantText; - } - - return MimeType::Unknown; - } - -} // namespace core diff --git a/src/core/MimeTypes.h b/src/core/MimeTypes.h deleted file mode 100644 index a748055a21..0000000000 --- a/src/core/MimeTypes.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2025 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 . - */ - -#pragma once - -#include - -namespace core -{ - enum class MimeType : uint8_t - { - Image, - PlantText, - Unknown - }; - - MimeType toMimeType(const QString& mimeName); - -} // namespace core diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 5ee4c72984..39c7ab8eb1 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -475,4 +475,32 @@ namespace Tools return pattern; } + + MimeType toMimeType(const QString& mimeName) + { + static QStringList textFormats = { + "text/", + "application/json", + "application/xml", + "application/soap+xml", + "application/x-yaml", + "application/protobuf", + }; + static QStringList imageFormats = {"image/"}; + + static auto isCompatible = [](const QString& format, const QStringList& list) { + return std::any_of( + list.cbegin(), list.cend(), [&format](const auto& item) { return format.startsWith(item); }); + }; + + if (isCompatible(mimeName, imageFormats)) { + return MimeType::Image; + } + + if (isCompatible(mimeName, textFormats)) { + return MimeType::PlainText; + } + + return MimeType::Unknown; + } } // namespace Tools diff --git a/src/core/Tools.h b/src/core/Tools.h index d170d76d0d..7265f68bbf 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -114,6 +114,15 @@ namespace Tools QVariantMap qo2qvm(const QObject* object, const QStringList& ignoredProperties = {"objectName"}); QString substituteBackupFilePath(QString pattern, const QString& databasePath); + + enum class MimeType : uint8_t + { + Image, + PlainText, + Unknown + }; + + MimeType toMimeType(const QString& mimeName); } // namespace Tools #endif // KEEPASSX_TOOLS_H diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp index 877e155416..eeaa1fa134 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp @@ -90,11 +90,11 @@ void PreviewEntryAttachmentsDialog::setAttachment(const QString& name) void PreviewEntryAttachmentsDialog::update() { - if (m_type == core::MimeType::Unknown) { + if (m_type == Tools::MimeType::Unknown) { updateTextAttachment(tr("No preview available").toUtf8()); - } else if (const auto data = m_attachments->value(m_name); m_type == core::MimeType::Image) { + } else if (const auto data = m_attachments->value(m_name); m_type == Tools::MimeType::Image) { updateImageAttachment(data); - } else if (m_type == core::MimeType::PlantText) { + } else if (m_type == Tools::MimeType::PlainText) { updateTextAttachment(data); } } @@ -115,21 +115,21 @@ void PreviewEntryAttachmentsDialog::updateImageAttachment(const QByteArray& data cursor.insertImage(image.scaled(m_ui->attachmentTextEdit->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } -core::MimeType PreviewEntryAttachmentsDialog::attachmentType(const QString& name) const +Tools::MimeType PreviewEntryAttachmentsDialog::attachmentType(const QString& name) const { const auto data = m_attachments->value(name); QMimeDatabase mimeDb{}; const auto mime = mimeDb.mimeTypeForData(data); - return core::toMimeType(mime.name()); + return Tools::toMimeType(mime.name()); } void PreviewEntryAttachmentsDialog::resizeEvent(QResizeEvent* event) { QDialog::resizeEvent(event); - if (m_type == core::MimeType::Image) { + if (m_type == Tools::MimeType::Image) { update(); } } diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.h b/src/gui/entry/PreviewEntryAttachmentsDialog.h index a2ef0caa07..337ee3759e 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.h +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.h @@ -17,7 +17,7 @@ #pragma once -#include "core/MimeTypes.h" +#include "core/Tools.h" #include #include @@ -49,17 +49,14 @@ class PreviewEntryAttachmentsDialog : public QDialog void resizeEvent(QResizeEvent* event) override; - core::MimeType attachmentType(const QString& name) const; + Tools::MimeType attachmentType(const QString& name) const; void update(); void updateTextAttachment(const QByteArray& data); void updateImageAttachment(const QByteArray& data); -private: - QPointer m_attachments; - QScopedPointer m_ui; - + QPointer m_attachments; QString m_name; - core::MimeType m_type{core::MimeType::Unknown}; + Tools::MimeType m_type{Tools::MimeType::Unknown}; }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4be77cd579..8ed6868df0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -217,9 +217,6 @@ endif() add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp LIBS testsupport ${TEST_LIBRARIES}) -add_unit_test(NAME testmimetypes SOURCES TestMimeTypes.cpp - LIBS testsupport ${TEST_LIBRARIES}) - add_unit_test(NAME testtools SOURCES TestTools.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/TestMimeTypes.cpp b/tests/TestMimeTypes.cpp deleted file mode 100644 index 43158d9520..0000000000 --- a/tests/TestMimeTypes.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "TestMimeTypes.h" - -#include - -#include - -QTEST_GUILESS_MAIN(TestMimeType) - -void TestMimeType::testMimePlantText() -{ - const std::array TextMimeTypes = { - "text/plain", // Plain text - "text/html", // HTML documents - "text/css", // CSS stylesheets - "text/javascript", // JavaScript files - "text/markdown", // Markdown documents - "text/xml", // XML documents - "text/rtf", // Rich Text Format - "text/vcard", // vCard files - "text/tab-separated-values", // Tab-separated values - "application/json", // JSON data - "application/xml", // XML data - "application/soap+xml", // SOAP messages - "application/x-yaml", // YAML data - "application/protobuf", // Protocol Buffers - }; - - for (const auto& mime : TextMimeTypes) { - QCOMPARE(core::toMimeType(mime), core::MimeType::PlantText); - } -} - -void TestMimeType::testMimeImage() -{ - const std::array ImageMimeTypes = { - "image/jpeg", // JPEG images - "image/png", // PNG images - "image/gif", // GIF images - "image/bmp", // BMP images - "image/webp", // WEBP images - "image/svg+xml" // SVG images - }; - - for (const auto& mime : ImageMimeTypes) { - QCOMPARE(core::toMimeType(mime), core::MimeType::Image); - } -} - -void TestMimeType::testMimeUnknown() -{ - const std::array UnknownMimeTypes = { - "audio/mpeg", // MPEG audio files - "video/mp4", // MP4 video files - "application/pdf", // PDF documents - "application/zip", // ZIP archives - "application/x-tar", // TAR archives - "application/x-rar-compressed", // RAR archives - "application/x-7z-compressed", // 7z archives - "application/x-shockwave-flash", // Adobe Flash files - "application/vnd.ms-excel", // Microsoft Excel files - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // Microsoft Excel (OpenXML) files - "application/vnd.ms-powerpoint", // Microsoft PowerPoint files - "application/vnd.openxmlformats-officedocument.presentationml.presentation", // Microsoft PowerPoint (OpenXML) - // files - "application/msword", // Microsoft Word files - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", // Microsoft Word (OpenXML) files - "application/vnd.oasis.opendocument.text", // OpenDocument Text - "application/vnd.oasis.opendocument.spreadsheet", // OpenDocument Spreadsheet - "application/vnd.oasis.opendocument.presentation", // OpenDocument Presentation - "application/x-httpd-php", // PHP files - "application/x-perl", // Perl scripts - "application/x-python", // Python scripts - "application/x-ruby", // Ruby scripts - "application/x-shellscript", // Shell scripts - }; - - for (const auto& mime : UnknownMimeTypes) { - QCOMPARE(core::toMimeType(mime), core::MimeType::Unknown); - } -} diff --git a/tests/TestMimeTypes.h b/tests/TestMimeTypes.h deleted file mode 100644 index b2583db5c5..0000000000 --- a/tests/TestMimeTypes.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2025 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 . - */ - -#pragma once - -#include - -class TestMimeType : public QObject -{ - Q_OBJECT -private slots: - void testMimePlantText(); - void testMimeImage(); - void testMimeUnknown(); -}; diff --git a/tests/TestTools.cpp b/tests/TestTools.cpp index fd15128035..27a468929c 100644 --- a/tests/TestTools.cpp +++ b/tests/TestTools.cpp @@ -272,3 +272,70 @@ void TestTools::testArrayContainsValues() const auto result3 = Tools::getMissingValuesFromList(numberValues, QList({6, 7, 8})); QCOMPARE(result3.length(), 3); } + +void TestTools::testMimeTypes() +{ + const QStringList TextMimeTypes = { + "text/plain", // Plain text + "text/html", // HTML documents + "text/css", // CSS stylesheets + "text/javascript", // JavaScript files + "text/markdown", // Markdown documents + "text/xml", // XML documents + "text/rtf", // Rich Text Format + "text/vcard", // vCard files + "text/tab-separated-values", // Tab-separated values + "application/json", // JSON data + "application/xml", // XML data + "application/soap+xml", // SOAP messages + "application/x-yaml", // YAML data + "application/protobuf", // Protocol Buffers + }; + + const QStringList ImageMimeTypes = { + "image/jpeg", // JPEG images + "image/png", // PNG images + "image/gif", // GIF images + "image/bmp", // BMP images + "image/webp", // WEBP images + "image/svg+xml" // SVG images + }; + + const QStringList UnknownMimeTypes = { + "audio/mpeg", // MPEG audio files + "video/mp4", // MP4 video files + "application/pdf", // PDF documents + "application/zip", // ZIP archives + "application/x-tar", // TAR archives + "application/x-rar-compressed", // RAR archives + "application/x-7z-compressed", // 7z archives + "application/x-shockwave-flash", // Adobe Flash files + "application/vnd.ms-excel", // Microsoft Excel files + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // Microsoft Excel (OpenXML) files + "application/vnd.ms-powerpoint", // Microsoft PowerPoint files + "application/vnd.openxmlformats-officedocument.presentationml.presentation", // Microsoft PowerPoint (OpenXML) + // files + "application/msword", // Microsoft Word files + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", // Microsoft Word (OpenXML) files + "application/vnd.oasis.opendocument.text", // OpenDocument Text + "application/vnd.oasis.opendocument.spreadsheet", // OpenDocument Spreadsheet + "application/vnd.oasis.opendocument.presentation", // OpenDocument Presentation + "application/x-httpd-php", // PHP files + "application/x-perl", // Perl scripts + "application/x-python", // Python scripts + "application/x-ruby", // Ruby scripts + "application/x-shellscript", // Shell scripts + }; + + for (const auto& mime : TextMimeTypes) { + QCOMPARE(Tools::toMimeType(mime), Tools::MimeType::PlainText); + } + + for (const auto& mime : ImageMimeTypes) { + QCOMPARE(Tools::toMimeType(mime), Tools::MimeType::Image); + } + + for (const auto& mime : UnknownMimeTypes) { + QCOMPARE(Tools::toMimeType(mime), Tools::MimeType::Unknown); + } +} diff --git a/tests/TestTools.h b/tests/TestTools.h index e8a44b8b3c..5f4b6b6e09 100644 --- a/tests/TestTools.h +++ b/tests/TestTools.h @@ -37,6 +37,7 @@ private slots: void testConvertToRegex(); void testConvertToRegex_data(); void testArrayContainsValues(); + void testMimeTypes(); }; #endif // KEEPASSX_TESTTOOLS_H From 23c567ee01455b83782e436ebd3dff48e0de5ca1 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 11 Jan 2025 10:31:48 -0500 Subject: [PATCH 08/10] Improve code for preview widget * Reduce multi-selection to single selection when previewing to avoid opening and saving more than the previewed attachment * Don't share EntryAttachments with the preview widget, just pass name/data pair * Reduce number of single-use function calls --- share/translations/keepassxc_en.ts | 2 +- src/gui/entry/EntryAttachmentsWidget.cpp | 26 +++++-- src/gui/entry/EntryAttachmentsWidget.h | 2 +- .../entry/PreviewEntryAttachmentsDialog.cpp | 78 ++++++++----------- src/gui/entry/PreviewEntryAttachmentsDialog.h | 21 +++-- 5 files changed, 65 insertions(+), 64 deletions(-) diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index 8445fc9e92..f657da7fb0 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -7171,7 +7171,7 @@ Do you want to overwrite it? - Open + Image format not supported diff --git a/src/gui/entry/EntryAttachmentsWidget.cpp b/src/gui/entry/EntryAttachmentsWidget.cpp index 9d035e1f7c..e837f61e71 100644 --- a/src/gui/entry/EntryAttachmentsWidget.cpp +++ b/src/gui/entry/EntryAttachmentsWidget.cpp @@ -71,7 +71,7 @@ EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) connect(m_ui->openAttachmentButton, SIGNAL(clicked()), SLOT(openSelectedAttachments())); connect(m_ui->addAttachmentButton, SIGNAL(clicked()), SLOT(insertAttachments())); connect(m_ui->newAttachmentButton, SIGNAL(clicked()), SLOT(newAttachments())); - connect(m_ui->previewAttachmentButton, SIGNAL(clicked()), SLOT(previewAttachments())); + connect(m_ui->previewAttachmentButton, SIGNAL(clicked()), SLOT(previewSelectedAttachment())); connect(m_ui->removeAttachmentButton, SIGNAL(clicked()), SLOT(removeSelectedAttachments())); connect(m_ui->renameAttachmentButton, SIGNAL(clicked()), SLOT(renameSelectedAttachments())); @@ -181,7 +181,7 @@ void EntryAttachmentsWidget::newAttachments() } } -void EntryAttachmentsWidget::previewAttachments() +void EntryAttachmentsWidget::previewSelectedAttachment() { Q_ASSERT(m_entryAttachments); @@ -191,11 +191,27 @@ void EntryAttachmentsWidget::previewAttachments() return; } - PreviewEntryAttachmentsDialog previewDialog{m_entryAttachments}; - previewDialog.setAttachment(m_attachmentsModel->keyByIndex(index)); + // Set selection to the first + m_ui->attachmentsView->clearSelection(); + m_ui->attachmentsView->setCurrentIndex(index); + + auto name = m_attachmentsModel->keyByIndex(index); + auto data = m_entryAttachments->value(name); + + PreviewEntryAttachmentsDialog previewDialog; + previewDialog.setAttachment(name, data); connect(&previewDialog, SIGNAL(openAttachment(QString)), SLOT(openSelectedAttachments())); connect(&previewDialog, SIGNAL(saveAttachment(QString)), SLOT(saveSelectedAttachments())); + // Refresh the preview if the attachment changes + connect(m_entryAttachments, + &EntryAttachments::keyModified, + &previewDialog, + [&previewDialog, &name, this](const QString& key) { + if (key == name) { + previewDialog.setAttachment(name, m_entryAttachments->value(name)); + } + }); previewDialog.exec(); @@ -327,7 +343,7 @@ void EntryAttachmentsWidget::openSelectedAttachments() if (!m_entryAttachments->openAttachment(m_attachmentsModel->keyByIndex(index), &errorMessage)) { const QString filename = m_attachmentsModel->keyByIndex(index); errors.append(QString("%1 - %2").arg(filename, errorMessage)); - }; + } } if (!errors.isEmpty()) { diff --git a/src/gui/entry/EntryAttachmentsWidget.h b/src/gui/entry/EntryAttachmentsWidget.h index 3dfdf0ef6f..6dc1ee94db 100644 --- a/src/gui/entry/EntryAttachmentsWidget.h +++ b/src/gui/entry/EntryAttachmentsWidget.h @@ -58,7 +58,7 @@ public slots: private slots: void insertAttachments(); void newAttachments(); - void previewAttachments(); + void previewSelectedAttachment(); void removeSelectedAttachments(); void renameSelectedAttachments(); void saveSelectedAttachments(); diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp index eeaa1fa134..12e50d817a 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp @@ -16,7 +16,6 @@ */ #include "PreviewEntryAttachmentsDialog.h" -#include "core/EntryAttachments.h" #include "ui_EntryAttachmentsDialog.h" #include @@ -25,66 +24,47 @@ #include #include -PreviewEntryAttachmentsDialog::PreviewEntryAttachmentsDialog(QPointer attachments, QWidget* parent) +PreviewEntryAttachmentsDialog::PreviewEntryAttachmentsDialog(QWidget* parent) : QDialog(parent) - , m_attachments(std::move(attachments)) , m_ui(new Ui::EntryAttachmentsDialog) { - Q_ASSERT(m_attachments); - m_ui->setupUi(this); setWindowTitle(tr("Preview entry attachment")); // Disable the help button in the title bar setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - connect(m_attachments, &EntryAttachments::keyModified, [this](const QString& name) { - if (m_name == name) { - update(); - } - }); - - enableReadOnlyMode(); - initDialogButtons(); -} - -PreviewEntryAttachmentsDialog::~PreviewEntryAttachmentsDialog() = default; - -void PreviewEntryAttachmentsDialog::initDialogButtons() -{ - m_ui->dialogButtons->clear(); - m_ui->dialogButtons->addButton(QDialogButtonBox::Close); - m_ui->dialogButtons->addButton(QDialogButtonBox::Save); + // Set to read-only + m_ui->titleEdit->setReadOnly(true); + m_ui->attachmentTextEdit->setReadOnly(true); + m_ui->errorLabel->setVisible(false); - if (auto openButton = m_ui->dialogButtons->addButton(QDialogButtonBox::Open); openButton) { - openButton->setText(tr("Open")); - } + // Initialize dialog buttons + m_ui->dialogButtons->setStandardButtons(QDialogButtonBox::Close | QDialogButtonBox::Open | QDialogButtonBox::Save); + auto closeButton = m_ui->dialogButtons->button(QDialogButtonBox::Close); + closeButton->setDefault(true); connect(m_ui->dialogButtons, SIGNAL(rejected()), this, SLOT(reject())); connect(m_ui->dialogButtons, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { - if (auto standartButton = m_ui->dialogButtons->standardButton(button); - standartButton == QDialogButtonBox::Open) { + auto pressedButton = m_ui->dialogButtons->standardButton(button); + if (pressedButton == QDialogButtonBox::Open) { emit openAttachment(m_name); - } else if (standartButton == QDialogButtonBox::Save) { + } else if (pressedButton == QDialogButtonBox::Save) { emit saveAttachment(m_name); } }); } -void PreviewEntryAttachmentsDialog::enableReadOnlyMode() -{ - m_ui->titleEdit->setReadOnly(true); - m_ui->attachmentTextEdit->setReadOnly(true); - m_ui->errorLabel->setVisible(false); -} +PreviewEntryAttachmentsDialog::~PreviewEntryAttachmentsDialog() = default; -void PreviewEntryAttachmentsDialog::setAttachment(const QString& name) +void PreviewEntryAttachmentsDialog::setAttachment(const QString& name, const QByteArray& data) { m_name = name; - m_type = attachmentType(m_name); - m_ui->titleEdit->setText(m_name); + m_type = attachmentType(data); + m_data = data; + update(); } @@ -92,10 +72,10 @@ void PreviewEntryAttachmentsDialog::update() { if (m_type == Tools::MimeType::Unknown) { updateTextAttachment(tr("No preview available").toUtf8()); - } else if (const auto data = m_attachments->value(m_name); m_type == Tools::MimeType::Image) { - updateImageAttachment(data); + } else if (m_type == Tools::MimeType::Image) { + updateImageAttachment(m_data); } else if (m_type == Tools::MimeType::PlainText) { - updateTextAttachment(data); + updateTextAttachment(m_data); } } @@ -107,18 +87,26 @@ void PreviewEntryAttachmentsDialog::updateTextAttachment(const QByteArray& data) void PreviewEntryAttachmentsDialog::updateImageAttachment(const QByteArray& data) { QImage image{}; - image.loadFromData(data); + if (!image.loadFromData(data)) { + updateTextAttachment(tr("Image format not supported").toUtf8()); + return; + } m_ui->attachmentTextEdit->clear(); auto cursor = m_ui->attachmentTextEdit->textCursor(); - cursor.insertImage(image.scaled(m_ui->attachmentTextEdit->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + // Scale the image to the contents rect minus another set of margins to avoid scrollbars + auto margins = m_ui->attachmentTextEdit->contentsMargins(); + auto size = m_ui->attachmentTextEdit->contentsRect().size(); + size.setWidth(size.width() - margins.left() - margins.right()); + size.setHeight(size.height() - margins.top() - margins.bottom()); + image = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + cursor.insertImage(image); } -Tools::MimeType PreviewEntryAttachmentsDialog::attachmentType(const QString& name) const +Tools::MimeType PreviewEntryAttachmentsDialog::attachmentType(const QByteArray& data) const { - const auto data = m_attachments->value(name); - QMimeDatabase mimeDb{}; const auto mime = mimeDb.mimeTypeForData(data); diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.h b/src/gui/entry/PreviewEntryAttachmentsDialog.h index 337ee3759e..87b128ee09 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.h +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.h @@ -27,36 +27,33 @@ namespace Ui class EntryAttachmentsDialog; } -class QByteArray; -class EntryAttachments; - class PreviewEntryAttachmentsDialog : public QDialog { Q_OBJECT + public: - explicit PreviewEntryAttachmentsDialog(QPointer attachments, QWidget* parent = nullptr); + explicit PreviewEntryAttachmentsDialog(QWidget* parent = nullptr); ~PreviewEntryAttachmentsDialog() override; - void setAttachment(const QString& name); + void setAttachment(const QString& name, const QByteArray& data); -Q_SIGNALS: +signals: void openAttachment(const QString& name); void saveAttachment(const QString& name); -private: - void initDialogButtons(); - void enableReadOnlyMode(); - +protected: void resizeEvent(QResizeEvent* event) override; - Tools::MimeType attachmentType(const QString& name) const; +private: + Tools::MimeType attachmentType(const QByteArray& data) const; void update(); void updateTextAttachment(const QByteArray& data); void updateImageAttachment(const QByteArray& data); QScopedPointer m_ui; - QPointer m_attachments; + QString m_name; + QByteArray m_data; Tools::MimeType m_type{Tools::MimeType::Unknown}; }; From 6feea6ac9c2719a2a7cccff431f59b41938b6313 Mon Sep 17 00:00:00 2001 From: w15dev Date: Sat, 11 Jan 2025 21:43:07 +0300 Subject: [PATCH 09/10] Refactor attachment widget: remove rename functionality and reorder buttons on the widget --- share/translations/keepassxc_en.ts | 8 --- src/gui/entry/EntryAttachmentsWidget.cpp | 22 ++++---- src/gui/entry/EntryAttachmentsWidget.h | 3 +- src/gui/entry/EntryAttachmentsWidget.ui | 64 +++++++++++++++--------- 4 files changed, 55 insertions(+), 42 deletions(-) diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index f657da7fb0..519a325bec 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -3878,14 +3878,6 @@ This may cause the affected plugins to malfunction. Remove - - Rename selected attachment - - - - Rename - - Open selected attachment diff --git a/src/gui/entry/EntryAttachmentsWidget.cpp b/src/gui/entry/EntryAttachmentsWidget.cpp index e837f61e71..cb00cb2357 100644 --- a/src/gui/entry/EntryAttachmentsWidget.cpp +++ b/src/gui/entry/EntryAttachmentsWidget.cpp @@ -73,7 +73,6 @@ EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) connect(m_ui->newAttachmentButton, SIGNAL(clicked()), SLOT(newAttachments())); connect(m_ui->previewAttachmentButton, SIGNAL(clicked()), SLOT(previewSelectedAttachment())); connect(m_ui->removeAttachmentButton, SIGNAL(clicked()), SLOT(removeSelectedAttachments())); - connect(m_ui->renameAttachmentButton, SIGNAL(clicked()), SLOT(renameSelectedAttachments())); updateButtonsVisible(); updateButtonsEnabled(); @@ -248,12 +247,6 @@ void EntryAttachmentsWidget::removeSelectedAttachments() } } -void EntryAttachmentsWidget::renameSelectedAttachments() -{ - Q_ASSERT(m_entryAttachments); - m_ui->attachmentsView->edit(m_ui->attachmentsView->selectionModel()->selectedIndexes().first()); -} - void EntryAttachmentsWidget::saveSelectedAttachments() { Q_ASSERT(m_entryAttachments); @@ -358,11 +351,21 @@ void EntryAttachmentsWidget::updateButtonsEnabled() m_ui->addAttachmentButton->setEnabled(!m_readOnly); m_ui->newAttachmentButton->setEnabled(!m_readOnly); m_ui->removeAttachmentButton->setEnabled(hasSelection && !m_readOnly); - m_ui->renameAttachmentButton->setEnabled(hasSelection && !m_readOnly); m_ui->saveAttachmentButton->setEnabled(hasSelection); m_ui->previewAttachmentButton->setEnabled(hasSelection); m_ui->openAttachmentButton->setEnabled(hasSelection); + + updateSpacers(); +} + +void EntryAttachmentsWidget::updateSpacers() +{ + if (m_buttonsVisible && !m_readOnly) { + m_ui->previewVSpacer->changeSize(20, 40, QSizePolicy::Fixed, QSizePolicy::Expanding); + } else { + m_ui->previewVSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); + } } void EntryAttachmentsWidget::updateButtonsVisible() @@ -370,7 +373,8 @@ void EntryAttachmentsWidget::updateButtonsVisible() m_ui->addAttachmentButton->setVisible(m_buttonsVisible && !m_readOnly); m_ui->newAttachmentButton->setVisible(m_buttonsVisible && !m_readOnly); m_ui->removeAttachmentButton->setVisible(m_buttonsVisible && !m_readOnly); - m_ui->renameAttachmentButton->setVisible(m_buttonsVisible && !m_readOnly); + + updateSpacers(); } bool EntryAttachmentsWidget::insertAttachments(const QStringList& filenames, QString& errorMessage) diff --git a/src/gui/entry/EntryAttachmentsWidget.h b/src/gui/entry/EntryAttachmentsWidget.h index 6dc1ee94db..8c15fd68a5 100644 --- a/src/gui/entry/EntryAttachmentsWidget.h +++ b/src/gui/entry/EntryAttachmentsWidget.h @@ -60,7 +60,6 @@ private slots: void newAttachments(); void previewSelectedAttachment(); void removeSelectedAttachments(); - void renameSelectedAttachments(); void saveSelectedAttachments(); void openAttachment(const QModelIndex& index); void openSelectedAttachments(); @@ -69,6 +68,8 @@ private slots: void attachmentModifiedExternally(const QString& key, const QString& filePath); private: + void updateSpacers(); + bool insertAttachments(const QStringList& fileNames, QString& errorMessage); QStringList confirmAttachmentSelection(const QStringList& filenames); diff --git a/src/gui/entry/EntryAttachmentsWidget.ui b/src/gui/entry/EntryAttachmentsWidget.ui index 99a16ca4e9..495a4ca38e 100644 --- a/src/gui/entry/EntryAttachmentsWidget.ui +++ b/src/gui/entry/EntryAttachmentsWidget.ui @@ -7,7 +7,7 @@ 0 0 337 - 294 + 258 @@ -38,7 +38,7 @@ - + 0 @@ -75,30 +75,17 @@ - - - false - - - Remove selected attachment - - - Remove - - - - - - - false - - - Rename selected attachment + + + Qt::Vertical - - Rename + + + 20 + 40 + - + @@ -149,6 +136,35 @@ + + + + false + + + Remove selected attachment + + + Remove + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 0 + + + + From 3116ded7a34521fdf0cfb7ac99bb250549cb522c Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 11 Jan 2025 18:33:09 -0500 Subject: [PATCH 10/10] Final cleanup round * Fix sizing of attachment columns * Parent new/preview attachment dialog to widget so it closes properly on database auto-lock * Fix targeting of preview widget styling to not impact unintended children * Add padding to attachment table items --- src/gui/EntryPreviewWidget.ui | 21 +++++++++++++++ src/gui/entry/EntryAttachmentsWidget.cpp | 24 ++++++++--------- src/gui/entry/EntryAttachmentsWidget.ui | 9 +++++++ src/gui/entry/NewEntryAttachmentsDialog.cpp | 27 +++++++++++-------- src/gui/entry/NewEntryAttachmentsDialog.h | 7 ++--- .../entry/PreviewEntryAttachmentsDialog.cpp | 2 +- src/gui/entry/PreviewEntryAttachmentsDialog.h | 2 +- src/gui/styles/base/basestyle.qss | 7 ++--- 8 files changed, 66 insertions(+), 33 deletions(-) diff --git a/src/gui/EntryPreviewWidget.ui b/src/gui/EntryPreviewWidget.ui index 9b4e499605..b2cdecbbab 100644 --- a/src/gui/EntryPreviewWidget.ui +++ b/src/gui/EntryPreviewWidget.ui @@ -288,6 +288,9 @@ Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + true + @@ -325,6 +328,9 @@ Qt::TextBrowserInteraction + + true + @@ -409,6 +415,9 @@ true + + true + @@ -482,6 +491,9 @@ true + + true + @@ -494,6 +506,9 @@ Tags list + + true + @@ -516,6 +531,9 @@ Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + true + @@ -1109,6 +1127,9 @@ true + + true + diff --git a/src/gui/entry/EntryAttachmentsWidget.cpp b/src/gui/entry/EntryAttachmentsWidget.cpp index cb00cb2357..744a659310 100644 --- a/src/gui/entry/EntryAttachmentsWidget.cpp +++ b/src/gui/entry/EntryAttachmentsWidget.cpp @@ -16,11 +16,13 @@ */ #include "EntryAttachmentsWidget.h" + +#include "EntryAttachmentsModel.h" #include "NewEntryAttachmentsDialog.h" #include "PreviewEntryAttachmentsDialog.h" #include "ui_EntryAttachmentsWidget.h" -#include +#include #include #include #include @@ -31,7 +33,6 @@ #include "core/Tools.h" #include "gui/FileDialog.h" #include "gui/MessageBox.h" -#include EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) : QWidget(parent) @@ -48,12 +49,12 @@ EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) m_ui->attachmentsView->viewport()->installEventFilter(this); m_ui->attachmentsView->setModel(m_attachmentsModel); - m_ui->attachmentsView->verticalHeader()->hide(); - m_ui->attachmentsView->horizontalHeader()->setStretchLastSection(true); - m_ui->attachmentsView->horizontalHeader()->resizeSection(EntryAttachmentsModel::NameColumn, 400); - m_ui->attachmentsView->setSelectionBehavior(QAbstractItemView::SelectRows); - m_ui->attachmentsView->setSelectionMode(QAbstractItemView::ExtendedSelection); - m_ui->attachmentsView->setEditTriggers(QAbstractItemView::SelectedClicked); + m_ui->attachmentsView->horizontalHeader()->setMinimumSectionSize(70); + m_ui->attachmentsView->horizontalHeader()->setSectionResizeMode(EntryAttachmentsModel::NameColumn, + QHeaderView::Stretch); + m_ui->attachmentsView->horizontalHeader()->setSectionResizeMode(EntryAttachmentsModel::SizeColumn, + QHeaderView::ResizeToContents); + m_ui->attachmentsView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); connect(this, SIGNAL(buttonsVisibleChanged(bool)), this, SLOT(updateButtonsVisible())); connect(this, SIGNAL(readOnlyChanged(bool)), SLOT(updateButtonsEnabled())); @@ -66,7 +67,7 @@ EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) // clang-format on connect(this, SIGNAL(readOnlyChanged(bool)), m_attachmentsModel, SLOT(setReadOnly(bool))); - connect(m_ui->attachmentsView, SIGNAL(doubleClicked(QModelIndex)), SLOT(openAttachment(QModelIndex))); + connect(m_ui->attachmentsView, SIGNAL(doubleClicked(QModelIndex)), SLOT(previewSelectedAttachment())); connect(m_ui->saveAttachmentButton, SIGNAL(clicked()), SLOT(saveSelectedAttachments())); connect(m_ui->openAttachmentButton, SIGNAL(clicked()), SLOT(openSelectedAttachments())); connect(m_ui->addAttachmentButton, SIGNAL(clicked()), SLOT(insertAttachments())); @@ -174,7 +175,7 @@ void EntryAttachmentsWidget::newAttachments() return; } - NewEntryAttachmentsDialog newEntryDialog{m_entryAttachments}; + NewEntryAttachmentsDialog newEntryDialog(m_entryAttachments, this); if (newEntryDialog.exec() == QDialog::Accepted) { emit widgetUpdated(); } @@ -191,13 +192,12 @@ void EntryAttachmentsWidget::previewSelectedAttachment() } // Set selection to the first - m_ui->attachmentsView->clearSelection(); m_ui->attachmentsView->setCurrentIndex(index); auto name = m_attachmentsModel->keyByIndex(index); auto data = m_entryAttachments->value(name); - PreviewEntryAttachmentsDialog previewDialog; + PreviewEntryAttachmentsDialog previewDialog(this); previewDialog.setAttachment(name, data); connect(&previewDialog, SIGNAL(openAttachment(QString)), SLOT(openSelectedAttachments())); diff --git a/src/gui/entry/EntryAttachmentsWidget.ui b/src/gui/entry/EntryAttachmentsWidget.ui index 495a4ca38e..5b6de67aa2 100644 --- a/src/gui/entry/EntryAttachmentsWidget.ui +++ b/src/gui/entry/EntryAttachmentsWidget.ui @@ -34,6 +34,15 @@ QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + QAbstractItemView::SelectRows + + + false + + + false + diff --git a/src/gui/entry/NewEntryAttachmentsDialog.cpp b/src/gui/entry/NewEntryAttachmentsDialog.cpp index 84f290de66..b8da3b791f 100644 --- a/src/gui/entry/NewEntryAttachmentsDialog.cpp +++ b/src/gui/entry/NewEntryAttachmentsDialog.cpp @@ -46,17 +46,19 @@ NewEntryAttachmentsDialog::NewEntryAttachmentsDialog(QPointer NewEntryAttachmentsDialog::~NewEntryAttachmentsDialog() = default; -std::optional NewEntryAttachmentsDialog::ValidateFileName(const QString& fileName) const +bool NewEntryAttachmentsDialog::validateFileName(const QString& fileName, QString& error) const { if (fileName.isEmpty()) { - return tr("Attachment name cannot be empty"); + error = tr("Attachment name cannot be empty"); + return false; } if (m_attachments->hasKey(fileName)) { - return tr("Attachment with the same name already exists"); + error = tr("Attachment with the same name already exists"); + return false; } - return std::nullopt; + return true; } void NewEntryAttachmentsDialog::saveAttachment() @@ -64,8 +66,9 @@ void NewEntryAttachmentsDialog::saveAttachment() auto fileName = m_ui->titleEdit->text(); auto text = m_ui->attachmentTextEdit->toPlainText().toUtf8(); - if (auto error = ValidateFileName(fileName); error) { - QMessageBox::warning(this, tr("Save attachment"), error.value()); + QString error; + if (validateFileName(fileName, error)) { + QMessageBox::warning(this, tr("Save attachment"), error); return; } @@ -76,12 +79,14 @@ void NewEntryAttachmentsDialog::saveAttachment() void NewEntryAttachmentsDialog::fileNameTextChanged(const QString& fileName) { - const auto error = ValidateFileName(fileName); + QString error; + bool valid = validateFileName(fileName, error); - m_ui->errorLabel->setText(error.value_or(QString{})); - m_ui->errorLabel->setVisible(error.has_value()); + m_ui->errorLabel->setText(error); + m_ui->errorLabel->setVisible(!valid); - if (auto okButton = m_ui->dialogButtons->button(QDialogButtonBox::Ok); okButton) { - okButton->setDisabled(error.has_value()); + auto okButton = m_ui->dialogButtons->button(QDialogButtonBox::Ok); + if (okButton) { + okButton->setDisabled(!valid); } } diff --git a/src/gui/entry/NewEntryAttachmentsDialog.h b/src/gui/entry/NewEntryAttachmentsDialog.h index 467a0f9867..651100f65b 100644 --- a/src/gui/entry/NewEntryAttachmentsDialog.h +++ b/src/gui/entry/NewEntryAttachmentsDialog.h @@ -20,8 +20,6 @@ #include #include -#include - namespace Ui { class EntryAttachmentsDialog; @@ -33,6 +31,7 @@ class EntryAttachments; class NewEntryAttachmentsDialog : public QDialog { Q_OBJECT + public: explicit NewEntryAttachmentsDialog(QPointer attachments, QWidget* parent = nullptr); ~NewEntryAttachmentsDialog() override; @@ -42,10 +41,8 @@ private slots: void fileNameTextChanged(const QString& fileName); private: - std::optional ValidateFileName(const QString& fileName) const; + bool validateFileName(const QString& fileName, QString& error) const; -private: QPointer m_attachments; - QScopedPointer m_ui; }; diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp index 12e50d817a..6926effbb4 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 KeePassXC Team + * Copyright (C) 2025 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 diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.h b/src/gui/entry/PreviewEntryAttachmentsDialog.h index 87b128ee09..b01d1e7ddd 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.h +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 KeePassXC Team + * Copyright (C) 2025 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 diff --git a/src/gui/styles/base/basestyle.qss b/src/gui/styles/base/basestyle.qss index 8d40281a38..34cc283dd2 100644 --- a/src/gui/styles/base/basestyle.qss +++ b/src/gui/styles/base/basestyle.qss @@ -21,7 +21,9 @@ QCheckBox, QRadioButton { spacing: 10px; } -ReportsDialog QTableView::item { +ReportsDialog QTableView::item, +EntryAttachmentsWidget QTableView::item +{ padding: 4px; } @@ -30,8 +32,7 @@ DatabaseWidget, DatabaseWidget #groupView, DatabaseWidget #tagView { border: none; } -EntryPreviewWidget QLineEdit, EntryPreviewWidget QTextEdit, -EntryPreviewWidget TagsEdit +EntryPreviewWidget *[blendIn="true"] { background-color: palette(window); border: none;