File indexing completed on 2024-06-16 05:01:30
0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net> 0002 0003 This file is part of the Trojita Qt IMAP e-mail client, 0004 http://trojita.flaska.net/ 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License as 0008 published by the Free Software Foundation; either version 2 of 0009 the License or (at your option) version 3 or any later version 0010 accepted by the membership of KDE e.V. (or its successor approved 0011 by the membership of KDE e.V.), which shall act as a proxy 0012 defined in Section 14 of version 3 of the license. 0013 0014 This program is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 GNU General Public License for more details. 0018 0019 You should have received a copy of the GNU General Public License 0020 along with this program. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 #include <QApplication> 0023 #include <QClipboard> 0024 #include <QFileDialog> 0025 #include <QFontDatabase> 0026 #include <QMenu> 0027 #include <QNetworkReply> 0028 #include <QWebFrame> 0029 #include <QWheelEvent> 0030 0031 #include "SimplePartWidget.h" 0032 #include "Common/MetaTypes.h" 0033 #include "Common/Paths.h" 0034 #include "Composer/Mailto.h" 0035 #include "Gui/MessageView.h" // so that the compiler knows that it's a QObject 0036 #include "Gui/Util.h" 0037 #include "Imap/Encoders.h" 0038 #include "Imap/Model/ItemRoles.h" 0039 #include "Imap/Model/MailboxTree.h" 0040 #include "Imap/Model/Model.h" 0041 #include "Imap/Network/FileDownloadManager.h" 0042 #include "Imap/Model/Utils.h" 0043 #include "UiUtils/Color.h" 0044 #include "UiUtils/IconLoader.h" 0045 0046 namespace Gui 0047 { 0048 0049 SimplePartWidget::SimplePartWidget(QWidget *parent, Imap::Network::MsgPartNetAccessManager *manager, 0050 const QModelIndex &partIndex, MessageView *messageView): 0051 EmbeddedWebView(parent, manager, messageView->profileSettings()), m_partIndex(partIndex), m_messageView(messageView), m_netAccessManager(manager) 0052 { 0053 Q_ASSERT(partIndex.isValid()); 0054 0055 if (m_messageView) { 0056 connect(this, &QWebView::loadStarted, m_messageView, &MessageView::onWebViewLoadStarted); 0057 connect(this, &QWebView::loadFinished, m_messageView, &MessageView::onWebViewLoadFinished); 0058 } 0059 0060 QUrl url; 0061 url.setScheme(QStringLiteral("trojita-imap")); 0062 url.setHost(QStringLiteral("msg")); 0063 url.setPath(partIndex.data(Imap::Mailbox::RolePartPathToPart).toString()); 0064 if (partIndex.data(Imap::Mailbox::RolePartMimeType).toString() == QLatin1String("text/plain")) { 0065 if (partIndex.data(Imap::Mailbox::RolePartOctets).toULongLong() < 100 * 1024) { 0066 connect(this, &QWebView::loadFinished, this, &SimplePartWidget::slotMarkupPlainText); 0067 } else { 0068 QFont font(QFontDatabase::systemFont(QFontDatabase::FixedFont)); 0069 setStaticWidth(QFontMetrics(font).maxWidth()*90); 0070 addCustomStylesheet(QStringLiteral("pre{word-wrap:normal !important;white-space:pre !important;}")); 0071 QWebSettings *s = settings(); 0072 s->setFontFamily(QWebSettings::StandardFont, font.family()); 0073 } 0074 } 0075 load(url); 0076 0077 m_savePart = new QAction(UiUtils::loadIcon(QStringLiteral("document-save")), tr("Save this message part..."), this); 0078 connect(m_savePart, &QAction::triggered, this, &SimplePartWidget::slotDownloadPart); 0079 this->addAction(m_savePart); 0080 0081 m_saveMessage = new QAction(UiUtils::loadIcon(QStringLiteral("document-save-all")), tr("Save whole message..."), this); 0082 connect(m_saveMessage, &QAction::triggered, this, &SimplePartWidget::slotDownloadMessage); 0083 this->addAction(m_saveMessage); 0084 0085 m_findAction = new QAction(UiUtils::loadIcon(QStringLiteral("edit-find")), tr("Search..."), this); 0086 connect(m_findAction, &QAction::triggered, this, &SimplePartWidget::searchDialogRequested); 0087 0088 m_copyMail = new QAction(UiUtils::loadIcon(QStringLiteral("edit-copy")), tr("Copy e-mail address"), this); 0089 connect(m_copyMail, &QAction::triggered, this, [this](){ 0090 QGuiApplication::clipboard()->setText(m_copyMail->data().toString()); 0091 }); 0092 this->addAction(m_copyMail); 0093 0094 setContextMenuPolicy(Qt::CustomContextMenu); 0095 0096 // It is actually OK to construct this widget without any connection to a messageView -- this is often used when 0097 // displaying message source or message headers. Let's silence the QObject::connect warning. 0098 if (m_messageView) { 0099 connect(this, &QWidget::customContextMenuRequested, m_messageView, &MessageView::partContextMenuRequested); 0100 // The targets expect the sender() of the signal to be a SimplePartWidget, not a QWebPage, 0101 // which means we have to do this indirection 0102 connect(page(), &QWebPage::linkHovered, this, &SimplePartWidget::linkHovered); 0103 connect(this, &SimplePartWidget::linkHovered, m_messageView, &MessageView::partLinkHovered); 0104 connect(page(), &QWebPage::downloadRequested, this, &SimplePartWidget::slotDownloadImage); 0105 0106 installEventFilter(m_messageView); 0107 } 0108 } 0109 0110 void SimplePartWidget::slotMarkupPlainText() 0111 { 0112 // NOTICE "single shot", we get a recursion otherwise! 0113 disconnect(this, &QWebView::loadFinished, this, &SimplePartWidget::slotMarkupPlainText); 0114 0115 // If there's no data, don't try to "fix it up" 0116 if (!m_partIndex.isValid() || !m_partIndex.data(Imap::Mailbox::RoleIsFetched).toBool()) 0117 return; 0118 0119 QPalette palette = QApplication::palette(); 0120 0121 // and finally set the marked up page. 0122 page()->mainFrame()->setHtml(UiUtils::htmlizedTextPart(m_partIndex, QFontDatabase::systemFont(QFontDatabase::FixedFont), 0123 palette.base().color(), palette.text().color(), 0124 palette.link().color(), palette.linkVisited().color())); 0125 } 0126 0127 void SimplePartWidget::slotFileNameRequested(QString *fileName) 0128 { 0129 *fileName = QFileDialog::getSaveFileName(this, tr("Save Attachment"), 0130 *fileName, QString(), 0131 0, QFileDialog::HideNameFilterDetails 0132 ); 0133 } 0134 0135 QString SimplePartWidget::quoteMe() const 0136 { 0137 QString selection = selectedText(); 0138 if (selection.isEmpty()) 0139 return page()->mainFrame()->toPlainText(); 0140 else 0141 return selection; 0142 } 0143 0144 void SimplePartWidget::reloadContents() 0145 { 0146 EmbeddedWebView::reload(); 0147 } 0148 0149 const auto zoomConstant = 1.1; 0150 0151 void SimplePartWidget::zoomIn() 0152 { 0153 setZoomFactor(zoomFactor() * zoomConstant); 0154 constrainSize(); 0155 } 0156 0157 void SimplePartWidget::zoomOut() 0158 { 0159 setZoomFactor(zoomFactor() / zoomConstant); 0160 constrainSize(); 0161 } 0162 0163 void SimplePartWidget::zoomOriginal() 0164 { 0165 setZoomFactor(1); 0166 constrainSize(); 0167 } 0168 0169 bool SimplePartWidget::searchDialogRequested() 0170 { 0171 m_messageView->triggerSearchDialogBy(this); 0172 return true; 0173 } 0174 0175 void SimplePartWidget::buildContextMenu(const QPoint &point, QMenu &menu) const 0176 { 0177 menu.addAction(m_findAction); 0178 auto a = pageAction(QWebPage::Copy); 0179 a->setIcon(UiUtils::loadIcon(QStringLiteral("edit-copy"))); 0180 menu.addAction(a); 0181 a = pageAction(QWebPage::SelectAll); 0182 a->setIcon(UiUtils::loadIcon(QStringLiteral("edit-select-all"))); 0183 menu.addAction(a); 0184 auto linkUrl = page()->mainFrame()->hitTestContent(point).linkUrl(); 0185 if (!linkUrl.isEmpty()) { 0186 menu.addSeparator(); 0187 auto oneMail = Composer::extractOneMailAddress(linkUrl); 0188 if (!oneMail.isEmpty()) { 0189 a = m_copyMail; 0190 a->setData(oneMail); 0191 } else { 0192 a = pageAction(QWebPage::CopyLinkToClipboard); 0193 a->setIcon(UiUtils::loadIcon(QStringLiteral("edit-copy"))); 0194 } 0195 menu.addAction(a); 0196 } 0197 menu.addSeparator(); 0198 menu.addAction(m_savePart); 0199 menu.addAction(m_saveMessage); 0200 if (!page()->mainFrame()->hitTestContent(point).imageUrl().isEmpty()) { 0201 a = pageAction(QWebPage::DownloadImageToDisk); 0202 a->setIcon(UiUtils::loadIcon(QStringLiteral("download"))); 0203 menu.addAction(a); 0204 } 0205 menu.addSeparator(); 0206 QMenu *colorSchemeMenu = menu.addMenu(UiUtils::loadIcon(QStringLiteral("colorneg")), tr("Color scheme")); 0207 QActionGroup *ag = new QActionGroup(colorSchemeMenu); 0208 for (auto item: supportedColorSchemes()) { 0209 QAction *a = colorSchemeMenu->addAction(item.second); 0210 connect(a, &QAction::triggered, this, [this, item](){ 0211 const_cast<SimplePartWidget*>(this)->changeColorScheme(item.first); 0212 }); 0213 a->setCheckable(true); 0214 if (item.first == m_colorScheme) { 0215 a->setChecked(true); 0216 } 0217 a->setActionGroup(ag); 0218 } 0219 0220 auto zoomMenu = menu.addMenu(UiUtils::loadIcon(QStringLiteral("zoom")), tr("Zoom")); 0221 if (m_messageView) { 0222 zoomMenu->addAction(m_messageView->m_zoomIn); 0223 zoomMenu->addAction(m_messageView->m_zoomOut); 0224 zoomMenu->addAction(m_messageView->m_zoomOriginal); 0225 } else { 0226 auto zoomIn = zoomMenu->addAction(UiUtils::loadIcon(QStringLiteral("zoom-in")), tr("Zoom In")); 0227 zoomIn->setShortcut(QKeySequence::ZoomIn); 0228 connect(zoomIn, &QAction::triggered, this, &SimplePartWidget::zoomIn); 0229 0230 auto zoomOut = zoomMenu->addAction(UiUtils::loadIcon(QStringLiteral("zoom-out")), tr("Zoom Out")); 0231 zoomOut->setShortcut(QKeySequence::ZoomOut); 0232 connect(zoomOut, &QAction::triggered, this, &SimplePartWidget::zoomOut); 0233 0234 auto zoomOriginal = zoomMenu->addAction(UiUtils::loadIcon(QStringLiteral("zoom-original")), tr("Original Size")); 0235 connect(zoomOriginal, &QAction::triggered, this, &SimplePartWidget::zoomOriginal); 0236 } 0237 } 0238 0239 void SimplePartWidget::slotDownloadPart() 0240 { 0241 Imap::Network::FileDownloadManager *manager = new Imap::Network::FileDownloadManager(this, m_netAccessManager, m_partIndex); 0242 connect(manager, &Imap::Network::FileDownloadManager::fileNameRequested, this, &SimplePartWidget::slotFileNameRequested); 0243 connect(manager, &Imap::Network::FileDownloadManager::transferError, m_messageView, &MessageView::transferError); 0244 connect(manager, &Imap::Network::FileDownloadManager::transferError, manager, &QObject::deleteLater); 0245 connect(manager, &Imap::Network::FileDownloadManager::succeeded, manager, &QObject::deleteLater); 0246 manager->downloadPart(); 0247 } 0248 0249 void SimplePartWidget::slotDownloadMessage() 0250 { 0251 QModelIndex index = m_partIndex.data(Imap::Mailbox::RolePartMessageIndex).toModelIndex(); 0252 Imap::Network::FileDownloadManager *manager = new Imap::Network::FileDownloadManager(this, m_netAccessManager, index); 0253 connect(manager, &Imap::Network::FileDownloadManager::fileNameRequested, this, &SimplePartWidget::slotFileNameRequested); 0254 connect(manager, &Imap::Network::FileDownloadManager::transferError, m_messageView, &MessageView::transferError); 0255 connect(manager, &Imap::Network::FileDownloadManager::transferError, manager, &QObject::deleteLater); 0256 connect(manager, &Imap::Network::FileDownloadManager::succeeded, manager, &QObject::deleteLater); 0257 manager->downloadMessage(); 0258 } 0259 0260 void SimplePartWidget::slotDownloadImage(const QNetworkRequest &req) 0261 { 0262 Imap::Network::FileDownloadManager *manager = new Imap::Network::FileDownloadManager(this, m_netAccessManager, req.url(), m_partIndex.parent()); 0263 connect(manager, &Imap::Network::FileDownloadManager::fileNameRequested, this, &SimplePartWidget::slotFileNameRequested); 0264 connect(manager, &Imap::Network::FileDownloadManager::transferError, m_messageView, &MessageView::transferError); 0265 connect(manager, &Imap::Network::FileDownloadManager::transferError, manager, &QObject::deleteLater); 0266 connect(manager, &Imap::Network::FileDownloadManager::succeeded, manager, &QObject::deleteLater); 0267 manager->downloadPart(); 0268 } 0269 0270 } 0271