From 81344771dd03fd844daeedcbf3545103db3898c0 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Wed, 13 Dec 2017 09:49:09 +0200 Subject: Locked dialog fixes (fix mainwindow modalness; use normal window to fix parentless scenario) --- src/mainwindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 632b64e9..1e1a4558 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1471,7 +1471,8 @@ ILockedWaitingForProcess* MainWindow::lock() ++m_LockCount; return m_LockDialog; } - m_LockDialog = new LockedDialog(qApp->activeWindow()); + m_LockDialog = new LockedDialog(this, true); + m_LockDialog->setModal(true); m_LockDialog->show(); setEnabled(false); m_LockDialog->setEnabled(true); //What's the point otherwise? -- cgit v1.3.1 From 240cbd6a27addf46943cda1d016ec9c14089fb27 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Wed, 13 Dec 2017 16:18:54 +0200 Subject: Wait for injected processes on MO close --- src/CMakeLists.txt | 5 ++ src/ilockedwaitingforprocess.h | 2 +- src/lockeddialog.cpp | 37 ++----------- src/lockeddialog.h | 33 ++---------- src/lockeddialogbase.cpp | 73 +++++++++++++++++++++++++ src/lockeddialogbase.h | 62 +++++++++++++++++++++ src/mainwindow.cpp | 20 ++++++- src/mainwindow.h | 6 ++- src/organizercore.cpp | 45 +++++++++------- src/organizercore.h | 1 + src/waitingonclosedialog.cpp | 71 ++++++++++++++++++++++++ src/waitingonclosedialog.h | 56 +++++++++++++++++++ src/waitingonclosedialog.ui | 119 +++++++++++++++++++++++++++++++++++++++++ 13 files changed, 443 insertions(+), 87 deletions(-) create mode 100644 src/lockeddialogbase.cpp create mode 100644 src/lockeddialogbase.h create mode 100644 src/waitingonclosedialog.cpp create mode 100644 src/waitingonclosedialog.h create mode 100644 src/waitingonclosedialog.ui (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 39aabb91..914bdd12 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,7 +42,9 @@ SET(organizer_SRCS main.cpp loghighlighter.cpp logbuffer.cpp + lockeddialogbase.cpp lockeddialog.cpp + waitingonclosedialog.cpp loadmechanism.cpp installationmanager.cpp helper.cpp @@ -128,7 +130,9 @@ SET(organizer_HDRS mainwindow.h loghighlighter.h logbuffer.h + lockeddialogbase.h lockeddialog.h + waitingonclosedialog.h loadmechanism.h installationmanager.h helper.h @@ -200,6 +204,7 @@ SET(organizer_UIS messagedialog.ui mainwindow.ui lockeddialog.ui + waitingonclosedialog.ui installdialog.ui finddialog.ui editexecutablesdialog.ui diff --git a/src/ilockedwaitingforprocess.h b/src/ilockedwaitingforprocess.h index 5bf1f1ca..9475ddb9 100644 --- a/src/ilockedwaitingforprocess.h +++ b/src/ilockedwaitingforprocess.h @@ -6,7 +6,7 @@ class QString; class ILockedWaitingForProcess { public: - virtual bool unlockForced() = 0; + virtual bool unlockForced() const = 0; virtual void setProcessName(QString const &) = 0; }; diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp index 09538a0f..143d5838 100644 --- a/src/lockeddialog.cpp +++ b/src/lockeddialog.cpp @@ -26,10 +26,8 @@ along with Mod Organizer. If not, see . #include // for Qt::FramelessWindowHint, etc LockedDialog::LockedDialog(QWidget *parent, bool unlockByButton) - : QDialog(parent) + : LockedDialogBase(parent, !unlockByButton) , ui(new Ui::LockedDialog) - , m_Unlocked(false) - , m_allowClose(!unlockByButton) { ui->setupUi(this); @@ -38,7 +36,7 @@ LockedDialog::LockedDialog(QWidget *parent, bool unlockByButton) // seem to work. We will ignore pressing the close button if unlockByButton == true Qt::WindowFlags flags = this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; - if (!unlockByButton) + if (m_allowClose) flags |= Qt::WindowCloseButtonHint; this->setWindowFlags(flags); @@ -48,13 +46,6 @@ LockedDialog::LockedDialog(QWidget *parent, bool unlockByButton) ui->verticalLayout->addItem( new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding)); } - - if (parent != nullptr) { - QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); - position.rx() -= this->width() / 2; - position.ry() -= this->height() / 2; - move(position); - } } LockedDialog::~LockedDialog() @@ -68,35 +59,13 @@ void LockedDialog::setProcessName(const QString &name) ui->processLabel->setText(name); } - -void LockedDialog::resizeEvent(QResizeEvent *event) -{ - QWidget *par = parentWidget(); - if (par != nullptr) { - QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); - position.rx() -= event->size().width() / 2; - position.ry() -= event->size().height() / 2; - move(position); - } -} - void LockedDialog::on_unlockButton_clicked() { unlock(); } -void LockedDialog::reject() -{ - if (m_allowClose) - unlock(); -} - -bool LockedDialog::unlockForced() { - return m_Unlocked; -} - void LockedDialog::unlock() { - m_Unlocked = true; + LockedDialogBase::unlock(); ui->label->setText("unlocking may take a few seconds"); ui->unlockButton->setEnabled(false); } diff --git a/src/lockeddialog.h b/src/lockeddialog.h index 82a15a93..36c16429 100644 --- a/src/lockeddialog.h +++ b/src/lockeddialog.h @@ -17,16 +17,9 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#ifndef LOCKEDDIALOG_H -#define LOCKEDDIALOG_H +#pragma once -#include "ilockedwaitingforprocess.h" -#include // for QDialog -#include // for Q_OBJECT, slots -#include // for QString - -class QResizeEvent; -class QWidget; +#include "lockeddialogbase.h" namespace Ui { class LockedDialog; @@ -40,7 +33,7 @@ namespace Ui { * data on which Mod Organizer works. After the UI is unlocked (manually or after the * external application closed) MO will refresh all of its data sources **/ -class LockedDialog : public QDialog, public ILockedWaitingForProcess +class LockedDialog : public LockedDialogBase { Q_OBJECT @@ -48,35 +41,17 @@ public: explicit LockedDialog(QWidget *parent = 0, bool unlockByButton = false); ~LockedDialog(); - /** - * @brief see if the user clicked the unlock-button - * - * @return true if the user clicked the unlock button - **/ - bool unlockForced() override; - - /** - * @brief set the name of the process being run - * @param name of process - */ void setProcessName(const QString &name) override; protected: - virtual void resizeEvent(QResizeEvent *event); - - virtual void reject(); + void unlock() override; private slots: void on_unlockButton_clicked(); private: - void unlock(); Ui::LockedDialog *ui; - bool m_Unlocked; - bool m_allowClose; }; - -#endif // LOCKEDDIALOG_H diff --git a/src/lockeddialogbase.cpp b/src/lockeddialogbase.cpp new file mode 100644 index 00000000..b18f7429 --- /dev/null +++ b/src/lockeddialogbase.cpp @@ -0,0 +1,73 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "lockeddialogbase.h" + +#include +#include +#include +#include // for Qt::FramelessWindowHint, etc + +LockedDialogBase::LockedDialogBase(QWidget *parent, bool allowClose) + : QDialog(parent) + , m_Unlocked(false) + , m_Canceled(false) + , m_allowClose(allowClose) +{ + if (parent != nullptr) { + QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); + position.rx() -= this->width() / 2; + position.ry() -= this->height() / 2; + move(position); + } +} + +void LockedDialogBase::resizeEvent(QResizeEvent *event) +{ + QWidget *par = parentWidget(); + if (par != nullptr) { + QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); + position.rx() -= event->size().width() / 2; + position.ry() -= event->size().height() / 2; + move(position); + } +} + +void LockedDialogBase::reject() +{ + if (m_allowClose) + unlock(); +} + +bool LockedDialogBase::unlockForced() const { + return m_Unlocked; +} + +bool LockedDialogBase::canceled() const { + return m_Canceled; +} + +void LockedDialogBase::unlock() { + m_Unlocked = true; +} + +void LockedDialogBase::cancel() { + m_Canceled = true; +} + diff --git a/src/lockeddialogbase.h b/src/lockeddialogbase.h new file mode 100644 index 00000000..3c974a38 --- /dev/null +++ b/src/lockeddialogbase.h @@ -0,0 +1,62 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#pragma once + +#include "ilockedwaitingforprocess.h" +#include // for QDialog +#include // for Q_OBJECT, slots +#include // for QString + +class QResizeEvent; +class QWidget; + +/** + * a small borderless dialog displayed while the Mod Organizer UI is locked + * The dialog contains only a label and a button to force the UI to be unlocked + * + * The UI gets locked while running external applications since they may modify the + * data on which Mod Organizer works. After the UI is unlocked (manually or after the + * external application closed) MO will refresh all of its data sources + **/ +class LockedDialogBase : public QDialog, public ILockedWaitingForProcess +{ + Q_OBJECT + +public: + explicit LockedDialogBase(QWidget *parent, bool allowClose); + + bool unlockForced() const override; + + virtual bool canceled() const; + +protected: + + virtual void resizeEvent(QResizeEvent *event); + + virtual void reject(); + + virtual void unlock(); + + virtual void cancel(); + + bool m_Unlocked; + bool m_Canceled; + bool m_allowClose; +}; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1e1a4558..85808e8d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -59,6 +59,7 @@ along with Mod Organizer. If not, see . #include "messagedialog.h" #include "installationmanager.h" #include "lockeddialog.h" +#include "waitingonclosedialog.h" #include "logbuffer.h" #include "downloadlistsortproxy.h" #include "motddialog.h" @@ -855,6 +856,8 @@ void MainWindow::showEvent(QShowEvent *event) void MainWindow::closeEvent(QCloseEvent* event) { + m_closing = true; + if (m_OrganizerCore.downloadManager()->downloadsInProgress()) { if (QMessageBox::question(this, tr("Downloads in progress"), tr("There are still downloads in progress, do you really want to quit?"), @@ -866,6 +869,16 @@ void MainWindow::closeEvent(QCloseEvent* event) } } + HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(); + if (injected_process_still_running != INVALID_HANDLE_VALUE) + { + m_OrganizerCore.waitForApplication(injected_process_still_running); + if (!m_closing) { // if operation cancelled + event->ignore(); + return; + } + } + setCursor(Qt::WaitCursor); } @@ -1471,7 +1484,10 @@ ILockedWaitingForProcess* MainWindow::lock() ++m_LockCount; return m_LockDialog; } - m_LockDialog = new LockedDialog(this, true); + if (m_closing) + m_LockDialog = new WaitingOnCloseDialog(this); + else + m_LockDialog = new LockedDialog(this, true); m_LockDialog->setModal(true); m_LockDialog->show(); setEnabled(false); @@ -1489,6 +1505,8 @@ void MainWindow::unlock() } --m_LockCount; if (m_LockCount == 0) { + if (m_closing && m_LockDialog->canceled()) + m_closing = false; m_LockDialog->hide(); m_LockDialog->deleteLater(); m_LockDialog = nullptr; diff --git a/src/mainwindow.h b/src/mainwindow.h index f6f11157..575bf020 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -35,7 +35,7 @@ along with Mod Organizer. If not, see . //when I get round to cleaning up main.cpp struct Executable; class CategoryFactory; -class LockedDialog; +class LockedDialogBase; class OrganizerCore; #include "plugincontainer.h" //class PluginContainer; class PluginListSortProxy; @@ -348,9 +348,11 @@ private: bool m_DidUpdateMasterList; - LockedDialog *m_LockDialog { nullptr }; + LockedDialogBase *m_LockDialog { nullptr }; uint64_t m_LockCount { 0 }; + bool m_closing{ false }; + std::vector> m_PersistedGeometry; enum class ShortcutType { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 1f42bf95..c4bcaf3d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1367,26 +1367,8 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL QThread::msleep(500); // search if there is another usvfs process active and if so wait for it - // in theory a querySize of 1 is probably enough since the MO process doesn't seem to be returned by GetVFSProcessList - constexpr size_t querySize = 2; // just to be on the safe side - DWORD pids[querySize]; - size_t found = querySize; - if (!::GetVFSProcessList(&found, pids)) { - qWarning() << "Failed waiting for process completion : GetVFSProcessList failed?!"; - break; - } - - for (size_t i = 0; i < found; ++i) { - if (pids[i] == GetCurrentProcessId()) - continue; // obviously don't wait for MO process - handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION|SYNCHRONIZE, FALSE, pids[i]); - if (handle == INVALID_HANDLE_VALUE) { - qWarning() << "Failed waiting for process completion : OpenProcess failed" << GetLastError(); - continue; - } - newHandle = true; - break; - } + handle = findAndOpenAUSVFSProcess(); + newHandle = handle != INVALID_HANDLE_VALUE; } } @@ -1403,6 +1385,29 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL return res == WAIT_OBJECT_0; } +HANDLE OrganizerCore::findAndOpenAUSVFSProcess() { + // in theory a querySize of 1 is probably enough since the MO process doesn't seem to be returned by GetVFSProcessList + constexpr size_t querySize = 2; // just to be on the safe side + DWORD pids[querySize]; + size_t found = querySize; + if (!::GetVFSProcessList(&found, pids)) { + qWarning() << "Failed seeking USVFS processes : GetVFSProcessList failed?!"; + return INVALID_HANDLE_VALUE; + } + + for (size_t i = 0; i < found; ++i) { + if (pids[i] == GetCurrentProcessId()) + continue; // obviously don't wait for MO process + HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]); + if (handle != INVALID_HANDLE_VALUE) + return handle; + else + qWarning() << "Failed openning USVFS process " << pids[i] << " : OpenProcess failed" << GetLastError(); + } + + return INVALID_HANDLE_VALUE; +} + bool OrganizerCore::onAboutToRun( const std::function &func) { diff --git a/src/organizercore.h b/src/organizercore.h index f8806289..cf030e0a 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -206,6 +206,7 @@ public: HANDLE runShortcut(const QString &title); HANDLE startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile); bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); + HANDLE findAndOpenAUSVFSProcess(); bool onModInstalled(const std::function &func); bool onAboutToRun(const std::function &func); bool onFinishedRun(const std::function &func); diff --git a/src/waitingonclosedialog.cpp b/src/waitingonclosedialog.cpp new file mode 100644 index 00000000..565d0a36 --- /dev/null +++ b/src/waitingonclosedialog.cpp @@ -0,0 +1,71 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "waitingonclosedialog.h" +#include "ui_waitingonclosedialog.h" + +#include +#include +#include +#include // for Qt::FramelessWindowHint, etc + +WaitingOnCloseDialog::WaitingOnCloseDialog(QWidget *parent) + : LockedDialogBase(parent,true) + , ui(new Ui::WaitingOnCloseDialog) +{ + ui->setupUi(this); + + // Supposedly the Qt::CustomizeWindowHint should use a customized window + // allowing us to select if there is a close button. In practice this doesn't + // seem to work. We will ignore pressing the close button if unlockByButton == true + Qt::WindowFlags flags = + this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; + if (m_allowClose) + flags |= Qt::WindowCloseButtonHint; + this->setWindowFlags(flags); +} + +WaitingOnCloseDialog::~WaitingOnCloseDialog() +{ + delete ui; +} + + +void WaitingOnCloseDialog::setProcessName(const QString &name) +{ + ui->processLabel->setText(name); +} + +void WaitingOnCloseDialog::on_closeButton_clicked() +{ + unlock(); +} + +void WaitingOnCloseDialog::on_cancelButton_clicked() +{ + cancel(); + unlock(); +} + +void WaitingOnCloseDialog::unlock() { + LockedDialogBase::unlock(); + ui->label->setText("unlocking may take a few seconds"); + ui->closeButton->setEnabled(false); + ui->cancelButton->setEnabled(false); +} diff --git a/src/waitingonclosedialog.h b/src/waitingonclosedialog.h new file mode 100644 index 00000000..6650c390 --- /dev/null +++ b/src/waitingonclosedialog.h @@ -0,0 +1,56 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#pragma once + +#include "lockeddialogbase.h" + +namespace Ui { + class WaitingOnCloseDialog; +} + +/** + * Similar to the LockedDialog but used for waiting on running process during + * a process close request which requries a slightly different dialog. + **/ +class WaitingOnCloseDialog : public LockedDialogBase +{ + Q_OBJECT + +public: + explicit WaitingOnCloseDialog(QWidget *parent = 0); + ~WaitingOnCloseDialog(); + + bool canceled() const { return m_Canceled; } + + void setProcessName(const QString &name) override; + +protected: + + void unlock() override; + +private slots: + + void on_closeButton_clicked(); + void on_cancelButton_clicked(); + +private: + + Ui::WaitingOnCloseDialog *ui; +}; diff --git a/src/waitingonclosedialog.ui b/src/waitingonclosedialog.ui new file mode 100644 index 00000000..9c7818e0 --- /dev/null +++ b/src/waitingonclosedialog.ui @@ -0,0 +1,119 @@ + + + WaitingOnCloseDialog + + + + 0 + 0 + 317 + 151 + + + + Waiting for virtualized processes + + + + + + This dialog should disappear automatically if the application/game is done. + + + Virtualized processes are still running, it is prefered to keep MO running until they are finished. + + + true + + + + + + + Qt::Vertical + + + + 10 + + + + + + + + + true + + + + color: grey; + + + + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 10 + + + + + + + + + + Close Now + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + + + Qt::Vertical + + + + 10 + + + + + + + + + -- cgit v1.3.1 From 7b45d807d6c2c8b5663ec53183e81e16a5baf739 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Fri, 15 Dec 2017 15:01:39 -0600 Subject: Restore archive tab and partial functionality --- src/iuserinterface.h | 4 + src/mainwindow.cpp | 191 ++++++++++++++++++++++++++++++++++++++++++++++- src/mainwindow.h | 13 ++++ src/mainwindow.ui | 107 ++++++++++++++++++++++++++ src/modinfo.cpp | 2 +- src/modinfooverwrite.cpp | 2 +- src/modinforegular.cpp | 2 +- src/modlist.cpp | 2 +- src/organizercore.cpp | 13 ++++ src/organizercore.h | 1 + 10 files changed, 331 insertions(+), 6 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 255c7ac0..7ce71c24 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -28,6 +28,10 @@ public: virtual void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) = 0; + virtual void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives) = 0; + + virtual MOBase::DelayedFileWriterBase &archivesWriter() = 0; + virtual ILockedWaitingForProcess* lock() = 0; virtual void unlock() = 0; }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 85808e8d..141bcf2a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -200,6 +200,7 @@ MainWindow::MainWindow(QSettings &initSettings , m_OrganizerCore(organizerCore) , m_PluginContainer(pluginContainer) , m_DidUpdateMasterList(false) + , m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)) { QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); @@ -278,6 +279,8 @@ MainWindow::MainWindow(QSettings &initSettings ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(ui->espList)); ui->espList->installEventFilter(m_OrganizerCore.pluginList()); + ui->bsaList->setLocalMoveOnly(true); + bool pluginListAdjusted = registerWidgetState(ui->espList->objectName(), ui->espList->header(), "plugin_list_state"); registerWidgetState(ui->dataTree->objectName(), ui->dataTree->header()); registerWidgetState(ui->downloadView->objectName(), @@ -358,6 +361,9 @@ MainWindow::MainWindow(QSettings &initSettings connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString))); + m_CheckBSATimer.setSingleShot(true); + connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); + connect(ui->espList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(esplistSelectionsChanged(QItemSelection))); connect(ui->modList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(modlistSelectionsChanged(QItemSelection))); @@ -1378,6 +1384,147 @@ static QStringList toStringList(InputIterator current, InputIterator end) return result; } +void MainWindow::updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives) +{ + m_DefaultArchives = defaultArchives; + ui->bsaList->clear(); + ui->bsaList->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + std::vector> items; + + BSAInvalidation * invalidation = m_OrganizerCore.managedGame()->feature(); + std::vector files = m_OrganizerCore.directoryStructure()->getFiles(); + + QStringList plugins = m_OrganizerCore.findFiles("", [](const QString &fileName) -> bool { + return fileName.endsWith(".esp", Qt::CaseInsensitive) + || fileName.endsWith(".esm", Qt::CaseInsensitive); + }); + + auto hasAssociatedPlugin = [&](const QString &bsaName) -> bool { + for (const QString &pluginName : plugins) { + QFileInfo pluginInfo(pluginName); + if (bsaName.startsWith(QFileInfo(pluginName).baseName(), Qt::CaseInsensitive) + && (m_OrganizerCore.pluginList()->state(pluginInfo.fileName()) == IPluginList::STATE_ACTIVE)) { + return true; + } + } + return false; + }; + + for (FileEntry::Ptr current : files) { + QFileInfo fileInfo(ToQString(current->getName().c_str())); + + if (fileInfo.suffix().toLower() == "bsa" || fileInfo.suffix().toLower() == "ba2") { + int index = activeArchives.indexOf(fileInfo.fileName()); + if (index == -1) { + index = 0xFFFF; + } + else { + index += 2; + } + + if ((invalidation != nullptr) && invalidation->isInvalidationBSA(fileInfo.fileName())) { + index = 1; + } + + int originId = current->getOrigin(); + FilesOrigin & origin = m_OrganizerCore.directoryStructure()->getOriginByID(originId); + + QTreeWidgetItem * newItem = new QTreeWidgetItem(QStringList() + << fileInfo.fileName() + << ToQString(origin.getName())); + newItem->setData(0, Qt::UserRole, index); + newItem->setData(1, Qt::UserRole, originId); + newItem->setFlags(newItem->flags() & ~Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable); + newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); + newItem->setData(0, Qt::UserRole, false); + if (m_OrganizerCore.settings().forceEnableCoreFiles() + && defaultArchives.contains(fileInfo.fileName())) { + newItem->setCheckState(0, Qt::Checked); + newItem->setDisabled(true); + newItem->setData(0, Qt::UserRole, true); + } else if (fileInfo.fileName().compare("update.bsa", Qt::CaseInsensitive) == 0) { + newItem->setCheckState(0, Qt::Checked); + newItem->setDisabled(true); + } else if (hasAssociatedPlugin(fileInfo.fileName())) { + newItem->setCheckState(0, Qt::Checked); + newItem->setDisabled(true); + } + if (index < 0) index = 0; + + UINT32 sortValue = ((origin.getPriority() & 0xFFFF) << 16) | (index & 0xFFFF); + items.push_back(std::make_pair(sortValue, newItem)); + } + } + std::sort(items.begin(), items.end(), BySortValue); + + for (auto iter = items.begin(); iter != items.end(); ++iter) { + int originID = iter->second->data(1, Qt::UserRole).toInt(); + + FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); + QString modName("data"); + unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + modName = modInfo->name(); + } + QList items = ui->bsaList->findItems(modName, Qt::MatchFixedString); + QTreeWidgetItem * subItem = nullptr; + if (items.length() > 0) { + subItem = items.at(0); + } + else { + subItem = new QTreeWidgetItem(QStringList(modName)); + subItem->setFlags(subItem->flags() & ~Qt::ItemIsDragEnabled); + ui->bsaList->addTopLevelItem(subItem); + } + subItem->addChild(iter->second); + subItem->setExpanded(true); + } + checkBSAList(); +} + +void MainWindow::checkBSAList() +{ + DataArchives * archives = m_OrganizerCore.managedGame()->feature(); + + if (archives != nullptr) { + ui->bsaList->blockSignals(true); + ON_BLOCK_EXIT([&]() { ui->bsaList->blockSignals(false); }); + + QStringList defaultArchives = archives->archives(m_OrganizerCore.currentProfile()); + + bool warning = false; + + for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) { + bool modWarning = false; + QTreeWidgetItem * tlItem = ui->bsaList->topLevelItem(i); + for (int j = 0; j < tlItem->childCount(); ++j) { + QTreeWidgetItem * item = tlItem->child(j); + QString filename = item->text(0); + item->setIcon(0, QIcon()); + item->setToolTip(0, QString()); + + if (item->checkState(0) == Qt::Unchecked) { + if (defaultArchives.contains(filename)) { + item->setIcon(0, QIcon(":/MO/gui/warning")); + item->setToolTip(0, tr("This bsa is enabled in the ini file so it may be required!")); + modWarning = true; + } + } + } + if (modWarning) { + ui->bsaList->expandItem(ui->bsaList->topLevelItem(i)); + warning = true; + } + } + if (warning) { + ui->tabWidget->setTabIcon(1, QIcon(":/MO/gui/warning")); + } else { + ui->tabWidget->setTabIcon(1, QIcon()); + } + } +} + void MainWindow::saveModMetas() { for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { @@ -1524,10 +1671,12 @@ void MainWindow::on_tabWidget_currentChanged(int index) if (index == 0) { m_OrganizerCore.refreshESPList(); } else if (index == 1) { - refreshDataTree(); + m_OrganizerCore.refreshBSAList(); } else if (index == 2) { - refreshSaveList(); + refreshDataTree(); } else if (index == 3) { + refreshSaveList(); + } else if (index == 4) { ui->downloadView->scrollToBottom(); } } @@ -1806,6 +1955,7 @@ void MainWindow::modorder_changed() } m_OrganizerCore.refreshBSAList(); m_OrganizerCore.currentProfile()->modlistWriter().write(); + m_ArchiveListWriter.write(); m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins(); { // refresh selection @@ -2741,6 +2891,31 @@ void MainWindow::replaceCategories_MenuHandler() { refreshFilters(); } +void MainWindow::saveArchiveList() +{ + if (m_OrganizerCore.isArchivesInit()) { + SafeWriteFile archiveFile(m_OrganizerCore.currentProfile()->getArchivesFileName()); + for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) { + QTreeWidgetItem * tlItem = ui->bsaList->topLevelItem(i); + for (int j = 0; j < tlItem->childCount(); ++j) { + QTreeWidgetItem * item = tlItem->child(j); + if (item->checkState(0) == Qt::Checked) { + // in managed mode, "register" all enabled archives, otherwise register only the files registered in the ini + if (ui->manageArchivesBox->isChecked() || + item->data(0, Qt::UserRole).toBool()) { + archiveFile->write(item->text(0).toUtf8().append("\r\n")); + } + } + } + } + if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName()))); + } + } else { + qWarning("archive list not initialised"); + } +} + void MainWindow::checkModsForUpdates() { statusBar()->show(); @@ -3957,6 +4132,12 @@ void MainWindow::displayColumnSelection(const QPoint &pos) } } +void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) +{ + m_ArchiveListWriter.write(); + m_CheckBSATimer.start(500); +} + void MainWindow::on_actionProblems_triggered() { ProblemsDialog problems(m_PluginContainer.plugins(), this); @@ -4543,6 +4724,12 @@ void MainWindow::on_categoriesOrBtn_toggled(bool checked) } } +void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) +{ + QToolTip::showText(QCursor::pos(), + ui->managedArchiveLabel->toolTip()); +} + void MainWindow::dragEnterEvent(QDragEnterEvent *event) { //Accept copy or move drags to the download window. Link drags are not diff --git a/src/mainwindow.h b/src/mainwindow.h index 575bf020..45a41137 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -120,12 +120,15 @@ public: virtual void unlock() override; bool addProfile(); + void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives); void refreshDataTree(); void refreshSaveList(); void setModListSorting(int index); void setESPListSorting(int index); + void saveArchiveList(); + void registerPluginTool(MOBase::IPluginTool *tool); void registerModPage(MOBase::IPluginModPage *modPage); @@ -148,6 +151,8 @@ public: virtual bool closeWindow(); virtual void setWindowEnabled(bool enabled); + virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; } + public slots: void displayColumnSelection(const QPoint &pos); @@ -346,6 +351,8 @@ private: std::vector m_RemoveWidget; + QByteArray m_ArchiveListHash; + bool m_DidUpdateMasterList; LockedDialogBase *m_LockDialog { nullptr }; @@ -355,6 +362,8 @@ private: std::vector> m_PersistedGeometry; + MOBase::DelayedFileWriter m_ArchiveListWriter; + enum class ShortcutType { Toolbar, Desktop, @@ -478,6 +487,8 @@ private slots: void startExeAction(); + void checkBSAList(); + void updateProblemsButton(); void saveModMetas(); @@ -552,6 +563,7 @@ private slots: // ui slots void on_categoriesList_itemSelectionChanged(); void on_linkButton_pressed(); void on_showHiddenBox_toggled(bool checked); + void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); void on_bossButton_clicked(); void on_saveButton_clicked(); @@ -561,6 +573,7 @@ private slots: // ui slots void on_actionCopy_Log_to_Clipboard_triggered(); void on_categoriesAndBtn_toggled(bool checked); void on_categoriesOrBtn_toggled(bool checked); + void on_managedArchiveLabel_linkHovered(const QString &link); }; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 26b9e375..1e7339b8 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -902,6 +902,113 @@ p, li { white-space: pre-wrap; } + + + Archives + + + + 6 + + + + 6 + + + 6 + + + 6 + + + + + + + + + + true + + + + + + + <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> + + + <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> + + + true + + + + + + + + + Qt::CustomContextMenu + + + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. + + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! + + BSAs checked here are loaded in such a way that your installation order is obeyed properly. + + + QAbstractItemView::NoEditTriggers + + + true + + + false + + + false + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + 20 + + + true + + + 1 + + + false + + + 200 + + + + File + + + + + + Data diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 4f74086f..d3687d31 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -94,7 +94,7 @@ QString ModInfo::getContentTypeName(int contentType) case CONTENT_PLUGIN: return tr("Plugins"); case CONTENT_TEXTURE: return tr("Textures"); case CONTENT_MESH: return tr("Meshes"); - case CONTENT_BSA: return tr("BSA"); + case CONTENT_BSA: return tr("Bethesda Archive"); case CONTENT_INTERFACE: return tr("UI Changes"); case CONTENT_SOUND: return tr("Sound Effects"); case CONTENT_SCRIPT: return tr("Scripts"); diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index 6b8c9bd9..360212c0 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -53,7 +53,7 @@ QStringList ModInfoOverwrite::archives() const { QStringList result; QDir dir(this->absolutePath()); - for (const QString &archive : dir.entryList(QStringList("*.bsa"))) { + for (const QString &archive : dir.entryList(QStringList({ "*.bsa", "*.ba2" }))) { result.append(this->absolutePath() + "/" + archive); } return result; diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 9d94002b..5486bb2c 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -551,7 +551,7 @@ QStringList ModInfoRegular::archives() const { QStringList result; QDir dir(this->absolutePath()); - for (const QString &archive : dir.entryList(QStringList("*.bsa"))) { + for (const QString &archive : dir.entryList(QStringList({ "*.bsa", "*.ba2" }))) { result.append(this->absolutePath() + "/" + archive); } return result; diff --git a/src/modlist.cpp b/src/modlist.cpp index 53350c59..d7b5f471 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -64,7 +64,7 @@ ModList::ModList(QObject *parent) m_ContentIcons[ModInfo::CONTENT_PLUGIN] = std::make_tuple(":/MO/gui/content/plugin", tr("Game Plugins (ESP/ESM/ESL)")); m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(":/MO/gui/content/interface", tr("Interface")); m_ContentIcons[ModInfo::CONTENT_MESH] = std::make_tuple(":/MO/gui/content/mesh", tr("Meshes")); - m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", tr("BSA")); + m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", tr("Bethesda Archive")); m_ContentIcons[ModInfo::CONTENT_SCRIPT] = std::make_tuple(":/MO/gui/content/script", tr("Scripts (Papyrus)")); m_ContentIcons[ModInfo::CONTENT_SKSE] = std::make_tuple(":/MO/gui/content/skse", tr("Script Extender Plugin")); m_ContentIcons[ModInfo::CONTENT_SKYPROC] = std::make_tuple(":/MO/gui/content/skyproc", tr("SkyProc Patcher")); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index dd77f19a..1a336d91 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1494,6 +1494,10 @@ void OrganizerCore::refreshBSAList() if (m_ActiveArchives.isEmpty()) { m_ActiveArchives = m_DefaultArchives; } + + if (m_UserInterface != nullptr) { + m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives); + } m_ArchivesInit = true; } @@ -1572,6 +1576,9 @@ void OrganizerCore::updateModInDirectoryStructure(unsigned int index, // now we need to refresh the bsa list and save it so there is no confusion // about what archives are avaiable and active refreshBSAList(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().writeImmediately(false); + } std::vector archives = enabledArchives(); m_DirectoryRefresher.setMods( @@ -1729,6 +1736,9 @@ void OrganizerCore::modStatusChanged(unsigned int index) = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); origin.enable(false); } + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); + } } modInfo->clearCaches(); @@ -1885,6 +1895,9 @@ bool OrganizerCore::saveCurrentLists() try { savePluginList(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); + } } catch (const std::exception &e) { reportError(tr("failed to save load order: %1").arg(e.what())); } diff --git a/src/organizercore.h b/src/organizercore.h index cf030e0a..decb3d01 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -315,6 +315,7 @@ private: ModList m_ModList; PluginList m_PluginList; + QList> m_PostLoginTasks; QList> m_PostRefreshTasks; -- cgit v1.3.1 From 385a888c0bc8e5b4d4a6791e46eed3b5963c403a Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sun, 17 Dec 2017 21:33:04 -0600 Subject: Implement file priority for loose files > archives --- src/mainwindow.cpp | 24 +++++++-------- src/mainwindow.ui | 30 +++++++++--------- src/modinfo.cpp | 3 +- src/modinfodialog.cpp | 10 +++--- src/modinfowithconflictinfo.cpp | 10 +++--- src/modlist.cpp | 11 +++---- src/organizercore.cpp | 8 ++--- src/pluginlist.cpp | 26 ++++++++-------- src/shared/directoryentry.cpp | 68 ++++++++++++++++++++++------------------- src/shared/directoryentry.h | 6 ++-- src/syncoverwritedialog.cpp | 6 ++-- 11 files changed, 103 insertions(+), 99 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 141bcf2a..22e99e37 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1148,17 +1148,17 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director fileChild->setData(1, Qt::UserRole, source); fileChild->setData(1, Qt::UserRole + 1, originID); - std::vector alternatives = current->getAlternatives(); + std::vector> alternatives = current->getAlternatives(); if (!alternatives.empty()) { std::wostringstream altString; altString << ToWString(tr("Also in:
")); - for (std::vector::iterator altIter = alternatives.begin(); + for (std::vector>::iterator altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { if (altIter != alternatives.begin()) { altString << " , "; } - altString << "" << m_OrganizerCore.directoryStructure()->getOriginByID(*altIter).getName() << ""; + altString << "" << m_OrganizerCore.directoryStructure()->getOriginByID(altIter->first).getName() << ""; } fileChild->setToolTip(1, QString("%1").arg(ToQString(altString.str()))); fileChild->setForeground(1, QBrush(Qt::red)); @@ -1396,7 +1396,8 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString QStringList plugins = m_OrganizerCore.findFiles("", [](const QString &fileName) -> bool { return fileName.endsWith(".esp", Qt::CaseInsensitive) - || fileName.endsWith(".esm", Qt::CaseInsensitive); + || fileName.endsWith(".esm", Qt::CaseInsensitive) + || fileName.endsWith(".esl", Qt::CaseInsensitive); }); auto hasAssociatedPlugin = [&](const QString &bsaName) -> bool { @@ -1434,7 +1435,7 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString << ToQString(origin.getName())); newItem->setData(0, Qt::UserRole, index); newItem->setData(1, Qt::UserRole, originId); - newItem->setFlags(newItem->flags() & ~Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable); + newItem->setFlags(newItem->flags() & ~(Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable)); newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); newItem->setData(0, Qt::UserRole, false); if (m_OrganizerCore.settings().forceEnableCoreFiles() @@ -1448,6 +1449,9 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString } else if (hasAssociatedPlugin(fileInfo.fileName())) { newItem->setCheckState(0, Qt::Checked); newItem->setDisabled(true); + } else { + newItem->setCheckState(0, Qt::Unchecked); + newItem->setDisabled(true); } if (index < 0) index = 0; @@ -2900,11 +2904,7 @@ void MainWindow::saveArchiveList() for (int j = 0; j < tlItem->childCount(); ++j) { QTreeWidgetItem * item = tlItem->child(j); if (item->checkState(0) == Qt::Checked) { - // in managed mode, "register" all enabled archives, otherwise register only the files registered in the ini - if (ui->manageArchivesBox->isChecked() || - item->data(0, Qt::UserRole).toBool()) { - archiveFile->write(item->text(0).toUtf8().append("\r\n")); - } + archiveFile->write(item->text(0).toUtf8().append("\r\n")); } } } @@ -3763,8 +3763,8 @@ void MainWindow::previewDataFile() }; addFunc(file->getOrigin()); - for (int i : file->getAlternatives()) { - addFunc(i); + for (auto alt : file->getAlternatives()) { + addFunc(alt.first); } if (preview.numVariants() > 0) { preview.exec(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 1e7339b8..d8fa1432 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -922,23 +922,23 @@ p, li { white-space: pre-wrap; } - - - - - - - true - - - + + + + + + + + + + - <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> true @@ -965,7 +965,7 @@ p, li { white-space: pre-wrap; } QAbstractItemView::NoEditTriggers - true + false false @@ -974,10 +974,10 @@ p, li { white-space: pre-wrap; } false - QAbstractItemView::DragDrop + QAbstractItemView::NoDragDrop - Qt::MoveAction + Qt::IgnoreAction QAbstractItemView::SingleSelection diff --git a/src/modinfo.cpp b/src/modinfo.cpp index d3687d31..c14eedb7 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -123,9 +123,10 @@ ModInfo::Ptr ModInfo::getByIndex(unsigned int index) { QMutexLocker locker(&s_Mutex); - if (index >= s_Collection.size()) { + if (index >= s_Collection.size() && index != ULONG_MAX) { throw MyException(tr("invalid mod index %1").arg(index)); } + if (index == ULONG_MAX) return s_Collection[ModInfo::getIndex("Overwrite")]; return s_Collection[index]; } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index d43808b6..ccd2a122 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -266,22 +266,22 @@ void ModInfoDialog::refreshLists() QString fileName = relativeName.mid(0).prepend(m_RootPath); bool archive; if ((*iter)->getOrigin(archive) == m_Origin->getID()) { - std::vector alternatives = (*iter)->getAlternatives(); + std::vector> alternatives = (*iter)->getAlternatives(); if (!alternatives.empty()) { std::wostringstream altString; - for (std::vector::iterator altIter = alternatives.begin(); + for (std::vector>::iterator altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { if (altIter != alternatives.begin()) { altString << ", "; } - altString << m_Directory->getOriginByID(*altIter).getName(); + altString << m_Directory->getOriginByID(altIter->first).getName(); } QStringList fields(relativeName.prepend("...")); fields.append(ToQString(altString.str())); QTreeWidgetItem *item = new QTreeWidgetItem(fields); item->setData(0, Qt::UserRole, fileName); - item->setData(1, Qt::UserRole, ToQString(m_Directory->getOriginByID(alternatives[0]).getName())); - item->setData(1, Qt::UserRole + 1, alternatives[0]); + item->setData(1, Qt::UserRole, ToQString(m_Directory->getOriginByID(alternatives.begin()->first).getName())); + item->setData(1, Qt::UserRole + 1, alternatives.begin()->first); item->setData(1, Qt::UserRole + 2, archive); ui->overwriteTree->addTopLevelItem(item); ++numOverwrite; diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp index fbf2345f..b8ece783 100644 --- a/src/modinfowithconflictinfo.cpp +++ b/src/modinfowithconflictinfo.cpp @@ -57,8 +57,8 @@ void ModInfoWithConflictInfo::doConflictCheck() const std::vector files = origin.getFiles(); // for all files in this origin for (FileEntry::Ptr file : files) { - const std::vector &alternatives = file->getAlternatives(); - if ((alternatives.size() == 0) || (alternatives[0] == dataID)) { + const std::vector> &alternatives = file->getAlternatives(); + if ((alternatives.size() == 0) || (alternatives.begin()->first == dataID)) { // no alternatives -> no conflict providesAnything = true; } else { @@ -71,9 +71,9 @@ void ModInfoWithConflictInfo::doConflictCheck() const } // for all non-providing alternative origins - for (int altId : alternatives) { - if ((altId != dataID) && (altId != origin.getID())) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(altId); + for (auto altInfo : alternatives) { + if ((altInfo.first != dataID) && (altInfo.first != origin.getID())) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(altInfo.first); unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); if (origin.getPriority() > altOrigin.getPriority()) { m_OverwriteList.insert(altIndex); diff --git a/src/modlist.cpp b/src/modlist.cpp index d7b5f471..3afa94b5 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -701,14 +701,13 @@ void ModList::highlightMods(const QItemSelection &selected, const MOShared::Dire if (fileEntry.get() != nullptr) { QString fileName; bool archive = false; - std::vector origins; + std::vector> origins; { - std::vector alternatives = fileEntry->getAlternatives(); - origins.push_back(fileEntry->getOrigin(archive)); - origins.insert(origins.end(), alternatives.begin(), alternatives.end()); + std::vector> alternatives = fileEntry->getAlternatives(); + origins.insert(origins.end(), std::pair(fileEntry->getOrigin(archive), fileEntry->getArchive())); } - for (int originId : origins) { - MOShared::FilesOrigin &origin = directoryEntry.getOriginByID(originId); + for (auto originInfo : origins) { + MOShared::FilesOrigin &origin = directoryEntry.getOriginByID(originInfo.first); for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { if (ModInfo::getByIndex(i)->internalName() == QString::fromStdWString(origin.getName())) { ModInfo::getByIndex(i)->setPluginSelected(true); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 1a336d91..98920479 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1002,9 +1002,9 @@ QStringList OrganizerCore::getFileOrigins(const QString &fileName) const if (file.get() != nullptr) { result.append(ToQString( m_DirectoryStructure->getOriginByID(file->getOrigin()).getName())); - foreach (int i, file->getAlternatives()) { + foreach (auto i, file->getAlternatives()) { result.append( - ToQString(m_DirectoryStructure->getOriginByID(i).getName())); + ToQString(m_DirectoryStructure->getOriginByID(i.first).getName())); } } else { qDebug("%s not found", qPrintable(fileName)); @@ -1030,9 +1030,9 @@ QList OrganizerCore::findFileInfos( m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)) .getName())); info.archive = fromArchive ? ToQString(file->getArchive()) : ""; - foreach (int idx, file->getAlternatives()) { + foreach (auto idx, file->getAlternatives()) { info.origins.append( - ToQString(m_DirectoryStructure->getOriginByID(idx).getName())); + ToQString(m_DirectoryStructure->getOriginByID(idx.first).getName())); } if (filter(info)) { diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 6392d44b..775086fd 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -115,23 +115,23 @@ void PluginList::highlightPlugins(const QItemSelection &selected, const MOShared } for (QModelIndex idx : selected.indexes()) { ModInfo::Ptr selectedMod = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - if (!selectedMod.isNull() && profile.modEnabled(idx.data(Qt::UserRole + 1).toInt())) { - QDir dir(selectedMod->absolutePath()); - QStringList plugins = dir.entryList(QStringList() << "*.esp" << "*.esm" << "*.esl"); - MOShared::FilesOrigin origin = directoryEntry.getOriginByName(selectedMod->internalName().toStdWString()); - if (plugins.size() > 0) { - for (auto plugin : plugins) { + if (!selectedMod.isNull() && profile.modEnabled(idx.data(Qt::UserRole + 1).toInt())) { + QDir dir(selectedMod->absolutePath()); + QStringList plugins = dir.entryList(QStringList() << "*.esp" << "*.esm" << "*.esl"); + MOShared::FilesOrigin origin = directoryEntry.getOriginByName(selectedMod->internalName().toStdWString()); + if (plugins.size() > 0) { + for (auto plugin : plugins) { MOShared::FileEntry::Ptr file = directoryEntry.findFile(plugin.toStdWString()); if (file->getOrigin() != origin.getID()) { - const std::vector alternatives = file->getAlternatives(); - if (std::find(alternatives.begin(), alternatives.end(), origin.getID()) == alternatives.end()) + const std::vector> alternatives = file->getAlternatives(); + if (std::find_if(alternatives.begin(), alternatives.end(), [&](const std::pair& element) { return element.first == origin.getID(); }) == alternatives.end()) continue; } - std::map::iterator iter = m_ESPsByName.find(plugin.toLower()); - if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].m_ModSelected = true; - } - } + std::map::iterator iter = m_ESPsByName.find(plugin.toLower()); + if (iter != m_ESPsByName.end()) { + m_ESPs[iter->second].m_ModSelected = true; + } + } } } } diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index df6a7b5a..e1cba08c 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -236,9 +236,10 @@ void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring &arc m_FileTime = fileTime; m_Archive = archive; } else if ((m_Parent != nullptr) - && (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority())) { - if (std::find(m_Alternatives.begin(), m_Alternatives.end(), m_Origin) == m_Alternatives.end()) { - m_Alternatives.push_back(m_Origin); + && (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) + && (archive.size() == 0 || m_Archive.size() > 0 )) { + if (std::find_if(m_Alternatives.begin(), m_Alternatives.end(), [&](const std::pair &i) -> bool { return i.first == m_Origin; }) == m_Alternatives.end()) { + m_Alternatives.push_back(std::pair(m_Origin, m_Archive)); } m_Origin = origin; m_FileTime = fileTime; @@ -249,20 +250,20 @@ void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring &arc // already an origin return; } - for (std::vector::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { - if (*iter == origin) { + for (std::vector>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { + if (iter->first == origin) { // already an origin return; } if ((m_Parent != nullptr) - && (m_Parent->getOriginByID(*iter).getPriority() < m_Parent->getOriginByID(origin).getPriority())) { - m_Alternatives.insert(iter, origin); + && (m_Parent->getOriginByID(iter->first).getPriority() < m_Parent->getOriginByID(origin).getPriority())) { + m_Alternatives.insert(iter, std::pair(origin, archive)); found = true; break; } } if (!found) { - m_Alternatives.push_back(origin); + m_Alternatives.push_back(std::pair(origin, archive)); } } } @@ -272,14 +273,14 @@ bool FileEntry::removeOrigin(int origin) if (m_Origin == origin) { if (!m_Alternatives.empty()) { // find alternative with the highest priority - std::vector::iterator currentIter = m_Alternatives.begin(); - for (std::vector::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { - if ((m_Parent->getOriginByID(*iter).getPriority() > m_Parent->getOriginByID(*currentIter).getPriority()) && - (*iter != origin)) { + std::vector>::iterator currentIter = m_Alternatives.begin(); + for (std::vector>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { + if ((m_Parent->getOriginByID(iter->first).getPriority() > m_Parent->getOriginByID(currentIter->first).getPriority()) && + (iter->first != origin)) { currentIter = iter; } } - int currentID = *currentIter; + int currentID = currentIter->first; m_Alternatives.erase(currentIter); m_Origin = currentID; @@ -303,23 +304,13 @@ bool FileEntry::removeOrigin(int origin) return true; } } else { - std::vector::iterator newEnd = std::remove(m_Alternatives.begin(), m_Alternatives.end(), origin); - m_Alternatives.erase(newEnd, m_Alternatives.end()); + std::vector>::iterator newEnd = std::find_if(m_Alternatives.begin(), m_Alternatives.end(), [&](const std::pair &i) -> bool { return i.first == origin; }); + if (newEnd != m_Alternatives.end()) + m_Alternatives.erase(newEnd, m_Alternatives.end()); } return false; } - -// sorted by priority descending -static bool ByOriginPriority(DirectoryEntry *entry, int LHS, int RHS) -{ - int l = entry->getOriginByID(LHS).getPriority(); if (l < 0) l = INT_MAX; - int r = entry->getOriginByID(RHS).getPriority(); if (r < 0) r = INT_MAX; - - return l < r; -} - - FileEntry::FileEntry() : m_Index(UINT_MAX), m_Name(), m_Origin(-1), m_Parent(nullptr), m_LastAccessed(time(nullptr)) { @@ -339,10 +330,23 @@ FileEntry::~FileEntry() void FileEntry::sortOrigins() { - m_Alternatives.push_back(m_Origin); - std::sort(m_Alternatives.begin(), m_Alternatives.end(), boost::bind(ByOriginPriority, m_Parent, _1, _2)); - m_Origin = m_Alternatives[m_Alternatives.size() - 1]; - m_Alternatives.pop_back(); + m_Alternatives.push_back(std::pair(m_Origin, m_Archive)); + std::sort(m_Alternatives.begin(), m_Alternatives.end(), [&](const std::pair &LHS, const std::pair &RHS) -> bool { + if ((!LHS.second.size() && !RHS.second.size()) || (LHS.second.size() && RHS.second.size())) { + int l = m_Parent->getOriginByID(LHS.first).getPriority(); if (l < 0) l = INT_MAX; + int r = m_Parent->getOriginByID(RHS.first).getPriority(); if (r < 0) r = INT_MAX; + + return l < r; + } + + if (RHS.second.size()) return false; + return true; + }); + if (!m_Alternatives.empty()) { + m_Origin = m_Alternatives.back().first; + m_Archive = m_Alternatives.back().second; + m_Alternatives.pop_back(); + } } @@ -886,9 +890,9 @@ void FileRegister::unregisterFile(FileEntry::Ptr file) // unregister from origin int originID = file->getOrigin(ignore); m_OriginConnection->getByID(originID).removeFile(file->getIndex()); - const std::vector &alternatives = file->getAlternatives(); + const std::vector> &alternatives = file->getAlternatives(); for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) { - m_OriginConnection->getByID(*iter).removeFile(file->getIndex()); + m_OriginConnection->getByID(iter->first).removeFile(file->getIndex()); } // unregister from directory diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 04b8782b..8dbedaf4 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -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 &getAlternatives() const { return m_Alternatives; } + const std::vector> &getAlternatives() const { return m_Alternatives; } const std::wstring &getName() const { return m_Name; } int getOrigin() const { return m_Origin; } @@ -96,9 +96,9 @@ private: Index m_Index; std::wstring m_Name; - int m_Origin; + int m_Origin = -1; std::wstring m_Archive; - std::vector m_Alternatives; + std::vector> m_Alternatives; DirectoryEntry *m_Parent; mutable FILETIME m_FileTime; diff --git a/src/syncoverwritedialog.cpp b/src/syncoverwritedialog.cpp index aeed0a55..f03e29c5 100644 --- a/src/syncoverwritedialog.cpp +++ b/src/syncoverwritedialog.cpp @@ -98,9 +98,9 @@ void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *director bool ignore; int origin = entry->getOrigin(ignore); addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(origin).getName()), origin); - const std::vector &alternatives = entry->getAlternatives(); - for (std::vector::const_iterator iter = alternatives.begin(); iter != alternatives.end(); ++iter) { - addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(*iter).getName()), *iter); + const std::vector> &alternatives = entry->getAlternatives(); + for (std::vector>::const_iterator iter = alternatives.begin(); iter != alternatives.end(); ++iter) { + addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(iter->first).getName()), iter->first); } combo->setCurrentIndex(combo->count() - 1); } else { -- cgit v1.3.1