diff --git a/src/widgets/dialogs/attachment/listattachmentdelegate.cpp b/src/widgets/dialogs/attachment/listattachmentdelegate.cpp index 2eaa9946..2564448f 100644 --- a/src/widgets/dialogs/attachment/listattachmentdelegate.cpp +++ b/src/widgets/dialogs/attachment/listattachmentdelegate.cpp @@ -1,178 +1,178 @@ /* Copyright (c) 2020 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "listattachmentdelegate.h" #include "ruqola.h" #include "rocketchataccount.h" #include "common/delegatepaintutil.h" -#include #include #include #include #include #include "model/filesforroommodel.h" #include #include #include +#include "room/delegate/messagedelegatehelperbase.h" ListAttachmentDelegate::ListAttachmentDelegate(QObject *parent) : QItemDelegate(parent) , mDownloadIcon(QIcon::fromTheme(QStringLiteral("cloud-download"))) , mDeleteIcon(QIcon::fromTheme(QStringLiteral("delete"))) { } ListAttachmentDelegate::~ListAttachmentDelegate() { } static qreal basicMargin() { return 8; } void ListAttachmentDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); QStyleOptionViewItem optionCopy = option; optionCopy.showDecorationSelected = true; drawBackground(painter, optionCopy, index); // Draw Mimetype Icon const Layout layout = doLayout(option, index); const File *file = index.data(FilesForRoomModel::FilePointer).value(); const bool fileComplete = file->complete(); QMimeDatabase db; const QMimeType mimeType = db.mimeTypeForName(file->mimeType()); QPixmap pix; if (mimeType.isValid()) { pix = KIconLoader::global()->loadMimeTypeIcon(mimeType.iconName(), KIconLoader::Desktop); } else { pix = QIcon::fromTheme(QStringLiteral("unknown")).pixmap(layout.mimetypeHeight, layout.mimetypeHeight); } painter->drawPixmap(option.rect.x(), option.rect.y(), pix); // Draw filename const QFont oldFont = painter->font(); if (!fileComplete) { QFont newFont = oldFont; newFont.setStrikeOut(true); painter->setFont(newFont); } painter->drawText(basicMargin() + option.rect.x() + layout.mimetypeHeight, layout.attachmentNameY + painter->fontMetrics().ascent(), layout.attachmentName); // Draw the sender (below the filename) painter->setFont(layout.senderFont); painter->drawText(basicMargin() + option.rect.x() + layout.mimetypeHeight, layout.senderY + painter->fontMetrics().ascent(), layout.senderText); painter->setFont(oldFont); // Draw the timestamp (below the sender) DelegatePaintUtil::drawTimestamp(painter, layout.timeStampText, QPoint(basicMargin() + option.rect.x() + layout.mimetypeHeight, layout.timeStampY + painter->fontMetrics().ascent())); // Draw delete icon (for our own messages) if (file->userId() == Ruqola::self()->rocketChatAccount()->userID()) { mDeleteIcon.paint(painter, layout.deleteAttachmentRect); } if (fileComplete) { // Draw download icon mDownloadIcon.paint(painter, layout.downloadAttachmentRect); } painter->restore(); } bool ListAttachmentDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { const QEvent::Type eventType = event->type(); if (eventType == QEvent::MouseButtonRelease) { auto *mev = static_cast(event); const File *file = index.data(FilesForRoomModel::FilePointer).value(); const Layout layout = doLayout(option, index); if (layout.downloadAttachmentRect.contains(mev->pos())) { QWidget *parentWidget = const_cast(option.widget); - const QString fileName = QFileDialog::getSaveFileName(parentWidget, i18n("Save Attachment")); + const QString fileName = MessageDelegateHelperBase::querySaveFileName(parentWidget, i18n("Save Attachment"), QUrl(file->url())); if (!fileName.isEmpty()) { Ruqola::self()->rocketChatAccount()->downloadFile(file->url(), QUrl::fromLocalFile(fileName)); } return true; } if (layout.deleteAttachmentRect.contains(mev->pos()) && (file->userId() == Ruqola::self()->rocketChatAccount()->userID())) { QWidget *parentWidget = const_cast(option.widget); if (KMessageBox::Yes == KMessageBox::questionYesNo(parentWidget, i18n("Do you want to Delete this File?"), i18n("Delete File"))) { const QString fileId = file->fileId(); Q_EMIT deleteAttachment(fileId); //TODO //appid.rocketChatAccount.deleteFileMessage(appid.selectedRoomID, fileId, appid.selectedRoom.channelType) } return true; } } return QItemDelegate::editorEvent(event, model, option, index); } QSize ListAttachmentDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { // Note: option.rect in this method is huge (as big as the viewport) const Layout layout = doLayout(option, index); const int contentsHeight = layout.timeStampY + option.fontMetrics.height() - option.rect.y(); return QSize(option.rect.width(), contentsHeight); } ListAttachmentDelegate::Layout ListAttachmentDelegate::doLayout(const QStyleOptionViewItem &option, const QModelIndex &index) const { const File *file = index.data(FilesForRoomModel::FilePointer).value(); Layout layout; QRect usableRect = option.rect; layout.usableRect = usableRect; // Just for the top, for now. The left will move later on. layout.attachmentName = file->fileName(); layout.attachmentNameY = usableRect.top(); layout.senderText = file->userName(); layout.senderFont = option.font; layout.senderFont.setItalic(true); layout.senderY = layout.attachmentNameY + option.fontMetrics.height(); // Timestamp layout.timeStampText = file->uploadedDateTimeStr(); layout.timeStampY = layout.senderY + option.fontMetrics.height(); layout.mimetypeHeight = option.rect.height(); usableRect.setLeft(layout.mimetypeHeight); const int iconSize = layout.mimetypeHeight; layout.downloadAttachmentRect = QRect(option.rect.width() - iconSize, option.rect.y(), iconSize, iconSize); layout.deleteAttachmentRect = QRect(option.rect.width() - 2 * iconSize - basicMargin(), option.rect.y(), iconSize, iconSize); return layout; } diff --git a/src/widgets/room/delegate/messagedelegatehelperbase.cpp b/src/widgets/room/delegate/messagedelegatehelperbase.cpp index 4a3ea4ab..ad6a1724 100644 --- a/src/widgets/room/delegate/messagedelegatehelperbase.cpp +++ b/src/widgets/room/delegate/messagedelegatehelperbase.cpp @@ -1,34 +1,64 @@ /* Copyright (c) 2020 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagedelegatehelperbase.h" +#include +#include +#include +#include +#include +#include + MessageDelegateHelperBase::~MessageDelegateHelperBase() { } bool MessageDelegateHelperBase::handleMouseEvent(QMouseEvent *mouseEvent, const QRect &attachmentsRect, const QStyleOptionViewItem &option, const QModelIndex &index) { Q_UNUSED(mouseEvent) Q_UNUSED(attachmentsRect) Q_UNUSED(option) Q_UNUSED(index) return false; } + +QString MessageDelegateHelperBase::querySaveFileName(QWidget *parent, const QString &title, const QUrl &fileToSave) +{ + const auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); + const auto info = QFileInfo(fileToSave.path()); + auto fileName = info.fileName(); + if (fileToSave.isLocalFile() && info.exists() && info.suffix().isEmpty()) { + // guess a proper file suffix if none is given + [&]() { + QFile file(info.absoluteFilePath()); + if (!file.open(QIODevice::ReadOnly)) + return; + QMimeDatabase mimeDb; + const auto mime = mimeDb.mimeTypeForFileNameAndData(fileName, &file); + if (!mime.isValid()) + return; + const auto suffix = mime.preferredSuffix(); + if (!suffix.isEmpty()) + fileName += QLatin1Char('.') + suffix; + }(); + } + return QFileDialog::getSaveFileName(parent, title, dir.absoluteFilePath(fileName)); +} diff --git a/src/widgets/room/delegate/messagedelegatehelperbase.h b/src/widgets/room/delegate/messagedelegatehelperbase.h index 7c53493b..600cca97 100644 --- a/src/widgets/room/delegate/messagedelegatehelperbase.h +++ b/src/widgets/room/delegate/messagedelegatehelperbase.h @@ -1,44 +1,48 @@ /* Copyright (c) 2020 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MESSAGEDELEGATEHELPERBASE_H #define MESSAGEDELEGATEHELPERBASE_H #include "libruqolawidgets_private_export.h" #include class QPainter; class QRect; class QModelIndex; class QMouseEvent; class QStyleOptionViewItem; +class QWidget; +class QUrl; class Message; class LIBRUQOLAWIDGETS_TESTS_EXPORT MessageDelegateHelperBase { public: virtual ~MessageDelegateHelperBase(); virtual void draw(QPainter *painter, const QRect &attachmentsRect, const QModelIndex &index, const QStyleOptionViewItem &option) const = 0; virtual QSize sizeHint(const QModelIndex &index, int maxWidth, const QStyleOptionViewItem &option) const = 0; virtual bool handleMouseEvent(QMouseEvent *mouseEvent, const QRect &attachmentsRect, const QStyleOptionViewItem &option, const QModelIndex &index); + + static QString querySaveFileName(QWidget *parent, const QString &title, const QUrl &fileToSave); }; #endif // MESSAGEDELEGATEHELPERBASE_H diff --git a/src/widgets/room/delegate/messagedelegatehelperfile.cpp b/src/widgets/room/delegate/messagedelegatehelperfile.cpp index 9142e7b8..cc995679 100644 --- a/src/widgets/room/delegate/messagedelegatehelperfile.cpp +++ b/src/widgets/room/delegate/messagedelegatehelperfile.cpp @@ -1,146 +1,145 @@ /* Copyright (c) 2020 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagedelegatehelperfile.h" #include "model/messagemodel.h" #include "rocketchataccount.h" #include "ruqolawidgets_debug.h" #include "ruqola.h" #include "ruqolautils.h" #include -#include #include #include #include // Name // Description static const int vMargin = 8; void MessageDelegateHelperFile::draw(QPainter *painter, const QRect &attachmentsRect, const QModelIndex &index, const QStyleOptionViewItem &option) const { const Message *message = index.data(MessageModel::MessagePointer).value(); const QVector layouts = doLayout(message, option); const QIcon downloadIcon = QIcon::fromTheme(QStringLiteral("cloud-download")); const QPen oldPen = painter->pen(); const QFont oldFont = painter->font(); QFont underlinedFont = oldFont; underlinedFont.setUnderline(true); for (const FileLayout &layout : layouts) { const int y = attachmentsRect.y() + layout.y; if (!layout.link.isEmpty()) { painter->setPen(option.palette.color(QPalette::Link)); painter->setFont(underlinedFont); } painter->drawText(attachmentsRect.x(), y + option.fontMetrics.ascent(), layout.title); if (layout.downloadButtonRect.isValid()) { downloadIcon.paint(painter, layout.downloadButtonRect.translated(attachmentsRect.topLeft())); } if (!layout.link.isEmpty()) { painter->setPen(oldPen); painter->setFont(oldFont); } if (!layout.description.isEmpty()) { const int descriptionY = y + layout.titleSize.height() + vMargin; painter->drawText(attachmentsRect.x(), descriptionY + option.fontMetrics.ascent(), layout.description); } } } QSize MessageDelegateHelperFile::sizeHint(const QModelIndex &index, int maxWidth, const QStyleOptionViewItem &option) const { const Message *message = index.data(MessageModel::MessagePointer).value(); const QVector layouts = doLayout(message, option); if (layouts.isEmpty()) { return QSize(); } return QSize(maxWidth, // should be qMax of all sizes, but doesn't really matter layouts.last().y + layouts.last().height + vMargin); } QVector MessageDelegateHelperFile::doLayout(const Message *message, const QStyleOptionViewItem &option) const { QVector layouts; const QVector &attachments = message->attachements(); layouts.reserve(attachments.count()); const int buttonMargin = 8; const int iconSize = option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize); int y = 0; for (const MessageAttachment &msgAttach : attachments) { FileLayout layout; layout.title = msgAttach.title(); layout.description = msgAttach.description(); layout.link = msgAttach.link(); layout.titleSize = option.fontMetrics.size(Qt::TextSingleLine, layout.title); layout.descriptionSize = option.fontMetrics.size(Qt::TextSingleLine, layout.description); layout.y = y; layout.height = layout.titleSize.height() + (layout.description.isEmpty() ? 0 : vMargin + layout.descriptionSize.height()); if (msgAttach.canDownloadAttachment()) { layout.downloadButtonRect = QRect(layout.titleSize.width() + buttonMargin, y, iconSize, iconSize); } layouts.push_back(layout); y += layout.height; } return layouts; } bool MessageDelegateHelperFile::handleMouseEvent(QMouseEvent *mouseEvent, const QRect &attachmentsRect, const QStyleOptionViewItem &option, const QModelIndex &index) { if (mouseEvent->type() == QEvent::MouseButtonRelease) { const Message *message = index.data(MessageModel::MessagePointer).value(); const QVector layouts = doLayout(message, option); const QPoint pos = mouseEvent->pos(); auto download = [&](const FileLayout &layout) { - const QString file = QFileDialog::getSaveFileName(const_cast(option.widget), i18n("Save File")); + const QString file = querySaveFileName(const_cast(option.widget), i18n("Save File"), QUrl(layout.link)); if (!file.isEmpty()) { const QUrl fileUrl = QUrl::fromLocalFile(file); Ruqola::self()->rocketChatAccount()->downloadFile(layout.link, fileUrl); return true; } return false; }; for (const FileLayout &layout : layouts) { if (layout.downloadButtonRect.translated(attachmentsRect.topLeft()).contains(pos)) { return download(layout); } if (!layout.link.isEmpty()) { const int y = attachmentsRect.y() + layout.y; const QSize linkSize = option.fontMetrics.size(Qt::TextSingleLine, layout.title); const QRect linkRect(attachmentsRect.x(), y, linkSize.width(), linkSize.height()); if (linkRect.contains(pos)) { if (layout.downloadButtonRect.isValid()) { return download(layout); } else { RuqolaUtils::self()->openUrl(layout.link); return true; } } } } } return false; } diff --git a/src/widgets/room/delegate/messagedelegatehelperimage.cpp b/src/widgets/room/delegate/messagedelegatehelperimage.cpp index 2acdba3d..78aee298 100644 --- a/src/widgets/room/delegate/messagedelegatehelperimage.cpp +++ b/src/widgets/room/delegate/messagedelegatehelperimage.cpp @@ -1,235 +1,234 @@ /* Copyright (c) 2020 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagedelegatehelperimage.h" #include "ruqolawidgets_debug.h" #include "ruqola.h" #include "rocketchataccount.h" #include "dialogs/showimagedialog.h" #include "common/delegatepaintutil.h" #include #include -#include #include #include #include #include #include #include #include void MessageDelegateHelperImage::draw(QPainter *painter, const QRect &messageRect, const QModelIndex &index, const QStyleOptionViewItem &option) const { const Message *message = index.data(MessageModel::MessagePointer).value(); ImageLayout layout = layoutImage(message, option, messageRect.width(), messageRect.height()); if (!layout.pixmap.isNull()) { // Draw title and buttons painter->drawText(messageRect.x(), messageRect.y() + option.fontMetrics.ascent(), layout.title); const QIcon hideShowIcon = QIcon::fromTheme(layout.isShown ? QStringLiteral("visibility") : QStringLiteral("hint")); hideShowIcon.paint(painter, layout.hideShowButtonRect.translated(messageRect.topLeft())); const QIcon downloadIcon = QIcon::fromTheme(QStringLiteral("cloud-download")); downloadIcon.paint(painter, layout.downloadButtonRect.translated(messageRect.topLeft())); // Draw main pixmap (if shown) int nextY = messageRect.y() + layout.titleSize.height() + DelegatePaintUtil::margin(); if (layout.isShown) { QPixmap scaledPixmap; if (layout.isAnimatedImage) { auto it = findRunningAnimatedImage(index); if (it != mRunningAnimatedImages.end()) { scaledPixmap = (*it).movie->currentPixmap(); } else { mRunningAnimatedImages.emplace_back(index); auto &rai = mRunningAnimatedImages.back(); rai.movie->setFileName(layout.imagePath); rai.movie->setScaledSize(layout.imageSize); auto *view = qobject_cast(const_cast(option.widget)); const QPersistentModelIndex &idx = rai.index; QObject::connect(rai.movie, &QMovie::frameChanged, view, [view, idx, this]() { if (view->viewport()->rect().contains(view->visualRect(idx))) { view->update(idx); } else { removeRunningAnimatedImage(idx); } }, Qt::QueuedConnection); rai.movie->start(); scaledPixmap = rai.movie->currentPixmap(); } } else { scaledPixmap = layout.pixmap.scaled(layout.imageSize); } painter->drawPixmap(messageRect.x(), nextY, scaledPixmap); nextY += scaledPixmap.height() + DelegatePaintUtil::margin(); } // Draw description (if any) if (!layout.description.isEmpty()) { painter->drawText(messageRect.x(), nextY + option.fontMetrics.ascent(), layout.description); } } } QSize MessageDelegateHelperImage::sizeHint(const QModelIndex &index, int maxWidth, const QStyleOptionViewItem &option) const { const Message *message = index.data(MessageModel::MessagePointer).value(); const ImageLayout layout = layoutImage(message, option, maxWidth, -1); int height = layout.titleSize.height() + DelegatePaintUtil::margin(); int pixmapWidth = 0; if (layout.isShown) { pixmapWidth = qMin(layout.pixmap.width(), maxWidth); height += qMin(layout.pixmap.height(), 200) + DelegatePaintUtil::margin(); } int descriptionWidth = 0; if (!layout.description.isEmpty()) { descriptionWidth = layout.descriptionSize.width(); height += layout.descriptionSize.height() + DelegatePaintUtil::margin(); } return QSize(qMax(qMax(pixmapWidth, layout.titleSize.width()), descriptionWidth), height); } bool MessageDelegateHelperImage::handleMouseEvent(QMouseEvent *mouseEvent, const QRect &attachmentsRect, const QStyleOptionViewItem &option, const QModelIndex &index) { if (mouseEvent->type() == QEvent::MouseButtonRelease) { const Message *message = index.data(MessageModel::MessagePointer).value(); const QPoint pos = mouseEvent->pos(); ImageLayout layout = layoutImage(message, option, attachmentsRect.width(), attachmentsRect.height()); if (layout.hideShowButtonRect.translated(attachmentsRect.topLeft()).contains(pos)) { auto *model = const_cast(index.model()); model->setData(index, !layout.isShown, MessageModel::DisplayAttachment); return true; } else if (layout.downloadButtonRect.translated(attachmentsRect.topLeft()).contains(pos)) { QWidget *parentWidget = const_cast(option.widget); - const QString file = QFileDialog::getSaveFileName(parentWidget, i18n("Save Image")); + const auto file = querySaveFileName(parentWidget, i18n("Save Image"), QUrl::fromLocalFile(layout.imagePath)); if (!file.isEmpty()) { QFile::remove(file); // copy() doesn't overwrite QFile sourceFile(layout.imagePath); if (!sourceFile.copy(file)) { QMessageBox::warning(parentWidget, i18n("Error saving file"), sourceFile.errorString()); } } return true; } else if (!layout.pixmap.isNull()) { const int imageY = attachmentsRect.y() + layout.titleSize.height() + DelegatePaintUtil::margin(); const QRect imageRect(attachmentsRect.x(), imageY, layout.imageSize.width(), layout.imageSize.height()); if (imageRect.contains(pos)) { QWidget *parentWidget = const_cast(option.widget); QPointer dlg = new ShowImageDialog(parentWidget); dlg->setIsAnimatedPixmap(layout.isAnimatedImage); if (layout.isAnimatedImage) { dlg->setImagePath(layout.imagePath); } else { dlg->setImage(layout.pixmap); } dlg->exec(); delete dlg; } return true; } } return false; } MessageDelegateHelperImage::ImageLayout MessageDelegateHelperImage::layoutImage(const Message *message, const QStyleOptionViewItem &option, int attachmentsWidth, int attachmentsHeight) const { ImageLayout layout; if (message->attachements().isEmpty()) { qCWarning(RUQOLAWIDGETS_LOG) << "No attachments in Image message"; return layout; } if (message->attachements().count() > 1) { qCWarning(RUQOLAWIDGETS_LOG) << "Multiple attachments in Image message? Can this happen?"; } const MessageAttachment &msgAttach = message->attachements().at(0); const QUrl url = Ruqola::self()->rocketChatAccount()->attachmentUrl(msgAttach.link()); if (url.isLocalFile()) { layout.imagePath = url.toLocalFile(); layout.pixmap = mPixmapCache.pixmapForLocalFile(layout.imagePath); //or we could do layout.attachment = msgAttach; if we need many fields from it layout.title = msgAttach.title(); layout.description = msgAttach.description(); layout.isShown = message->showAttachment(); layout.isAnimatedImage = msgAttach.isAnimatedImage(); layout.titleSize = option.fontMetrics.size(Qt::TextSingleLine, layout.title); layout.descriptionSize = layout.description.isEmpty() ? QSize(0, 0) : option.fontMetrics.size(Qt::TextSingleLine, layout.description); const int iconSize = option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize); layout.hideShowButtonRect = QRect(layout.titleSize.width() + DelegatePaintUtil::margin(), 0, iconSize, iconSize); layout.downloadButtonRect = layout.hideShowButtonRect.translated(iconSize + DelegatePaintUtil::margin(), 0); if (attachmentsHeight > 0) { // Vertically: attachmentsHeight = title | DelegatePaintUtil::margin() | image | DelegatePaintUtil::margin() [| description | DelegatePaintUtil::margin()] int imageMaxHeight = attachmentsHeight - layout.titleSize.height() - DelegatePaintUtil::margin() * 2; if (!layout.description.isEmpty()) { imageMaxHeight -= layout.descriptionSize.height() + DelegatePaintUtil::margin(); } layout.imageSize = layout.pixmap.size().scaled(attachmentsWidth, imageMaxHeight, Qt::KeepAspectRatio); } } return layout; } std::vector::iterator MessageDelegateHelperImage::findRunningAnimatedImage(const QModelIndex &index) const { auto matchesIndex = [&](const RunningAnimatedImage &rai) { return rai.index == index; }; return std::find_if(mRunningAnimatedImages.begin(), mRunningAnimatedImages.end(), matchesIndex); } void MessageDelegateHelperImage::removeRunningAnimatedImage(const QModelIndex &index) const { auto it = findRunningAnimatedImage(index); if (it != mRunningAnimatedImages.end()) { mRunningAnimatedImages.erase(it); } } MessageDelegateHelperImage::RunningAnimatedImage::RunningAnimatedImage(const QModelIndex &idx) : index(idx) , movie(new QMovie) { } MessageDelegateHelperImage::RunningAnimatedImage::~RunningAnimatedImage() { // Note that this happens (with a nullptr movie) when the vector is re-allocated delete movie; } MessageDelegateHelperImage::RunningAnimatedImage::RunningAnimatedImage(RunningAnimatedImage &&other) noexcept : index(other.index) , movie(other.movie) { other.movie = nullptr; } MessageDelegateHelperImage::RunningAnimatedImage &MessageDelegateHelperImage::RunningAnimatedImage::operator=(RunningAnimatedImage &&other) { index = other.index; movie = other.movie; other.movie = nullptr; return *this; } diff --git a/src/widgets/room/delegate/messagedelegatehelpersound.cpp b/src/widgets/room/delegate/messagedelegatehelpersound.cpp index ecde31ea..029c6afd 100644 --- a/src/widgets/room/delegate/messagedelegatehelpersound.cpp +++ b/src/widgets/room/delegate/messagedelegatehelpersound.cpp @@ -1,129 +1,128 @@ /* Copyright (c) 2020 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagedelegatehelpersound.h" #include "ruqolawidgets_debug.h" #include "ruqola.h" #include "rocketchataccount.h" #include "common/delegatepaintutil.h" #include "dialogs/playsounddialog.h" #include #include -#include #include #include #include #include #include void MessageDelegateHelperSound::draw(QPainter *painter, const QRect &messageRect, const QModelIndex &index, const QStyleOptionViewItem &option) const { const Message *message = index.data(MessageModel::MessagePointer).value(); const SoundLayout layout = layoutSound(message, option); // Draw title and buttons painter->drawText(messageRect.x(), messageRect.y() + option.fontMetrics.ascent(), layout.title); const QIcon playerVolumeIcon = QIcon::fromTheme(QStringLiteral("player-volume")); playerVolumeIcon.paint(painter, layout.playerVolumeButtonRect.translated(messageRect.topLeft())); const QIcon downloadIcon = QIcon::fromTheme(QStringLiteral("cloud-download")); downloadIcon.paint(painter, layout.downloadButtonRect.translated(messageRect.topLeft())); // Draw main pixmap (if shown) const int nextY = messageRect.y() + layout.titleSize.height() + DelegatePaintUtil::margin(); // Draw description (if any) if (!layout.description.isEmpty()) { painter->drawText(messageRect.x(), nextY + option.fontMetrics.ascent(), layout.description); } } QSize MessageDelegateHelperSound::sizeHint(const QModelIndex &index, int maxWidth, const QStyleOptionViewItem &option) const { Q_UNUSED(maxWidth) const Message *message = index.data(MessageModel::MessagePointer).value(); const SoundLayout layout = layoutSound(message, option); int height = layout.titleSize.height() + DelegatePaintUtil::margin(); int pixmapWidth = 0; int descriptionWidth = 0; if (!layout.description.isEmpty()) { descriptionWidth = layout.descriptionSize.width(); height += layout.descriptionSize.height() + DelegatePaintUtil::margin(); } return QSize(qMax(qMax(pixmapWidth, layout.titleSize.width()), descriptionWidth), height); } bool MessageDelegateHelperSound::handleMouseEvent(QMouseEvent *mouseEvent, const QRect &attachmentsRect, const QStyleOptionViewItem &option, const QModelIndex &index) { if (mouseEvent->type() == QEvent::MouseButtonRelease) { const Message *message = index.data(MessageModel::MessagePointer).value(); const QPoint pos = mouseEvent->pos(); const SoundLayout layout = layoutSound(message, option); if (layout.downloadButtonRect.translated(attachmentsRect.topLeft()).contains(pos)) { QWidget *parentWidget = const_cast(option.widget); - const QString file = QFileDialog::getSaveFileName(parentWidget, i18n("Save Sound")); + const QString file = querySaveFileName(parentWidget, i18n("Save Sound"), QUrl::fromLocalFile(layout.audioPath)); if (!file.isEmpty()) { QFile::remove(file); // copy() doesn't overwrite QFile sourceFile(layout.audioPath); if (!sourceFile.copy(file)) { QMessageBox::warning(parentWidget, i18n("Error saving file"), sourceFile.errorString()); } } return true; } else if (attachmentsRect.contains(pos) || layout.playerVolumeButtonRect.translated(attachmentsRect.topLeft()).contains(pos)) { QWidget *parentWidget = const_cast(option.widget); QPointer dlg = new PlaySoundDialog(parentWidget); dlg->setAudioUrl(QUrl::fromLocalFile(layout.audioPath)); dlg->exec(); delete dlg; return true; } } return false; } MessageDelegateHelperSound::SoundLayout MessageDelegateHelperSound::layoutSound(const Message *message, const QStyleOptionViewItem &option) const { SoundLayout layout; if (message->attachements().isEmpty()) { qCWarning(RUQOLAWIDGETS_LOG) << "No attachments in Sound message"; return layout; } if (message->attachements().count() > 1) { qCWarning(RUQOLAWIDGETS_LOG) << "Multiple attachments in Sound message? Can this happen?"; } const MessageAttachment &msgAttach = message->attachements().at(0); const QUrl url = Ruqola::self()->rocketChatAccount()->attachmentUrl(msgAttach.link()); if (url.isLocalFile()) { layout.audioPath = url.toLocalFile(); //or we could do layout.attachment = msgAttach; if we need many fields from it layout.title = msgAttach.title(); layout.description = msgAttach.description(); layout.titleSize = option.fontMetrics.size(Qt::TextSingleLine, layout.title); layout.descriptionSize = layout.description.isEmpty() ? QSize(0, 0) : option.fontMetrics.size(Qt::TextSingleLine, layout.description); const int iconSize = option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize); layout.playerVolumeButtonRect = QRect(layout.titleSize.width() + DelegatePaintUtil::margin(), 0, iconSize, iconSize); layout.downloadButtonRect = layout.playerVolumeButtonRect.translated(iconSize + DelegatePaintUtil::margin(), 0); } return layout; } diff --git a/src/widgets/room/delegate/messagedelegatehelpervideo.cpp b/src/widgets/room/delegate/messagedelegatehelpervideo.cpp index 8d3a1ed6..7f22f267 100644 --- a/src/widgets/room/delegate/messagedelegatehelpervideo.cpp +++ b/src/widgets/room/delegate/messagedelegatehelpervideo.cpp @@ -1,130 +1,129 @@ /* Copyright (c) 2020 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagedelegatehelpervideo.h" #include "ruqolawidgets_debug.h" #include "ruqola.h" #include "rocketchataccount.h" #include "dialogs/showvideodialog.h" #include "common/delegatepaintutil.h" #include #include -#include #include #include #include #include #include void MessageDelegateHelperVideo::draw(QPainter *painter, const QRect &messageRect, const QModelIndex &index, const QStyleOptionViewItem &option) const { const Message *message = index.data(MessageModel::MessagePointer).value(); const VideoLayout layout = layoutVideo(message, option); // Draw title and buttons painter->drawText(messageRect.x(), messageRect.y() + option.fontMetrics.ascent(), layout.title); const QIcon showIcon = QIcon::fromTheme(QStringLiteral("visibility")); showIcon.paint(painter, layout.showButtonRect.translated(messageRect.topLeft())); const QIcon downloadIcon = QIcon::fromTheme(QStringLiteral("cloud-download")); downloadIcon.paint(painter, layout.downloadButtonRect.translated(messageRect.topLeft())); const int nextY = messageRect.y() + layout.titleSize.height() + DelegatePaintUtil::margin(); // Draw description (if any) if (!layout.description.isEmpty()) { painter->drawText(messageRect.x(), nextY + option.fontMetrics.ascent(), layout.description); } } QSize MessageDelegateHelperVideo::sizeHint(const QModelIndex &index, int maxWidth, const QStyleOptionViewItem &option) const { Q_UNUSED(maxWidth) const Message *message = index.data(MessageModel::MessagePointer).value(); const VideoLayout layout = layoutVideo(message, option); int height = layout.titleSize.height() + DelegatePaintUtil::margin(); int pixmapWidth = 0; int descriptionWidth = 0; if (!layout.description.isEmpty()) { descriptionWidth = layout.descriptionSize.width(); height += layout.descriptionSize.height() + DelegatePaintUtil::margin(); } return QSize(qMax(qMax(pixmapWidth, layout.titleSize.width()), descriptionWidth), height); } bool MessageDelegateHelperVideo::handleMouseEvent(QMouseEvent *mouseEvent, const QRect &attachmentsRect, const QStyleOptionViewItem &option, const QModelIndex &index) { if (mouseEvent->type() == QEvent::MouseButtonRelease) { const Message *message = index.data(MessageModel::MessagePointer).value(); const QPoint pos = mouseEvent->pos(); VideoLayout layout = layoutVideo(message, option); if (layout.downloadButtonRect.translated(attachmentsRect.topLeft()).contains(pos)) { QWidget *parentWidget = const_cast(option.widget); - const QString file = QFileDialog::getSaveFileName(parentWidget, i18n("Save Video")); + const QString file = querySaveFileName(parentWidget, i18n("Save Video"), QUrl::fromLocalFile(layout.videoPath)); if (!file.isEmpty()) { QFile::remove(file); // copy() doesn't overwrite QFile sourceFile(layout.videoPath); if (!sourceFile.copy(file)) { QMessageBox::warning(parentWidget, i18n("Error saving file"), sourceFile.errorString()); } } return true; } else if (attachmentsRect.contains(pos) || layout.showButtonRect.translated(attachmentsRect.topLeft()).contains(pos)) { QWidget *parentWidget = const_cast(option.widget); QPointer dlg = new ShowVideoDialog(parentWidget); dlg->setVideoUrl(QUrl::fromLocalFile(layout.videoPath)); dlg->exec(); delete dlg; return true; } } return false; } MessageDelegateHelperVideo::VideoLayout MessageDelegateHelperVideo::layoutVideo(const Message *message, const QStyleOptionViewItem &option) const { VideoLayout layout; if (message->attachements().isEmpty()) { qCWarning(RUQOLAWIDGETS_LOG) << "No attachments in Video message"; return layout; } if (message->attachements().count() > 1) { qCWarning(RUQOLAWIDGETS_LOG) << "Multiple attachments in Video message? Can this happen?"; } const MessageAttachment &msgAttach = message->attachements().at(0); const QUrl url = Ruqola::self()->rocketChatAccount()->attachmentUrl(msgAttach.link()); if (url.isLocalFile()) { layout.videoPath = url.toLocalFile(); //or we could do layout.attachment = msgAttach; if we need many fields from it layout.title = msgAttach.title(); layout.description = msgAttach.description(); layout.titleSize = option.fontMetrics.size(Qt::TextSingleLine, layout.title); layout.descriptionSize = layout.description.isEmpty() ? QSize(0, 0) : option.fontMetrics.size(Qt::TextSingleLine, layout.description); const int iconSize = option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize); layout.showButtonRect = QRect(layout.titleSize.width() + DelegatePaintUtil::margin(), 0, iconSize, iconSize); layout.downloadButtonRect = layout.showButtonRect.translated(iconSize + DelegatePaintUtil::margin(), 0); } return layout; }