1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License or (at your option) version 3 or any later version
10 accepted by the membership of KDE e.V. (or its successor approved
11 by the membership of KDE e.V.), which shall act as a proxy
12 defined in Section 14 of version 3 of the license.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include <QApplication>
24 #include <QFileDialog>
25 #include <QFontDatabase>
27 #include <QNetworkReply>
29 #include <QWheelEvent>
31 #include "SimplePartWidget.h"
32 #include "Common/MetaTypes.h"
33 #include "Common/Paths.h"
34 #include "Composer/Mailto.h"
35 #include "Gui/MessageView.h" // so that the compiler knows that it's a QObject
37 #include "Imap/Encoders.h"
38 #include "Imap/Model/ItemRoles.h"
39 #include "Imap/Model/MailboxTree.h"
40 #include "Imap/Model/Model.h"
41 #include "Imap/Network/FileDownloadManager.h"
42 #include "Imap/Model/Utils.h"
43 #include "UiUtils/Color.h"
44 #include "UiUtils/IconLoader.h"
49 SimplePartWidget::SimplePartWidget(QWidget
*parent
, Imap::Network::MsgPartNetAccessManager
*manager
,
50 const QModelIndex
&partIndex
, MessageView
*messageView
):
51 EmbeddedWebView(parent
, manager
), m_partIndex(partIndex
), m_messageView(messageView
), m_netAccessManager(manager
)
53 Q_ASSERT(partIndex
.isValid());
56 connect(this, &QWebView::loadStarted
, m_messageView
, &MessageView::onWebViewLoadStarted
);
57 connect(this, &QWebView::loadFinished
, m_messageView
, &MessageView::onWebViewLoadFinished
);
61 url
.setScheme(QStringLiteral("trojita-imap"));
62 url
.setHost(QStringLiteral("msg"));
63 url
.setPath(partIndex
.data(Imap::Mailbox::RolePartPathToPart
).toString());
64 if (partIndex
.data(Imap::Mailbox::RolePartMimeType
).toString() == QLatin1String("text/plain")) {
65 if (partIndex
.data(Imap::Mailbox::RolePartOctets
).toULongLong() < 100 * 1024) {
66 connect(this, &QWebView::loadFinished
, this, &SimplePartWidget::slotMarkupPlainText
);
68 QFont
font(QFontDatabase::systemFont(QFontDatabase::FixedFont
));
69 setStaticWidth(QFontMetrics(font
).maxWidth()*90);
70 addCustomStylesheet(QStringLiteral("pre{word-wrap:normal !important;white-space:pre !important;}"));
71 QWebSettings
*s
= settings();
72 s
->setFontFamily(QWebSettings::StandardFont
, font
.family());
77 m_savePart
= new QAction(UiUtils::loadIcon(QStringLiteral("document-save")), tr("Save this message part..."), this);
78 connect(m_savePart
, &QAction::triggered
, this, &SimplePartWidget::slotDownloadPart
);
79 this->addAction(m_savePart
);
81 m_saveMessage
= new QAction(UiUtils::loadIcon(QStringLiteral("document-save-all")), tr("Save whole message..."), this);
82 connect(m_saveMessage
, &QAction::triggered
, this, &SimplePartWidget::slotDownloadMessage
);
83 this->addAction(m_saveMessage
);
85 m_findAction
= new QAction(UiUtils::loadIcon(QStringLiteral("edit-find")), tr("Search..."), this);
86 m_findAction
->setShortcut(tr("Ctrl+F"));
87 connect(m_findAction
, &QAction::triggered
, this, &SimplePartWidget::searchDialogRequested
);
88 addAction(m_findAction
);
90 m_copyMail
= new QAction(UiUtils::loadIcon(QStringLiteral("edit-copy")), tr("Copy e-mail address"), this);
91 connect(m_copyMail
, &QAction::triggered
, this, [this](){
92 QGuiApplication::clipboard()->setText(m_copyMail
->data().toString());
94 this->addAction(m_copyMail
);
96 setContextMenuPolicy(Qt::CustomContextMenu
);
98 // It is actually OK to construct this widget without any connection to a messageView -- this is often used when
99 // displaying message source or message headers. Let's silence the QObject::connect warning.
101 connect(this, &QWidget::customContextMenuRequested
, m_messageView
, &MessageView::partContextMenuRequested
);
102 connect(this, &SimplePartWidget::searchDialogRequested
, m_messageView
, &MessageView::triggerSearchDialog
);
103 // The targets expect the sender() of the signal to be a SimplePartWidget, not a QWebPage,
104 // which means we have to do this indirection
105 connect(page(), &QWebPage::linkHovered
, this, &SimplePartWidget::linkHovered
);
106 connect(this, &SimplePartWidget::linkHovered
, m_messageView
, &MessageView::partLinkHovered
);
107 connect(page(), &QWebPage::downloadRequested
, this, &SimplePartWidget::slotDownloadImage
);
109 installEventFilter(m_messageView
);
113 void SimplePartWidget::slotMarkupPlainText()
115 // NOTICE "single shot", we get a recursion otherwise!
116 disconnect(this, &QWebView::loadFinished
, this, &SimplePartWidget::slotMarkupPlainText
);
118 // If there's no data, don't try to "fix it up"
119 if (!m_partIndex
.isValid() || !m_partIndex
.data(Imap::Mailbox::RoleIsFetched
).toBool())
122 QPalette palette
= QApplication::palette();
124 // and finally set the marked up page.
125 page()->mainFrame()->setHtml(UiUtils::htmlizedTextPart(m_partIndex
, QFontDatabase::systemFont(QFontDatabase::FixedFont
),
126 palette
.base().color(), palette
.text().color(),
127 palette
.link().color(), palette
.linkVisited().color()));
130 void SimplePartWidget::slotFileNameRequested(QString
*fileName
)
132 *fileName
= QFileDialog::getSaveFileName(this, tr("Save Attachment"),
133 *fileName
, QString(),
134 0, QFileDialog::HideNameFilterDetails
138 QString
SimplePartWidget::quoteMe() const
140 QString selection
= selectedText();
141 if (selection
.isEmpty())
142 return page()->mainFrame()->toPlainText();
147 void SimplePartWidget::reloadContents()
149 EmbeddedWebView::reload();
152 const auto zoomConstant
= 1.1;
154 void SimplePartWidget::zoomIn()
156 setZoomFactor(zoomFactor() * zoomConstant
);
160 void SimplePartWidget::zoomOut()
162 setZoomFactor(zoomFactor() / zoomConstant
);
166 void SimplePartWidget::zoomOriginal()
172 void SimplePartWidget::buildContextMenu(const QPoint
&point
, QMenu
&menu
) const
174 menu
.addAction(m_findAction
);
175 auto a
= pageAction(QWebPage::Copy
);
176 a
->setIcon(UiUtils::loadIcon(QStringLiteral("edit-copy")));
178 a
= pageAction(QWebPage::SelectAll
);
179 a
->setIcon(UiUtils::loadIcon(QStringLiteral("edit-select-all")));
181 auto linkUrl
= page()->mainFrame()->hitTestContent(point
).linkUrl();
182 if (!linkUrl
.isEmpty()) {
184 auto oneMail
= Composer::extractOneMailAddress(linkUrl
);
185 if (!oneMail
.isEmpty()) {
189 a
= pageAction(QWebPage::CopyLinkToClipboard
);
190 a
->setIcon(UiUtils::loadIcon(QStringLiteral("edit-copy")));
195 menu
.addAction(m_savePart
);
196 menu
.addAction(m_saveMessage
);
197 if (!page()->mainFrame()->hitTestContent(point
).imageUrl().isEmpty()) {
198 a
= pageAction(QWebPage::DownloadImageToDisk
);
199 a
->setIcon(UiUtils::loadIcon(QStringLiteral("download")));
203 QMenu
*colorSchemeMenu
= menu
.addMenu(UiUtils::loadIcon(QStringLiteral("colorneg")), tr("Color scheme"));
204 QActionGroup
*ag
= new QActionGroup(colorSchemeMenu
);
205 for (auto item
: supportedColorSchemes()) {
206 QAction
*a
= colorSchemeMenu
->addAction(item
.second
);
207 connect(a
, &QAction::triggered
, this, [this, item
](){
208 const_cast<SimplePartWidget
*>(this)->setColorScheme(item
.first
);
210 a
->setCheckable(true);
211 if (item
.first
== m_colorScheme
) {
214 a
->setActionGroup(ag
);
217 auto zoomMenu
= menu
.addMenu(UiUtils::loadIcon(QStringLiteral("zoom")), tr("Zoom"));
219 zoomMenu
->addAction(m_messageView
->m_zoomIn
);
220 zoomMenu
->addAction(m_messageView
->m_zoomOut
);
221 zoomMenu
->addAction(m_messageView
->m_zoomOriginal
);
223 auto zoomIn
= zoomMenu
->addAction(UiUtils::loadIcon(QStringLiteral("zoom-in")), tr("Zoom In"));
224 zoomIn
->setShortcut(QKeySequence::ZoomIn
);
225 connect(zoomIn
, &QAction::triggered
, this, &SimplePartWidget::zoomIn
);
227 auto zoomOut
= zoomMenu
->addAction(UiUtils::loadIcon(QStringLiteral("zoom-out")), tr("Zoom Out"));
228 zoomOut
->setShortcut(QKeySequence::ZoomOut
);
229 connect(zoomOut
, &QAction::triggered
, this, &SimplePartWidget::zoomOut
);
231 auto zoomOriginal
= zoomMenu
->addAction(UiUtils::loadIcon(QStringLiteral("zoom-original")), tr("Original Size"));
232 connect(zoomOriginal
, &QAction::triggered
, this, &SimplePartWidget::zoomOriginal
);
236 void SimplePartWidget::slotDownloadPart()
238 Imap::Network::FileDownloadManager
*manager
= new Imap::Network::FileDownloadManager(this, m_netAccessManager
, m_partIndex
);
239 connect(manager
, &Imap::Network::FileDownloadManager::fileNameRequested
, this, &SimplePartWidget::slotFileNameRequested
);
240 connect(manager
, &Imap::Network::FileDownloadManager::transferError
, m_messageView
, &MessageView::transferError
);
241 connect(manager
, &Imap::Network::FileDownloadManager::transferError
, manager
, &QObject::deleteLater
);
242 connect(manager
, &Imap::Network::FileDownloadManager::succeeded
, manager
, &QObject::deleteLater
);
243 manager
->downloadPart();
246 void SimplePartWidget::slotDownloadMessage()
248 QModelIndex index
= m_partIndex
.data(Imap::Mailbox::RolePartMessageIndex
).toModelIndex();
249 Imap::Network::FileDownloadManager
*manager
= new Imap::Network::FileDownloadManager(this, m_netAccessManager
, index
);
250 connect(manager
, &Imap::Network::FileDownloadManager::fileNameRequested
, this, &SimplePartWidget::slotFileNameRequested
);
251 connect(manager
, &Imap::Network::FileDownloadManager::transferError
, m_messageView
, &MessageView::transferError
);
252 connect(manager
, &Imap::Network::FileDownloadManager::transferError
, manager
, &QObject::deleteLater
);
253 connect(manager
, &Imap::Network::FileDownloadManager::succeeded
, manager
, &QObject::deleteLater
);
254 manager
->downloadMessage();
257 void SimplePartWidget::slotDownloadImage(const QNetworkRequest
&req
)
259 Imap::Network::FileDownloadManager
*manager
= new Imap::Network::FileDownloadManager(this, m_netAccessManager
, req
.url(), m_partIndex
.parent());
260 connect(manager
, &Imap::Network::FileDownloadManager::fileNameRequested
, this, &SimplePartWidget::slotFileNameRequested
);
261 connect(manager
, &Imap::Network::FileDownloadManager::transferError
, m_messageView
, &MessageView::transferError
);
262 connect(manager
, &Imap::Network::FileDownloadManager::transferError
, manager
, &QObject::deleteLater
);
263 connect(manager
, &Imap::Network::FileDownloadManager::succeeded
, manager
, &QObject::deleteLater
);
264 manager
->downloadPart();