1 /* Copyright (C) 2006 - 2013 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>
23 #include <QDesktopServices>
24 #include <QFileDialog>
25 #include <QNetworkReply>
26 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
27 # include <QStandardPaths>
31 #include "SimplePartWidget.h"
32 #include "Gui/MessageView.h" // so that the compiler knows that it's a QObject
34 #include "Imap/Encoders.h"
35 #include "Imap/Model/ItemRoles.h"
36 #include "Imap/Model/MailboxTree.h"
37 #include "Imap/Model/Model.h"
38 #include "Imap/Network/FileDownloadManager.h"
43 SimplePartWidget::SimplePartWidget(QWidget
*parent
, Imap::Network::MsgPartNetAccessManager
*manager
,
44 const QModelIndex
&partIndex
, MessageView
*messageView
):
45 EmbeddedWebView(parent
, manager
), m_partIndex(partIndex
), m_netAccessManager(manager
), m_messageView(messageView
),
46 flowedFormat(Composer::Util::FORMAT_PLAIN
)
48 Q_ASSERT(partIndex
.isValid());
50 url
.setScheme(QLatin1String("trojita-imap"));
51 url
.setHost(QLatin1String("msg"));
52 url
.setPath(partIndex
.data(Imap::Mailbox::RolePartPathToPart
).toString());
53 if (partIndex
.data(Imap::Mailbox::RolePartMimeType
).toString() == "text/plain")
54 connect(this, SIGNAL(loadFinished(bool)), this, SLOT(slotMarkupPlainText()));
55 if (partIndex
.data(Imap::Mailbox::RolePartContentFormat
).toString().toLower() == QLatin1String("flowed"))
56 flowedFormat
= Composer::Util::FORMAT_FLOWED
;
59 m_savePart
= new QAction(tr("Save this message part..."), this);
60 connect(m_savePart
, SIGNAL(triggered()), this, SLOT(slotDownloadPart()));
61 this->addAction(m_savePart
);
63 m_saveMessage
= new QAction(tr("Save whole message..."), this);
64 connect(m_saveMessage
, SIGNAL(triggered()), this, SLOT(slotDownloadMessage()));
65 this->addAction(m_saveMessage
);
67 m_findAction
= new QAction(tr("Search..."), this);
68 m_findAction
->setShortcut(tr("Ctrl+F"));
69 connect(m_findAction
, SIGNAL(triggered()), this, SIGNAL(searchDialogRequested()));
70 addAction(m_findAction
);
72 setContextMenuPolicy(Qt::CustomContextMenu
);
74 connect(this, SIGNAL(customContextMenuRequested(QPoint
)), messageView
, SLOT(partContextMenuRequested(QPoint
)));
75 connect(this, SIGNAL(searchDialogRequested()), messageView
, SLOT(triggerSearchDialog()));
76 // The targets expect the sender() of the signal to be a SimplePartWidget, not a QWebPage,
77 // which means we have to do this indirection
78 connect(page(), SIGNAL(linkHovered(QString
,QString
,QString
)), this, SIGNAL(linkHovered(QString
,QString
,QString
)));
79 connect(this, SIGNAL(linkHovered(QString
,QString
,QString
)),
80 messageView
, SLOT(partLinkHovered(QString
,QString
,QString
)));
82 installEventFilter(messageView
);
85 void SimplePartWidget::slotMarkupPlainText() {
86 // NOTICE "single shot", we get a recursion otherwise!
87 disconnect(this, SIGNAL(loadFinished(bool)), this, SLOT(slotMarkupPlainText()));
89 // If there's no data, don't try to "fix it up"
90 if (!m_partIndex
.isValid() || !m_partIndex
.data(Imap::Mailbox::RoleIsFetched
).toBool())
93 static const QString defaultStyle
= QString::fromUtf8(
94 "pre{word-wrap: break-word; white-space: pre-wrap;}"
95 // The following line, sadly, produces a warning "QFont::setPixelSize: Pixel size <= 0 (0)".
96 // However, if it is not in place or if the font size is set higher, even to 0.1px, WebKit reserves space for the
97 // quotation characters and therefore a weird white area appears. Even width: 0px doesn't help, so it looks like
98 // we will have to live with this warning for the time being.
99 ".quotemarks{color:transparent;font-size:0px;}"
100 "blockquote{font-size:90%; margin: 4pt 0 4pt 0; padding: 0 0 0 1em; border-left: 2px solid %1;}"
101 // Stop the font size from getting smaller after reaching two levels of quotes
102 // (ie. starting on the third level, don't make the size any smaller than what it already is)
103 "blockquote blockquote blockquote {font-size: 100%}"
104 ".signature{opacity: 0.6;}"
105 // Dynamic quote collapsing via pure CSS, yay
106 "input {display: none}"
107 "input ~ span.full {display: block}"
108 "input ~ span.short {display: none}"
109 "input:checked ~ span.full {display: none}"
110 "input:checked ~ span.short {display: block}"
111 "label {border: 1px solid %2; border-radius: 5px; padding: 0px 4px 0px 4px; white-space: nowrap}"
112 // BLACK UP-POINTING SMALL TRIANGLE (U+25B4)
113 // BLACK DOWN-POINTING SMALL TRIANGLE (U+25BE)
114 "span.full > blockquote > label:before {content: \"\u25b4\"}"
115 "span.short > blockquote > label:after {content: \" \u25be\"}"
116 "span.shortquote > blockquote > label {display: none}"
119 QFontInfo
monospaceInfo(Gui::Util::systemMonospaceFont());
120 QString
fontSpecification(QLatin1String("pre{"));
121 if (monospaceInfo
.italic())
122 fontSpecification
+= QLatin1String("font-style: italic; ");
123 if (monospaceInfo
.bold())
124 fontSpecification
+= QLatin1String("font-weight: bold; ");
125 fontSpecification
+= QString::fromUtf8("font-size: %1px; font-family: \"%2\", monospace }").arg(
126 QString::number(monospaceInfo
.pixelSize()), monospaceInfo
.family());
128 QPalette palette
= QApplication::palette();
129 QString textColors
= QString::fromUtf8("body { background-color: %1; color: %2 }"
130 "a:link { color: %3 } a:visited { color: %4 } a:hover { color: %3 }").arg(
131 palette
.base().color().name(), palette
.text().color().name(),
132 palette
.link().color().name(), palette
.linkVisited().color().name());
133 // looks like there's no special color for hovered links in Qt
135 // build stylesheet and html header
136 QColor tintForQuoteIndicator
= palette
.base().color();
137 tintForQuoteIndicator
.setAlpha(0x66);
138 static QString stylesheet
= defaultStyle
.arg(palette
.link().color().name(),
139 Gui::Util::tintColor(palette
.text().color(), tintForQuoteIndicator
).name());
140 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
141 static QFile
file(QStandardPaths::writableLocation(QStandardPaths::DataLocation
) + QLatin1String("message.css"));
143 static QFile
file(QDesktopServices::storageLocation(QDesktopServices::DataLocation
) + QLatin1String("/message.css"));
145 static QDateTime lastVersion
;
146 QDateTime
lastTouched(file
.exists() ? QFileInfo(file
).lastModified() : QDateTime());
147 if (lastVersion
< lastTouched
) {
148 stylesheet
= defaultStyle
;
149 if (file
.open(QIODevice::ReadOnly
| QIODevice::Text
)) {
150 const QString userSheet
= QString::fromLocal8Bit(file
.readAll().data());
151 lastVersion
= lastTouched
;
152 stylesheet
+= "\n" + userSheet
;
156 QString
htmlHeader("<html><head><style type=\"text/css\"><!--" + textColors
+ fontSpecification
+ stylesheet
+ "--></style></head><body><pre>");
157 static QString
htmlFooter("\n</pre></body></html>");
159 // We cannot rely on the QWebFrame's toPlainText because of https://bugs.kde.org/show_bug.cgi?id=321160
160 QString markup
= Composer::Util::plainTextToHtml(
161 Imap::decodeByteArray(m_partIndex
.data(Imap::Mailbox::RolePartData
).toByteArray(),
162 m_partIndex
.data(Imap::Mailbox::RolePartCharset
).toString()),
165 // and finally set the marked up page.
166 page()->mainFrame()->setHtml(htmlHeader
+ markup
+ htmlFooter
);
169 void SimplePartWidget::slotFileNameRequested(QString
*fileName
)
171 *fileName
= QFileDialog::getSaveFileName(this, tr("Save Attachment"),
172 *fileName
, QString(),
173 0, QFileDialog::HideNameFilterDetails
177 QString
SimplePartWidget::quoteMe() const
179 QString selection
= selectedText();
180 if (selection
.isEmpty())
181 return page()->mainFrame()->toPlainText();
186 void SimplePartWidget::reloadContents()
188 EmbeddedWebView::reload();
191 QList
<QAction
*> SimplePartWidget::contextMenuSpecificActions() const
193 return QList
<QAction
*>() << m_savePart
<< m_saveMessage
<< m_findAction
;
196 void SimplePartWidget::slotDownloadPart()
198 Imap::Network::FileDownloadManager
*manager
= new Imap::Network::FileDownloadManager(this, m_netAccessManager
, m_partIndex
);
199 connect(manager
, SIGNAL(fileNameRequested(QString
*)), this, SLOT(slotFileNameRequested(QString
*)));
200 connect(manager
, SIGNAL(transferError(QString
)), m_messageView
, SIGNAL(transferError(QString
)));
201 connect(manager
, SIGNAL(transferError(QString
)), manager
, SLOT(deleteLater()));
202 connect(manager
, SIGNAL(succeeded()), manager
, SLOT(deleteLater()));
203 manager
->downloadPart();
206 void SimplePartWidget::slotDownloadMessage()
209 if (m_partIndex
.isValid()) {
210 const Imap::Mailbox::Model
*model
= 0;
211 Imap::Mailbox::TreeItem
*item
= Imap::Mailbox::Model::realTreeItem(m_partIndex
, &model
);
214 Imap::Mailbox::TreeItemMessage
*messagePtr
= dynamic_cast<Imap::Mailbox::TreeItemPart
*>(item
)->message();
215 Q_ASSERT(messagePtr
);
216 index
= messagePtr
->toIndex(const_cast<Imap::Mailbox::Model
*>(model
));
219 Imap::Network::FileDownloadManager
*manager
= new Imap::Network::FileDownloadManager(this, m_netAccessManager
, index
);
220 connect(manager
, SIGNAL(fileNameRequested(QString
*)), this, SLOT(slotFileNameRequested(QString
*)));
221 connect(manager
, SIGNAL(transferError(QString
)), m_messageView
, SIGNAL(transferError(QString
)));
222 connect(manager
, SIGNAL(transferError(QString
)), manager
, SLOT(deleteLater()));
223 connect(manager
, SIGNAL(succeeded()), manager
, SLOT(deleteLater()));
224 manager
->downloadMessage();