File indexing completed on 2024-12-22 05:05:21
0001 // SPDX-FileCopyrightText: 2023 g10 Code GmbH 0002 // SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com> 0003 // SPDX-License-Identifier: GPL-2.0-or-later 0004 0005 #include "messageviewerdialog.h" 0006 0007 #include "messageviewer.h" 0008 #include "mimetreeparser_widgets_debug.h" 0009 #include <MimeTreeParserCore/CryptoHelper> 0010 #include <MimeTreeParserCore/FileOpener> 0011 0012 #include <KLocalizedString> 0013 #include <KMessageBox> 0014 #include <KMessageWidget> 0015 0016 #include <QDialogButtonBox> 0017 #include <QFileDialog> 0018 #include <QMenuBar> 0019 #include <QPainter> 0020 #include <QPrintDialog> 0021 #include <QPrintPreviewDialog> 0022 #include <QPrinter> 0023 #include <QPushButton> 0024 #include <QRegularExpression> 0025 #include <QSaveFile> 0026 #include <QStandardPaths> 0027 #include <QStyle> 0028 #include <QToolBar> 0029 #include <QVBoxLayout> 0030 0031 using namespace MimeTreeParser::Widgets; 0032 0033 namespace 0034 { 0035 0036 /// On windows, force the filename to end with .eml 0037 /// On Linux, do nothing as this is handled by the file picker 0038 inline QString changeExtension(const QString &fileName) 0039 { 0040 #ifdef Q_OS_WIN 0041 auto renamedFileName = fileName; 0042 renamedFileName.replace(QRegularExpression(QStringLiteral("\\.(mbox|p7m|asc)$")), QStringLiteral(".eml")); 0043 0044 // In case the file name didn't contain any of the expected extension: mbox, p7m, asc and eml 0045 // or doesn't contains an extension at all. 0046 if (!renamedFileName.endsWith(QStringLiteral(".eml"))) { 0047 renamedFileName += QStringLiteral(".eml"); 0048 } 0049 0050 return renamedFileName; 0051 #else 0052 // Handled automatically by the file picker on linux 0053 return fileName; 0054 #endif 0055 } 0056 0057 } 0058 0059 class MessageViewerDialog::Private 0060 { 0061 public: 0062 int currentIndex = 0; 0063 QList<KMime::Message::Ptr> messages; 0064 QString fileName; 0065 MimeTreeParser::Widgets::MessageViewer *messageViewer = nullptr; 0066 QAction *nextAction = nullptr; 0067 QAction *previousAction = nullptr; 0068 QToolBar *toolBar = nullptr; 0069 0070 void setCurrentIndex(int currentIndex); 0071 QMenuBar *createMenuBar(QWidget *parent); 0072 0073 private: 0074 void save(QWidget *parent); 0075 void saveDecrypted(QWidget *parent); 0076 void print(QWidget *parent); 0077 void printPreview(QWidget *parent); 0078 void printInternal(QPrinter *printer); 0079 }; 0080 0081 void MessageViewerDialog::Private::setCurrentIndex(int index) 0082 { 0083 Q_ASSERT(index >= 0); 0084 Q_ASSERT(index < messages.count()); 0085 0086 currentIndex = index; 0087 messageViewer->setMessage(messages[currentIndex]); 0088 0089 previousAction->setEnabled(currentIndex != 0); 0090 nextAction->setEnabled(currentIndex != messages.count() - 1); 0091 } 0092 0093 QMenuBar *MessageViewerDialog::Private::createMenuBar(QWidget *parent) 0094 { 0095 const auto menuBar = new QMenuBar(parent); 0096 0097 // File menu 0098 const auto fileMenu = menuBar->addMenu(i18nc("@action:inmenu", "&File")); 0099 0100 const auto saveAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18nc("@action:inmenu", "&Save")); 0101 QObject::connect(saveAction, &QAction::triggered, parent, [parent, this] { 0102 save(parent); 0103 }); 0104 fileMenu->addAction(saveAction); 0105 0106 const auto saveDecryptedAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18nc("@action:inmenu", "Save Decrypted")); 0107 QObject::connect(saveDecryptedAction, &QAction::triggered, parent, [parent, this] { 0108 saveDecrypted(parent); 0109 }); 0110 fileMenu->addAction(saveDecryptedAction); 0111 0112 const auto printPreviewAction = new QAction(QIcon::fromTheme(QStringLiteral("document-print-preview")), i18nc("@action:inmenu", "Print Preview")); 0113 QObject::connect(printPreviewAction, &QAction::triggered, parent, [parent, this] { 0114 printPreview(parent); 0115 }); 0116 fileMenu->addAction(printPreviewAction); 0117 0118 const auto printAction = new QAction(QIcon::fromTheme(QStringLiteral("document-print")), i18nc("@action:inmenu", "&Print")); 0119 QObject::connect(printAction, &QAction::triggered, parent, [parent, this] { 0120 print(parent); 0121 }); 0122 fileMenu->addAction(printAction); 0123 0124 // Navigation menu 0125 const auto navigationMenu = menuBar->addMenu(i18nc("@action:inmenu", "&Navigation")); 0126 previousAction = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18nc("@action:button Previous email", "Previous Message"), parent); 0127 previousAction->setEnabled(false); 0128 navigationMenu->addAction(previousAction); 0129 0130 nextAction = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18nc("@action:button Next email", "Next Message"), parent); 0131 nextAction->setEnabled(false); 0132 navigationMenu->addAction(nextAction); 0133 0134 return menuBar; 0135 } 0136 0137 void MessageViewerDialog::Private::save(QWidget *parent) 0138 { 0139 const QString location = QFileDialog::getSaveFileName(parent, 0140 i18nc("@title:window", "Save File"), 0141 changeExtension(fileName), 0142 i18nc("File dialog accepted files", "Email files (*.eml *.mbox)")); 0143 0144 QSaveFile file(location); 0145 if (!file.open(QIODevice::WriteOnly)) { 0146 KMessageBox::error(parent, i18n("File %1 could not be created.", location), i18n("Error saving message")); 0147 return; 0148 } 0149 file.write(messages[currentIndex]->encodedContent()); 0150 file.commit(); 0151 } 0152 0153 void MessageViewerDialog::Private::saveDecrypted(QWidget *parent) 0154 { 0155 const QString location = QFileDialog::getSaveFileName(parent, 0156 i18nc("@title:window", "Save Decrypted File"), 0157 changeExtension(fileName), 0158 i18nc("File dialog accepted files", "Email files (*.eml *.mbox)")); 0159 0160 QSaveFile file(location); 0161 if (!file.open(QIODevice::WriteOnly)) { 0162 KMessageBox::error(parent, i18nc("Error message", "File %1 could not be created.", location), i18n("Error saving message")); 0163 return; 0164 } 0165 auto message = messages[currentIndex]; 0166 bool wasEncrypted = false; 0167 auto decryptedMessage = CryptoUtils::decryptMessage(message, wasEncrypted); 0168 if (!wasEncrypted) { 0169 decryptedMessage = message; 0170 } 0171 file.write(decryptedMessage->encodedContent()); 0172 0173 file.commit(); 0174 } 0175 0176 void MessageViewerDialog::Private::print(QWidget *parent) 0177 { 0178 QPrinter printer; 0179 QPrintDialog dialog(&printer, parent); 0180 dialog.setWindowTitle(i18nc("@title:window", "Print Document")); 0181 if (dialog.exec() != QDialog::Accepted) 0182 return; 0183 0184 printInternal(&printer); 0185 } 0186 0187 void MessageViewerDialog::Private::printPreview(QWidget *parent) 0188 { 0189 auto dialog = new QPrintPreviewDialog(parent); 0190 dialog->setAttribute(Qt::WA_DeleteOnClose); 0191 dialog->resize(800, 750); 0192 dialog->setWindowTitle(i18nc("@title:window", "Print Document")); 0193 QObject::connect(dialog, &QPrintPreviewDialog::paintRequested, parent, [this](QPrinter *printer) { 0194 printInternal(printer); 0195 }); 0196 dialog->open(); 0197 } 0198 0199 void MessageViewerDialog::Private::printInternal(QPrinter *printer) 0200 { 0201 QPainter painter; 0202 painter.begin(printer); 0203 const auto pageLayout = printer->pageLayout(); 0204 const auto pageRect = pageLayout.paintRectPixels(printer->resolution()); 0205 const double xscale = pageRect.width() / double(messageViewer->width()); 0206 const double yscale = pageRect.height() / double(messageViewer->height()); 0207 const double scale = qMin(qMin(xscale, yscale), 1.); 0208 painter.translate(pageRect.x(), pageRect.y()); 0209 painter.scale(scale, scale); 0210 messageViewer->print(&painter, pageRect.width()); 0211 } 0212 0213 MessageViewerDialog::MessageViewerDialog(const QList<KMime::Message::Ptr> &messages, QWidget *parent) 0214 : QDialog(parent) 0215 , d(std::make_unique<Private>()) 0216 { 0217 d->messages += messages; 0218 initGUI(); 0219 } 0220 0221 MessageViewerDialog::MessageViewerDialog(const QString &fileName, QWidget *parent) 0222 : QDialog(parent) 0223 , d(std::make_unique<Private>()) 0224 { 0225 d->fileName = fileName; 0226 d->messages += MimeTreeParser::Core::FileOpener::openFile(fileName); 0227 initGUI(); 0228 } 0229 0230 void MessageViewerDialog::initGUI() 0231 { 0232 const auto mainLayout = new QVBoxLayout(this); 0233 mainLayout->setContentsMargins({}); 0234 mainLayout->setSpacing(0); 0235 0236 const auto layout = new QVBoxLayout; 0237 0238 const auto menuBar = d->createMenuBar(this); 0239 mainLayout->setMenuBar(menuBar); 0240 0241 if (d->messages.isEmpty()) { 0242 auto errorMessage = new KMessageWidget(this); 0243 errorMessage->setMessageType(KMessageWidget::Error); 0244 errorMessage->setText(i18nc("@info", "Unable to read file")); 0245 layout->addWidget(errorMessage); 0246 return; 0247 } 0248 0249 const bool multipleMessages = d->messages.length() > 1; 0250 d->toolBar = new QToolBar(this); 0251 0252 if (multipleMessages) { 0253 #ifdef Q_OS_UNIX 0254 d->toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle); 0255 #else 0256 // on other platforms the default is IconOnly which is bad for 0257 // accessibility and can't be changed by the user. 0258 toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 0259 #endif 0260 0261 d->toolBar->addAction(d->previousAction); 0262 connect(d->previousAction, &QAction::triggered, this, [this] { 0263 d->setCurrentIndex(d->currentIndex - 1); 0264 }); 0265 0266 const auto spacer = new QWidget(this); 0267 spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 0268 d->toolBar->addWidget(spacer); 0269 0270 d->toolBar->addAction(d->nextAction); 0271 connect(d->nextAction, &QAction::triggered, this, [this] { 0272 d->setCurrentIndex(d->currentIndex + 1); 0273 }); 0274 d->nextAction->setEnabled(true); 0275 0276 mainLayout->addWidget(d->toolBar); 0277 } else { 0278 mainLayout->addWidget(d->toolBar); 0279 d->toolBar->hide(); 0280 } 0281 0282 mainLayout->addLayout(layout); 0283 0284 d->messageViewer = new MimeTreeParser::Widgets::MessageViewer(this); 0285 d->messageViewer->setMessage(d->messages[0]); 0286 layout->addWidget(d->messageViewer); 0287 0288 auto buttonBox = new QDialogButtonBox(this); 0289 buttonBox->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin, nullptr, this), 0290 style()->pixelMetric(QStyle::PM_LayoutTopMargin, nullptr, this), 0291 style()->pixelMetric(QStyle::PM_LayoutRightMargin, nullptr, this), 0292 style()->pixelMetric(QStyle::PM_LayoutBottomMargin, nullptr, this)); 0293 auto closeButton = buttonBox->addButton(QDialogButtonBox::Close); 0294 connect(closeButton, &QPushButton::pressed, this, &QDialog::accept); 0295 layout->addWidget(buttonBox); 0296 0297 setMinimumSize(300, 300); 0298 resize(600, 600); 0299 } 0300 0301 MessageViewerDialog::~MessageViewerDialog() = default; 0302 0303 QToolBar *MessageViewerDialog::toolBar() const 0304 { 0305 return d->toolBar; 0306 } 0307 0308 QList<KMime::Message::Ptr> MessageViewerDialog::messages() const 0309 { 0310 return d->messages; 0311 } 0312 0313 #include "moc_messageviewerdialog.cpp"