/* Copyright (C) 2012 Sebastian Herbord. All rights reserved. This file is part of Mod Organizer. Mod Organizer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Mod Organizer 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 General Public License for more details. You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ #include "downloadlistview.h" #include "downloadlist.h" #include #include #include #include #include #include #include #include #include #include #include using namespace MOBase; DownloadProgressDelegate::DownloadProgressDelegate(DownloadManager* manager, DownloadListView* list) : QStyledItemDelegate(list), m_Manager(manager), m_List(list) {} void DownloadProgressDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QModelIndex sourceIndex; if (auto* proxy = dynamic_cast(m_List->model())) { sourceIndex = proxy->mapToSource(index); } else { sourceIndex = index; } bool pendingDownload = (sourceIndex.row() >= m_Manager->numTotalDownloads()); if (sourceIndex.column() == DownloadList::COL_STATUS && !pendingDownload && m_Manager->getState(sourceIndex.row()) == DownloadManager::STATE_DOWNLOADING) { QProgressBar progressBar; progressBar.setProperty("downloadView", option.widget->property("downloadView")); progressBar.setProperty("downloadProgress", true); progressBar.resize(option.rect.width(), option.rect.height()); progressBar.setTextVisible(true); progressBar.setAlignment(Qt::AlignCenter); progressBar.setMinimum(0); progressBar.setMaximum(100); progressBar.setValue(m_Manager->getProgress(sourceIndex.row()).first); progressBar.setFormat(m_Manager->getProgress(sourceIndex.row()).second); progressBar.setStyle(QApplication::style()); // paint the background with default delegate first to preserve table cell styling QStyledItemDelegate::paint(painter, option, index); painter->save(); painter->translate(option.rect.topLeft()); progressBar.render(painter); painter->restore(); } else { QStyledItemDelegate::paint(painter, option, index); } } void DownloadListHeader::customResizeSections() { // find the rightmost column that is not hidden int rightVisible = count() - 1; while (isSectionHidden(rightVisible) && rightVisible > 0) rightVisible--; // if that column is already squashed, squash others to the right side -- // otherwise to the left if (sectionSize(rightVisible) == minimumSectionSize()) { for (int idx = rightVisible; idx >= 0; idx--) { if (!isSectionHidden(idx)) { if (length() != width()) resizeSection(idx, std::max(sectionSize(idx) + width() - length(), minimumSectionSize())); else break; } } } else { for (int idx = 0; idx <= rightVisible; idx++) { if (!isSectionHidden(idx)) { if (length() != width()) resizeSection(idx, std::max(sectionSize(idx) + width() - length(), minimumSectionSize())); else break; } } } } void DownloadListHeader::mouseReleaseEvent(QMouseEvent* event) { QHeaderView::mouseReleaseEvent(event); customResizeSections(); } DownloadListView::DownloadListView(QWidget* parent) : QTreeView(parent) { setHeader(new DownloadListHeader(Qt::Horizontal, this)); header()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); header()->setSectionsMovable(true); header()->setContextMenuPolicy(Qt::CustomContextMenu); header()->setCascadingSectionResizes(true); header()->setStretchLastSection(false); header()->setSectionResizeMode(QHeaderView::Interactive); header()->setDefaultSectionSize(100); setUniformRowHeights(true); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); sortByColumn(1, Qt::DescendingOrder); connect(header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onHeaderCustomContextMenu(QPoint))); connect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(onDoubleClick(QModelIndex))); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomContextMenu(QPoint))); } DownloadListView::~DownloadListView() {} void DownloadListView::setManager(DownloadManager* manager) { m_Manager = manager; // hide these columns by default // // note that this is overridden by the ini if MO has been started at least // once before, which is handled in MainWindow::processUpdates() for older // versions header()->hideSection(DownloadList::COL_MODNAME); header()->hideSection(DownloadList::COL_VERSION); header()->hideSection(DownloadList::COL_ID); header()->hideSection(DownloadList::COL_SOURCEGAME); } void DownloadListView::setSourceModel(DownloadList* sourceModel) { m_SourceModel = sourceModel; } void DownloadListView::onDoubleClick(const QModelIndex& index) { QModelIndex sourceIndex = qobject_cast(model())->mapToSource(index); if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_READY) emit installDownload(sourceIndex.row()); else if ((m_Manager->getState(sourceIndex.row()) == DownloadManager::STATE_PAUSED) || (m_Manager->getState(sourceIndex.row()) == DownloadManager::STATE_PAUSING)) emit resumeDownload(sourceIndex.row()); } void DownloadListView::onHeaderCustomContextMenu(const QPoint& point) { QMenu menu; // display a list of all headers as checkboxes QAbstractItemModel* model = header()->model(); for (int i = 1; i < model->columnCount(); ++i) { QString columnName = model->headerData(i, Qt::Horizontal).toString(); QCheckBox* checkBox = new QCheckBox(&menu); checkBox->setText(columnName); checkBox->setChecked(!header()->isSectionHidden(i)); QWidgetAction* checkableAction = new QWidgetAction(&menu); checkableAction->setDefaultWidget(checkBox); menu.addAction(checkableAction); } menu.exec(header()->viewport()->mapToGlobal(point)); // view/hide columns depending on check-state int i = 1; for (const QAction* action : menu.actions()) { const QWidgetAction* widgetAction = qobject_cast(action); if (widgetAction != nullptr) { const QCheckBox* checkBox = qobject_cast(widgetAction->defaultWidget()); if (checkBox != nullptr) { header()->setSectionHidden(i, !checkBox->isChecked()); } } ++i; } qobject_cast(header())->customResizeSections(); } void DownloadListView::resizeEvent(QResizeEvent* event) { QTreeView::resizeEvent(event); qobject_cast(header())->customResizeSections(); } void DownloadListView::onCustomContextMenu(const QPoint& point) { QMenu menu(this); QModelIndex index = indexAt(point); bool hidden = false; try { if (index.row() >= 0) { const int row = qobject_cast(model())->mapToSource(index).row(); DownloadManager::DownloadState state = m_Manager->getState(row); hidden = m_Manager->isHidden(row); if (state >= DownloadManager::STATE_READY) { menu.addAction(tr("Install"), [=] { issueInstall(row); }); if (m_Manager->isInfoIncomplete(row)) { menu.addAction(tr("Query Info"), [=] { issueQueryInfoMd5(row); }); } else { menu.addAction(tr("Visit on Nexus"), [=] { issueVisitOnNexus(row); }); menu.addAction(tr("Visit the uploader's profile"), [=] { issueVisitUploaderProfile(row); }); } menu.addAction(tr("Open File"), [=] { issueOpenFile(row); }); menu.addAction(tr("Open Meta File"), [=] { issueOpenMetaFile(row); }); menu.addAction(tr("Reveal in Explorer"), [=] { issueOpenInDownloadsFolder(row); }); menu.addSeparator(); menu.addAction(tr("Delete..."), [=] { issueDelete(row); }); if (hidden) menu.addAction(tr("Un-Hide"), [=] { issueRestoreToView(row); }); else menu.addAction(tr("Hide"), [=] { issueRemoveFromView(row); }); } else if (state == DownloadManager::STATE_DOWNLOADING) { menu.addAction(tr("Cancel"), [=] { issueCancel(row); }); menu.addAction(tr("Pause"), [=] { issuePause(row); }); menu.addAction(tr("Reveal in Explorer"), [=] { issueOpenInDownloadsFolder(row); }); } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) || (state == DownloadManager::STATE_PAUSING)) { menu.addAction(tr("Delete..."), [=] { issueDelete(row); }); menu.addAction(tr("Resume"), [=] { issueResume(row); }); menu.addAction(tr("Reveal in Explorer"), [=] { issueOpenInDownloadsFolder(row); }); } menu.addSeparator(); } } catch (std::exception&) { // this happens when the download index is not found, ignore it and don't // display download-specific actions } menu.addAction(tr("Delete Installed Downloads..."), [=] { issueDeleteCompleted(); }); menu.addAction(tr("Delete Uninstalled Downloads..."), [=] { issueDeleteUninstalled(); }); menu.addAction(tr("Delete All Downloads..."), [=] { issueDeleteAll(); }); menu.addSeparator(); if (!hidden) { menu.addAction(tr("Hide Installed..."), [=] { issueRemoveFromViewCompleted(); }); menu.addAction(tr("Hide Uninstalled..."), [=] { issueRemoveFromViewUninstalled(); }); menu.addAction(tr("Hide All..."), [=] { issueRemoveFromViewAll(); }); } else { menu.addAction(tr("Un-Hide All..."), [=] { issueRestoreToViewAll(); }); } menu.exec(viewport()->mapToGlobal(point)); } void DownloadListView::keyPressEvent(QKeyEvent* event) { if (selectionModel()->hasSelection()) { const int row = qobject_cast(model()) ->mapToSource(currentIndex()) .row(); auto state = m_Manager->getState(row); if (state >= DownloadManager::STATE_READY) { if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { issueInstall(row); } else if (event->key() == Qt::Key_Delete) { issueDelete(row); } } else if (state == DownloadManager::STATE_DOWNLOADING) { if (event->key() == Qt::Key_Delete) { issueCancel(row); } else if (event->key() == Qt::Key_Space) { issuePause(event->key()); } } else if (state == DownloadManager::STATE_PAUSED || state == DownloadManager::STATE_ERROR || state == DownloadManager::STATE_PAUSING) { if (event->key() == Qt::Key_Delete) { issueDelete(row); } else if (event->key() == Qt::Key_Space) { issueResume(row); } } } QTreeView::keyPressEvent(event); } void DownloadListView::issueInstall(int index) { emit installDownload(index); } void DownloadListView::issueQueryInfo(int index) { emit queryInfo(index); } void DownloadListView::issueQueryInfoMd5(int index) { emit queryInfoMd5(index); } void DownloadListView::issueDelete(int index) { const auto r = MOBase::TaskDialog(this, tr("Delete download")) .main("Are you sure you want to delete this download?") .content(m_Manager->getFilePath(index)) .icon(QMessageBox::Question) .button({tr("Move to the Recycle Bin"), QMessageBox::Yes}) .button({tr("Cancel"), QMessageBox::Cancel}) .exec(); if (r != QMessageBox::Yes) { return; } emit removeDownload(index, true); } void DownloadListView::issueRemoveFromView(int index) { log::debug("removing from view: {}", index); emit removeDownload(index, false); } void DownloadListView::issueRestoreToView(int index) { emit restoreDownload(index); } void DownloadListView::issueRestoreToViewAll() { emit restoreDownload(-1); } void DownloadListView::issueVisitOnNexus(int index) { emit visitOnNexus(index); } void DownloadListView::issueVisitUploaderProfile(int index) { emit visitUploaderProfile(index); } void DownloadListView::issueOpenFile(int index) { emit openFile(index); } void DownloadListView::issueOpenMetaFile(int index) { emit openMetaFile(index); } void DownloadListView::issueOpenInDownloadsFolder(int index) { emit openInDownloadsFolder(index); } void DownloadListView::issueCancel(int index) { emit cancelDownload(index); } void DownloadListView::issuePause(int index) { emit pauseDownload(index); } void DownloadListView::issueResume(int index) { emit resumeDownload(index); } void DownloadListView::issueDeleteAll() { if (QMessageBox::warning( nullptr, tr("Delete Files?"), tr("This will remove all finished downloads from this list and from " "disk.\n\nAre you absolutely sure you want to proceed?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-1, true); } } void DownloadListView::issueDeleteCompleted() { if (QMessageBox::warning( nullptr, tr("Delete Files?"), tr("This will remove all installed downloads from this list and from " "disk.\n\nAre you absolutely sure you want to proceed?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-2, true); } } void DownloadListView::issueDeleteUninstalled() { if (QMessageBox::warning( nullptr, tr("Delete Files?"), tr("This will remove all uninstalled downloads from this list and from " "disk.\n\nAre you absolutely sure you want to proceed?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-3, true); } } void DownloadListView::issueRemoveFromViewAll() { if (QMessageBox::question(nullptr, tr("Hide Files?"), tr("This will remove all finished downloads from this list " "(but NOT from disk)."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-1, false); } } void DownloadListView::issueRemoveFromViewCompleted() { if (QMessageBox::question(nullptr, tr("Hide Files?"), tr("This will remove all installed downloads from this " "list (but NOT from disk)."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-2, false); } } void DownloadListView::issueRemoveFromViewUninstalled() { if (QMessageBox::question(nullptr, tr("Hide Files?"), tr("This will remove all uninstalled downloads from this " "list (but NOT from disk)."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-3, false); } }