diff --git a/src/libs/ui/docsetsdialog.cpp b/src/libs/ui/docsetsdialog.cpp index 20b911c4e..015bf6f89 100644 --- a/src/libs/ui/docsetsdialog.cpp +++ b/src/libs/ui/docsetsdialog.cpp @@ -67,6 +67,7 @@ constexpr int CacheTimeout = 24 * 60 * 60 * 1000; // 24 hours in microseconds const char DocsetNameProperty[] = "docsetName"; const char DownloadTypeProperty[] = "downloadType"; const char DownloadPreviousReceived[] = "downloadPreviousReceived"; +const char DownloadTotalSize[] = "downloadTotalSize"; const char ListItemIndexProperty[] = "listItem"; } @@ -236,6 +237,7 @@ void DocsetsDialog::downloadSelectedDocsets() QAbstractItemModel *model = ui->availableDocsetList->model(); model->setData(index, tr("Downloading: %p%"), ProgressItemDelegate::FormatRole); + model->setData(index, true, ProgressItemDelegate::CancellableRole); model->setData(index, 0, ProgressItemDelegate::ValueRole); model->setData(index, true, ProgressItemDelegate::ShowProgressRole); @@ -391,6 +393,7 @@ void DocsetsDialog::downloadCompleted() QListWidgetItem *item = findDocsetListItem(docsetName); if (item) { item->setData(ProgressItemDelegate::ValueRole, 0); + item->setData(ProgressItemDelegate::CancellableRole, false); item->setData(ProgressItemDelegate::FormatRole, tr("Installing: %p%")); } @@ -437,11 +440,12 @@ void DocsetsDialog::downloadProgress(qint64 received, qint64 total) qint64 previousReceived = 0; const QVariant previousReceivedVariant = reply->property(DownloadPreviousReceived); - if (!previousReceivedVariant.isValid()) + if (!previousReceivedVariant.isValid()) { + reply->setProperty(DownloadTotalSize, total); m_combinedTotal += total; - else + } else { previousReceived = previousReceivedVariant.toLongLong(); - + } m_combinedReceived += received - previousReceived; reply->setProperty(DownloadPreviousReceived, received); @@ -571,7 +575,9 @@ void DocsetsDialog::setupAvailableDocsetsTab() { using Registry::DocsetRegistry; - ui->availableDocsetList->setItemDelegate(new ProgressItemDelegate(this)); + ProgressItemDelegate *delegate = new ProgressItemDelegate(this); + connect(delegate, &ProgressItemDelegate::cancelButtonClicked, this, &DocsetsDialog::cancelDownload); + ui->availableDocsetList->setItemDelegate(delegate); connect(m_docsetRegistry, &DocsetRegistry::docsetUnloaded, this, [this](const QString name) { QListWidgetItem *item = findDocsetListItem(name); @@ -606,6 +612,7 @@ void DocsetsDialog::setupAvailableDocsetsTab() QAbstractItemModel *model = ui->availableDocsetList->model(); model->setData(index, tr("Downloading: %p%"), ProgressItemDelegate::FormatRole); + model->setData(index, true, ProgressItemDelegate::CancellableRole); model->setData(index, 0, ProgressItemDelegate::ValueRole); model->setData(index, true, ProgressItemDelegate::ShowProgressRole); @@ -702,6 +709,31 @@ QNetworkReply *DocsetsDialog::download(const QUrl &url) return reply; } +void DocsetsDialog::cancelDownload(const QModelIndex &index) +{ + // Find and delete download jobs corresponding to index + for (QNetworkReply *reply : m_replies) { + if (reply->property(ListItemIndexProperty).toInt() != index.row() + || reply->property(DownloadTypeProperty).toInt() != DownloadDocset) { + continue; + } + + QListWidgetItem *listItem = ui->availableDocsetList->item(index.row()); + listItem->setData(ProgressItemDelegate::ShowProgressRole, false); + delete m_tmpFiles.take(reply->property(DocsetNameProperty).toString()); + reply->abort(); + + m_combinedReceived -= reply->property(DownloadPreviousReceived).toLongLong(); + m_combinedTotal -= reply->property(DownloadTotalSize).toLongLong(); + } + + // As the current download is cancelled, unselect the current selected item + // This also triggers selectionChanged() and the state of downloadDocsetsButton + // is recomputed on the next selection + ui->availableDocsetList->selectionModel()->clearSelection(); + updateCombinedProgress(); +} + void DocsetsDialog::cancelDownloads() { for (QNetworkReply *reply : m_replies) { diff --git a/src/libs/ui/docsetsdialog.h b/src/libs/ui/docsetsdialog.h index 5fb6ffdf4..c20ac4be1 100644 --- a/src/libs/ui/docsetsdialog.h +++ b/src/libs/ui/docsetsdialog.h @@ -112,6 +112,7 @@ private slots: bool updatesAvailable() const; QNetworkReply *download(const QUrl &url); + void cancelDownload(const QModelIndex &index); void cancelDownloads(); void loadUserFeedList(); diff --git a/src/libs/ui/progressitemdelegate.cpp b/src/libs/ui/progressitemdelegate.cpp index 331f3c4b7..e16daab93 100644 --- a/src/libs/ui/progressitemdelegate.cpp +++ b/src/libs/ui/progressitemdelegate.cpp @@ -23,8 +23,11 @@ #include "progressitemdelegate.h" +#include +#include #include #include +#include using namespace Zeal::WidgetUi; @@ -51,7 +54,7 @@ void ProgressItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & // Adjust maximum text width QStyleOptionViewItem styleOption = option; - styleOption.rect.setRight(styleOption.rect.right() - progressBarWidth); + styleOption.rect.setRight(styleOption.rect.right() - progressBarWidth - cancelButtonWidth); // Size progress bar QScopedPointer renderer(new QProgressBar()); @@ -60,6 +63,7 @@ void ProgressItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & renderer->setValue(value); const QString format = index.model()->data(index, FormatRole).toString(); + const bool isCancellable = index.model()->data(index, CancellableRole).toBool(); if (!format.isEmpty()) renderer->setFormat(format); @@ -69,7 +73,30 @@ void ProgressItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & painter->translate(styleOption.rect.topRight()); renderer->render(painter); + // Button + if (isCancellable) { + QScopedPointer buttonRenderer(new QPushButton(tr("Cancel"))); + buttonRenderer->resize(cancelButtonWidth, styleOption.rect.height()); + painter->translate(progressBarWidth, 0); + buttonRenderer->render(painter); + } painter->restore(); QStyledItemDelegate::paint(painter, styleOption, index); } + +bool ProgressItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) +{ + QMouseEvent *mouseEvent = static_cast(event); + QRect cancelBounds = option.rect; + cancelBounds.setLeft(cancelBounds.right() - cancelButtonWidth); + + if (event->type() == QEvent::MouseButtonRelease + && index.model()->data(index, ShowProgressRole).toBool() + && cancelBounds.contains(mouseEvent->pos())) { + emit cancelButtonClicked(index); + } + + return QStyledItemDelegate::editorEvent(event, model, option, index); +} diff --git a/src/libs/ui/progressitemdelegate.h b/src/libs/ui/progressitemdelegate.h index 89594e317..01a94d573 100644 --- a/src/libs/ui/progressitemdelegate.h +++ b/src/libs/ui/progressitemdelegate.h @@ -36,7 +36,8 @@ class ProgressItemDelegate : public QStyledItemDelegate enum ProgressRoles { ValueRole = Qt::UserRole + 10, FormatRole, - ShowProgressRole + ShowProgressRole, + CancellableRole }; explicit ProgressItemDelegate(QObject *parent = nullptr); @@ -44,8 +45,16 @@ class ProgressItemDelegate : public QStyledItemDelegate void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +protected: + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) override; + +signals: + void cancelButtonClicked(const QModelIndex& index); + private: static const int progressBarWidth = 150; + static const int cancelButtonWidth = 50; }; } // namespace WidgetUi