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 <http://www.gnu.org/licenses/>.
*/
#include "downloadlistview.h"
#include "downloadlist.h"
#include <QApplication>
#include <QCheckBox>
#include <QHeaderView>
#include <QMenu>
#include <QMessageBox>
#include <QMouseEvent>
#include <QPainter>
#include <QSortFilterProxyModel>
#include <QWidgetAction>
#include <log.h>
#include <report.h>
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<QSortFilterProxyModel*>(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());
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()
{
int rightVisible = count() - 1;
while (isSectionHidden(rightVisible) && rightVisible > 0)
rightVisible--;
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;
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<QSortFilterProxyModel*>(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;
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));
int i = 1;
for (const QAction* action : menu.actions()) {
const QWidgetAction* widgetAction = qobject_cast<const QWidgetAction*>(action);
if (widgetAction != nullptr) {
const QCheckBox* checkBox =
qobject_cast<const QCheckBox*>(widgetAction->defaultWidget());
if (checkBox != nullptr) {
header()->setSectionHidden(i, !checkBox->isChecked());
}
}
++i;
}
qobject_cast<DownloadListHeader*>(header())->customResizeSections();
}
void DownloadListView::resizeEvent(QResizeEvent* event)
{
QTreeView::resizeEvent(event);
qobject_cast<DownloadListHeader*>(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<QSortFilterProxyModel*>(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&) {
}
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<QSortFilterProxyModel*>(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);
}
}