diff options
| author | Al <gabriel.cortesi@outlook.com> | 2019-06-01 09:54:09 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-06-01 09:54:09 +0200 |
| commit | f00314442b9c3c28e846c64da3c3f776e50656de (patch) | |
| tree | 371f52175eaa83db0b3b354b95d382bd7080ca38 /src | |
| parent | 41f8da011d1df7a368e3fb73f972cee1f4efd0c0 (diff) | |
| parent | d2eb890f2b9df13c402db6adeaafa7e3f4a7d736 (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.txt | 3 | ||||
| -rw-r--r-- | src/filterwidget.cpp | 144 | ||||
| -rw-r--r-- | src/filterwidget.h | 33 | ||||
| -rw-r--r-- | src/modinfodialog.cpp | 635 | ||||
| -rw-r--r-- | src/modinfodialog.h | 59 | ||||
| -rw-r--r-- | src/modinfodialog.ui | 640 | ||||
| -rw-r--r-- | src/organizercore.cpp | 2 | ||||
| -rw-r--r-- | src/shared/directoryentry.cpp | 5 | ||||
| -rw-r--r-- | src/shared/directoryentry.h | 7 |
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; }
|
