summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2019-06-19 17:24:23 -0400
committerisanae <14251494+isanae@users.noreply.github.com>2019-07-02 10:10:17 -0400
commitc98115e2f6f59b807382c41256d7f1a86009aa40 (patch)
tree88a78f5e805981a9981604c2f997d0795a35d102 /src
parentf8a037a409d1b6bbb2f34b237b8b66d454179f56 (diff)
in the process of moving stuff to modinfodialogconflicts.h/cpp
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/modinfodialog.cpp480
-rw-r--r--src/modinfodialog.h45
-rw-r--r--src/modinfodialog.ui58
-rw-r--r--src/modinfodialogconflicts.cpp530
-rw-r--r--src/modinfodialogconflicts.h86
6 files changed, 689 insertions, 513 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index aba922f9..eead6541 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -54,6 +54,7 @@ SET(organizer_SRCS
modlist.cpp
modidlineedit.cpp
modinfodialog.cpp
+ modinfodialogconflicts.cpp
modinfodialogesps.cpp
modinfodialogimages.cpp
modinfodialogtextfiles.cpp
@@ -157,6 +158,7 @@ SET(organizer_HDRS
modlist.h
modidlineedit.h
modinfodialog.h
+ modinfodialogconflicts.h
modinfodialogesps.h
modinfodialogimages.h
modinfodialogtextfiles.h
@@ -358,6 +360,7 @@ set(modinfo
modinfo
modinfobackup
modinfodialog
+ modinfodialogconflicts
modinfodialogesps
modinfodialogimages
modinfodialogtextfiles
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp
index a52f2031..c24176b3 100644
--- a/src/modinfodialog.cpp
+++ b/src/modinfodialog.cpp
@@ -41,6 +41,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "modinfodialogtextfiles.h"
#include "modinfodialogimages.h"
#include "modinfodialogesps.h"
+#include "modinfodialogconflicts.h"
#include <QDir>
#include <QDirIterator>
@@ -72,6 +73,7 @@ std::vector<std::unique_ptr<ModInfoDialogTab>> ModInfoDialogTab::createTabs(
v.push_back(std::make_unique<IniFilesTab>(ui));
v.push_back(std::make_unique<ImagesTab>(ui));
v.push_back(std::make_unique<ESPsTab>(ui));
+ v.push_back(std::make_unique<ConflictsTab>(ui));
return v;
}
@@ -81,6 +83,15 @@ bool ModInfoDialogTab::canClose()
return true;
}
+void ModInfoDialogTab::saveState(Settings&)
+{
+ // no-op
+}
+
+void ModInfoDialogTab::restoreState(const Settings& s)
+{
+ // no-op
+}
class ModFileListWidget : public QListWidgetItem {
@@ -103,20 +114,6 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS
const int max_scan_for_context_menu = 50;
-class ElideLeftDelegate : public QStyledItemDelegate
-{
-public:
- using QStyledItemDelegate::QStyledItemDelegate;
-
-protected:
- void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const
- {
- QStyledItemDelegate::initStyleOption(option, index);
- option->textElideMode = Qt::ElideLeft;
- }
-};
-
-
bool canPreviewFile(
PluginContainer* pluginContainer, bool isArchive, const QString& filename)
{
@@ -165,110 +162,6 @@ bool canUnhideFile(bool isArchive, const QString& filename)
}
-int naturalCompare(const QString& a, const QString& b)
-{
- static QCollator c = []{
- QCollator c;
- c.setNumericMode(true);
- c.setCaseSensitivity(Qt::CaseInsensitive);
- return c;
- }();
-
- return c.compare(a, b);
-}
-
-
-class ConflictItem : public QTreeWidgetItem
-{
-public:
- static const int FILENAME_USERROLE = Qt::UserRole + 1;
- static const int ALT_ORIGIN_USERROLE = Qt::UserRole + 2;
- static const int ARCHIVE_USERROLE = Qt::UserRole + 3;
- static const int INDEX_USERROLE = Qt::UserRole + 4;
- static const int HAS_ALTS_USERROLE = Qt::UserRole + 5;
-
- ConflictItem(
- QStringList columns, FileEntry::Index index, const QString& fileName,
- bool hasAltOrigins, const QString& altOrigin, bool archive)
- : QTreeWidgetItem(columns)
- {
- setData(0, FILENAME_USERROLE, fileName);
- setData(0, ALT_ORIGIN_USERROLE, altOrigin);
- setData(0, ARCHIVE_USERROLE, archive);
- setData(0, INDEX_USERROLE, index);
- setData(0, HAS_ALTS_USERROLE, hasAltOrigins);
-
- if (archive) {
- QFont f = font(0);
- f.setItalic(true);
-
- for (int i=0; i<columnCount(); ++i) {
- setFont(i, f);
- }
- }
- }
-
- QString fileName() const
- {
- return data(0, FILENAME_USERROLE).toString();
- }
-
- QString altOrigin() const
- {
- return data(0, ALT_ORIGIN_USERROLE).toString();
- }
-
- bool hasAlts() const
- {
- return data(0, HAS_ALTS_USERROLE).toBool();
- }
-
- bool isArchive() const
- {
- return data(0, ARCHIVE_USERROLE).toBool();
- }
-
- FileEntry::Index fileIndex() const
- {
- static_assert(std::is_same_v<FileEntry::Index, unsigned int>);
- return data(0, INDEX_USERROLE).toUInt();
- }
-
- 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 operator<(const QTreeWidgetItem& other) const
- {
- const int column = treeWidget()->sortColumn();
-
- if (column >= columnCount() || column >= other.columnCount()) {
- // shouldn't happen
- qWarning().nospace() << "ConflictItem::operator<() mistmatch in column count";
- return false;
- }
-
- return (naturalCompare(text(column), other.text(column)) < 0);
- }
-};
-
-
ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent)
: TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo),
m_RequestStarted(false),
@@ -356,6 +249,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo
ui->tabWidget->setTabEnabled(TAB_INIFILES, false);
ui->tabWidget->setTabEnabled(TAB_IMAGES, false);
ui->tabWidget->setTabEnabled(TAB_ESPS, false);
+ ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true);
ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false);
ui->tabWidget->setTabEnabled(TAB_NEXUS, false);
ui->tabWidget->setTabEnabled(TAB_FILETREE, false);
@@ -365,13 +259,13 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo
ui->tabWidget->setTabEnabled(TAB_INIFILES, true);
ui->tabWidget->setTabEnabled(TAB_IMAGES, true);
ui->tabWidget->setTabEnabled(TAB_ESPS, true);
+ ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true);
initFiletree(modInfo);
addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0);
refreshPrimaryCategoriesBox();
}
- ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != nullptr);
ui->endorseBtn->setVisible(Settings::instance().endorsementIntegration());
@@ -389,36 +283,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo
if (ui->tabWidget->currentIndex() == TAB_NEXUS) {
activateNexusTab();
}
-
- m_overwriteExpander.set(ui->overwriteExpander, ui->overwriteTree, true);
- m_overwrittenExpander.set(ui->overwrittenExpander, ui->overwrittenTree, true);
- m_nonconflictExpander.set(ui->noConflictExpander, ui->noConflictTree);
-
-
- m_advancedConflictFilter.set(ui->conflictsAdvancedFilter);
- m_advancedConflictFilter.changed = [&]{ refreshConflictLists(false, true); };
-
- // 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
-
- connect(ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked, [&] {
- refreshConflictLists(false, true);
- });
-
- connect(ui->conflictsAdvancedShowAll, &QRadioButton::clicked, [&] {
- refreshConflictLists(false, true);
- });
-
- connect(ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&] {
- refreshConflictLists(false, true);
- });
}
@@ -488,41 +352,19 @@ int ModInfoDialog::tabIndex(const QString &tabId)
void ModInfoDialog::saveState(Settings& s) const
{
s.directInterface().setValue("mod_info_tabs", saveTabState());
- s.directInterface().setValue("mod_info_conflicts", saveConflictsState());
-
- s.directInterface().setValue(
- "mod_info_conflicts_overwrite",
- ui->overwriteTree->header()->saveState());
-
- s.directInterface().setValue(
- "mod_info_conflicts_noconflict",
- ui->noConflictTree->header()->saveState());
- s.directInterface().setValue(
- "mod_info_conflicts_overwritten",
- ui->overwrittenTree->header()->saveState());
-
- s.directInterface().setValue(
- "mod_info_advanced_conflicts",
- ui->conflictsAdvancedList->header()->saveState());
+ for (const auto& tab : m_tabs) {
+ tab->saveState(s);
+ }
}
void ModInfoDialog::restoreState(const Settings& s)
{
restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray());
- restoreConflictsState(s.directInterface().value("mod_info_conflicts").toByteArray());
-
- ui->overwriteTree->header()->restoreState(
- s.directInterface().value("mod_info_conflicts_overwrite").toByteArray());
- ui->noConflictTree->header()->restoreState(
- s.directInterface().value("mod_info_conflicts_noconflict").toByteArray());
-
- ui->overwrittenTree->header()->restoreState(
- s.directInterface().value("mod_info_conflicts_overwritten").toByteArray());
-
- ui->conflictsAdvancedList->header()->restoreState(
- s.directInterface().value("mod_info_advanced_conflicts").toByteArray());
+ for (const auto& tab : m_tabs) {
+ tab->restoreState(s);
+ }
}
void ModInfoDialog::restoreTabState(const QByteArray &state)
@@ -556,37 +398,6 @@ void ModInfoDialog::restoreTabState(const QByteArray &state)
ui->tabWidget->blockSignals(false);
}
-void ModInfoDialog::restoreConflictsState(const QByteArray &state)
-{
- QDataStream stream(state);
-
- bool overwriteExpanded = false;
- bool overwrittenExpanded = false;
- bool noConflictExpanded = false;
-
- stream >> overwriteExpanded >> overwrittenExpanded >> noConflictExpanded;
-
- if (stream.status() == QDataStream::Ok) {
- m_overwriteExpander.toggle(overwriteExpanded);
- m_overwrittenExpander.toggle(overwrittenExpanded);
- m_nonconflictExpander.toggle(noConflictExpanded);
- }
-
- int index = 0;
- bool noConflictChecked = false;
- bool showAllChecked = false;
- bool showNearestChecked = false;
-
- stream >> index >> noConflictChecked >> showAllChecked >> showNearestChecked;
-
- if (stream.status() == QDataStream::Ok) {
- ui->tabConflictsTabs->setCurrentIndex(index);
- ui->conflictsAdvancedShowNoConflict->setChecked(noConflictChecked);
- ui->conflictsAdvancedShowAll->setChecked(showAllChecked);
- ui->conflictsAdvancedShowNearest->setChecked(showNearestChecked);
- }
-}
-
QByteArray ModInfoDialog::saveTabState() const
{
QByteArray result;
@@ -599,251 +410,12 @@ QByteArray ModInfoDialog::saveTabState() const
return result;
}
-QByteArray ModInfoDialog::saveConflictsState() const
-{
- QByteArray result;
- QDataStream stream(&result, QIODevice::WriteOnly);
-
- stream
- << m_overwriteExpander.opened()
- << m_overwrittenExpander.opened()
- << m_nonconflictExpander.opened()
- << ui->tabConflictsTabs->currentIndex()
- << ui->conflictsAdvancedShowNoConflict->isChecked()
- << ui->conflictsAdvancedShowAll->isChecked()
- << ui->conflictsAdvancedShowNearest->isChecked();
-
- return result;
-}
-
void ModInfoDialog::refreshLists()
{
refreshConflictLists(true, true);
refreshFiles();
}
-void ModInfoDialog::refreshConflictLists(
- bool refreshGeneral, bool refreshAdvanced)
-{
- int numNonConflicting = 0;
- int numOverwrite = 0;
- int numOverwritten = 0;
-
- if (refreshGeneral) {
- ui->overwriteTree->clear();
- ui->overwrittenTree->clear();
- ui->noConflictTree->clear();
- }
-
- if (refreshAdvanced) {
- ui->conflictsAdvancedList->clear();
- }
-
- if (m_Origin != nullptr) {
- std::vector<FileEntry::Ptr> files = m_Origin->getFiles();
-
- for (const auto& file : m_Origin->getFiles()) {
- const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath()));
- const QString fileName = relativeName.mid(0).prepend(m_RootPath);
-
- bool archive = false;
- const int fileOrigin = file->getOrigin(archive);
- const auto& alternatives = file->getAlternatives();
-
- if (refreshGeneral) {
- if (fileOrigin == m_Origin->getID()) {
- if (!alternatives.empty()) {
- ui->overwriteTree->addTopLevelItem(createOverwriteItem(
- file->getIndex(), archive, fileName, relativeName, alternatives));
-
- ++numOverwrite;
- } else {
- // otherwise, put the file in the noconflict tree
- ui->noConflictTree->addTopLevelItem(createNoConflictItem(
- file->getIndex(), archive, fileName, relativeName));
-
- ++numNonConflicting;
- }
- } else {
- ui->overwrittenTree->addTopLevelItem(createOverwrittenItem(
- file->getIndex(), fileOrigin, archive, fileName, relativeName));
-
- ++numOverwritten;
- }
- }
-
- if (refreshAdvanced) {
- auto* advancedItem = createAdvancedConflictItem(
- file->getIndex(), fileOrigin, archive,
- fileName, relativeName, alternatives);
-
- if (advancedItem) {
- ui->conflictsAdvancedList->addTopLevelItem(advancedItem);
- }
- }
- }
- }
-
- if (refreshGeneral) {
- ui->overwriteCount->display(numOverwrite);
- ui->overwrittenCount->display(numOverwritten);
- ui->noConflictCount->display(numNonConflicting);
- }
-}
-
-QTreeWidgetItem* ModInfoDialog::createOverwriteItem(
- FileEntry::Index index, bool archive,
- const QString& fileName, const QString& relativeName,
- const FileEntry::AlternativesVector& alternatives)
-{
- QString altString;
-
- for (const auto& alt : alternatives) {
- if (!altString.isEmpty()) {
- altString += ", ";
- }
-
- altString += ToQString(m_Directory->getOriginByID(alt.first).getName());
- }
-
- QStringList fields(relativeName);
- fields.append(altString);
-
- const auto origin = ToQString(m_Directory->getOriginByID(alternatives.back().first).getName());
-
- return new ConflictItem(fields, index, fileName, true, origin, archive);
-}
-
-QTreeWidgetItem* ModInfoDialog::createNoConflictItem(
- FileEntry::Index index, bool archive,
- const QString& fileName, const QString& relativeName)
-{
- return new ConflictItem({relativeName}, index, fileName, false, "", archive);
-}
-
-QTreeWidgetItem* ModInfoDialog::createOverwrittenItem(
- FileEntry::Index index, int fileOrigin, bool archive,
- const QString& fileName, const QString& relativeName)
-{
- const FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin);
-
- QStringList fields(relativeName);
- fields.append(ToQString(realOrigin.getName()));
-
- return new ConflictItem(
- fields, index, fileName, true, ToQString(realOrigin.getName()), archive);
-}
-
-QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem(
- FileEntry::Index index,int fileOrigin, bool archive,
- const QString& fileName, const QString& relativeName,
- const MOShared::FileEntry::AlternativesVector& alternatives)
-{
- QString before, after;
-
- if (!alternatives.empty()) {
- int beforePrio = 0;
- int afterPrio = std::numeric_limits<int>::max();
-
- for (const auto& alt : alternatives)
- {
- const auto altOrigin = m_Directory->getOriginByID(alt.first);
-
- if (ui->conflictsAdvancedShowAll->isChecked()) {
- // fills 'before' and 'after' with all the alternatives that come
- // before and after this mod in terms of priority
-
- if (altOrigin.getPriority() < m_Origin->getPriority()) {
- // add all the mods having a lower priority than this one
- if (!before.isEmpty()) {
- before += ", ";
- }
-
- before += ToQString(altOrigin.getName());
- } else if (altOrigin.getPriority() > m_Origin->getPriority()) {
- // add all the mods having a higher priority than this one
- if (!after.isEmpty()) {
- after += ", ";
- }
-
- after += ToQString(altOrigin.getName());
- }
- } else {
- // keep track of the nearest mods that come before and after this one
- // in terms of priority
-
- if (altOrigin.getPriority() < m_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 = ToQString(altOrigin.getName());
- beforePrio = altOrigin.getPriority();
- }
- }
-
- if (altOrigin.getPriority() > m_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 = ToQString(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.isEmpty() || ui->conflictsAdvancedShowAll->isChecked()) {
- FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin);
-
- // if no mods overwrite this file, the primary origin is the same as this
- // mod, so ignore that
- if (realOrigin.getID() != m_Origin->getID()) {
- if (!after.isEmpty()) {
- after += ", ";
- }
-
- after += ToQString(realOrigin.getName());
- }
- }
- }
-
- bool hasAlts = !before.isEmpty() || !after.isEmpty();
-
- if (!ui->conflictsAdvancedShowNoConflict->isChecked()) {
- // 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 (!hasAlts) {
- return nullptr;
- }
- }
-
- bool matched = m_advancedConflictFilter.matches([&](auto&& what) {
- return
- before.contains(what, Qt::CaseInsensitive) ||
- relativeName.contains(what, Qt::CaseInsensitive) ||
- after.contains(what, Qt::CaseInsensitive);
- });
-
- if (!matched) {
- return nullptr;
- }
-
- return new ConflictItem(
- {before, relativeName, after}, index, fileName, hasAlts, "", archive);
-}
-
void ModInfoDialog::refreshFiles()
{
// clearing
@@ -1475,7 +1047,6 @@ void ModInfoDialog::refreshPrimaryCategoriesBox()
}
}
-
void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index)
{
if (index != -1) {
@@ -1483,19 +1054,6 @@ void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index)
}
}
-
-void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int)
-{
- if (auto* ci=dynamic_cast<ConflictItem*>(item)) {
- const auto origin = ci->altOrigin();
-
- if (!origin.isEmpty()) {
- close();
- emit modOpen(origin, TAB_CONFLICTS);
- }
- }
-}
-
FileRenamer::RenameResults ModInfoDialog::hideFile(FileRenamer& renamer, const QString &oldName)
{
const QString newName = oldName + ModInfo::s_HiddenExt;
diff --git a/src/modinfodialog.h b/src/modinfodialog.h
index 6ecf16bf..53b5d94a 100644
--- a/src/modinfodialog.h
+++ b/src/modinfodialog.h
@@ -70,9 +70,31 @@ public:
virtual void clear() = 0;
virtual bool feedFile(const QString& rootPath, const QString& filename) = 0;
virtual bool canClose();
+ virtual void saveState(Settings& s);
+ virtual void restoreState(const Settings& s);
};
+class ElideLeftDelegate : public QStyledItemDelegate
+{
+public:
+ using QStyledItemDelegate::QStyledItemDelegate;
+
+protected:
+ void initStyleOption(QStyleOptionViewItem* o, const QModelIndex& i) const
+ {
+ QStyledItemDelegate::initStyleOption(o, i);
+ o->textElideMode = Qt::ElideLeft;
+ }
+};
+
+
+bool canPreviewFile(PluginContainer* pluginContainer, bool isArchive, const QString& filename);
+bool canOpenFile(bool isArchive, const QString& filename);
+bool canHideFile(bool isArchive, const QString& filename);
+bool canUnhideFile(bool isArchive, const QString& filename);
+
+
/**
* this is a larger dialog used to visualise information about the mod.
* @todo this would probably a good place for a plugin-system
@@ -262,36 +284,13 @@ private:
std::map<int, int> m_RealTabPos;
- ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander;
- FilterWidget m_advancedConflictFilter;
void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced);
void refreshFiles();
- QTreeWidgetItem* createOverwriteItem(
- FileEntry::Index index, bool archive,
- const QString& fileName, const QString& relativeName,
- const MOShared::FileEntry::AlternativesVector& alternatives);
-
- QTreeWidgetItem* createNoConflictItem(
- FileEntry::Index index, bool archive,
- const QString& fileName, const QString& relativeName);
-
- QTreeWidgetItem* createOverwrittenItem(
- FileEntry::Index index, int fileOrigin, bool archive,
- const QString& fileName, const QString& relativeName);
-
- QTreeWidgetItem* createAdvancedConflictItem(
- FileEntry::Index index, int fileOrigin, bool archive,
- const QString& fileName, const QString& relativeName,
- const MOShared::FileEntry::AlternativesVector& alternatives);
-
void restoreTabState(const QByteArray &state);
- void restoreConflictsState(const QByteArray &state);
-
QByteArray saveTabState() const;
- QByteArray saveConflictsState() const;
void changeFiletreeVisibility(bool visible);
diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui
index f1212634..a26d15ab 100644
--- a/src/modinfodialog.ui
+++ b/src/modinfodialog.ui
@@ -241,7 +241,7 @@
</widget>
</item>
<item>
- <widget class="QListWidget" name="inactiveESPList1">
+ <widget class="QListWidget" name="inactiveESPList">
<property name="toolTip">
<string>List of esps, esms, and esls that can not be loaded by the game.</string>
</property>
@@ -285,7 +285,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e
</spacer>
</item>
<item>
- <widget class="QToolButton" name="activateESP1">
+ <widget class="QToolButton" name="activateESP">
<property name="toolTip">
<string>Move a file to the data directory.</string>
</property>
@@ -308,7 +308,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e
</widget>
</item>
<item>
- <widget class="QToolButton" name="deactivateESP1">
+ <widget class="QToolButton" name="deactivateESP">
<property name="toolTip">
<string>Make the selected mod in the lower list unavailable.</string>
</property>
@@ -368,7 +368,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e
</widget>
</item>
<item>
- <widget class="QListWidget" name="activeESPList1">
+ <widget class="QListWidget" name="activeESPList">
<property name="toolTip">
<string>ESPs in the data directory and thus visible to the game.</string>
</property>
@@ -386,23 +386,23 @@ Most mods do not have optional esps, so chances are good you are looking at an e
</item>
</layout>
</widget>
- <widget class="QWidget" name="tabConflicts">
+ <widget class="QWidget" name="tabConflicts1">
<attribute name="title">
<string>Conflicts</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_8" stretch="0">
<item>
- <widget class="QTabWidget" name="tabConflictsTabs">
+ <widget class="QTabWidget" name="tabConflictsTabs1">
<property name="currentIndex">
- <number>0</number>
+ <number>1</number>
</property>
- <widget class="QWidget" name="tab">
+ <widget class="QWidget" name="tab1">
<attribute name="title">
<string>General</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
- <widget class="QWidget" name="widget" native="true">
+ <widget class="QWidget" name="widget1" native="true">
<property name="minimumSize">
<size>
<width>0</width>
@@ -423,11 +423,11 @@ Most mods do not have optional esps, so chances are good you are looking at an e
<number>0</number>
</property>
<item>
- <layout class="QVBoxLayout" name="overwriteRoot">
+ <layout class="QVBoxLayout" name="overwriteRoot1">
<item>
- <layout class="QHBoxLayout" name="overwriteHeader" stretch="1,0">
+ <layout class="QHBoxLayout" name="overwriteHeader1" stretch="1,0">
<item>
- <widget class="QToolButton" name="overwriteExpander">
+ <widget class="QToolButton" name="overwriteExpander1">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -444,7 +444,7 @@ text-align: left;</string>
</widget>
</item>
<item>
- <widget class="QLCDNumber" name="overwriteCount">
+ <widget class="QLCDNumber" name="overwriteCount1">
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
@@ -461,7 +461,7 @@ text-align: left;</string>
</layout>
</item>
<item>
- <widget class="QTreeWidget" name="overwriteTree">
+ <widget class="QTreeWidget" name="overwriteTree1">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
@@ -502,11 +502,11 @@ text-align: left;</string>
</widget>
</item>
<item>
- <layout class="QVBoxLayout" name="overwrittenRoot">
+ <layout class="QVBoxLayout" name="overwrittenRoot1">
<item>
- <layout class="QHBoxLayout" name="overwrittenHeader" stretch="1,0">
+ <layout class="QHBoxLayout" name="overwrittenHeader1" stretch="1,0">
<item>
- <widget class="QToolButton" name="overwrittenExpander">
+ <widget class="QToolButton" name="overwrittenExpander1">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -523,7 +523,7 @@ text-align: left;</string>
</widget>
</item>
<item>
- <widget class="QLCDNumber" name="overwrittenCount">
+ <widget class="QLCDNumber" name="overwrittenCount1">
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
@@ -537,7 +537,7 @@ text-align: left;</string>
</layout>
</item>
<item>
- <widget class="QTreeWidget" name="overwrittenTree">
+ <widget class="QTreeWidget" name="overwrittenTree1">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
@@ -578,11 +578,11 @@ text-align: left;</string>
</widget>
</item>
<item>
- <layout class="QVBoxLayout" name="noConflictRoot">
+ <layout class="QVBoxLayout" name="noConflictRoot1">
<item>
- <layout class="QHBoxLayout" name="noConflictHeader" stretch="1,0">
+ <layout class="QHBoxLayout" name="noConflictHeader1" stretch="1,0">
<item>
- <widget class="QToolButton" name="noConflictExpander">
+ <widget class="QToolButton" name="noConflictExpander1">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -599,7 +599,7 @@ text-align: left;</string>
</widget>
</item>
<item>
- <widget class="QLCDNumber" name="noConflictCount">
+ <widget class="QLCDNumber" name="noConflictCount1">
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
@@ -613,7 +613,7 @@ text-align: left;</string>
</layout>
</item>
<item>
- <widget class="QTreeWidget" name="noConflictTree">
+ <widget class="QTreeWidget" name="noConflictTree1">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
@@ -696,7 +696,7 @@ text-align: left;</string>
<number>0</number>
</property>
<item>
- <widget class="QTreeWidget" name="conflictsAdvancedList">
+ <widget class="QTreeWidget" name="conflictsAdvancedList1">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
@@ -751,7 +751,7 @@ text-align: left;</string>
<number>0</number>
</property>
<item>
- <widget class="QCheckBox" name="conflictsAdvancedShowNoConflict">
+ <widget class="QCheckBox" name="conflictsAdvancedShowNoConflict1">
<property name="toolTip">
<string>Whether files that have no conflicts should be visible in the list</string>
</property>
@@ -764,7 +764,7 @@ text-align: left;</string>
</widget>
</item>
<item>
- <widget class="QRadioButton" name="conflictsAdvancedShowAll">
+ <widget class="QRadioButton" name="conflictsAdvancedShowAll1">
<property name="toolTip">
<string>Shows all mods overwriting or being overwritten by this mod</string>
</property>
@@ -777,7 +777,7 @@ text-align: left;</string>
</widget>
</item>
<item>
- <widget class="QRadioButton" name="conflictsAdvancedShowNearest">
+ <widget class="QRadioButton" name="conflictsAdvancedShowNearest1">
<property name="toolTip">
<string>Shows only the nearest conflicting mods, in order of priority</string>
</property>
@@ -802,7 +802,7 @@ text-align: left;</string>
<number>0</number>
</property>
<item>
- <widget class="QLineEdit" name="conflictsAdvancedFilter">
+ <widget class="QLineEdit" name="conflictsAdvancedFilter1">
<property name="placeholderText">
<string>Filter</string>
</property>
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp
new file mode 100644
index 00000000..17313436
--- /dev/null
+++ b/src/modinfodialogconflicts.cpp
@@ -0,0 +1,530 @@
+#include "modinfodialogconflicts.h"
+#include "ui_modinfodialog.h"
+#include "utility.h"
+
+using namespace MOShared;
+using namespace MOBase;
+
+int naturalCompare(const QString& a, const QString& b)
+{
+ static QCollator c = []{
+ QCollator c;
+ c.setNumericMode(true);
+ c.setCaseSensitivity(Qt::CaseInsensitive);
+ return c;
+ }();
+
+ return c.compare(a, b);
+}
+
+
+class ConflictItem : public QTreeWidgetItem
+{
+public:
+ static const int FILENAME_USERROLE = Qt::UserRole + 1;
+ static const int ALT_ORIGIN_USERROLE = Qt::UserRole + 2;
+ static const int ARCHIVE_USERROLE = Qt::UserRole + 3;
+ static const int INDEX_USERROLE = Qt::UserRole + 4;
+ static const int HAS_ALTS_USERROLE = Qt::UserRole + 5;
+
+ ConflictItem(
+ QStringList columns, FileEntry::Index index, const QString& fileName,
+ bool hasAltOrigins, const QString& altOrigin, bool archive)
+ : QTreeWidgetItem(columns)
+ {
+ setData(0, FILENAME_USERROLE, fileName);
+ setData(0, ALT_ORIGIN_USERROLE, altOrigin);
+ setData(0, ARCHIVE_USERROLE, archive);
+ setData(0, INDEX_USERROLE, index);
+ setData(0, HAS_ALTS_USERROLE, hasAltOrigins);
+
+ if (archive) {
+ QFont f = font(0);
+ f.setItalic(true);
+
+ for (int i=0; i<columnCount(); ++i) {
+ setFont(i, f);
+ }
+ }
+ }
+
+ QString fileName() const
+ {
+ return data(0, FILENAME_USERROLE).toString();
+ }
+
+ QString altOrigin() const
+ {
+ return data(0, ALT_ORIGIN_USERROLE).toString();
+ }
+
+ bool hasAlts() const
+ {
+ return data(0, HAS_ALTS_USERROLE).toBool();
+ }
+
+ bool isArchive() const
+ {
+ return data(0, ARCHIVE_USERROLE).toBool();
+ }
+
+ FileEntry::Index fileIndex() const
+ {
+ static_assert(std::is_same_v<FileEntry::Index, unsigned int>);
+ return data(0, INDEX_USERROLE).toUInt();
+ }
+
+ 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 operator<(const QTreeWidgetItem& other) const
+ {
+ const int column = treeWidget()->sortColumn();
+
+ if (column >= columnCount() || column >= other.columnCount()) {
+ // shouldn't happen
+ qWarning().nospace() << "ConflictItem::operator<() mistmatch in column count";
+ return false;
+ }
+
+ return (naturalCompare(text(column), other.text(column)) < 0);
+ }
+};
+
+
+ConflictsTab::ConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc)
+ : ui(ui), m_general(ui, oc), m_advanced(ui, oc)
+{
+}
+
+void ConflictsTab::saveState(Settings& s)
+{
+ s.directInterface().setValue(
+ "mod_info_conflicts_tab", ui->tabConflictsTabs1->currentIndex());
+
+ m_general.saveState(s);
+ m_advanced.saveState(s);
+}
+
+void ConflictsTab::restoreState(const Settings& s)
+{
+ ui->tabConflictsTabs1->setCurrentIndex(
+ s.directInterface().value("mod_info_conflicts_tab", 0).toInt());
+
+ m_general.restoreState(s);
+ m_advanced.restoreState(s);
+}
+
+
+GeneralConflictsTab::GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc)
+ : ui(ui), m_core(oc), m_origin(nullptr)
+{
+ m_expanders.overwrite.set(ui->overwriteExpander1, ui->overwriteTree1, true);
+ m_expanders.overwritten.set(ui->overwrittenExpander1, ui->overwrittenTree1, true);
+ m_expanders.nonconflict.set(ui->noConflictExpander1, ui->noConflictTree1);
+
+ QObject::connect(
+ ui->overwriteTree1, &QTreeWidget::itemDoubleClicked,
+ [&](auto* item, int col){ onOverwriteActivated(item, col); });
+}
+
+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->overwriteTree1->header()->saveState());
+
+ s.directInterface().setValue(
+ "mod_info_conflicts_general_noconflict",
+ ui->noConflictTree1->header()->saveState());
+
+ s.directInterface().setValue(
+ "mod_info_conflicts_general_overwritten",
+ ui->overwrittenTree1->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->overwriteTree1->header()->restoreState(s.directInterface()
+ .value("mod_info_conflicts_general_overwrite").toByteArray());
+
+ ui->noConflictTree1->header()->restoreState(s.directInterface()
+ .value("mod_info_conflicts_general_noconflict").toByteArray());
+
+ ui->overwrittenTree1->header()->restoreState(s.directInterface()
+ .value("mod_info_conflicts_general_overwritten").toByteArray());
+}
+
+void GeneralConflictsTab::rebuild(ModInfo::Ptr mod, FilesOrigin* origin)
+{
+ m_mod = mod;
+ m_origin = origin;
+
+ update();
+}
+
+void GeneralConflictsTab::update()
+{
+ int numNonConflicting = 0;
+ int numOverwrite = 0;
+ int numOverwritten = 0;
+
+ ui->overwriteTree1->clear();
+ ui->overwrittenTree1->clear();
+ ui->noConflictTree1->clear();
+
+ if (m_origin != nullptr) {
+ const auto rootPath = m_mod->absolutePath();
+
+ for (const auto& file : m_origin->getFiles()) {
+ const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath()));
+ const QString fileName = relativeName.mid(0).prepend(rootPath);
+
+ bool archive = false;
+ const int fileOrigin = file->getOrigin(archive);
+ const auto& alternatives = file->getAlternatives();
+
+ if (fileOrigin == m_origin->getID()) {
+ if (!alternatives.empty()) {
+ ui->overwriteTree1->addTopLevelItem(createOverwriteItem(
+ file->getIndex(), archive, fileName, relativeName, alternatives));
+
+ ++numOverwrite;
+ } else {
+ // otherwise, put the file in the noconflict tree
+ ui->noConflictTree1->addTopLevelItem(createNoConflictItem(
+ file->getIndex(), archive, fileName, relativeName));
+
+ ++numNonConflicting;
+ }
+ } else {
+ ui->overwrittenTree1->addTopLevelItem(createOverwrittenItem(
+ file->getIndex(), fileOrigin, archive, fileName, relativeName));
+
+ ++numOverwritten;
+ }
+ }
+ }
+
+ ui->overwriteCount1->display(numOverwrite);
+ ui->overwrittenCount1->display(numOverwritten);
+ ui->noConflictCount1->display(numNonConflicting);
+}
+
+QTreeWidgetItem* GeneralConflictsTab::createOverwriteItem(
+ FileEntry::Index index, bool archive,
+ const QString& fileName, const QString& relativeName,
+ const FileEntry::AlternativesVector& alternatives)
+{
+ const auto& ds = *m_core.directoryStructure();
+
+ QString altString;
+
+ for (const auto& alt : alternatives) {
+ if (!altString.isEmpty()) {
+ altString += ", ";
+ }
+
+ altString += ToQString(ds.getOriginByID(alt.first).getName());
+ }
+
+ QStringList fields(relativeName);
+ fields.append(altString);
+
+ const auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName());
+
+ return new ConflictItem(fields, index, fileName, true, origin, archive);
+}
+
+QTreeWidgetItem* GeneralConflictsTab::createNoConflictItem(
+ FileEntry::Index index, bool archive,
+ const QString& fileName, const QString& relativeName)
+{
+ return new ConflictItem({relativeName}, index, fileName, false, "", archive);
+}
+
+QTreeWidgetItem* GeneralConflictsTab::createOverwrittenItem(
+ FileEntry::Index index, int fileOrigin, bool archive,
+ const QString& fileName, const QString& relativeName)
+{
+ const auto& ds = *m_core.directoryStructure();
+
+ const FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin);
+
+ QStringList fields(relativeName);
+ fields.append(ToQString(realOrigin.getName()));
+
+ return new ConflictItem(
+ fields, index, fileName, true, ToQString(realOrigin.getName()), archive);
+}
+
+void GeneralConflictsTab::onOverwriteActivated(QTreeWidgetItem *item, int)
+{
+ if (auto* ci=dynamic_cast<ConflictItem*>(item)) {
+ const auto origin = ci->altOrigin();
+
+ if (!origin.isEmpty()) {
+ close();
+ emit modOpen(origin, TAB_CONFLICTS);
+ }
+ }
+}
+
+
+AdvancedConflictsTab::AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc)
+ : ui(ui), m_core(oc), m_origin(nullptr)
+{
+ // left-elide the overwrites column so that the nearest are visible
+ ui->conflictsAdvancedList1->setItemDelegateForColumn(
+ 0, new ElideLeftDelegate(ui->conflictsAdvancedList1));
+
+ // left-elide the file column to see filenames
+ ui->conflictsAdvancedList1->setItemDelegateForColumn(
+ 1, new ElideLeftDelegate(ui->conflictsAdvancedList1));
+
+ // don't elide the overwritten by column so that the nearest are visible
+
+ QObject::connect(ui->conflictsAdvancedShowNoConflict1, &QCheckBox::clicked, [&] {
+ update();
+ });
+
+ QObject::connect(ui->conflictsAdvancedShowAll1, &QRadioButton::clicked, [&] {
+ update();
+ });
+
+ QObject::connect(ui->conflictsAdvancedShowNearest1, &QRadioButton::clicked, [&] {
+ update();
+ });
+
+ m_filter.set(ui->conflictsAdvancedFilter1);
+ m_filter.changed = [&]{ update(); };
+}
+
+void AdvancedConflictsTab::saveState(Settings& s)
+{
+ s.directInterface().setValue(
+ "mod_info_conflicts_advanced_list",
+ ui->conflictsAdvancedList1->header()->saveState());
+
+ QByteArray result;
+ QDataStream stream(&result, QIODevice::WriteOnly);
+
+ stream
+ << ui->conflictsAdvancedShowNoConflict1->isChecked()
+ << ui->conflictsAdvancedShowAll1->isChecked()
+ << ui->conflictsAdvancedShowNearest1->isChecked();
+
+ s.directInterface().setValue(
+ "mod_info_conflicts_advanced_options",
+ ui->conflictsAdvancedList1->header()->saveState());
+}
+
+void AdvancedConflictsTab::restoreState(const Settings& s)
+{
+ ui->conflictsAdvancedList1->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->conflictsAdvancedShowNoConflict1->setChecked(noConflictChecked);
+ ui->conflictsAdvancedShowAll1->setChecked(showAllChecked);
+ ui->conflictsAdvancedShowNearest1->setChecked(showNearestChecked);
+ }
+}
+
+void AdvancedConflictsTab::rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin)
+{
+ m_mod = mod;
+ m_origin = origin;
+
+ update();
+}
+
+void AdvancedConflictsTab::update()
+{
+ ui->conflictsAdvancedList1->clear();
+
+ if (m_origin != nullptr) {
+ const auto rootPath = m_mod->absolutePath();
+
+ for (const auto& file : m_origin->getFiles()) {
+ const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath()));
+ const QString fileName = relativeName.mid(0).prepend(rootPath);
+
+ bool archive = false;
+ const int fileOrigin = file->getOrigin(archive);
+ const auto& alternatives = file->getAlternatives();
+
+ auto* advancedItem = createItem(
+ file->getIndex(), fileOrigin, archive,
+ fileName, relativeName, alternatives);
+
+ if (advancedItem) {
+ ui->conflictsAdvancedList1->addTopLevelItem(advancedItem);
+ }
+ }
+ }
+}
+
+QTreeWidgetItem* AdvancedConflictsTab::createItem(
+ FileEntry::Index index, int fileOrigin, bool archive,
+ const QString& fileName, const QString& relativeName,
+ const MOShared::FileEntry::AlternativesVector& alternatives)
+{
+ const auto& ds = *m_core.directoryStructure();
+
+ QString before, after;
+
+ if (!alternatives.empty()) {
+ int beforePrio = 0;
+ int afterPrio = std::numeric_limits<int>::max();
+
+ for (const auto& alt : alternatives)
+ {
+ const auto altOrigin = ds.getOriginByID(alt.first);
+
+ if (ui->conflictsAdvancedShowAll1->isChecked()) {
+ // fills 'before' and 'after' with all the alternatives that come
+ // before and after this mod in terms of priority
+
+ if (altOrigin.getPriority() < m_origin->getPriority()) {
+ // add all the mods having a lower priority than this one
+ if (!before.isEmpty()) {
+ before += ", ";
+ }
+
+ before += ToQString(altOrigin.getName());
+ } else if (altOrigin.getPriority() > m_origin->getPriority()) {
+ // add all the mods having a higher priority than this one
+ if (!after.isEmpty()) {
+ after += ", ";
+ }
+
+ after += ToQString(altOrigin.getName());
+ }
+ } else {
+ // keep track of the nearest mods that come before and after this one
+ // in terms of priority
+
+ if (altOrigin.getPriority() < m_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 = ToQString(altOrigin.getName());
+ beforePrio = altOrigin.getPriority();
+ }
+ }
+
+ if (altOrigin.getPriority() > m_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 = ToQString(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.isEmpty() || ui->conflictsAdvancedShowAll1->isChecked()) {
+ 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_origin->getID()) {
+ if (!after.isEmpty()) {
+ after += ", ";
+ }
+
+ after += ToQString(realOrigin.getName());
+ }
+ }
+ }
+
+ bool hasAlts = !before.isEmpty() || !after.isEmpty();
+
+ if (!ui->conflictsAdvancedShowNoConflict1->isChecked()) {
+ // 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 (!hasAlts) {
+ return nullptr;
+ }
+ }
+
+ bool matched = m_filter.matches([&](auto&& what) {
+ return
+ before.contains(what, Qt::CaseInsensitive) ||
+ relativeName.contains(what, Qt::CaseInsensitive) ||
+ after.contains(what, Qt::CaseInsensitive);
+ });
+
+ if (!matched) {
+ return nullptr;
+ }
+
+ return new ConflictItem(
+ {before, relativeName, after}, index, fileName, hasAlts, "", archive);
+}
diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h
new file mode 100644
index 00000000..894f9dd4
--- /dev/null
+++ b/src/modinfodialogconflicts.h
@@ -0,0 +1,86 @@
+#ifndef MODINFODIALOGCONFLICTS_H
+#define MODINFODIALOGCONFLICTS_H
+
+#include "modinfodialog.h"
+#include "expanderwidget.h"
+
+class GeneralConflictsTab
+{
+public:
+ GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc);
+
+ void saveState(Settings& s);
+ void restoreState(const Settings& s);
+
+ void rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin);
+ void update();
+
+private:
+ struct Expanders
+ {
+ ExpanderWidget overwrite, overwritten, nonconflict;
+ };
+
+ Ui::ModInfoDialog* ui;
+ OrganizerCore& m_core;
+ ModInfo::Ptr m_mod;
+ MOShared::FilesOrigin* m_origin;
+ Expanders m_expanders;
+
+ QTreeWidgetItem* createOverwriteItem(
+ MOShared::FileEntry::Index index, bool archive,
+ const QString& fileName, const QString& relativeName,
+ const MOShared::FileEntry::AlternativesVector& alternatives);
+
+ QTreeWidgetItem* createNoConflictItem(
+ MOShared::FileEntry::Index index, bool archive,
+ const QString& fileName, const QString& relativeName);
+
+ QTreeWidgetItem* createOverwrittenItem(
+ MOShared::FileEntry::Index index, int fileOrigin, bool archive,
+ const QString& fileName, const QString& relativeName);
+
+ void onOverwriteActivated(QTreeWidgetItem* item, int);
+};
+
+
+class AdvancedConflictsTab
+{
+public:
+ AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc);
+
+ void saveState(Settings& s);
+ void restoreState(const Settings& s);
+
+ void rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin);
+ void update();
+
+private:
+ Ui::ModInfoDialog* ui;
+ OrganizerCore& m_core;
+ ModInfo::Ptr m_mod;
+ MOShared::FilesOrigin* m_origin;
+ FilterWidget m_filter;
+
+ QTreeWidgetItem* createItem(
+ MOShared::FileEntry::Index index, int fileOrigin, bool archive,
+ const QString& fileName, const QString& relativeName,
+ const MOShared::FileEntry::AlternativesVector& alternatives);
+};
+
+
+class ConflictsTab : public ModInfoDialogTab
+{
+public:
+ ConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc);
+
+ void saveState(Settings& s) override;
+ void restoreState(const Settings& s) override;
+
+private:
+ Ui::ModInfoDialog* ui;
+ GeneralConflictsTab m_general;
+ AdvancedConflictsTab m_advanced;
+};
+
+#endif // MODINFODIALOGCONFLICTS_H