You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw/components/contentselector/view/contentselector.cpp

345 lines
10 KiB
C++

#include "contentselector.hpp"
#include "ui_contentselector.h"
#include <components/contentselector/model/esmfile.hpp>
#include <QClipboard>
#include <QMenu>
#include <QModelIndex>
#include <QSortFilterProxyModel>
ContentSelectorView::ContentSelector::ContentSelector(QWidget* parent, bool showOMWScripts)
: QObject(parent)
, ui(std::make_unique<Ui::ContentSelector>())
{
ui->setupUi(parent);
ui->addonView->setDragDropMode(QAbstractItemView::InternalMove);
if (!showOMWScripts)
{
ui->languageComboBox->setHidden(true);
ui->refreshButton->setHidden(true);
}
buildContentModel(showOMWScripts);
buildGameFileView();
buildAddonView();
}
ContentSelectorView::ContentSelector::~ContentSelector() = default;
void ContentSelectorView::ContentSelector::buildContentModel(bool showOMWScripts)
{
QIcon warningIcon(ui->addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning));
mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon, showOMWScripts);
}
void ContentSelectorView::ContentSelector::buildGameFileView()
{
ui->gameFileView->addItem(tr("<No game file>"));
ui->gameFileView->setVisible(true);
connect(ui->gameFileView, qOverload<int>(&ComboBox::currentIndexChanged), this,
&ContentSelector::slotCurrentGameFileIndexChanged);
ui->gameFileView->setCurrentIndex(0);
}
class AddOnProxyModel : public QSortFilterProxyModel
{
public:
explicit AddOnProxyModel(QObject* parent = nullptr)
: QSortFilterProxyModel(parent)
{
}
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override
{
static const QString ContentTypeAddon = QString::number((int)ContentSelectorModel::ContentType_Addon);
QModelIndex nameIndex = sourceModel()->index(sourceRow, 0, sourceParent);
const QString userRole = sourceModel()->data(nameIndex, Qt::UserRole).toString();
return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent) && userRole == ContentTypeAddon;
}
};
bool ContentSelectorView::ContentSelector::isGamefileSelected() const
{
return ui->gameFileView->currentIndex() > 0;
}
QWidget* ContentSelectorView::ContentSelector::uiWidget() const
{
return ui->contentGroupBox;
}
QComboBox* ContentSelectorView::ContentSelector::languageBox() const
{
return ui->languageComboBox;
}
QToolButton* ContentSelectorView::ContentSelector::refreshButton() const
{
return ui->refreshButton;
}
QLineEdit* ContentSelectorView::ContentSelector::searchFilter() const
{
return ui->searchFilter;
}
void ContentSelectorView::ContentSelector::buildAddonView()
{
ui->addonView->setVisible(true);
mAddonProxyModel = new AddOnProxyModel(this);
mAddonProxyModel->setFilterRegularExpression(searchFilter()->text());
mAddonProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
mAddonProxyModel->setDynamicSortFilter(true);
mAddonProxyModel->setSourceModel(mContentModel);
connect(ui->searchFilter, &QLineEdit::textEdited, mAddonProxyModel, &QSortFilterProxyModel::setFilterWildcard);
connect(ui->searchFilter, &QLineEdit::textEdited, this, &ContentSelector::slotSearchFilterTextChanged);
ui->addonView->setModel(mAddonProxyModel);
connect(ui->addonView, &QTableView::activated, this, &ContentSelector::slotAddonTableItemActivated);
connect(mContentModel, &ContentSelectorModel::ContentModel::dataChanged, this,
&ContentSelector::signalAddonDataChanged);
connect(mContentModel, &ContentSelectorModel::ContentModel::dataChanged, this, &ContentSelector::slotRowsMoved);
buildContextMenu();
}
void ContentSelectorView::ContentSelector::buildContextMenu()
{
ui->addonView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->addonView, &QTableView::customContextMenuRequested, this, &ContentSelector::slotShowContextMenu);
mContextMenu = new QMenu(ui->addonView);
mContextMenu->addAction(tr("&Check Selected"), this, SLOT(slotCheckMultiSelectedItems()));
mContextMenu->addAction(tr("&Uncheck Selected"), this, SLOT(slotUncheckMultiSelectedItems()));
mContextMenu->addAction(tr("&Copy Path(s) to Clipboard"), this, SLOT(slotCopySelectedItemsPaths()));
}
void ContentSelectorView::ContentSelector::setNonUserContent(const QStringList& fileList)
{
mContentModel->setNonUserContent(fileList);
}
void ContentSelectorView::ContentSelector::setProfileContent(const QStringList& fileList)
{
clearCheckStates();
for (const QString& filepath : fileList)
{
const ContentSelectorModel::EsmFile* file = mContentModel->item(filepath);
if (file && file->isGameFile())
{
setGameFile(filepath);
break;
}
}
setContentList(fileList);
}
void ContentSelectorView::ContentSelector::setGameFile(const QString& filename)
{
int index = 0;
if (!filename.isEmpty())
{
const ContentSelectorModel::EsmFile* file = mContentModel->item(filename);
index = ui->gameFileView->findText(file->fileName());
// verify that the current index is also checked in the model
if (!mContentModel->setCheckState(filename, true))
{
// throw error in case file not found?
return;
}
}
ui->gameFileView->setCurrentIndex(index);
}
void ContentSelectorView::ContentSelector::clearCheckStates()
{
mContentModel->uncheckAll();
}
void ContentSelectorView::ContentSelector::setEncoding(const QString& encoding)
{
mContentModel->setEncoding(encoding);
}
void ContentSelectorView::ContentSelector::setContentList(const QStringList& list)
{
if (list.isEmpty())
{
slotCurrentGameFileIndexChanged(ui->gameFileView->currentIndex());
}
else
mContentModel->setContentList(list);
}
ContentSelectorModel::ContentFileList ContentSelectorView::ContentSelector::selectedFiles() const
{
if (!mContentModel)
return ContentSelectorModel::ContentFileList();
return mContentModel->checkedItems();
}
void ContentSelectorView::ContentSelector::addFiles(const QString& path, bool newfiles)
{
mContentModel->addFiles(path, newfiles);
// add any game files to the combo box
for (const QString& gameFileName : mContentModel->gameFiles())
{
if (ui->gameFileView->findText(gameFileName) == -1)
{
ui->gameFileView->addItem(gameFileName);
}
}
if (ui->gameFileView->currentIndex() != 0)
ui->gameFileView->setCurrentIndex(0);
mContentModel->uncheckAll();
mContentModel->checkForLoadOrderErrors();
}
void ContentSelectorView::ContentSelector::sortFiles()
{
mContentModel->sortFiles();
}
bool ContentSelectorView::ContentSelector::containsDataFiles(const QString& path)
{
return mContentModel->containsDataFiles(path);
}
void ContentSelectorView::ContentSelector::clearFiles()
{
mContentModel->clearFiles();
}
QString ContentSelectorView::ContentSelector::currentFile() const
{
QModelIndex currentIdx = ui->addonView->currentIndex();
if (!currentIdx.isValid() && ui->gameFileView->currentIndex() > 0)
return ui->gameFileView->currentText();
QModelIndex idx = mContentModel->index(mAddonProxyModel->mapToSource(currentIdx).row(), 0, QModelIndex());
return mContentModel->data(idx, Qt::DisplayRole).toString();
}
void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int index)
{
static int oldIndex = -1;
if (index != oldIndex)
{
if (oldIndex > -1)
{
setGameFileSelected(oldIndex, false);
}
oldIndex = index;
setGameFileSelected(index, true);
mContentModel->checkForLoadOrderErrors();
}
emit signalCurrentGamefileIndexChanged(index);
}
void ContentSelectorView::ContentSelector::setGameFileSelected(int index, bool selected)
{
QString fileName = ui->gameFileView->itemText(index);
const ContentSelectorModel::EsmFile* file = mContentModel->item(fileName);
if (file != nullptr)
{
QModelIndex index2(mContentModel->indexFromItem(file));
mContentModel->setData(index2, selected, Qt::UserRole + 1);
}
mContentModel->setCurrentGameFile(selected ? file : nullptr);
}
void ContentSelectorView::ContentSelector::slotAddonTableItemActivated(const QModelIndex& index)
{
// toggles check state when an AddOn file is double clicked or activated by keyboard
QModelIndex sourceIndex = mAddonProxyModel->mapToSource(index);
if (!mContentModel->isEnabled(sourceIndex))
return;
Qt::CheckState checkState = Qt::Unchecked;
if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() == Qt::Unchecked)
checkState = Qt::Checked;
mContentModel->setData(sourceIndex, checkState, Qt::CheckStateRole);
}
void ContentSelectorView::ContentSelector::slotShowContextMenu(const QPoint& pos)
{
QPoint globalPos = ui->addonView->viewport()->mapToGlobal(pos);
mContextMenu->exec(globalPos);
}
void ContentSelectorView::ContentSelector::setCheckStateForMultiSelectedItems(bool checked)
{
Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked;
for (const QModelIndex& index : ui->addonView->selectionModel()->selectedIndexes())
{
QModelIndex sourceIndex = mAddonProxyModel->mapToSource(index);
if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() != checkState)
{
mContentModel->setData(sourceIndex, checkState, Qt::CheckStateRole);
}
}
}
void ContentSelectorView::ContentSelector::slotUncheckMultiSelectedItems()
{
setCheckStateForMultiSelectedItems(false);
}
void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems()
{
setCheckStateForMultiSelectedItems(true);
}
void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths()
{
QClipboard* clipboard = QApplication::clipboard();
QString filepaths;
for (const QModelIndex& index : ui->addonView->selectionModel()->selectedIndexes())
{
int row = mAddonProxyModel->mapToSource(index).row();
const ContentSelectorModel::EsmFile* file = mContentModel->item(row);
filepaths += file->filePath() + "\n";
}
if (!filepaths.isEmpty())
{
clipboard->setText(filepaths);
}
}
void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QString& newText)
{
ui->addonView->setDragEnabled(newText.isEmpty());
}
void ContentSelectorView::ContentSelector::slotRowsMoved()
{
ui->addonView->selectionModel()->clearSelection();
}