Check value of QFile::{open,flush,write}() when saving messages/attachments
[trojita.git] / src / Gui / SimplePartWidget.cpp
blob9209237b09f6a5856bcc3470eca87ba7742c0f67
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>
28 #endif
29 #include <QWebFrame>
31 #include "SimplePartWidget.h"
32 #include "Gui/MessageView.h" // so that the compiler knows that it's a QObject
33 #include "Gui/Util.h"
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"
40 namespace Gui
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());
49 QUrl url;
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;
57 load(url);
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())
91 return;
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"));
142 #else
143 static QFile file(QDesktopServices::storageLocation(QDesktopServices::DataLocation) + QLatin1String("/message.css"));
144 #endif
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;
153 file.close();
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()),
163 flowedFormat);
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();
182 else
183 return selection;
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()
208 QModelIndex index;
209 if (m_partIndex.isValid()) {
210 const Imap::Mailbox::Model *model = 0;
211 Imap::Mailbox::TreeItem *item = Imap::Mailbox::Model::realTreeItem(m_partIndex, &model);
212 Q_ASSERT(model);
213 Q_ASSERT(item);
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();