refactoring: nullptr and formatting
[trojita.git] / src / Gui / AttachmentView.cpp
blob10051823d101fc5fe0e659f4d049d9c622ad37a5
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 "AttachmentView.h"
23 #include <QAction>
24 #include <QApplication>
25 #include <QDesktopServices>
26 #include <QDrag>
27 #include <QFileDialog>
28 #include <QHBoxLayout>
29 #include <QMenu>
30 #include <QMimeData>
31 #include <QMimeDatabase>
32 #include <QMouseEvent>
33 #include <QPainter>
34 #include <QPushButton>
35 #include <QLabel>
36 #include <QStyle>
37 #include <QStyleOption>
38 #include <QTemporaryFile>
39 #include <QTimer>
40 #include <QToolButton>
42 #include "Common/DeleteAfter.h"
43 #include "Common/Paths.h"
44 #include "Gui/MessageView.h" // so that the compiler knows it's a QObject
45 #include "Gui/Window.h"
46 #include "Imap/Network/FileDownloadManager.h"
47 #include "Imap/Model/DragAndDrop.h"
48 #include "Imap/Model/MailboxTree.h"
49 #include "Imap/Model/ItemRoles.h"
50 #include "UiUtils/Formatting.h"
51 #include "UiUtils/IconLoader.h"
53 namespace Gui
56 AttachmentView::AttachmentView(QWidget *parent, Imap::Network::MsgPartNetAccessManager *manager,
57 const QModelIndex &partIndex, MessageView *messageView, QWidget *contentWidget)
58 : QFrame(parent)
59 , m_partIndex(partIndex)
60 , m_messageView(messageView)
61 , m_downloadAttachment(nullptr)
62 , m_openAttachment(nullptr)
63 , m_showHideAttachment(nullptr)
64 , m_showSource(nullptr)
65 , m_netAccess(manager)
66 , m_tmpFile(nullptr)
67 , m_contentWidget(contentWidget)
69 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
70 setFrameStyle(QFrame::NoFrame);
71 setCursor(Qt::OpenHandCursor);
72 setAttribute(Qt::WA_Hover);
74 // not actually required, but styles may assume the parameter and segfault on nullptr deref
75 QStyleOption opt;
76 opt.initFrom(this);
78 const int padding = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this);
79 setContentsMargins(padding, 0, padding, 0);
81 QHBoxLayout *layout = new QHBoxLayout();
82 layout->setContentsMargins(0,0,0,0);
84 // should be PM_LayoutHorizontalSpacing, but is not implemented by many Qt4 styles -including oxygen- for other conflicts
85 int spacing = style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing, &opt, this);
86 if (spacing < 0)
87 spacing = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing, &opt, this);
88 layout->setSpacing(0);
90 m_menu = new QMenu(this);
91 m_downloadAttachment = m_menu->addAction(UiUtils::loadIcon(QStringLiteral("document-save-as")), tr("Download"));
92 m_openAttachment = m_menu->addAction(tr("Open Directly"));
93 connect(m_downloadAttachment, &QAction::triggered, this, &AttachmentView::slotDownloadAttachment);
94 connect(m_openAttachment, &QAction::triggered, this, &AttachmentView::slotOpenAttachment);
95 connect(m_menu, &QMenu::aboutToShow, this, &AttachmentView::updateShowHideAttachmentState);
96 if (m_contentWidget) {
97 m_showHideAttachment = m_menu->addAction(UiUtils::loadIcon(QStringLiteral("view-preview")), tr("Show Preview"));
98 m_showHideAttachment->setCheckable(true);
99 m_showHideAttachment->setChecked(!m_contentWidget->isHidden());
100 connect(m_showHideAttachment, &QAction::triggered, m_contentWidget, &QWidget::setVisible);
101 connect(m_showHideAttachment, &QAction::triggered, this, &AttachmentView::updateShowHideAttachmentState);
103 if (partIndex.data(Imap::Mailbox::RolePartMimeType).toByteArray() == "message/rfc822") {
104 m_showSource = m_menu->addAction(UiUtils::loadIcon(QStringLiteral("text-x-hex")), tr("Show Message Source"));
105 connect(m_showSource, &QAction::triggered, this, &AttachmentView::showMessageSource);
108 // Icon on the left
109 m_icon = new QToolButton(this);
110 m_icon->setAttribute(Qt::WA_NoMousePropagation, false); // inform us for DnD
111 m_icon->setAutoRaise(true);
112 m_icon->setIconSize(QSize(22,22));
113 m_icon->setToolButtonStyle(Qt::ToolButtonIconOnly);
114 m_icon->setPopupMode(QToolButton::MenuButtonPopup);
115 m_icon->setMenu(m_menu);
116 connect(m_icon, &QAbstractButton::pressed, this, &AttachmentView::toggleIconCursor);
117 connect(m_icon, &QAbstractButton::clicked, this, &AttachmentView::showMenuOrPreview);
118 connect(m_icon, &QAbstractButton::released, this, &AttachmentView::toggleIconCursor);
119 m_icon->setCursor(Qt::ArrowCursor);
121 QString mimeDescription = partIndex.data(Imap::Mailbox::RolePartMimeType).toString();
122 QString rawMime = mimeDescription;
123 QMimeType mimeType = QMimeDatabase().mimeTypeForName(mimeDescription);
124 if (mimeType.isValid() && !mimeType.isDefault()) {
125 mimeDescription = mimeType.comment();
126 QIcon icon;
127 if (rawMime == QLatin1String("message/rfc822")) {
128 // Special case for plain e-mail messages. Motivation for this is that most of the OSes ship these icons
129 // with a pixmap which shows something like a sheet of paper as the background. I find it rather dumb
130 // to do this in the context of a MUA where attached messages are pretty common, which is why this special
131 // case is in place. Comments welcome.
132 icon = UiUtils::loadIcon(QStringLiteral("trojita"));
133 } else {
134 icon = QIcon::fromTheme(mimeType.iconName(),
135 QIcon::fromTheme(mimeType.genericIconName(), UiUtils::loadIcon(QStringLiteral("mail-attachment")))
138 m_icon->setIcon(icon);
139 } else {
140 m_icon->setIcon(UiUtils::loadIcon(QStringLiteral("mail-attachment")));
143 layout->addWidget(m_icon);
145 // space between icon and label
146 layout->addSpacing(spacing);
148 QVBoxLayout *subLayout = new QVBoxLayout;
149 subLayout->setContentsMargins(0,0,0,0);
150 // The file name shall be mouse-selectable
151 m_fileName = new QLabel(this);
152 m_fileName->setTextFormat(Qt::PlainText);
153 m_fileName->setText(partIndex.data(Imap::Mailbox::RolePartFileName).toString());
154 m_fileName->setTextInteractionFlags(Qt::TextSelectableByMouse);
155 m_fileName->setCursor(Qt::IBeamCursor);
156 m_fileName->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
157 subLayout->addWidget(m_fileName);
159 // Some metainformation -- the MIME type and the file size
160 QLabel *lbl = new QLabel(tr("%2, %3").arg(mimeDescription,
161 UiUtils::Formatting::prettySize(partIndex.data(Imap::Mailbox::RolePartOctets).toULongLong())));
162 if (rawMime != mimeDescription) {
163 lbl->setToolTip(rawMime);
165 QFont f(lbl->font());
166 f.setItalic(true);
167 if (f.pointSize() > -1) // don't try the below on pixel fonts ever.
168 f.setPointSizeF(f.pointSizeF() * 0.8);
169 lbl->setFont(f);
170 subLayout->addWidget(lbl);
171 layout->addLayout(subLayout);
173 // space between label and arrow
174 layout->addSpacing(spacing);
176 layout->addStretch(100);
178 QVBoxLayout *contentLayout = new QVBoxLayout(this);
179 contentLayout->addLayout(layout);
180 if (m_contentWidget) {
181 contentLayout->addWidget(m_contentWidget);
182 m_contentWidget->setCursor(Qt::ArrowCursor);
185 updateShowHideAttachmentState();
188 void AttachmentView::slotDownloadAttachment()
190 m_downloadAttachment->setEnabled(false);
192 Imap::Network::FileDownloadManager *manager = new Imap::Network::FileDownloadManager(this, m_netAccess, m_partIndex);
193 connect(manager, &Imap::Network::FileDownloadManager::fileNameRequested, this, &AttachmentView::slotFileNameRequested);
194 connect(manager, &Imap::Network::FileDownloadManager::transferError, m_messageView, &MessageView::transferError);
195 connect(manager, &Imap::Network::FileDownloadManager::transferError, this, &AttachmentView::enableDownloadAgain);
196 connect(manager, &Imap::Network::FileDownloadManager::transferError, manager, &QObject::deleteLater);
197 connect(manager, &Imap::Network::FileDownloadManager::cancelled, this, &AttachmentView::enableDownloadAgain);
198 connect(manager, &Imap::Network::FileDownloadManager::cancelled, manager, &QObject::deleteLater);
199 connect(manager, &Imap::Network::FileDownloadManager::succeeded, this, &AttachmentView::enableDownloadAgain);
200 connect(manager, &Imap::Network::FileDownloadManager::succeeded, manager, &QObject::deleteLater);
201 manager->downloadPart();
204 void AttachmentView::slotOpenAttachment()
206 m_openAttachment->setEnabled(false);
208 Imap::Network::FileDownloadManager *manager = new Imap::Network::FileDownloadManager(this, m_netAccess, m_partIndex);
209 connect(manager, &Imap::Network::FileDownloadManager::fileNameRequested, this, &AttachmentView::slotFileNameRequestedOnOpen);
210 connect(manager, &Imap::Network::FileDownloadManager::transferError, m_messageView, &MessageView::transferError);
211 connect(manager, &Imap::Network::FileDownloadManager::transferError, this, &AttachmentView::onOpenFailed);
212 connect(manager, &Imap::Network::FileDownloadManager::transferError, manager, &QObject::deleteLater);
213 // we aren't connecting to cancelled() as it cannot really happen -- the filename is never empty
214 connect(manager, &Imap::Network::FileDownloadManager::succeeded, this, &AttachmentView::openDownloadedAttachment);
215 connect(manager, &Imap::Network::FileDownloadManager::succeeded, manager, &QObject::deleteLater);
216 manager->downloadPart();
219 void AttachmentView::slotFileNameRequestedOnOpen(QString *fileName)
221 Q_ASSERT(!m_tmpFile);
222 m_tmpFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/trojita-attachment-XXXXXX-") +
223 fileName->replace(QLatin1Char('/'), QLatin1Char('_')));
224 m_tmpFile->open();
225 *fileName = m_tmpFile->fileName();
228 void AttachmentView::slotFileNameRequested(QString *fileName)
230 static QDir lastDir = QDir(Common::writablePath(Common::LOCATION_DOWNLOAD));
231 if (!lastDir.exists())
232 lastDir = QDir(Common::writablePath(Common::LOCATION_DOWNLOAD));
233 QString fileLocation = lastDir.filePath(*fileName);
234 *fileName = QFileDialog::getSaveFileName(this, tr("Save Attachment"), fileLocation, QString(), nullptr, QFileDialog::HideNameFilterDetails);
235 if (!fileName->isEmpty())
236 lastDir = QFileInfo(*fileName).absoluteDir();
239 void AttachmentView::enableDownloadAgain()
241 m_downloadAttachment->setEnabled(true);
244 void AttachmentView::onOpenFailed()
246 delete m_tmpFile;
247 m_tmpFile = nullptr;
248 m_openAttachment->setEnabled(true);
251 void AttachmentView::openDownloadedAttachment()
253 Q_ASSERT(m_tmpFile);
255 // Make sure that the file is read-only so that the launched application does not attempt to modify it
256 m_tmpFile->setPermissions(QFile::ReadOwner);
258 QDesktopServices::openUrl(QUrl::fromLocalFile(m_tmpFile->fileName()));
260 // This will delete the temporary file in ten seconds. It should give the application plenty of time to start and also prevent
261 // leaving cruft behind.
262 new Common::DeleteAfter(m_tmpFile, 10000);
263 m_tmpFile = nullptr;
264 m_openAttachment->setEnabled(true);
267 bool AttachmentView::previewIsShown() const
269 return m_contentWidget && m_contentWidget->isVisibleTo(const_cast<AttachmentView*>(this));
272 void AttachmentView::updateShowHideAttachmentState()
274 if (m_showHideAttachment) {
275 m_showHideAttachment->setChecked(previewIsShown());
279 void AttachmentView::showMenuOrPreview()
281 if (previewIsShown() || !m_contentWidget) {
282 showMenu();
283 } else {
284 m_showHideAttachment->trigger();
288 void AttachmentView::showMenu()
290 if (QToolButton *btn = qobject_cast<QToolButton*>(sender())) {
291 btn->setDown(false);
293 QPoint p = QCursor::pos();
294 p.rx() -= m_menu->width()/2;
295 m_menu->popup(p);
298 void AttachmentView::toggleIconCursor()
300 if (m_icon->isDown())
301 m_icon->setCursor(Qt::OpenHandCursor);
302 else
303 m_icon->setCursor(Qt::ArrowCursor);
306 void AttachmentView::indicateHover()
308 if (m_menu->isVisible() || rect().contains(mapFromGlobal(QCursor::pos()))) { // WA_UnderMouse is wrong
309 if (!autoFillBackground()) {
310 setAutoFillBackground(true);
311 QPalette pal(palette());
312 QLinearGradient grad(0,0,0,height());
313 grad.setColorAt(0, pal.color(backgroundRole()));
314 grad.setColorAt(0.15, pal.color(backgroundRole()).lighter(110));
315 grad.setColorAt(0.8, pal.color(backgroundRole()).darker(110));
316 grad.setColorAt(1, pal.color(backgroundRole()));
317 pal.setBrush(backgroundRole(), grad);
318 setPalette(pal);
320 } else {
321 setAutoFillBackground(false);
322 setPalette(QPalette());
326 void AttachmentView::mousePressEvent(QMouseEvent *event)
328 event->accept();
329 if (event->button() == Qt::RightButton) {
330 showMenu();
331 return;
333 m_dragStartPos = event->pos();
334 QFrame::mousePressEvent(event);
337 void AttachmentView::mouseMoveEvent(QMouseEvent *event)
339 QFrame::mouseMoveEvent(event);
341 if (!(event->buttons() & Qt::LeftButton)) {
342 return;
345 if ((m_dragStartPos - event->pos()).manhattanLength() < QApplication::startDragDistance())
346 return;
348 QMimeData *mimeData = Imap::Mailbox::mimeDataForDragAndDrop(m_partIndex);
349 if (!mimeData)
350 return;
351 event->accept();
352 QDrag *drag = new QDrag(this);
353 drag->setMimeData(mimeData);
354 drag->setHotSpot(event->pos());
355 drag->exec(Qt::CopyAction, Qt::CopyAction);
358 void AttachmentView::paintEvent(QPaintEvent *event)
360 QFrame::paintEvent(event);
361 QPainter p(this);
362 const int x = m_icon->geometry().width() + m_fileName->sizeHint().width() + 32;
363 if (x >= rect().width())
364 return;
365 QLinearGradient grad(x, 0, rect().right(), 0);
366 const QColor c = testAttribute(Qt::WA_UnderMouse) ? palette().color(QPalette::Highlight) :
367 palette().color(backgroundRole()).darker(120);
368 grad.setColorAt(0, palette().color(backgroundRole()));
369 grad.setColorAt(0.5, c);
370 grad.setColorAt(1, palette().color(backgroundRole()));
371 p.setBrush(grad);
372 p.setPen(Qt::NoPen);
373 p.drawRect(x, m_fileName->geometry().center().y(), width(), 1);
374 p.end();
377 QString AttachmentView::quoteMe() const
379 const AbstractPartWidget *widget = dynamic_cast<const AbstractPartWidget *>(m_contentWidget);
380 return widget && !m_contentWidget->isHidden() ? widget->quoteMe() : QString();
383 bool AttachmentView::searchDialogRequested()
385 if (AbstractPartWidget *widget = dynamic_cast<AbstractPartWidget*>(m_contentWidget))
386 return widget->searchDialogRequested();
387 return false;
390 #define IMPL_PART_FORWARD_ONE_METHOD(METHOD) \
391 void AttachmentView::METHOD() \
393 if (AbstractPartWidget *w = dynamic_cast<AbstractPartWidget*>(m_contentWidget)) \
394 w->METHOD(); \
397 IMPL_PART_FORWARD_ONE_METHOD(reloadContents)
398 IMPL_PART_FORWARD_ONE_METHOD(zoomIn)
399 IMPL_PART_FORWARD_ONE_METHOD(zoomOut)
400 IMPL_PART_FORWARD_ONE_METHOD(zoomOriginal)
402 void AttachmentView::showMessageSource()
404 auto w = MainWindow::messageSourceWidget(m_partIndex);
405 w->setWindowTitle(tr("Source of Attached Message"));
406 w->show();