summaryrefslogtreecommitdiff
path: root/src/modinfodialogconflicts.cpp
diff options
context:
space:
mode:
authorSilarn <jrim@rimpo.org>2019-07-22 01:00:42 -0500
committerSilarn <jrim@rimpo.org>2019-07-22 01:00:42 -0500
commitdcd6d624672019727d7effd17aac86f72bff438b (patch)
tree1e8d3856f657d898c5992631599cf272d785f973 /src/modinfodialogconflicts.cpp
parent179a73857125ee604f42b0d5c2d765183c86d2c7 (diff)
parente73c309f08eff98f0dbd2590f594a83b67431eac (diff)
Merge branch 'Develop'
Diffstat (limited to 'src/modinfodialogconflicts.cpp')
-rw-r--r--src/modinfodialogconflicts.cpp1229
1 files changed, 1229 insertions, 0 deletions
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp
new file mode 100644
index 00000000..511d48ad
--- /dev/null
+++ b/src/modinfodialogconflicts.cpp
@@ -0,0 +1,1229 @@
+#include "modinfodialogconflicts.h"
+#include "ui_modinfodialog.h"
+#include "modinfodialog.h"
+#include "utility.h"
+#include "settings.h"
+#include "organizercore.h"
+
+using namespace MOShared;
+using namespace MOBase;
+namespace shell = MOBase::shell;
+
+// if there are more than 50 selected items in the conflict tree, don't bother
+// checking whether menu items apply to them, just show all of them
+const std::size_t max_small_selection = 50;
+
+
+class ConflictItem
+{
+public:
+ ConflictItem(
+ QString before, QString relativeName, QString after,
+ FileEntry::Index index, QString fileName,
+ bool hasAltOrigins, QString altOrigin, bool archive) :
+ m_before(std::move(before)),
+ m_relativeName(std::move(relativeName)),
+ m_after(std::move(after)),
+ m_index(index),
+ m_fileName(std::move(fileName)),
+ m_hasAltOrigins(hasAltOrigins),
+ m_altOrigin(std::move(altOrigin)),
+ m_isArchive(archive)
+ {
+ }
+
+ const QString& before() const
+ {
+ return m_before;
+ }
+
+ const QString& relativeName() const
+ {
+ return m_relativeName;
+ }
+
+ const QString& after() const
+ {
+ return m_after;
+ }
+
+ const QString& fileName() const
+ {
+ return m_fileName;
+ }
+
+ const QString& altOrigin() const
+ {
+ return m_altOrigin;
+ }
+
+ bool hasAlts() const
+ {
+ return m_hasAltOrigins;
+ }
+
+ bool isArchive() const
+ {
+ return m_isArchive;
+ }
+
+ FileEntry::Index fileIndex() const
+ {
+ return m_index;
+ }
+
+ bool canHide() const
+ {
+ return canHideFile(isArchive(), fileName());
+ }
+
+ bool canUnhide() const
+ {
+ return canUnhideFile(isArchive(), fileName());
+ }
+
+ bool canOpen() const
+ {
+ return canOpenFile(isArchive(), fileName());
+ }
+
+ bool canPreview(PluginContainer& pluginContainer) const
+ {
+ return canPreviewFile(pluginContainer, isArchive(), fileName());
+ }
+
+ bool canExplore() const
+ {
+ return canExploreFile(isArchive(), fileName());
+ }
+
+private:
+ QString m_before;
+ QString m_relativeName;
+ QString m_after;
+ FileEntry::Index m_index;
+ QString m_fileName;
+ bool m_hasAltOrigins;
+ QString m_altOrigin;
+ bool m_isArchive;
+};
+
+
+class ConflictListModel : public QAbstractItemModel
+{
+public:
+ struct Column
+ {
+ QString caption;
+ const QString& (ConflictItem::*getText)() const;
+ };
+
+ ConflictListModel(QTreeView* tree, std::vector<Column> columns) :
+ m_tree(tree), m_columns(std::move(columns)),
+ m_sortColumn(-1), m_sortOrder(Qt::AscendingOrder)
+ {
+ m_tree->setModel(this);
+ }
+
+ void clear()
+ {
+ m_items.clear();
+ endResetModel();
+ }
+
+ void reserve(std::size_t s)
+ {
+ m_items.reserve(s);
+ }
+
+ QModelIndex index(int row, int col, const QModelIndex& ={}) const override
+ {
+ return createIndex(row, col);
+ }
+
+ QModelIndex parent(const QModelIndex&) const override
+ {
+ return {};
+ }
+
+ int rowCount(const QModelIndex& parent={}) const override
+ {
+ if (parent.isValid()) {
+ return 0;
+ }
+
+ return static_cast<int>(m_items.size());
+ }
+
+ int columnCount(const QModelIndex& ={}) const override
+ {
+ return static_cast<int>(m_columns.size());
+ }
+
+ QVariant data(const QModelIndex& index, int role) const override
+ {
+ if (role == Qt::DisplayRole || role == Qt::FontRole) {
+ const auto row = index.row();
+ if (row < 0) {
+ return {};
+ }
+
+ const auto i = static_cast<std::size_t>(row);
+ if (i >= m_items.size()) {
+ return {};
+ }
+
+ const auto col = index.column();
+ if (col < 0) {
+ return {};
+ }
+
+ const auto c = static_cast<std::size_t>(col);
+ if (c >= m_columns.size()) {
+ return {};
+ }
+
+ const auto& item = m_items[i];
+
+ if (role == Qt::DisplayRole) {
+ return (item.*m_columns[c].getText)();
+ } else if (role == Qt::FontRole) {
+ if (item.isArchive()) {
+ QFont f = m_tree->font();
+ f.setItalic(true);
+ return f;
+ }
+ }
+ }
+
+ return {};
+ }
+
+ QVariant headerData(int col, Qt::Orientation, int role) const
+ {
+ if (role == Qt::DisplayRole) {
+ if (col < 0) {
+ return {};
+ }
+
+ const auto i = static_cast<std::size_t>(col);
+ if (i >= m_columns.size()) {
+ return {};
+ }
+
+ return m_columns[i].caption;
+ }
+
+ return {};
+ }
+
+ void sort(int colIndex, Qt::SortOrder order=Qt::AscendingOrder)
+ {
+ m_sortColumn = colIndex;
+ m_sortOrder = order;
+
+ doSort();
+ emit layoutChanged({}, QAbstractItemModel::VerticalSortHint);
+ }
+
+ void add(ConflictItem item)
+ {
+ m_items.emplace_back(std::move(item));
+ }
+
+ void finished()
+ {
+ endResetModel();
+ doSort();
+ }
+
+ const ConflictItem* getItem(std::size_t row) const
+ {
+ if (row >= m_items.size()) {
+ return nullptr;
+ }
+
+ return &m_items[row];
+ }
+
+private:
+ QTreeView* m_tree;
+ std::vector<Column> m_columns;
+ std::vector<ConflictItem> m_items;
+ int m_sortColumn;
+ Qt::SortOrder m_sortOrder;
+
+ void doSort()
+ {
+ if (m_items.empty()) {
+ return;
+ }
+
+ if (m_sortColumn < 0) {
+ return;
+ }
+
+ const auto c = static_cast<std::size_t>(m_sortColumn);
+ if (c >= m_columns.size()) {
+ return;
+ }
+
+ const auto& col = m_columns[c];
+
+ // avoids branching on sort order while sorting
+ auto sortAsc = [&](const auto& a, const auto& b) {
+ return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) < 0);
+ };
+
+ auto sortDesc = [&](const auto& a, const auto& b) {
+ return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) > 0);
+ };
+
+ if (m_sortOrder == Qt::AscendingOrder) {
+ std::sort(m_items.begin(), m_items.end(), sortAsc);
+ } else {
+ std::sort(m_items.begin(), m_items.end(), sortDesc);
+ }
+ }
+};
+
+
+class OverwriteConflictListModel : public ConflictListModel
+{
+public:
+ OverwriteConflictListModel(QTreeView* tree)
+ : ConflictListModel(tree, {
+ {tr("File"), &ConflictItem::relativeName},
+ {tr("Overwritten Mods"), &ConflictItem::before}
+ })
+ {
+ }
+};
+
+
+class OverwrittenConflictListModel : public ConflictListModel
+{
+public:
+ OverwrittenConflictListModel(QTreeView* tree)
+ : ConflictListModel(tree, {
+ {tr("File"), &ConflictItem::relativeName},
+ {tr("Providing Mod"), &ConflictItem::after}
+ })
+ {
+ }
+};
+
+
+class NoConflictListModel : public ConflictListModel
+{
+public:
+ NoConflictListModel(QTreeView* tree)
+ : ConflictListModel(tree, {
+ {tr("File"), &ConflictItem::relativeName}
+ })
+ {
+ }
+};
+
+
+class AdvancedConflictListModel : public ConflictListModel
+{
+public:
+ AdvancedConflictListModel(QTreeView* tree)
+ : ConflictListModel(tree, {
+ {tr("Overwrites"), &ConflictItem::before},
+ {tr("File"), &ConflictItem::relativeName},
+ {tr("Overwritten By"), &ConflictItem::after}
+ })
+ {
+ }
+};
+
+
+std::size_t smallSelectionSize(const QTreeView* tree)
+{
+ const std::size_t too_many = std::numeric_limits<std::size_t>::max();
+
+ std::size_t n = 0;
+ const auto* sel = tree->selectionModel();
+
+ for (const auto& range : sel->selection()) {
+ n += range.height();
+
+ if (n >= max_small_selection) {
+ return too_many;
+ }
+ }
+
+ return n;
+}
+
+template <class F>
+void for_each_in_selection(QTreeView* tree, F&& f)
+{
+ const auto* sel = tree->selectionModel();
+ const auto* model = dynamic_cast<ConflictListModel*>(tree->model());
+
+ if (!model) {
+ qCritical() << "tree doesn't have a ConflictListModel";
+ return;
+ }
+
+ for (const auto& range : sel->selection()) {
+ // ranges are inclusive
+ for (int row=range.top(); row<=range.bottom(); ++row) {
+ if (auto* item=model->getItem(static_cast<std::size_t>(row))) {
+ if (!f(item)) {
+ return;
+ }
+ }
+ }
+ }
+}
+
+
+ConflictsTab::ConflictsTab(ModInfoDialogTabContext cx) :
+ ModInfoDialogTab(cx), // don't move, cx is used again
+ m_general(this, cx.ui, cx.core), m_advanced(this, cx.ui, cx.core)
+{
+ connect(
+ &m_general, &GeneralConflictsTab::modOpen,
+ [&](const QString& name){ emitModOpen(name); });
+
+ connect(
+ &m_advanced, &AdvancedConflictsTab::modOpen,
+ [&](const QString& name){ emitModOpen(name); });
+}
+
+void ConflictsTab::update()
+{
+ setHasData(m_general.update());
+ m_advanced.update();
+}
+
+void ConflictsTab::clear()
+{
+ m_general.clear();
+ m_advanced.clear();
+ setHasData(false);
+}
+
+void ConflictsTab::saveState(Settings& s)
+{
+ s.directInterface().setValue(
+ "mod_info_conflicts_tab", ui->tabConflictsTabs->currentIndex());
+
+ m_general.saveState(s);
+ m_advanced.saveState(s);
+}
+
+void ConflictsTab::restoreState(const Settings& s)
+{
+ ui->tabConflictsTabs->setCurrentIndex(
+ s.directInterface().value("mod_info_conflicts_tab", 0).toInt());
+
+ m_general.restoreState(s);
+ m_advanced.restoreState(s);
+}
+
+bool ConflictsTab::canHandleUnmanaged() const
+{
+ return true;
+}
+
+void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible)
+{
+ bool changed = false;
+ bool stop = false;
+
+ const auto n = smallSelectionSize(tree);
+
+ qDebug().nospace().noquote()
+ << (visible ? "unhiding" : "hiding") << " "
+ << (n > max_small_selection ? "a lot of" : QString("%1").arg(n))
+ << " conflict files";
+
+ QFlags<FileRenamer::RenameFlags> flags =
+ (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE);
+
+ if (n > 1) {
+ flags |= FileRenamer::MULTIPLE;
+ }
+
+ FileRenamer renamer(parentWidget(), flags);
+
+ auto* model = dynamic_cast<ConflictListModel*>(tree->model());
+ if (!model) {
+ qCritical() << "list doesn't have a ConflictListModel";
+ return;
+ }
+
+ for_each_in_selection(tree, [&](const ConflictItem* item) {
+ if (stop) {
+ return false;
+ }
+
+ auto result = FileRenamer::RESULT_CANCEL;
+
+ if (visible) {
+ if (!item->canUnhide()) {
+ qDebug().nospace() << "cannot unhide " << item->relativeName() << ", skipping";
+ return true;
+ }
+
+ result = unhideFile(renamer, item->fileName());
+
+ } else {
+ if (!item->canHide()) {
+ qDebug().nospace() << "cannot hide " << item->relativeName() << ", skipping";
+ return true;
+ }
+
+ result = hideFile(renamer, item->fileName());
+ }
+
+ switch (result) {
+ case FileRenamer::RESULT_OK: {
+ // will trigger a refresh at the end
+ changed = true;
+ break;
+ }
+
+ case FileRenamer::RESULT_SKIP: {
+ // nop
+ break;
+ }
+
+ case FileRenamer::RESULT_CANCEL: {
+ // stop right now, but make sure to refresh if needed
+ stop = true;
+ break;
+ }
+ }
+
+ return true;
+ });
+
+ qDebug().nospace() << (visible ? "unhiding" : "hiding") << " conflict files done";
+
+ if (changed) {
+ qDebug().nospace() << "triggering refresh";
+
+ if (origin()) {
+ emitOriginModified();
+ }
+
+ update();
+ }
+}
+
+void ConflictsTab::openItems(QTreeView* tree)
+{
+ // the menu item is only shown for a single selection, but handle all of them
+ // in case this changes
+ for_each_in_selection(tree, [&](const ConflictItem* item) {
+ core().executeFileVirtualized(parentWidget(), item->fileName());
+ return true;
+ });
+}
+
+void ConflictsTab::previewItems(QTreeView* tree)
+{
+ // the menu item is only shown for a single selection, but handle all of them
+ // in case this changes
+ for_each_in_selection(tree, [&](const ConflictItem* item) {
+ core().previewFileWithAlternatives(parentWidget(), item->fileName());
+ return true;
+ });
+}
+
+void ConflictsTab::exploreItems(QTreeView* tree)
+{
+ // the menu item is only shown for a single selection, but handle all of them
+ // in case this changes
+ for_each_in_selection(tree, [&](const ConflictItem* item) {
+ shell::ExploreFile(item->fileName());
+ return true;
+ });
+}
+
+void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree)
+{
+ auto actions = createMenuActions(tree);
+
+ QMenu menu;
+
+ // open
+ if (actions.open) {
+ connect(actions.open, &QAction::triggered, [&]{
+ openItems(tree);
+ });
+
+ menu.addAction(actions.open);
+ }
+
+ // preview
+ if (actions.preview) {
+ connect(actions.preview, &QAction::triggered, [&]{
+ previewItems(tree);
+ });
+
+ menu.addAction(actions.preview);
+ }
+
+ // explore
+ if (actions.explore) {
+ connect(actions.explore, &QAction::triggered, [&]{
+ exploreItems(tree);
+ });
+
+ menu.addAction(actions.explore);
+ }
+
+ // hide
+ if (actions.hide) {
+ connect(actions.hide, &QAction::triggered, [&]{
+ changeItemsVisibility(tree, false);
+ });
+
+ menu.addAction(actions.hide);
+ }
+
+ // unhide
+ if (actions.unhide) {
+ connect(actions.unhide, &QAction::triggered, [&]{
+ changeItemsVisibility(tree, true);
+ });
+
+ menu.addAction(actions.unhide);
+ }
+
+ // goto
+ if (actions.gotoMenu) {
+ menu.addMenu(actions.gotoMenu);
+
+ for (auto* a : actions.gotoActions) {
+ connect(a, &QAction::triggered, [&, name=a->text()]{
+ emitModOpen(name);
+ });
+
+ actions.gotoMenu->addAction(a);
+ }
+ }
+
+ if (!menu.isEmpty()) {
+ menu.exec(tree->viewport()->mapToGlobal(pos));
+ }
+}
+
+ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree)
+{
+ if (tree->selectionModel()->selection().isEmpty()) {
+ return {};
+ }
+
+ bool enableHide = true;
+ bool enableUnhide = true;
+ bool enableOpen = true;
+ bool enablePreview = true;
+ bool enableExplore = true;
+ bool enableGoto = true;
+
+ const auto n = smallSelectionSize(tree);
+
+ const auto* model = dynamic_cast<ConflictListModel*>(tree->model());
+ if (!model) {
+ qCritical() << "tree doesn't have a ConflictListModel";
+ return {};
+ }
+
+ if (n == 1) {
+ // this is a single selection
+ const auto* item = model->getItem(static_cast<std::size_t>(
+ tree->selectionModel()->selectedRows()[0].row()));
+
+ if (!item) {
+ return {};
+ }
+
+ enableHide = item->canHide();
+ enableUnhide = item->canUnhide();
+ enableOpen = item->canOpen();
+ enablePreview = item->canPreview(plugin());
+ enableExplore = item->canExplore();
+ enableGoto = item->hasAlts();
+ }
+ else {
+ // this is a multiple selection, don't show open/preview so users don't open
+ // a thousand files
+ enableOpen = false;
+ enablePreview = false;
+
+ // can't explore multiple files
+ enableExplore = false;
+
+ // don't bother with this on multiple selection, at least for now
+ enableGoto = false;
+
+ if (n <= max_small_selection) {
+ // if the number of selected items is low, checking them to accurately
+ // show the menu items is worth it
+ enableHide = false;
+ enableUnhide = false;
+
+ for_each_in_selection(tree, [&](const ConflictItem* item) {
+ if (item->canHide()) {
+ enableHide = true;
+ }
+
+ if (item->canUnhide()) {
+ enableUnhide = true;
+ }
+
+ if (enableHide && enableUnhide && enableGoto) {
+ // found all, no need to check more
+ return false;
+ }
+
+ return true;
+ });
+ }
+ }
+
+ Actions actions;
+
+ actions.hide = new QAction(tr("&Hide"), parentWidget());
+ actions.hide->setEnabled(enableHide);
+
+ // note that it is possible for hidden files to appear if they override other
+ // hidden files from another mod
+ actions.unhide = new QAction(tr("&Unhide"), parentWidget());
+ actions.unhide->setEnabled(enableUnhide);
+
+ actions.open = new QAction(tr("&Open/Execute"), parentWidget());
+ actions.open->setEnabled(enableOpen);
+
+ actions.preview = new QAction(tr("&Preview"), parentWidget());
+ actions.preview->setEnabled(enablePreview);
+
+ actions.explore = new QAction(tr("Open in &Explorer"), parentWidget());
+ actions.explore->setEnabled(enableExplore);
+
+ actions.gotoMenu = new QMenu(tr("&Go to..."), parentWidget());
+ actions.gotoMenu->setEnabled(enableGoto);
+
+ if (enableGoto && n == 1) {
+ const auto* item = model->getItem(static_cast<std::size_t>(
+ tree->selectionModel()->selectedRows()[0].row()));
+
+ actions.gotoActions = createGotoActions(item);
+ }
+
+ return actions;
+}
+
+std::vector<QAction*> ConflictsTab::createGotoActions(const ConflictItem* item)
+{
+ if (!origin()) {
+ return {};
+ }
+
+ auto file = origin()->findFile(item->fileIndex());
+ if (!file) {
+ return {};
+ }
+
+
+ std::vector<QString> mods;
+ const auto& ds = *core().directoryStructure();
+
+ // add all alternatives
+ for (const auto& alt : file->getAlternatives()) {
+ const auto& o = ds.getOriginByID(alt.first);
+ if (o.getID() != origin()->getID()) {
+ mods.push_back(ToQString(o.getName()));
+ }
+ }
+
+ // add the real origin if different from this mod
+ const FilesOrigin& realOrigin = ds.getOriginByID(file->getOrigin());
+ if (realOrigin.getID() != origin()->getID()) {
+ mods.push_back(ToQString(realOrigin.getName()));
+ }
+
+ std::sort(mods.begin(), mods.end(), [](const auto& a, const auto& b) {
+ return (QString::localeAwareCompare(a, b) < 0);
+ });
+
+ std::vector<QAction*> actions;
+
+ for (const auto& name : mods) {
+ actions.push_back(new QAction(name, parentWidget()));
+ }
+
+ return actions;
+}
+
+
+GeneralConflictsTab::GeneralConflictsTab(
+ ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) :
+ m_tab(tab), ui(pui), m_core(oc),
+ m_overwriteModel(new OverwriteConflictListModel(ui->overwriteTree)),
+ m_overwrittenModel(new OverwrittenConflictListModel(ui->overwrittenTree)),
+ m_noConflictModel(new NoConflictListModel(ui->noConflictTree))
+{
+ m_expanders.overwrite.set(ui->overwriteExpander, ui->overwriteTree, true);
+ m_expanders.overwritten.set(ui->overwrittenExpander, ui->overwrittenTree, true);
+ m_expanders.nonconflict.set(ui->noConflictExpander, ui->noConflictTree);
+
+ QObject::connect(
+ ui->overwriteTree, &QTreeView::doubleClicked,
+ [&](auto&& item){ onOverwriteActivated(item); });
+
+ QObject::connect(
+ ui->overwrittenTree, &QTreeView::doubleClicked,
+ [&](auto&& item){ onOverwrittenActivated(item); });
+
+ QObject::connect(
+ ui->overwriteTree, &QTreeView::customContextMenuRequested,
+ [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwriteTree); });
+
+ QObject::connect(
+ ui->overwrittenTree, &QTreeView::customContextMenuRequested,
+ [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwrittenTree); });
+
+ QObject::connect(
+ ui->noConflictTree, &QTreeView::customContextMenuRequested,
+ [&](const QPoint& p){ m_tab->showContextMenu(p, ui->noConflictTree); });
+}
+
+void GeneralConflictsTab::clear()
+{
+ m_overwriteModel->clear();
+ m_overwrittenModel->clear();
+ m_noConflictModel->clear();
+
+ ui->overwriteCount->display(0);
+ ui->overwrittenCount->display(0);
+ ui->noConflictCount->display(0);
+}
+
+void GeneralConflictsTab::saveState(Settings& s)
+{
+ QByteArray result;
+ QDataStream stream(&result, QIODevice::WriteOnly);
+
+ stream
+ << m_expanders.overwrite.opened()
+ << m_expanders.overwritten.opened()
+ << m_expanders.nonconflict.opened();
+
+ s.directInterface().setValue(
+ "mod_info_conflicts_general_expanders", result);
+
+ s.directInterface().setValue(
+ "mod_info_conflicts_general_overwrite",
+ ui->overwriteTree->header()->saveState());
+
+ s.directInterface().setValue(
+ "mod_info_conflicts_general_noconflict",
+ ui->noConflictTree->header()->saveState());
+
+ s.directInterface().setValue(
+ "mod_info_conflicts_general_overwritten",
+ ui->overwrittenTree->header()->saveState());
+}
+
+void GeneralConflictsTab::restoreState(const Settings& s)
+{
+ QDataStream stream(s.directInterface()
+ .value("mod_info_conflicts_general_expanders").toByteArray());
+
+ bool overwriteExpanded = false;
+ bool overwrittenExpanded = false;
+ bool noConflictExpanded = false;
+
+ stream >> overwriteExpanded >> overwrittenExpanded >> noConflictExpanded;
+
+ if (stream.status() == QDataStream::Ok) {
+ m_expanders.overwrite.toggle(overwriteExpanded);
+ m_expanders.overwritten.toggle(overwrittenExpanded);
+ m_expanders.nonconflict.toggle(noConflictExpanded);
+ }
+
+ ui->overwriteTree->header()->restoreState(s.directInterface()
+ .value("mod_info_conflicts_general_overwrite").toByteArray());
+
+ ui->noConflictTree->header()->restoreState(s.directInterface()
+ .value("mod_info_conflicts_general_noconflict").toByteArray());
+
+ ui->overwrittenTree->header()->restoreState(s.directInterface()
+ .value("mod_info_conflicts_general_overwritten").toByteArray());
+}
+
+bool GeneralConflictsTab::update()
+{
+ clear();
+
+ int numNonConflicting = 0;
+ int numOverwrite = 0;
+ int numOverwritten = 0;
+
+ if (m_tab->origin() != nullptr) {
+ const auto rootPath = m_tab->mod().absolutePath();
+
+ for (const auto& file : m_tab->origin()->getFiles()) {
+ // careful: these two strings are moved into createXItem() below
+ QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath()));
+ QString fileName = rootPath + relativeName;
+
+ bool archive = false;
+ const int fileOrigin = file->getOrigin(archive);
+ const auto& alternatives = file->getAlternatives();
+
+ if (fileOrigin == m_tab->origin()->getID()) {
+ if (!alternatives.empty()) {
+ m_overwriteModel->add(createOverwriteItem(
+ file->getIndex(), archive,
+ std::move(fileName), std::move(relativeName), alternatives));
+
+ ++numOverwrite;
+ } else {
+ // otherwise, put the file in the noconflict tree
+ m_noConflictModel->add(createNoConflictItem(
+ file->getIndex(), archive,
+ std::move(fileName), std::move(relativeName)));
+
+ ++numNonConflicting;
+ }
+ } else {
+ m_overwrittenModel->add(createOverwrittenItem(
+ file->getIndex(), fileOrigin, archive,
+ std::move(fileName), std::move(relativeName)));
+
+ ++numOverwritten;
+ }
+ }
+
+ m_overwriteModel->finished();
+ m_overwrittenModel->finished();
+ m_noConflictModel->finished();
+ }
+
+ ui->overwriteCount->display(numOverwrite);
+ ui->overwrittenCount->display(numOverwritten);
+ ui->noConflictCount->display(numNonConflicting);
+
+ return (numOverwrite > 0 || numOverwritten > 0);
+}
+
+ConflictItem GeneralConflictsTab::createOverwriteItem(
+ FileEntry::Index index, bool archive, QString fileName, QString relativeName,
+ const FileEntry::AlternativesVector& alternatives)
+{
+ const auto& ds = *m_core.directoryStructure();
+ std::wstring altString;
+
+ for (const auto& alt : alternatives) {
+ if (!altString.empty()) {
+ altString += L", ";
+ }
+
+ altString += ds.getOriginByID(alt.first).getName();
+ }
+
+ auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName());
+
+ return ConflictItem(
+ ToQString(altString), std::move(relativeName), QString::null, index,
+ std::move(fileName), true, std::move(origin), archive);
+}
+
+ConflictItem GeneralConflictsTab::createNoConflictItem(
+ FileEntry::Index index, bool archive, QString fileName, QString relativeName)
+{
+ return ConflictItem(
+ QString::null, std::move(relativeName), QString::null, index,
+ std::move(fileName), false, QString::null, archive);
+}
+
+ConflictItem GeneralConflictsTab::createOverwrittenItem(
+ FileEntry::Index index, int fileOrigin, bool archive,
+ QString fileName, QString relativeName)
+{
+ const auto& ds = *m_core.directoryStructure();
+ const FilesOrigin& realOrigin = ds.getOriginByID(fileOrigin);
+
+ QString after = ToQString(realOrigin.getName());
+ QString altOrigin = after;
+
+ return ConflictItem(
+ QString::null, std::move(relativeName), std::move(after),
+ index, std::move(fileName), true, std::move(altOrigin), archive);
+}
+
+void GeneralConflictsTab::onOverwriteActivated(const QModelIndex& index)
+{
+ auto* model = dynamic_cast<ConflictListModel*>(ui->overwriteTree->model());
+ if (!model) {
+ return;
+ }
+
+ auto* item = model->getItem(static_cast<std::size_t>(index.row()));
+ if (!item) {
+ return;
+ }
+
+ const auto origin = item->altOrigin();
+ if (!origin.isEmpty()) {
+ emit modOpen(origin);
+ }
+}
+
+void GeneralConflictsTab::onOverwrittenActivated(const QModelIndex& index)
+{
+ auto* model = dynamic_cast<ConflictListModel*>(ui->overwrittenTree->model());
+ if (!model) {
+ return;
+ }
+
+ auto* item = model->getItem(static_cast<std::size_t>(index.row()));
+ if (!item) {
+ return;
+ }
+
+ const auto origin = item->altOrigin();
+ if (!origin.isEmpty()) {
+ emit modOpen(origin);
+ }
+}
+
+
+AdvancedConflictsTab::AdvancedConflictsTab(
+ ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) :
+ m_tab(tab), ui(pui), m_core(oc),
+ m_model(new AdvancedConflictListModel(ui->conflictsAdvancedList))
+{
+ // left-elide the overwrites column so that the nearest are visible
+ ui->conflictsAdvancedList->setItemDelegateForColumn(
+ 0, new ElideLeftDelegate(ui->conflictsAdvancedList));
+
+ // left-elide the file column to see filenames
+ ui->conflictsAdvancedList->setItemDelegateForColumn(
+ 1, new ElideLeftDelegate(ui->conflictsAdvancedList));
+
+ // don't elide the overwritten by column so that the nearest are visible
+
+ QObject::connect(
+ ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked,
+ [&]{ update(); });
+
+ QObject::connect(
+ ui->conflictsAdvancedShowAll, &QRadioButton::clicked,
+ [&]{ update(); });
+
+ QObject::connect(
+ ui->conflictsAdvancedShowNearest, &QRadioButton::clicked,
+ [&]{ update(); });
+
+ QObject::connect(
+ ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested,
+ [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); });
+
+ m_filter.setEdit(ui->conflictsAdvancedFilter);
+ QObject::connect(&m_filter, &FilterWidget::changed, [&]{ update(); });
+}
+
+void AdvancedConflictsTab::clear()
+{
+ m_model->clear();
+}
+
+void AdvancedConflictsTab::saveState(Settings& s)
+{
+ s.directInterface().setValue(
+ "mod_info_conflicts_advanced_list",
+ ui->conflictsAdvancedList->header()->saveState());
+
+ QByteArray result;
+ QDataStream stream(&result, QIODevice::WriteOnly);
+
+ stream
+ << ui->conflictsAdvancedShowNoConflict->isChecked()
+ << ui->conflictsAdvancedShowAll->isChecked()
+ << ui->conflictsAdvancedShowNearest->isChecked();
+
+ s.directInterface().setValue(
+ "mod_info_conflicts_advanced_options", result);
+}
+
+void AdvancedConflictsTab::restoreState(const Settings& s)
+{
+ ui->conflictsAdvancedList->header()->restoreState(
+ s.directInterface().value("mod_info_conflicts_advanced_list").toByteArray());
+
+ QDataStream stream(s.directInterface()
+ .value("mod_info_conflicts_advanced_options").toByteArray());
+
+ bool noConflictChecked = false;
+ bool showAllChecked = false;
+ bool showNearestChecked = false;
+
+ stream >> noConflictChecked >> showAllChecked >> showNearestChecked;
+
+ if (stream.status() == QDataStream::Ok) {
+ ui->conflictsAdvancedShowNoConflict->setChecked(noConflictChecked);
+ ui->conflictsAdvancedShowAll->setChecked(showAllChecked);
+ ui->conflictsAdvancedShowNearest->setChecked(showNearestChecked);
+ }
+}
+
+void AdvancedConflictsTab::update()
+{
+ clear();
+
+ if (m_tab->origin() != nullptr) {
+ const auto rootPath = m_tab->mod().absolutePath();
+
+ const auto& files = m_tab->origin()->getFiles();
+ m_model->reserve(files.size());
+
+ for (const auto& file : files) {
+ // careful: these two strings are moved into createItem() below
+ QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath()));
+ QString fileName = rootPath + relativeName;
+
+ bool archive = false;
+ const int fileOrigin = file->getOrigin(archive);
+ const auto& alternatives = file->getAlternatives();
+
+ auto item = createItem(
+ file->getIndex(), fileOrigin, archive,
+ std::move(fileName), std::move(relativeName), alternatives);
+
+ if (item) {
+ m_model->add(std::move(*item));
+ }
+ }
+
+ m_model->finished();
+ }
+}
+
+std::optional<ConflictItem> AdvancedConflictsTab::createItem(
+ FileEntry::Index index, int fileOrigin, bool archive,
+ QString fileName, QString relativeName,
+ const MOShared::FileEntry::AlternativesVector& alternatives)
+{
+ const auto& ds = *m_core.directoryStructure();
+
+ std::wstring before, after;
+
+ if (!alternatives.empty()) {
+ const bool showAllAlts = ui->conflictsAdvancedShowAll->isChecked();
+
+ int beforePrio = 0;
+ int afterPrio = std::numeric_limits<int>::max();
+
+ for (const auto& alt : alternatives)
+ {
+ const auto& altOrigin = ds.getOriginByID(alt.first);
+
+ if (showAllAlts) {
+ // fills 'before' and 'after' with all the alternatives that come
+ // before and after this mod in terms of priority
+
+ if (altOrigin.getPriority() < m_tab->origin()->getPriority()) {
+ // add all the mods having a lower priority than this one
+ if (!before.empty()) {
+ before += L", ";
+ }
+
+ before += altOrigin.getName();
+ } else if (altOrigin.getPriority() > m_tab->origin()->getPriority()) {
+ // add all the mods having a higher priority than this one
+ if (!after.empty()) {
+ after += L", ";
+ }
+
+ after += altOrigin.getName();
+ }
+ } else {
+ // keep track of the nearest mods that come before and after this one
+ // in terms of priority
+
+ if (altOrigin.getPriority() < m_tab->origin()->getPriority()) {
+ // the alternative has a lower priority than this mod
+
+ if (altOrigin.getPriority() > beforePrio) {
+ // the alternative has a higher priority and therefore is closer
+ // to this mod, use it
+ before = altOrigin.getName();
+ beforePrio = altOrigin.getPriority();
+ }
+ }
+
+ if (altOrigin.getPriority() > m_tab->origin()->getPriority()) {
+ // the alternative has a higher priority than this mod
+
+ if (altOrigin.getPriority() < afterPrio) {
+ // the alternative has a lower priority and there is closer
+ // to this mod, use it
+ after = altOrigin.getName();
+ afterPrio = altOrigin.getPriority();
+ }
+ }
+ }
+ }
+
+ // the primary origin is never in the list of alternatives, so it has to
+ // be handled separately
+ //
+ // if 'after' is not empty, it means at least one alternative with a higher
+ // priority than this mod was found; if the user only wants to see the
+ // nearest mods, it's not worth checking for the primary origin because it
+ // will always have a higher priority than the alternatives (or it wouldn't
+ // be the primary)
+ if (after.empty() || showAllAlts) {
+ const FilesOrigin& realOrigin = ds.getOriginByID(fileOrigin);
+
+ // if no mods overwrite this file, the primary origin is the same as this
+ // mod, so ignore that
+ if (realOrigin.getID() != m_tab->origin()->getID()) {
+ if (!after.empty()) {
+ after += L", ";
+ }
+
+ after += realOrigin.getName();
+ }
+ }
+ }
+
+ const bool hasAlts = !before.empty() || !after.empty();
+
+ if (!hasAlts) {
+ // if both before and after are empty, it means this file has no conflicts
+ // at all, only display it if the user wants it
+ if (!ui->conflictsAdvancedShowNoConflict->isChecked()) {
+ return {};
+ }
+ }
+
+ auto beforeQS = QString::fromStdWString(before);
+ auto afterQS = QString::fromStdWString(after);
+
+ const bool matched = m_filter.matches([&](auto&& what) {
+ return
+ beforeQS.contains(what, Qt::CaseInsensitive) ||
+ relativeName.contains(what, Qt::CaseInsensitive) ||
+ afterQS.contains(what, Qt::CaseInsensitive);
+ });
+
+ if (!matched) {
+ return {};
+ }
+
+ return ConflictItem(
+ std::move(beforeQS), std::move(relativeName), std::move(afterQS),
+ index, std::move(fileName), hasAlts, QString::null, archive);
+}