Page MenuHome GnuPG

No OneTemporary

diff --git a/src/widgets/messageviewer.cpp b/src/widgets/messageviewer.cpp
index b2e896c..6c267b1 100644
--- a/src/widgets/messageviewer.cpp
+++ b/src/widgets/messageviewer.cpp
@@ -1,460 +1,458 @@
// SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "messageviewer.h"
#include "attachmentview_p.h"
#include "messagecontainerwidget_p.h"
#include "mimetreeparser_widgets_debug.h"
#include "partmodel.h"
#include "urlhandler.h"
#include <KCalendarCore/Event>
#include <KCalendarCore/ICalFormat>
#include <KLocalizedString>
#include <KMessageWidget>
#include <MimeTreeParserCore/AttachmentModel>
#include <MimeTreeParserCore/MessageParser>
#include <MimeTreeParserCore/ObjectTreeParser>
#include <MimeTreeParserCore/PartModel>
#include <QAction>
#include <QDesktopServices>
#include <QFileDialog>
#include <QFontDatabase>
#include <QFormLayout>
#include <QGroupBox>
#include <QLabel>
#include <QMenu>
#include <QScrollArea>
#include <QStandardPaths>
#include <QVBoxLayout>
#include <qnamespace.h>
using namespace MimeTreeParser::Widgets;
class MessageViewer::Private
{
public:
Private(MessageViewer *q_ptr)
: q{q_ptr}
, messageWidget(new KMessageWidget(q_ptr))
{
createActions();
messageWidget->setCloseButtonVisible(true);
messageWidget->hide();
}
MessageViewer *const q;
QVBoxLayout *layout = nullptr;
KMime::Message::Ptr message;
MessageParser parser;
QScrollArea *scrollArea = nullptr;
QFormLayout *formLayout = nullptr;
AttachmentView *attachmentView = nullptr;
MimeTreeParser::MessagePart::List selectedParts;
UrlHandler *urlHandler = nullptr;
KMessageWidget *const messageWidget = nullptr;
QAction *saveAttachmentAction = nullptr;
QAction *openAttachmentAction = nullptr;
QAction *importPublicKeyAction = nullptr;
bool fixedFont = false;
void createActions()
{
saveAttachmentAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as-symbolic")), i18n("&Save Attachment As…"), q);
connect(saveAttachmentAction, &QAction::triggered, q, [this]() {
saveSelectedAttachments();
});
- qWarning() << "creating" << saveAttachmentAction;
openAttachmentAction = new QAction(QIcon::fromTheme(QStringLiteral("document-open-symbolic")), i18nc("to open", "Open"), q);
connect(openAttachmentAction, &QAction::triggered, q, [this]() {
openSelectedAttachments();
});
importPublicKeyAction = new QAction(QIcon::fromTheme(QStringLiteral("document-import-key-symbolic")), i18nc("@action:inmenu", "Import public key"), q);
connect(importPublicKeyAction, &QAction::triggered, q, [this]() {
importPublicKey();
});
}
void openSelectedAttachments();
void saveSelectedAttachments();
void selectionChanged();
void showContextMenu();
void importPublicKey();
void recursiveBuildViewer(PartModel *parts, QVBoxLayout *layout, const QModelIndex &parent);
};
void MessageViewer::Private::openSelectedAttachments()
{
Q_ASSERT(selectedParts.count() >= 1);
for (const auto &part : std::as_const(selectedParts)) {
- qWarning() << "opening part";
parser.attachments()->openAttachment(part);
}
}
void MessageViewer::Private::saveSelectedAttachments()
{
Q_ASSERT(selectedParts.count() >= 1);
for (const auto &part : std::as_const(selectedParts)) {
QString pname = part->filename();
if (pname.isEmpty()) {
pname = i18nc("Fallback when file has no name", "unnamed");
}
const QString path = QFileDialog::getSaveFileName(q, i18n("Save Attachment As"), pname);
parser.attachments()->saveAttachmentToPath(part, path);
}
}
void MessageViewer::Private::importPublicKey()
{
Q_ASSERT(selectedParts.count() == 1);
parser.attachments()->importPublicKey(selectedParts[0]);
}
void MessageViewer::Private::showContextMenu()
{
const int numberOfParts(selectedParts.count());
QMenu menu;
if (numberOfParts == 1) {
const QString mimetype = QString::fromLatin1(selectedParts.first()->mimeType());
if (mimetype == QLatin1StringView("application/pgp-keys")) {
menu.addAction(importPublicKeyAction);
}
}
menu.addAction(openAttachmentAction);
menu.addAction(saveAttachmentAction);
menu.exec(QCursor::pos());
}
void MessageViewer::Private::selectionChanged()
{
const QModelIndexList selectedRows = attachmentView->selectionModel()->selectedRows();
MimeTreeParser::MessagePart::List selectedParts;
selectedParts.reserve(selectedRows.count());
for (const QModelIndex &index : selectedRows) {
auto part = attachmentView->model()->data(index, AttachmentModel::AttachmentPartRole).value<MimeTreeParser::MessagePart::Ptr>();
selectedParts.append(part);
}
this->selectedParts = selectedParts;
}
MessageViewer::MessageViewer(QWidget *parent)
: QSplitter(Qt::Vertical, parent)
, d(std::make_unique<MessageViewer::Private>(this))
{
setObjectName(QLatin1StringView("MessageViewerSplitter"));
setChildrenCollapsible(false);
setSizes({0});
addWidget(d->messageWidget);
auto mainWidget = new QWidget(this);
auto mainLayout = new QVBoxLayout(mainWidget);
mainLayout->setContentsMargins({});
mainLayout->setSpacing(0);
auto headersArea = new QWidget(mainWidget);
headersArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
mainLayout->addWidget(headersArea);
d->urlHandler = new UrlHandler(this);
d->formLayout = new QFormLayout(headersArea);
auto widget = new QWidget(this);
d->layout = new QVBoxLayout(widget);
d->layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
d->layout->setObjectName(QLatin1StringView("PartLayout"));
d->scrollArea = new QScrollArea(this);
d->scrollArea->setWidget(widget);
d->scrollArea->setWidgetResizable(true);
d->scrollArea->setBackgroundRole(QPalette::Base);
mainLayout->addWidget(d->scrollArea);
mainLayout->setStretchFactor(d->scrollArea, 2);
setStretchFactor(1, 2);
d->attachmentView = new AttachmentView(this);
d->attachmentView->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::BottomEdge}));
d->attachmentView->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
addWidget(d->attachmentView);
connect(d->attachmentView, &AttachmentView::contextMenuRequested, this, [this] {
d->selectionChanged();
d->showContextMenu();
});
connect(d->parser.attachments(), &AttachmentModel::info, this, [this](const QString &message) {
d->messageWidget->setMessageType(KMessageWidget::Information);
d->messageWidget->setText(message);
d->messageWidget->animatedShow();
});
connect(d->parser.attachments(), &AttachmentModel::errorOccurred, this, [this](const QString &message) {
d->messageWidget->setMessageType(KMessageWidget::Error);
d->messageWidget->setText(message);
d->messageWidget->animatedShow();
});
connect(d->attachmentView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this] {
d->selectionChanged();
});
connect(d->attachmentView, &QAbstractItemView::doubleClicked, this, [this](const QModelIndex &) {
// Since this is only emitted if a valid index is double clicked we can assume
// that the first click of the double click set the selection accordingly.
d->openSelectedAttachments();
});
}
MessageViewer::~MessageViewer()
{
QLayoutItem *child;
while ((child = d->layout->takeAt(0)) != nullptr) {
delete child->widget();
delete child;
}
}
QString MessageViewer::subject() const
{
return d->parser.subject();
}
KMime::Message::Ptr MessageViewer::message() const
{
return d->parser.message();
}
void MessageViewer::Private::recursiveBuildViewer(PartModel *parts, QVBoxLayout *layout, const QModelIndex &parent)
{
for (int i = 0, count = parts->rowCount(parent); i < count; i++) {
const auto idx = parts->index(i, 0, parent);
const auto type = idx.data(PartModel::TypeRole).value<PartModel::Types>();
const auto content = idx.data(PartModel::ContentRole).toString();
// signature
const auto signatureSecurityLevel = idx.data(PartModel::SignatureSecurityLevelRole).value<PartModel::SecurityLevel>();
const auto signatureInfo = idx.data(PartModel::SignatureDetailsRole).toString();
const auto signatureIconName = idx.data(PartModel::SignatureIconNameRole).toString();
// encryption
const auto encryptionSecurityLevel = idx.data(PartModel::EncryptionSecurityLevelRole).value<PartModel::SecurityLevel>();
const auto encryptionInfo = idx.data(PartModel::EncryptionDetails).value<SignatureInfo>();
const auto encryptionIconName = idx.data(PartModel::EncryptionIconNameRole).toString();
// sidebar
const auto sidebarSecurityLevel = idx.data(PartModel::SidebarSecurityLevelRole).value<PartModel::SecurityLevel>();
switch (type) {
case PartModel::Types::Plain: {
auto container = new MessageWidgetContainer(signatureInfo,
signatureIconName,
signatureSecurityLevel,
encryptionInfo,
encryptionIconName,
encryptionSecurityLevel,
sidebarSecurityLevel,
urlHandler);
auto label = new QLabel(content);
label->setTextInteractionFlags(Qt::TextBrowserInteraction);
label->setOpenExternalLinks(true);
label->setWordWrap(true);
if (fixedFont) {
label->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
}
container->layout()->addWidget(label);
layout->addWidget(container);
break;
}
case PartModel::Types::Ical: {
auto container = new MessageWidgetContainer(signatureInfo,
signatureIconName,
signatureSecurityLevel,
encryptionInfo,
encryptionIconName,
encryptionSecurityLevel,
sidebarSecurityLevel,
urlHandler);
KCalendarCore::ICalFormat format;
auto incidence = format.fromString(content);
auto widget = new QGroupBox(container);
widget->setTitle(i18n("Invitation"));
auto incidenceLayout = new QFormLayout(widget);
incidenceLayout->addRow(i18n("&Summary:"), new QLabel(incidence->summary()));
incidenceLayout->addRow(i18n("&Organizer:"), new QLabel(incidence->organizer().fullName()));
if (incidence->location().length() > 0) {
incidenceLayout->addRow(i18n("&Location:"), new QLabel(incidence->location()));
}
incidenceLayout->addRow(i18n("&Start date:"), new QLabel(incidence->dtStart().toLocalTime().toString()));
if (const auto event = incidence.dynamicCast<KCalendarCore::Event>()) {
incidenceLayout->addRow(i18n("&End date:"), new QLabel(event->dtEnd().toLocalTime().toString()));
}
if (incidence->description().length() > 0) {
incidenceLayout->addRow(i18n("&Details:"), new QLabel(incidence->description()));
}
container->layout()->addWidget(widget);
layout->addWidget(container);
break;
}
case PartModel::Types::Encapsulated: {
auto container = new MessageWidgetContainer(signatureInfo,
signatureIconName,
signatureSecurityLevel,
encryptionInfo,
encryptionIconName,
encryptionSecurityLevel,
sidebarSecurityLevel,
urlHandler);
auto groupBox = new QGroupBox(container);
groupBox->setSizePolicy(QSizePolicy::MinimumExpanding, q->sizePolicy().verticalPolicy());
groupBox->setTitle(i18n("Encapsulated email"));
auto encapsulatedLayout = new QVBoxLayout(groupBox);
auto header = new QWidget(groupBox);
auto headerLayout = new QFormLayout(header);
const auto from = parts->data(parts->index(i, 0, parent), PartModel::SenderRole).toString();
const auto date = parts->data(parts->index(i, 0, parent), PartModel::DateRole).toDateTime();
headerLayout->addRow(i18n("From:"), new QLabel(from));
headerLayout->addRow(i18n("Date:"), new QLabel(date.toLocalTime().toString()));
encapsulatedLayout->addWidget(header);
recursiveBuildViewer(parts, encapsulatedLayout, parts->index(i, 0, parent));
container->layout()->addWidget(groupBox);
layout->addWidget(container);
break;
}
case PartModel::Types::Error: {
const auto errorString = idx.data(PartModel::ErrorString).toString();
auto errorWidget = new KMessageWidget(errorString);
errorWidget->setCloseButtonVisible(false);
errorWidget->setMessageType(KMessageWidget::MessageType::Error);
QObject::connect(errorWidget, &KMessageWidget::linkActivated, errorWidget, [this, errorWidget](const QString &link) {
QUrl url(link);
if (url.path() == QLatin1StringView("showCertificate")) {
urlHandler->handleClick(QUrl(link), errorWidget->window()->windowHandle());
}
});
errorWidget->setWordWrap(true);
layout->addWidget(errorWidget);
break;
}
default:
qCWarning(MIMETREEPARSER_WIDGET_LOG) << parts->data(parts->index(i, 0, parent), PartModel::ContentRole) << type;
}
}
}
class HeaderLabel : public QLabel
{
public:
HeaderLabel(const QString &content)
: QLabel(content)
{
setWordWrap(true);
setTextFormat(Qt::PlainText);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
}
void resizeEvent(QResizeEvent *event) override
{
int height = heightForWidth(width());
setMaximumHeight(height);
setMinimumHeight(height);
QLabel::resizeEvent(event);
}
};
void MessageViewer::setMessage(const KMime::Message::Ptr message)
{
setUpdatesEnabled(false);
d->parser.setMessage(message);
for (int i = d->formLayout->rowCount() - 1; i >= 0; i--) {
d->formLayout->removeRow(i);
}
if (!d->parser.subject().isEmpty()) {
const auto label = new QLabel(d->parser.subject());
label->setTextFormat(Qt::PlainText);
d->formLayout->addRow(i18n("&Subject:"), label);
}
if (!d->parser.from().isEmpty()) {
d->formLayout->addRow(i18n("&From:"), new HeaderLabel(d->parser.from()));
}
if (!d->parser.sender().isEmpty() && d->parser.from() != d->parser.sender()) {
d->formLayout->addRow(i18n("&Sender:"), new HeaderLabel(d->parser.sender()));
}
if (!d->parser.to().isEmpty()) {
d->formLayout->addRow(i18n("&To:"), new HeaderLabel(d->parser.to()));
}
if (!d->parser.cc().isEmpty()) {
d->formLayout->addRow(i18n("&CC:"), new HeaderLabel(d->parser.cc()));
}
if (!d->parser.bcc().isEmpty()) {
d->formLayout->addRow(i18n("&BCC:"), new HeaderLabel(d->parser.bcc()));
}
if (!d->parser.date().isNull()) {
d->formLayout->addRow(i18n("&Date:"), new HeaderLabel(QLocale::system().toString(d->parser.date().toLocalTime(), QLocale::ShortFormat)));
}
const auto parts = d->parser.parts();
QLayoutItem *child;
while ((child = d->layout->takeAt(0)) != nullptr) {
delete child->widget();
delete child;
}
d->recursiveBuildViewer(parts, d->layout, {});
d->layout->addStretch();
d->attachmentView->setModel(d->parser.attachments());
d->attachmentView->setVisible(d->parser.attachments()->rowCount() > 0);
setUpdatesEnabled(true);
}
void MessageViewer::print(QPainter *painter, int width)
{
const auto oldSize = size();
resize(width - 30, oldSize.height());
d->scrollArea->setFrameShape(QFrame::NoFrame);
render(painter);
d->scrollArea->setFrameShape(QFrame::StyledPanel);
resize(oldSize);
}
bool MessageViewer::fixedFont() const
{
return d->fixedFont;
}
void MessageViewer::setFixedFont(bool checked)
{
d->fixedFont = checked;
setMessage(message()); // rebuild UI
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Aug 6, 10:02 PM (4 h, 8 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
1d/bf/ea9ca9d35a33af53b040d98de0b2

Event Timeline