Check value of QFile::{open,flush,write}() when saving messages/attachments
[trojita.git] / src / Gui / PartWidgetFactory.cpp
blob1214ae876845ee2dd3bd28f2f634241d89781dac
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 "PartWidgetFactory.h"
23 #include "AttachmentView.h"
24 #include "MessageView.h" // so that the ocmpiler knows that it's an QObject
25 #include "LoadablePartWidget.h"
26 #include "PartWidget.h"
27 #include "SimplePartWidget.h"
28 #include "Common/SettingsNames.h"
29 #include "Imap/Model/ItemRoles.h"
30 #include "Imap/Model/MailboxTree.h"
31 #include "Imap/Model/Model.h"
33 #include <QGroupBox>
34 #include <QHBoxLayout>
35 #include <QLabel>
36 #include <QPushButton>
37 #include <QSettings>
38 #include <QTabWidget>
39 #include <QTextEdit>
40 #include <QVBoxLayout>
42 namespace Gui
45 PartWidgetFactory::PartWidgetFactory(Imap::Network::MsgPartNetAccessManager *manager, MessageView *messageView):
46 manager(manager), m_messageView(messageView)
50 QWidget *PartWidgetFactory::create(const QModelIndex &partIndex)
52 return create(partIndex, 0);
55 QWidget *PartWidgetFactory::create(const QModelIndex &partIndex, int recursionDepth, const PartLoadingMode loadingMode)
57 using namespace Imap::Mailbox;
58 Q_ASSERT(partIndex.isValid());
60 if (recursionDepth > 1000) {
61 return new QLabel(tr("This message contains too deep nesting of MIME message parts.\n"
62 "To prevent stack exhaustion and your head from exploding, only\n"
63 "the top-most thousand items or so are shown."), 0);
66 bool userPrefersPlaintext = QSettings().value(Common::SettingsNames::guiPreferPlaintextRendering, QVariant(true)).toBool();
68 QString mimeType = partIndex.data(Imap::Mailbox::RolePartMimeType).toString();
69 if (mimeType.startsWith(QLatin1String("multipart/"))) {
70 // it's a compound part
71 if (mimeType == QLatin1String("multipart/alternative")) {
72 return new MultipartAlternativeWidget(0, this, partIndex, recursionDepth,
73 userPrefersPlaintext ?
74 QLatin1String("text/plain") :
75 QLatin1String("text/html"));
76 } else if (mimeType == QLatin1String("multipart/signed")) {
77 return new MultipartSignedWidget(0, this, partIndex, recursionDepth);
78 } else if (mimeType == QLatin1String("multipart/related")) {
79 // The purpose of this section is to find a text/html e-mail, along with its associated body parts, and hide
80 // everything else than the HTML widget.
82 // At this point, it might be interesting to somehow respect the user's preference about using text/plain
83 // instead of text/html. However, things are a bit complicated; the widget used at this point really wants
84 // to either show just a single part or alternatively all of them in a sequence.
85 // Furthermore, if someone sends a text/plain and a text/html together inside a multipart/related, they're
86 // just wrong.
88 // Let's see if we know what the root part is
89 QModelIndex mainPartIndex;
90 QVariant mainPartCID = partIndex.data(RolePartMultipartRelatedMainCid);
91 if (mainPartCID.isValid()) {
92 const Imap::Mailbox::Model *constModel = 0;
93 Imap::Mailbox::TreeItemPart *part = dynamic_cast<Imap::Mailbox::TreeItemPart *>(Imap::Mailbox::Model::realTreeItem(partIndex, &constModel));
94 Imap::Mailbox::Model *model = const_cast<Imap::Mailbox::Model *>(constModel);
95 Imap::Mailbox::TreeItemPart *mainPartPtr = Imap::Network::MsgPartNetAccessManager::cidToPart(mainPartCID.toByteArray(), model, part);
96 if (mainPartPtr) {
97 mainPartIndex = mainPartPtr->toIndex(model);
101 if (!mainPartIndex.isValid()) {
102 // The Content-Type-based start parameter was not terribly useful. Let's find the HTML part manually.
103 QModelIndex candidate = partIndex.child(0, 0);
104 while (candidate.isValid()) {
105 if (candidate.data(RolePartMimeType).toString() == QLatin1String("text/html")) {
106 mainPartIndex = candidate;
107 break;
109 candidate = candidate.sibling(candidate.row() + 1, 0);
113 if (mainPartIndex.isValid()) {
114 if (mainPartIndex.data(RolePartMimeType).toString() == QLatin1String("text/html")) {
115 return PartWidgetFactory::create(mainPartIndex, recursionDepth+1);
116 } else {
117 // Sorry, but anything else than text/html is by definition suspicious here. Better than picking some random
118 // choice, let's just show everything.
119 return new GenericMultipartWidget(0, this, partIndex, recursionDepth);
121 } else {
122 // The RFC2387's wording is clear that in absence of an explicit START argument, the first part is the starting one.
123 // On the other hand, I've seen real-world messages whose first part is some utter garbage (an image sent as
124 // application/octet-stream, for example) and some *other* part is an HTML text. In that case (and if we somehow
125 // failed to pick the HTML part by a heuristic), it's better to show everything.
126 return new GenericMultipartWidget(0, this, partIndex, recursionDepth);
128 } else {
129 return new GenericMultipartWidget(0, this, partIndex, recursionDepth);
131 } else if (mimeType == QLatin1String("message/rfc822")) {
132 return new Message822Widget(0, this, partIndex, recursionDepth);
133 } else {
134 QStringList allowedMimeTypes;
135 allowedMimeTypes << "text/html" << "text/plain" << "image/jpeg" <<
136 "image/jpg" << "image/pjpeg" << "image/png" << "image/gif";
137 // The problem is that some nasty MUAs (hint hint Thunderbird) would
138 // happily attach a .tar.gz and call it "inline"
139 bool showInline = partIndex.data(Imap::Mailbox::RolePartBodyDisposition).toByteArray().toLower() != "attachment" &&
140 allowedMimeTypes.contains(mimeType);
142 if (showInline) {
143 const Imap::Mailbox::Model *constModel = 0;
144 Imap::Mailbox::TreeItemPart *part = dynamic_cast<Imap::Mailbox::TreeItemPart *>(Imap::Mailbox::Model::realTreeItem(partIndex, &constModel));
145 Imap::Mailbox::Model *model = const_cast<Imap::Mailbox::Model *>(constModel);
146 Q_ASSERT(model);
147 Q_ASSERT(part);
148 part->fetchFromCache(model);
150 bool showDirectly = loadingMode == LOAD_IMMEDIATELY;
151 if (!part->fetched())
152 showDirectly &= model->isNetworkOnline() || part->octets() <= ExpensiveFetchThreshold;
154 QWidget *widget = 0;
155 if (showDirectly) {
156 widget = new SimplePartWidget(0, manager, partIndex, m_messageView);
157 } else if (model->isNetworkAvailable() || part->fetched()) {
158 widget = new LoadablePartWidget(0, manager, partIndex, m_messageView,
159 loadingMode == LOAD_ON_SHOW && part->octets() <= ExpensiveFetchThreshold ?
160 LoadablePartWidget::LOAD_ON_SHOW :
161 LoadablePartWidget::LOAD_ON_CLICK);
162 } else {
163 widget = new QLabel(tr("Offline"), 0);
165 return widget;
166 } else {
167 return new AttachmentView(0, manager, partIndex, m_messageView);
170 QLabel *lbl = new QLabel(mimeType, 0);
171 return lbl;
174 MessageView *PartWidgetFactory::messageView() const
176 return m_messageView;