summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAl <gabriel.cortesi@outlook.com>2019-06-01 09:54:09 +0200
committerGitHub <noreply@github.com>2019-06-01 09:54:09 +0200
commitf00314442b9c3c28e846c64da3c3f776e50656de (patch)
tree371f52175eaa83db0b3b354b95d382bd7080ca38 /src
parent41f8da011d1df7a368e3fb73f972cee1f4efd0c0 (diff)
parentd2eb890f2b9df13c402db6adeaafa7e3f4a7d736 (diff)
Merge pull request #740 from isanae/new-conflict-list
New conflict list, new "go to" conflict menu item
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/filterwidget.cpp144
-rw-r--r--src/filterwidget.h33
-rw-r--r--src/modinfodialog.cpp635
-rw-r--r--src/modinfodialog.h59
-rw-r--r--src/modinfodialog.ui640
-rw-r--r--src/organizercore.cpp2
-rw-r--r--src/shared/directoryentry.cpp5
-rw-r--r--src/shared/directoryentry.h7
9 files changed, 1173 insertions, 355 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index cae26334..93597d62 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -115,6 +115,7 @@ SET(organizer_SRCS
lcdnumber.cpp
forcedloaddialog.cpp
forcedloaddialogwidget.cpp
+ filterwidget.cpp
shared/windows_error.cpp
shared/error_report.cpp
@@ -211,6 +212,7 @@ SET(organizer_HDRS
lcdnumber.h
forcedloaddialog.h
forcedloaddialogwidget.h
+ filterwidget.h
shared/windows_error.h
shared/error_report.h
@@ -391,6 +393,7 @@ set(utilities
set(widgets
descriptionpage
genericicondelegate
+ filterwidget
icondelegate
lcdnumber
logbuffer
diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp
new file mode 100644
index 00000000..16a46b0e
--- /dev/null
+++ b/src/filterwidget.cpp
@@ -0,0 +1,144 @@
+#include "filterwidget.h"
+#include "eventfilter.h"
+
+FilterWidget::FilterWidget()
+ : m_edit(nullptr), m_eventFilter(nullptr), m_clear(nullptr)
+{
+}
+
+void FilterWidget::set(QLineEdit* edit)
+{
+ unhook();
+
+ m_edit = edit;
+
+ if (!m_edit) {
+ return;
+ }
+
+ createClear();
+ hookEvents();
+ clear();
+}
+
+void FilterWidget::clear()
+{
+ if (!m_edit) {
+ return;
+ }
+
+ m_edit->clear();
+}
+
+bool FilterWidget::matches(std::function<bool (const QString& what)> pred) const
+{
+ const QStringList ORList = [&] {
+ QString filterCopy = QString(m_text);
+ filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";");
+ return filterCopy.split(";", QString::SkipEmptyParts);
+ }();
+
+ if (ORList.isEmpty() || !pred) {
+ return true;
+ }
+
+ // split in ORSegments that internally use AND logic
+ for (auto& ORSegment : ORList) {
+ QStringList ANDKeywords = ORSegment.split(" ", QString::SkipEmptyParts);
+ bool segmentGood = true;
+
+ // check each word in the segment for match, each word needs to be matched
+ // but it doesn't matter where.
+ for (auto& currentKeyword : ANDKeywords) {
+ if (!pred(currentKeyword)) {
+ segmentGood = false;
+ }
+ }
+
+ if (segmentGood) {
+ // the last AND loop didn't break so the ORSegments is true so mod
+ // matches filter
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void FilterWidget::unhook()
+{
+ if (m_clear) {
+ delete m_clear;
+ m_clear = nullptr;
+ }
+
+ if (m_edit) {
+ m_edit->removeEventFilter(m_eventFilter);
+ }
+}
+
+void FilterWidget::createClear()
+{
+ m_clear = new QToolButton(m_edit);
+
+ QPixmap pixmap(":/MO/gui/edit_clear");
+ m_clear->setIcon(QIcon(pixmap));
+ m_clear->setIconSize(pixmap.size());
+ m_clear->setCursor(Qt::ArrowCursor);
+ m_clear->setStyleSheet("QToolButton { border: none; padding: 0px; }");
+ m_clear->hide();
+
+ QObject::connect(m_clear, &QToolButton::clicked, [&]{ clear(); });
+ QObject::connect(m_edit, &QLineEdit::textChanged, [&]{ onTextChanged(); });
+
+ repositionClearButton();
+}
+
+void FilterWidget::hookEvents()
+{
+ m_eventFilter = new EventFilter(m_edit, [&](auto* w, auto* e) {
+ if (e->type() == QEvent::Resize) {
+ onResized();
+ }
+
+ return false;
+ });
+
+ m_edit->installEventFilter(m_eventFilter);
+}
+
+void FilterWidget::onTextChanged()
+{
+ m_clear->setVisible(!m_edit->text().isEmpty());
+
+ const auto text = m_edit->text();
+
+ if (text != m_text) {
+ m_text = text;
+
+ if (changed) {
+ changed();
+ }
+ }
+}
+
+void FilterWidget::onResized()
+{
+ repositionClearButton();
+}
+
+void FilterWidget::repositionClearButton()
+{
+ if (!m_clear) {
+ return;
+ }
+
+ const QSize sz = m_clear->sizeHint();
+ const int frame = m_edit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
+ const auto r = m_edit->rect();
+
+ const auto x = r.right() - frame - sz.width();
+ const auto y = (r.bottom() + 1 - sz.height()) / 2;
+
+ m_clear->move(x, y);
+}
diff --git a/src/filterwidget.h b/src/filterwidget.h
new file mode 100644
index 00000000..4fee5983
--- /dev/null
+++ b/src/filterwidget.h
@@ -0,0 +1,33 @@
+#ifndef FILTERWIDGET_H
+#define FILTERWIDGET_H
+
+class EventFilter;
+
+class FilterWidget
+{
+public:
+ std::function<void ()> changed;
+
+ FilterWidget();
+
+ void set(QLineEdit* edit);
+ void clear();
+
+ bool matches(std::function<bool (const QString& what)> pred) const;
+
+private:
+ QLineEdit* m_edit;
+ EventFilter* m_eventFilter;
+ QToolButton* m_clear;
+ QString m_text;
+
+ void unhook();
+ void createClear();
+ void hookEvents();
+ void repositionClearButton();
+
+ void onTextChanged();
+ void onResized();
+};
+
+#endif // FILTERWIDGET_H
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp
index b6a21705..5d2dd811 100644
--- a/src/modinfodialog.cpp
+++ b/src/modinfodialog.cpp
@@ -57,6 +57,12 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
using namespace MOBase;
using namespace MOShared;
+const auto FILENAME_USERROLE = Qt::UserRole + 1;
+const auto ALT_ORIGIN_USERROLE = Qt::UserRole + 2;
+const auto ARCHIVE_USERROLE = Qt::UserRole + 3;
+const auto INDEX_USERROLE = Qt::UserRole + 4;
+const auto HAS_ALTS_USERROLE = Qt::UserRole + 5;
+
class ModFileListWidget : public QListWidgetItem {
friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS);
@@ -74,8 +80,8 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS
}
// if there are more than 50 selected items in the conflict tree or filetree,
-// don't bother checking whether they're visible, just show both menu items
-const int max_scan_for_visibility = 50;
+// don't bother checking whether menu items apply to them, just show all of them
+const int max_scan_for_context_menu = 50;
FileRenamer::FileRenamer(QWidget* parent, QFlags<RenameFlags> flags)
@@ -319,6 +325,20 @@ bool ExpanderWidget::opened() const
}
+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;
+ }
+};
+
+
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_ThumbnailMapper(this), m_RequestStarted(false),
@@ -379,7 +399,11 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo
}
}
- refreshLists();
+ // refresh everything but the conflict lists, which are done in exec() because
+ // they depend on restoring the state to some widgets; this refresh has to be
+ // done here because some of the checks below depend on the ui to decide which
+ // tabs to enable
+ refreshFiles();
if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR))
{
@@ -437,6 +461,32 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo
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);
+ });
}
@@ -457,6 +507,13 @@ ModInfoDialog::~ModInfoDialog()
}
+int ModInfoDialog::exec()
+{
+ // no need to refresh the other stuff, that was done in the constructor
+ refreshConflictLists(true, true);
+ return TutorableDialog::exec();
+}
+
void ModInfoDialog::initINITweaks()
{
int numTweaks = m_Settings->beginReadArray("INI Tweaks");
@@ -513,13 +570,41 @@ int ModInfoDialog::tabIndex(const QString &tabId)
void ModInfoDialog::saveState(Settings& s) const
{
s.directInterface().setValue("mod_info_tabs", saveTabState());
- s.directInterface().setValue("mod_info_conflict_expanders", saveConflictExpandersState());
+ 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());
}
void ModInfoDialog::restoreState(const Settings& s)
{
restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray());
- restoreConflictExpandersState(s.directInterface().value("mod_info_conflict_expanders").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());
}
void ModInfoDialog::restoreTabState(const QByteArray &state)
@@ -553,7 +638,7 @@ void ModInfoDialog::restoreTabState(const QByteArray &state)
ui->tabWidget->blockSignals(false);
}
-void ModInfoDialog::restoreConflictExpandersState(const QByteArray &state)
+void ModInfoDialog::restoreConflictsState(const QByteArray &state)
{
QDataStream stream(state);
@@ -568,6 +653,20 @@ void ModInfoDialog::restoreConflictExpandersState(const QByteArray &state)
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
@@ -582,7 +681,7 @@ QByteArray ModInfoDialog::saveTabState() const
return result;
}
-QByteArray ModInfoDialog::saveConflictExpandersState() const
+QByteArray ModInfoDialog::saveConflictsState() const
{
QByteArray result;
QDataStream stream(&result, QIODevice::WriteOnly);
@@ -590,85 +689,306 @@ QByteArray ModInfoDialog::saveConflictExpandersState() const
stream
<< m_overwriteExpander.opened()
<< m_overwrittenExpander.opened()
- << m_nonconflictExpander.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;
- ui->overwriteTree->clear();
- ui->overwrittenTree->clear();
- ui->noConflictTree->clear();
+ 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 (auto iter = files.begin(); iter != files.end(); ++iter) {
- QString relativeName = QDir::fromNativeSeparators(ToQString((*iter)->getRelativePath()));
- QString fileName = relativeName.mid(0).prepend(m_RootPath);
- bool archive;
- if ((*iter)->getOrigin(archive) == m_Origin->getID()) {
- std::vector<std::pair<int, std::pair<std::wstring, int>>> alternatives = (*iter)->getAlternatives();
- if (!alternatives.empty()) {
- std::wostringstream altString;
- for (std::vector<std::pair<int, std::pair<std::wstring, int>>>::iterator altIter = alternatives.begin();
- altIter != alternatives.end(); ++altIter) {
- if (altIter != alternatives.begin()) {
- altString << ", ";
- }
- altString << m_Directory->getOriginByID(altIter->first).getName();
+
+ 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;
}
- QStringList fields(relativeName);
- fields.append(ToQString(altString.str()));
+ } 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());
+
+ QTreeWidgetItem *item = new QTreeWidgetItem(fields);
+ setConflictItem(item, index, fileName, true, origin, archive);
+
+ return item;
+}
+
+QTreeWidgetItem* ModInfoDialog::createNoConflictItem(
+ FileEntry::Index index, bool archive,
+ const QString& fileName, const QString& relativeName)
+{
+ QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({relativeName}));
+ setConflictItem(item, index, fileName, false, "", archive);
+
+ return item;
+}
- QTreeWidgetItem *item = new QTreeWidgetItem(fields);
- item->setData(0, Qt::UserRole, fileName);
- item->setData(1, Qt::UserRole, ToQString(m_Directory->getOriginByID(alternatives.back().first).getName()));
- item->setData(1, Qt::UserRole + 1, alternatives.back().first);
- item->setData(1, Qt::UserRole + 2, archive);
- if (archive) {
- QFont font = item->font(0);
- font.setItalic(true);
- item->setFont(0, font);
- item->setFont(1, font);
+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()));
+
+ QTreeWidgetItem *item = new QTreeWidgetItem(fields);
+ setConflictItem(item, index, fileName, true, ToQString(realOrigin.getName()), archive);
+
+ return item;
+}
+
+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 += ", ";
}
- ui->overwriteTree->addTopLevelItem(item);
- ++numOverwrite;
- } else {// otherwise, put the file in the nonconflict tree
- QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({relativeName}));
- item->setData(0, Qt::UserRole, fileName);
- if (archive) {
- QFont font = item->font(0);
- font.setItalic(true);
- item->setFont(0, font);
+
+ 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 += ", ";
}
- ui->noConflictTree->addTopLevelItem(item);
- ++numNonConflicting;
+
+ after += ToQString(altOrigin.getName());
}
} else {
- FilesOrigin &realOrigin = m_Directory->getOriginByID((*iter)->getOrigin(archive));
- QStringList fields(relativeName);
- fields.append(ToQString(realOrigin.getName()));
- QTreeWidgetItem *item = new QTreeWidgetItem(fields);
- item->setData(0, Qt::UserRole, fileName);
- item->setData(1, Qt::UserRole, ToQString(realOrigin.getName()));
- item->setData(1, Qt::UserRole + 2, archive);
- if (archive) {
- QFont font = item->font(0);
- font.setItalic(true);
- item->setFont(0, font);
- item->setFont(1, font);
+ // 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 += ", ";
}
- ui->overwrittenTree->addTopLevelItem(item);
- ++numOverwritten;
+
+ 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;
+ }
+
+ QTreeWidgetItem* item = new QTreeWidgetItem;
+ item->setText(0, before);
+ item->setText(1, relativeName);
+ item->setText(2, after);
+
+ setConflictItem(item, index, fileName, hasAlts, "", archive);
+
+ return item;
+}
+
+void ModInfoDialog::setConflictItem(
+ QTreeWidgetItem* item, FileEntry::Index index,
+ const QString& fileName, bool hasAltOrigins, const QString& altOrigin,
+ bool archive) const
+{
+ item->setData(0, FILENAME_USERROLE, fileName);
+ item->setData(0, ALT_ORIGIN_USERROLE, altOrigin);
+ item->setData(0, ARCHIVE_USERROLE, archive);
+ item->setData(0, INDEX_USERROLE, index);
+ item->setData(0, HAS_ALTS_USERROLE, hasAltOrigins);
+
+ if (archive) {
+ QFont font = item->font(0);
+ font.setItalic(true);
+
+ for (int i=0; i<item->columnCount(); ++i) {
+ item->setFont(i, font);
+ }
+ }
+}
+
+QString ModInfoDialog::conflictFileName(const QTreeWidgetItem* conflictItem) const
+{
+ return conflictItem->data(0, FILENAME_USERROLE).toString();
+}
+
+QString ModInfoDialog::conflictAltOrigin(const QTreeWidgetItem* conflictItem) const
+{
+ return conflictItem->data(0, ALT_ORIGIN_USERROLE).toString();
+}
+
+bool ModInfoDialog::conflictHasAlts(const QTreeWidgetItem* conflictItem) const
+{
+ return conflictItem->data(0, HAS_ALTS_USERROLE).toBool();
+}
+
+bool ModInfoDialog::conflictIsArchive(const QTreeWidgetItem* conflictItem) const
+{
+ return conflictItem->data(0, ARCHIVE_USERROLE).toBool();
+}
+
+FileEntry::Index ModInfoDialog::conflictFileIndex(const QTreeWidgetItem* conflictItem) const
+{
+ static_assert(std::is_same_v<FileEntry::Index, unsigned int>);
+ return conflictItem->data(0, INDEX_USERROLE).toUInt();
+}
+
+void ModInfoDialog::refreshFiles()
+{
if (m_RootPath.length() > 0) {
QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories);
while (dirIterator.hasNext()) {
@@ -677,7 +997,7 @@ void ModInfoDialog::refreshLists()
if (fileName.endsWith(".txt", Qt::CaseInsensitive)) {
ui->textFileList->addItem(fileName.mid(m_RootPath.length() + 1));
} else if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) &&
- !fileName.endsWith("meta.ini")) {
+ !fileName.endsWith("meta.ini")) {
QString namePart = fileName.mid(m_RootPath.length() + 1);
if (namePart.startsWith("INI Tweaks", Qt::CaseInsensitive)) {
QListWidgetItem *newItem = new QListWidgetItem(namePart.mid(11), ui->iniTweaksList);
@@ -689,8 +1009,8 @@ void ModInfoDialog::refreshLists()
ui->iniFileList->addItem(namePart);
}
} else if (fileName.endsWith(".esp", Qt::CaseInsensitive) ||
- fileName.endsWith(".esm", Qt::CaseInsensitive) ||
- fileName.endsWith(".esl", Qt::CaseInsensitive)) {
+ fileName.endsWith(".esm", Qt::CaseInsensitive) ||
+ fileName.endsWith(".esl", Qt::CaseInsensitive)) {
QString relativePath = fileName.mid(m_RootPath.length() + 1);
if (relativePath.contains('/')) {
QFileInfo fileInfo(fileName);
@@ -701,7 +1021,7 @@ void ModInfoDialog::refreshLists()
ui->activeESPList->addItem(relativePath);
}
} else if ((fileName.endsWith(".png", Qt::CaseInsensitive)) ||
- (fileName.endsWith(".jpg", Qt::CaseInsensitive))) {
+ (fileName.endsWith(".jpg", Qt::CaseInsensitive))) {
QImage image = QImage(fileName);
if (!image.isNull()) {
if (static_cast<float>(image.width()) / static_cast<float>(image.height()) > 1.34) {
@@ -719,13 +1039,8 @@ void ModInfoDialog::refreshLists()
}
}
}
-
- ui->overwriteCount->display(numOverwrite);
- ui->overwrittenCount->display(numOverwritten);
- ui->noConflictCount->display(numNonConflicting);
}
-
void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set<int> &enabledCategories, QTreeWidgetItem *root, int rootLevel)
{
for (int i = 0; i < static_cast<int>(factory.numCategories()); ++i) {
@@ -1517,7 +1832,7 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos)
enablePreview = false;
enableRename = false;
- if (m_FileSelection.size() < max_scan_for_visibility) {
+ if (m_FileSelection.size() < max_scan_for_context_menu) {
// if the number of selected items is low, checking them to accurately
// show the menu items is worth it
enableHide = false;
@@ -1621,8 +1936,12 @@ void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index)
void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int)
{
- this->close();
- emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS);
+ const auto origin = conflictAltOrigin(item);
+
+ if (!origin.isEmpty()) {
+ close();
+ emit modOpen(origin, TAB_CONFLICTS);
+ }
}
FileRenamer::RenameResults ModInfoDialog::hideFile(FileRenamer& renamer, const QString &oldName)
@@ -1668,14 +1987,14 @@ void ModInfoDialog::changeConflictItemsVisibility(
qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping";
continue;
}
- result = unhideFile(renamer, item->data(0, Qt::UserRole).toString());
+ result = unhideFile(renamer, conflictFileName(item));
} else {
if (!canHideConflictItem(item)) {
qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping";
continue;
}
- result = hideFile(renamer, item->data(0, Qt::UserRole).toString());
+ result = hideFile(renamer, conflictFileName(item));
}
switch (result) {
@@ -1714,7 +2033,9 @@ void ModInfoDialog::openConflictItems(const QList<QTreeWidgetItem*>& items)
// the menu item is only shown for a single selection, but handle all of them
// in case this changes
for (auto* item : items) {
- openDataFile(item);
+ if (item) {
+ m_OrganizerCore->executeFileVirtualized(this, conflictFileName(item));
+ }
}
}
@@ -1723,28 +2044,10 @@ void ModInfoDialog::previewConflictItems(const QList<QTreeWidgetItem*>& items)
// the menu item is only shown for a single selection, but handle all of them
// in case this changes
for (auto* item : items) {
- previewDataFile(item);
- }
-}
-
-void ModInfoDialog::openDataFile(const QTreeWidgetItem* item)
-{
- if (!item) {
- return;
- }
-
- QFileInfo targetInfo(item->data(0, Qt::UserRole).toString());
- m_OrganizerCore->executeFileVirtualized(this, targetInfo);
-}
-
-void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item)
-{
- if (!item) {
- return;
+ if (item) {
+ m_OrganizerCore->previewFileWithAlternatives(this, conflictFileName(item));
+ }
}
-
- QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString());
- m_OrganizerCore->previewFileWithAlternatives(this, fileName);
}
bool ModInfoDialog::canPreviewFile(bool isArchive, const QString& filename) const
@@ -1757,6 +2060,12 @@ bool ModInfoDialog::canPreviewFile(bool isArchive, const QString& filename) cons
return m_PluginContainer->previewGenerator().previewSupported(ext);
}
+bool ModInfoDialog::canOpenFile(bool isArchive, const QString&) const
+{
+ // can open anything as long as it's not in an archive
+ return !isArchive;
+}
+
bool ModInfoDialog::canHideFile(bool isArchive, const QString& filename) const
{
if (isArchive) {
@@ -1789,19 +2098,22 @@ bool ModInfoDialog::canUnhideFile(bool isArchive, const QString& filename) const
bool ModInfoDialog::canHideConflictItem(const QTreeWidgetItem* item) const
{
- return canHideFile(item->data(1, Qt::UserRole + 2).toBool(), item->text(0));
+ return canHideFile(conflictIsArchive(item), conflictFileName(item));
}
bool ModInfoDialog::canUnhideConflictItem(const QTreeWidgetItem* item) const
{
- return canUnhideFile(item->data(1, Qt::UserRole + 2).toBool(), item->text(0));
+ return canUnhideFile(conflictIsArchive(item), conflictFileName(item));
+}
+
+bool ModInfoDialog::canOpenConflictItem(const QTreeWidgetItem* item) const
+{
+ return canOpenFile(conflictIsArchive(item), conflictFileName(item));
}
bool ModInfoDialog::canPreviewConflictItem(const QTreeWidgetItem* item) const
{
- return canPreviewFile(
- item->data(1, Qt::UserRole + 2).toBool(),
- item->data(0, Qt::UserRole).toString());
+ return canPreviewFile(conflictIsArchive(item), conflictFileName(item));
}
void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos)
@@ -1819,12 +2131,18 @@ void ModInfoDialog::on_noConflictTree_customContextMenuRequested(const QPoint &p
showConflictMenu(pos, ui->noConflictTree);
}
+void ModInfoDialog::on_conflictsAdvancedList_customContextMenuRequested(const QPoint &pos)
+{
+ showConflictMenu(pos, ui->conflictsAdvancedList);
+}
+
void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree)
{
auto actions = createConflictMenuActions(tree->selectedItems());
QMenu menu;
+ // open
if (actions.open) {
connect(actions.open, &QAction::triggered, [&]{
openConflictItems(tree->selectedItems());
@@ -1833,6 +2151,7 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree)
menu.addAction(actions.open);
}
+ // preview
if (actions.preview) {
connect(actions.preview, &QAction::triggered, [&]{
previewConflictItems(tree->selectedItems());
@@ -1841,6 +2160,7 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree)
menu.addAction(actions.preview);
}
+ // hide
if (actions.hide) {
connect(actions.hide, &QAction::triggered, [&]{
changeConflictItemsVisibility(tree->selectedItems(), false);
@@ -1849,6 +2169,7 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree)
menu.addAction(actions.hide);
}
+ // unhide
if (actions.unhide) {
connect(actions.unhide, &QAction::triggered, [&]{
changeConflictItemsVisibility(tree->selectedItems(), true);
@@ -1857,15 +2178,27 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree)
menu.addAction(actions.unhide);
}
- if (menu.isEmpty()) {
- return;
+ // goto
+ if (actions.gotoMenu) {
+ menu.addMenu(actions.gotoMenu);
+
+ for (auto* a : actions.gotoActions) {
+ connect(a, &QAction::triggered, [&, name=a->text()]{
+ close();
+ emit modOpen(name, TAB_CONFLICTS);
+ });
+
+ actions.gotoMenu->addAction(a);
+ }
}
- menu.exec(tree->viewport()->mapToGlobal(pos));
+ if (!menu.isEmpty()) {
+ menu.exec(tree->viewport()->mapToGlobal(pos));
+ }
}
ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions(
- const QList<QTreeWidgetItem*> selection)
+ const QList<QTreeWidgetItem*>& selection)
{
if (selection.empty()) {
return {};
@@ -1875,6 +2208,7 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions(
bool enableUnhide = true;
bool enableOpen = true;
bool enablePreview = true;
+ bool enableGoto = true;
if (selection.size() == 1) {
// this is a single selection
@@ -1885,8 +2219,9 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions(
enableHide = canHideConflictItem(item);
enableUnhide = canUnhideConflictItem(item);
+ enableOpen = canOpenConflictItem(item);
enablePreview = canPreviewConflictItem(item);
- // open is always enabled
+ enableGoto = conflictHasAlts(item);
}
else {
// this is a multiple selection, don't show open/preview so users don't open
@@ -1894,7 +2229,10 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions(
enableOpen = false;
enablePreview = false;
- if (selection.size() < max_scan_for_visibility) {
+ // don't bother with this on multiple selection, at least for now
+ enableGoto = false;
+
+ if (selection.size() < max_scan_for_context_menu) {
// if the number of selected items is low, checking them to accurately
// show the menu items is worth it
enableHide = false;
@@ -1909,8 +2247,8 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions(
enableUnhide = true;
}
- if (enableHide && enableUnhide) {
- // found both, no need to check more
+ if (enableHide && enableUnhide && enableGoto) {
+ // found all, no need to check more
break;
}
}
@@ -1919,22 +2257,71 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions(
ConflictActions actions;
- if (enableHide) {
- actions.hide = new QAction(tr("Hide"));
- }
+ actions.hide = new QAction(tr("Hide"), this);
+ actions.hide->setEnabled(enableHide);
// note that it is possible for hidden files to appear if they override other
// hidden files from another mod
- if (enableUnhide) {
- actions.unhide = new QAction(tr("Unhide"));
+ actions.unhide = new QAction(tr("Unhide"), this);
+ actions.unhide->setEnabled(enableUnhide);
+
+ actions.open = new QAction(tr("Open/Execute"), this);
+ actions.open->setEnabled(enableOpen);
+
+ actions.preview = new QAction(tr("Preview"), this);
+ actions.preview->setEnabled(enablePreview);
+
+ actions.gotoMenu = new QMenu(tr("Go to..."), this);
+ actions.gotoMenu->setEnabled(enableGoto);
+
+ if (enableGoto) {
+ actions.gotoActions = createGotoActions(selection);
+ }
+
+ return actions;
+}
+
+std::vector<QAction*> ModInfoDialog::createGotoActions(const QList<QTreeWidgetItem*>& selection)
+{
+ if (!m_Origin || selection.size() != 1) {
+ return {};
+ }
+
+ auto* item = selection[0];
+ if (!item) {
+ return {};
}
- if (enableOpen) {
- actions.open = new QAction(tr("Open/Execute"));
+ auto file = m_Origin->findFile(conflictFileIndex(item));
+ if (!file) {
+ return {};
+ }
+
+
+ std::vector<QString> mods;
+
+ // add all alternatives
+ for (const auto& alt : file->getAlternatives()) {
+ const auto& o = m_Directory->getOriginByID(alt.first);
+ if (o.getID() != m_Origin->getID()) {
+ mods.push_back(ToQString(o.getName()));
+ }
}
- if (enablePreview) {
- actions.preview = new QAction(tr("Preview"));
+ // add the real origin if different from this mod
+ const FilesOrigin& realOrigin = m_Directory->getOriginByID(file->getOrigin());
+ if (realOrigin.getID() != m_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, this));
}
return actions;
@@ -1942,8 +2329,12 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions(
void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int)
{
- emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS);
- this->accept();
+ const auto origin = conflictAltOrigin(item);
+
+ if (!origin.isEmpty()) {
+ close();
+ emit modOpen(origin, TAB_CONFLICTS);
+ }
}
void ModInfoDialog::on_refreshButton_clicked()
diff --git a/src/modinfodialog.h b/src/modinfodialog.h
index 5f7d831b..8510c96d 100644
--- a/src/modinfodialog.h
+++ b/src/modinfodialog.h
@@ -25,6 +25,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "tutorabledialog.h"
#include "plugincontainer.h"
#include "organizercore.h"
+#include "filterwidget.h"
#include <QDialog>
#include <QSignalMapper>
@@ -279,6 +280,8 @@ public:
**/
void openTab(int tab);
+ int exec() override;
+
void saveState(Settings& s) const;
void restoreState(const Settings& s);
@@ -367,6 +370,7 @@ private slots:
void on_overwriteTree_customContextMenuRequested(const QPoint &pos);
void on_overwrittenTree_customContextMenuRequested(const QPoint &pos);
void on_noConflictTree_customContextMenuRequested(const QPoint &pos);
+ void on_conflictsAdvancedList_customContextMenuRequested(const QPoint &pos);
void on_fileTree_customContextMenuRequested(const QPoint &pos);
void on_refreshButton_clicked();
@@ -381,15 +385,20 @@ private slots:
void createTweak();
private:
+ using FileEntry = MOShared::FileEntry;
+
struct ConflictActions
{
QAction* hide;
QAction* unhide;
QAction* open;
QAction* preview;
+ QMenu* gotoMenu;
+ std::vector<QAction*> gotoActions;
- ConflictActions()
- : hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr)
+ ConflictActions() :
+ hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr),
+ gotoMenu(nullptr)
{
}
};
@@ -427,20 +436,52 @@ 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 setConflictItem(
+ QTreeWidgetItem* item, FileEntry::Index index,
+ const QString& fileName, bool hasAltOrigins, const QString& altOrigin,
+ bool archive) const;
+
+ QString conflictFileName(const QTreeWidgetItem* conflictItem) const;
+ QString conflictAltOrigin(const QTreeWidgetItem* conflictItem) const;
+ bool conflictHasAlts(const QTreeWidgetItem* conflictItem) const;
+ bool conflictIsArchive(const QTreeWidgetItem* conflictItem) const;
+ FileEntry::Index conflictFileIndex(const QTreeWidgetItem* conflictItem) const;
void restoreTabState(const QByteArray &state);
- void restoreConflictExpandersState(const QByteArray &state);
+ void restoreConflictsState(const QByteArray &state);
QByteArray saveTabState() const;
- QByteArray saveConflictExpandersState() const;
+ QByteArray saveConflictsState() const;
bool canHideConflictItem(const QTreeWidgetItem* item) const;
bool canUnhideConflictItem(const QTreeWidgetItem* item) const;
+ bool canOpenConflictItem(const QTreeWidgetItem* item) const;
bool canPreviewConflictItem(const QTreeWidgetItem* item) const;
- void openDataFile(const QTreeWidgetItem* item);
- void previewDataFile(const QTreeWidgetItem* item);
void changeFiletreeVisibility(bool visible);
void openConflictItems(const QList<QTreeWidgetItem*>& items);
@@ -449,13 +490,17 @@ private:
const QList<QTreeWidgetItem*>& items, bool visible);
bool canPreviewFile(bool isArchive, const QString& filename) const;
+ bool canOpenFile(bool isArchive, const QString& filename) const;
bool canHideFile(bool isArchive, const QString& filename) const;
bool canUnhideFile(bool isArchive, const QString& filename) const;
void showConflictMenu(const QPoint &pos, QTreeWidget* tree);
ConflictActions createConflictMenuActions(
- const QList<QTreeWidgetItem*> selection);
+ const QList<QTreeWidgetItem*>& selection);
+
+ std::vector<QAction*> createGotoActions(
+ const QList<QTreeWidgetItem*>& selection);
};
#endif // MODINFODIALOG_H
diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui
index 840c81ee..e009a599 100644
--- a/src/modinfodialog.ui
+++ b/src/modinfodialog.ui
@@ -400,243 +400,437 @@ Most mods do not have optional esps, so chances are good you are looking at an e
<attribute name="title">
<string>Conflicts</string>
</attribute>
- <layout class="QVBoxLayout" name="verticalLayout_8" stretch="0,1,0,1,0,1,0">
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <layout class="QVBoxLayout" name="overwriteRoot">
- <item>
- <layout class="QHBoxLayout" name="overwriteHeader" stretch="1,0">
- <item>
- <widget class="QToolButton" name="overwriteExpander">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="styleSheet">
- <string notr="true">border: none;
-text-align: left;</string>
- </property>
- <property name="text">
- <string>The following conflicted files are provided by this mod</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLCDNumber" name="overwriteCount">
- <property name="frameShadow">
- <enum>QFrame::Sunken</enum>
- </property>
- <property name="lineWidth">
- <number>1</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Flat</enum>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </item>
+ <layout class="QVBoxLayout" name="verticalLayout_8" stretch="0">
<item>
- <widget class="QTreeWidget" name="overwriteTree">
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
- </property>
- <property name="textElideMode">
- <enum>Qt::ElideLeft</enum>
- </property>
- <property name="uniformRowHeights">
- <bool>true</bool>
- </property>
- <property name="sortingEnabled">
- <bool>true</bool>
- </property>
- <property name="animated">
- <bool>true</bool>
- </property>
- <property name="columnCount">
- <number>2</number>
+ <widget class="QTabWidget" name="tabConflictsTabs">
+ <property name="currentIndex">
+ <number>0</number>
</property>
- <attribute name="headerDefaultSectionSize">
- <number>365</number>
- </attribute>
- <attribute name="headerMinimumSectionSize">
- <number>200</number>
- </attribute>
- <column>
- <property name="text">
- <string>File</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Overwritten Mods</string>
- </property>
- </column>
- </widget>
- </item>
- <item>
- <layout class="QVBoxLayout" name="overwrittenRoot">
- <item>
- <layout class="QHBoxLayout" name="overwrittenHeader" stretch="1,0">
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>General</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
<item>
- <widget class="QToolButton" name="overwrittenExpander">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <widget class="QWidget" name="widget" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>100</height>
+ </size>
</property>
- <property name="styleSheet">
- <string notr="true">border: none;
+ <layout class="QVBoxLayout" name="verticalLayout_11" stretch="0,1,0,1,0,1,0">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" name="overwriteRoot">
+ <item>
+ <layout class="QHBoxLayout" name="overwriteHeader" stretch="1,0">
+ <item>
+ <widget class="QToolButton" name="overwriteExpander">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">border: none;
text-align: left;</string>
- </property>
- <property name="text">
- <string>The following conflicted files are provided by other mods</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLCDNumber" name="overwrittenCount">
- <property name="frameShadow">
- <enum>QFrame::Sunken</enum>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Flat</enum>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QTreeWidget" name="overwrittenTree">
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
- </property>
- <property name="textElideMode">
- <enum>Qt::ElideLeft</enum>
- </property>
- <property name="uniformRowHeights">
- <bool>true</bool>
- </property>
- <property name="sortingEnabled">
- <bool>true</bool>
- </property>
- <property name="animated">
- <bool>true</bool>
- </property>
- <property name="columnCount">
- <number>2</number>
- </property>
- <attribute name="headerDefaultSectionSize">
- <number>365</number>
- </attribute>
- <attribute name="headerMinimumSectionSize">
- <number>200</number>
- </attribute>
- <column>
- <property name="text">
- <string>File</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Providing Mod</string>
- </property>
- </column>
- </widget>
- </item>
- <item>
- <layout class="QVBoxLayout" name="noConflictRoot">
- <item>
- <layout class="QHBoxLayout" name="noConflictHeader" stretch="1,0">
- <item>
- <widget class="QToolButton" name="noConflictExpander">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="styleSheet">
- <string notr="true">border: none;
+ </property>
+ <property name="text">
+ <string>The following conflicted files are provided by this mod</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLCDNumber" name="overwriteCount">
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="lineWidth">
+ <number>1</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="overwriteTree">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="textElideMode">
+ <enum>Qt::ElideLeft</enum>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="animated">
+ <bool>true</bool>
+ </property>
+ <property name="columnCount">
+ <number>2</number>
+ </property>
+ <attribute name="headerDefaultSectionSize">
+ <number>365</number>
+ </attribute>
+ <attribute name="headerMinimumSectionSize">
+ <number>200</number>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>File</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Overwritten Mods</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="overwrittenRoot">
+ <item>
+ <layout class="QHBoxLayout" name="overwrittenHeader" stretch="1,0">
+ <item>
+ <widget class="QToolButton" name="overwrittenExpander">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">border: none;
text-align: left;</string>
- </property>
- <property name="text">
- <string>The following files have no conflicts</string>
- </property>
+ </property>
+ <property name="text">
+ <string>The following conflicted files are provided by other mods</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLCDNumber" name="overwrittenCount">
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="overwrittenTree">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="textElideMode">
+ <enum>Qt::ElideLeft</enum>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="animated">
+ <bool>true</bool>
+ </property>
+ <property name="columnCount">
+ <number>2</number>
+ </property>
+ <attribute name="headerDefaultSectionSize">
+ <number>365</number>
+ </attribute>
+ <attribute name="headerMinimumSectionSize">
+ <number>200</number>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>File</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Providing Mod</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="noConflictRoot">
+ <item>
+ <layout class="QHBoxLayout" name="noConflictHeader" stretch="1,0">
+ <item>
+ <widget class="QToolButton" name="noConflictExpander">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">border: none;
+text-align: left;</string>
+ </property>
+ <property name="text">
+ <string>The following files have no conflicts</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLCDNumber" name="noConflictCount">
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="noConflictTree">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="textElideMode">
+ <enum>Qt::ElideLeft</enum>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="animated">
+ <bool>true</bool>
+ </property>
+ <property name="columnCount">
+ <number>1</number>
+ </property>
+ <column>
+ <property name="text">
+ <string>File</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
</widget>
</item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Advanced</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_13">
<item>
- <widget class="QLCDNumber" name="noConflictCount">
- <property name="frameShadow">
- <enum>QFrame::Sunken</enum>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Flat</enum>
- </property>
+ <widget class="QWidget" name="widget_2" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_14">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="widget_3" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTreeWidget" name="conflictsAdvancedList">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="columnCount">
+ <number>3</number>
+ </property>
+ <attribute name="headerDefaultSectionSize">
+ <number>243</number>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Overwrites</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>File</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Overwritten by</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget_4" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="conflictsAdvancedShowNoConflict">
+ <property name="toolTip">
+ <string>Whether files that have no conflicts should be visible in the list</string>
+ </property>
+ <property name="text">
+ <string>Show files that have no conflicts</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="conflictsAdvancedShowAll">
+ <property name="toolTip">
+ <string>Shows all mods overwriting or being overwritten by this mod</string>
+ </property>
+ <property name="text">
+ <string>Show all conflicting mods</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="conflictsAdvancedShowNearest">
+ <property name="toolTip">
+ <string>Shows only the nearest conflicting mods, in order of priority</string>
+ </property>
+ <property name="text">
+ <string>Show nearest conflicting mod</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget_5" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <property name="leftMargin">
+ <number>20</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="conflictsAdvancedFilter">
+ <property name="placeholderText">
+ <string>Filter</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
</layout>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QTreeWidget" name="noConflictTree">
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
- </property>
- <property name="textElideMode">
- <enum>Qt::ElideLeft</enum>
- </property>
- <property name="uniformRowHeights">
- <bool>true</bool>
- </property>
- <property name="sortingEnabled">
- <bool>true</bool>
- </property>
- <property name="animated">
- <bool>true</bool>
- </property>
- <property name="columnCount">
- <number>1</number>
- </property>
- <column>
- <property name="text">
- <string>File</string>
- </property>
- </column>
+ </widget>
</widget>
</item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>0</height>
- </size>
- </property>
- </spacer>
- </item>
</layout>
</widget>
<widget class="QWidget" name="tabCategories">
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 3ad4e586..892162f6 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -1321,6 +1321,8 @@ bool OrganizerCore::executeFileVirtualized(
bool OrganizerCore::previewFileWithAlternatives(
QWidget* parent, QString fileName, int selectedOrigin)
{
+ fileName = QDir::fromNativeSeparators(fileName);
+
// what we have is an absolute path to the file in its actual location (for the primary origin)
// what we want is the path relative to the virtual data directory
diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp
index 1179110a..bde515a9 100644
--- a/src/shared/directoryentry.cpp
+++ b/src/shared/directoryentry.cpp
@@ -220,6 +220,11 @@ std::vector<FileEntry::Ptr> FilesOrigin::getFiles() const
return result;
}
+FileEntry::Ptr FilesOrigin::findFile(FileEntry::Index index) const
+{
+ return m_FileRegister.lock()->getFile(index);
+}
+
bool FilesOrigin::containsArchive(std::wstring archiveName)
{
for (FileEntry::Index fileIdx : m_Files)
diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h
index 12cef11d..785c3ff6 100644
--- a/src/shared/directoryentry.h
+++ b/src/shared/directoryentry.h
@@ -49,8 +49,8 @@ class FileEntry {
public:
typedef unsigned int Index;
-
typedef boost::shared_ptr<FileEntry> Ptr;
+ typedef std::vector<std::pair<int, std::pair<std::wstring, int>>> AlternativesVector;
public:
@@ -72,7 +72,7 @@ public:
// gets the list of alternative origins (origins with lower priority than the primary one).
// if sortOrigins has been called, it is sorted by priority (ascending)
- const std::vector<std::pair<int, std::pair<std::wstring, int>>> &getAlternatives() const { return m_Alternatives; }
+ const AlternativesVector &getAlternatives() const { return m_Alternatives; }
const std::wstring &getName() const { return m_Name; }
int getOrigin() const { return m_Origin; }
@@ -98,7 +98,7 @@ private:
std::wstring m_Name;
int m_Origin = -1;
std::pair<std::wstring, int> m_Archive;
- std::vector<std::pair<int, std::pair<std::wstring, int>>> m_Alternatives;
+ AlternativesVector m_Alternatives;
DirectoryEntry *m_Parent;
mutable FILETIME m_FileTime;
@@ -135,6 +135,7 @@ public:
const std::wstring &getPath() const { return m_Path; }
std::vector<FileEntry::Ptr> getFiles() const;
+ FileEntry::Ptr findFile(FileEntry::Index index) const;
void enable(bool enabled, time_t notAfter = LONG_MAX);
bool isDisabled() const { return m_Disabled; }