From 84c66727bff954fd0820fc9f33833848496e9531 Mon Sep 17 00:00:00 2001 From: Tannin Date: Mon, 14 Jul 2014 21:22:44 +0200 Subject: - when highlighting a mod the overwritten and overwriting mods are now highlighted in the list - when starting an external application MO now wraps the process in a job and waits on that instead. This way MO is not unlocked early when skyrim is started through skse - mod info dialog no longer offers the esp tab for foreign mods because that caused confusion - updated translation files - download directory and mod directory are now created if necessary - bugfix: staging script created unnecessary copies of translation files - bugfix: potential invalid array access when trying to determine best mod order - bugfix: deleter file wasn't removed after esp hiding was disabled - bugfix: potential access to to un-initialized login reply - bugfix: changed the initialization order to allow more ui controls to be localized --- src/modlistview.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/modlistview.cpp') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 1a4f6266..2789afe1 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -36,8 +36,10 @@ void ModListViewStyle::drawPrimitive(PrimitiveElement element, const QStyleOptio ModListView::ModListView(QWidget *parent) : QTreeView(parent) + , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) { // setStyle(new ModListViewStyle(style(), indentation())); + setVerticalScrollBar(m_Scrollbar); } void ModListView::dragEnterEvent(QDragEnterEvent *event) @@ -46,3 +48,9 @@ void ModListView::dragEnterEvent(QDragEnterEvent *event) QTreeView::dragEnterEvent(event); } + +void ModListView::setModel(QAbstractItemModel *model) +{ + QTreeView::setModel(model); + setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); +} -- cgit v1.3.1 From 470b6ed0bf20525988d719d23725f7c9d19a9f36 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 27 Jul 2014 18:34:32 +0200 Subject: - browser dialog now has a hidden url-field (for testing only) - loot_cli is no longer part of this project. I will probably create a fork of loot that allows command-line usage instead - loot integration now works with such a modified loot version - integrated loot will now also integrate incompatibility messages in the MO UI - overwrite-markers are now updated as the list order is changed - fnis checker will now always allow the user to ignore fnis errors - plugin interface now has a function to wait for handles returned from startApplication (which can be job or process handles) - bugfix: non-mo mods sharing the name with regular mods now have a different internal name - bugfix: using hotkeys the vanilla game-plugins could be moved --- src/browserdialog.cpp | 30 ++++++++- src/browserdialog.h | 4 ++ src/browserdialog.ui | 29 ++++++++- src/browserview.cpp | 3 +- src/genericicondelegate.cpp | 39 ++++++++++++ src/genericicondelegate.h | 36 +++++++++++ src/mainwindow.cpp | 139 +++++++++++++++++++++++++++++++++++------ src/mainwindow.h | 4 +- src/modinfo.cpp | 103 +++++++++++++++--------------- src/modinfo.h | 13 ++-- src/modlist.cpp | 4 +- src/modlistsortproxy.cpp | 16 ++--- src/modlistsortproxy.h | 3 + src/modlistview.cpp | 1 - src/organizer.pro | 4 +- src/organizerproxy.cpp | 5 ++ src/organizerproxy.h | 1 + src/pluginflagicondelegate.cpp | 39 ------------ src/pluginflagicondelegate.h | 36 ----------- src/pluginlist.cpp | 7 +++ src/viewmarkingscrollbar.cpp | 3 + 21 files changed, 348 insertions(+), 171 deletions(-) create mode 100644 src/genericicondelegate.cpp create mode 100644 src/genericicondelegate.h delete mode 100644 src/pluginflagicondelegate.cpp delete mode 100644 src/pluginflagicondelegate.h (limited to 'src/modlistview.cpp') diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 4897ea4f..521459d0 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -37,6 +37,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include @@ -57,9 +58,12 @@ BrowserDialog::BrowserDialog(QWidget *parent) m_Tabs = this->findChild("browserTabWidget"); + installEventFilter(this); + connect(m_Tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int))); -} + ui->urlEdit->setVisible(false); +} BrowserDialog::~BrowserDialog() { @@ -108,13 +112,14 @@ BrowserView *BrowserDialog::getCurrentView() } -void BrowserDialog::urlChanged(const QUrl&) +void BrowserDialog::urlChanged(const QUrl &url) { BrowserView *currentView = getCurrentView(); if (currentView != NULL) { ui->backBtn->setEnabled(currentView->history()->canGoBack()); ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); } + ui->urlEdit->setText(url.toString()); } @@ -268,3 +273,24 @@ void BrowserDialog::on_browserTabWidget_currentChanged(int index) ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); } } + +void BrowserDialog::on_urlEdit_returnPressed() +{ + QWebView *currentView = getCurrentView(); + if (currentView != NULL) { + currentView->setUrl(QUrl(ui->urlEdit->text())); + } +} + +bool BrowserDialog::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = reinterpret_cast(event); + if ((keyEvent->modifiers() & Qt::ControlModifier) + && (keyEvent->key() == Qt::Key_U)) { + ui->urlEdit->setVisible(!ui->urlEdit->isVisible()); + return true; + } + } + return QDialog::eventFilter(object, event); +} diff --git a/src/browserdialog.h b/src/browserdialog.h index c6e0aab6..10b44fac 100644 --- a/src/browserdialog.h +++ b/src/browserdialog.h @@ -63,6 +63,7 @@ public: **/ void openUrl(const QUrl &url); + virtual bool eventFilter(QObject *object, QEvent *event); signals: /** @@ -103,6 +104,8 @@ private slots: void on_browserTabWidget_currentChanged(int index); + void on_urlEdit_returnPressed(); + private: QString guessFileName(const QString &url); @@ -119,6 +122,7 @@ private: QTabWidget *m_Tabs; + }; #endif // BROWSERDIALOG_H diff --git a/src/browserdialog.ui b/src/browserdialog.ui index 7d154fbb..c682fc5d 100644 --- a/src/browserdialog.ui +++ b/src/browserdialog.ui @@ -17,7 +17,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -180,7 +189,16 @@ 6 - + + 0 + + + 0 + + + 0 + + 0 @@ -237,6 +255,13 @@ + + + + true + + + diff --git a/src/browserview.cpp b/src/browserview.cpp index bd43a18e..aeb16520 100644 --- a/src/browserview.cpp +++ b/src/browserview.cpp @@ -28,6 +28,7 @@ along with Mod Organizer. If not, see . #include #include "utility.h" + BrowserView::BrowserView(QWidget *parent) : QWebView(parent) { @@ -36,7 +37,6 @@ BrowserView::BrowserView(QWidget *parent) page()->settings()->setMaximumPagesInCache(10); } - QWebView *BrowserView::createWindow(QWebPage::WebWindowType) { BrowserView *newView = new BrowserView(parentWidget()); @@ -44,7 +44,6 @@ QWebView *BrowserView::createWindow(QWebPage::WebWindowType) return newView; } - bool BrowserView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::ShortcutOverride) { diff --git a/src/genericicondelegate.cpp b/src/genericicondelegate.cpp new file mode 100644 index 00000000..c3471735 --- /dev/null +++ b/src/genericicondelegate.cpp @@ -0,0 +1,39 @@ +#include "genericicondelegate.h" +#include "pluginlist.h" +#include + + +GenericIconDelegate::GenericIconDelegate(QObject *parent, int role, int logicalIndex, int compactSize) + : IconDelegate(parent) + , m_Role(role) + , m_LogicalIndex(logicalIndex) + , m_CompactSize(compactSize) + , m_Compact(false) +{ +} + +void GenericIconDelegate::columnResized(int logicalIndex, int, int newSize) +{ + if (logicalIndex == m_LogicalIndex) { + m_Compact = newSize < m_CompactSize; + } +} + +QList GenericIconDelegate::getIcons(const QModelIndex &index) const +{ + QList result; + if (index.isValid()) { + foreach (const QVariant &var, index.data(m_Role).toList()) { + QIcon icon = var.value(); + if (!m_Compact || !icon.isNull()) { + result.append(icon); + } + } + } + return result; +} + +size_t GenericIconDelegate::getNumIcons(const QModelIndex &index) const +{ + return index.data(m_Role).toList().count(); +} diff --git a/src/genericicondelegate.h b/src/genericicondelegate.h new file mode 100644 index 00000000..8bc75e27 --- /dev/null +++ b/src/genericicondelegate.h @@ -0,0 +1,36 @@ +#ifndef GENERICICONDELEGATE_H +#define GENERICICONDELEGATE_H + +#include "icondelegate.h" + +/** + * @brief an icon delegate that takes the list of icons from a user-defines data role + */ +class GenericIconDelegate : public IconDelegate +{ +Q_OBJECT +public: + /** + * @brief constructor + * @param parent parent object + * @param role role of the itemmodel from which the icon list can be queried (as a QVariantList) + * @param logicalIndex logical index within the model. This is part of a "hack". Normally "empty" icons will be allocated the same + * space as a regular icon. This way the model can use empty icons as spacers and thus align same icons horizontally. + * Now, if you set the logical Index to a valid column and connect the columnResized slot to the sectionResized signal + * of the view, the delegate will turn off this behaviour if the column is smaller than "compactSize" + * @param compactSize see explanation of logicalIndex + */ + GenericIconDelegate(QObject *parent = NULL, int role = Qt::UserRole + 1, int logicalIndex = -1, int compactSize = 150); +public slots: + void columnResized(int logicalIndex, int oldSize, int newSize); +private: + virtual QList getIcons(const QModelIndex &index) const; + virtual size_t getNumIcons(const QModelIndex &index) const; +private: + int m_Role; + int m_LogicalIndex; + int m_CompactSize; + bool m_Compact; +}; + +#endif // GENERICICONDELEGATE_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a3887a23..4aea645f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -49,7 +49,7 @@ along with Mod Organizer. If not, see . #include "questionboxmemory.h" #include "tutorialmanager.h" #include "modflagicondelegate.h" -#include "pluginflagicondelegate.h" +#include "genericicondelegate.h" #include "credentialsdialog.h" #include "selectiondialog.h" #include "csvbuilder.h" @@ -728,6 +728,7 @@ void MainWindow::savePluginList() void MainWindow::modFilterActive(bool filterActive) { if (filterActive) { + m_ModList.setOverwriteMarkers(std::set(), std::set()); ui->modList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); } else if (ui->groupCombo->currentIndex() != 0) { ui->modList->setStyleSheet("QTreeView { border: 2px ridge #337733; }"); @@ -784,7 +785,6 @@ bool MainWindow::saveCurrentLists() return true; } - bool MainWindow::addProfile() { QComboBox *profileBox = findChild("profileBox"); @@ -807,7 +807,6 @@ bool MainWindow::addProfile() } } - void MainWindow::hookUpWindowTutorials() { QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", QStringList("*.js"), QDir::Files); @@ -829,7 +828,6 @@ void MainWindow::hookUpWindowTutorials() } } - void MainWindow::showEvent(QShowEvent *event) { refreshFilters(); @@ -2773,6 +2771,19 @@ void MainWindow::modorder_changed() m_CurrentProfile->writeModlist(); saveArchiveList(); m_DirectoryStructure->getFileRegister()->sortOrigins(); + + { // refresh selection + QModelIndex current = ui->modList->currentIndex(); + if (current.isValid()) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); + modInfo->doConflictCheck(); + m_ModList.setOverwriteMarkers(modInfo->getModOverwrite(), modInfo->getModOverwritten()); + if (m_ModListSortProxy != NULL) { + m_ModListSortProxy->invalidate(); + } + ui->modList->verticalScrollBar()->repaint(); + } + } } void MainWindow::procError(QProcess::ProcessError error) @@ -2960,6 +2971,7 @@ void MainWindow::refreshFilters() { QItemSelection currentSelection = ui->modList->selectionModel()->selection(); + QVariant currentIndexName = ui->modList->currentIndex().data(); ui->modList->setCurrentIndex(QModelIndex()); QStringList selectedItems; @@ -2998,8 +3010,11 @@ void MainWindow::refreshFilters() matches.at(0)->setSelected(true); } } - ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); + QModelIndexList matchList = ui->modList->model()->match(ui->modList->model()->index(0, 0), Qt::DisplayRole, currentIndexName); + if (matchList.size() > 0) { + ui->modList->setCurrentIndex(matchList.at(0)); + } } @@ -3069,9 +3084,14 @@ void MainWindow::modlistChanged(const QModelIndex&, int) void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&) { - ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); - m_ModList.setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); - if (m_ModListSortProxy != NULL) { + if (current.isValid()) { + ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); + m_ModList.setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); + } else { + m_ModList.setOverwriteMarkers(std::set(), std::set()); + } + if ((m_ModListSortProxy != NULL) + && !m_ModListSortProxy->beingInvalidated()) { m_ModListSortProxy->invalidate(); } ui->modList->verticalScrollBar()->repaint(); @@ -5277,11 +5297,12 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &reportU boost::split(lines, lootOut, boost::is_any_of("\r\n")); std::tr1::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); + std::tr1::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); foreach (const std::string &line, lines) { if (line.length() > 0) { size_t progidx = line.find("[progress]"); - size_t reportidx = line.find("[report]"); + size_t reportidx = line.find("[Report]"); size_t erroridx = line.find("[error]"); if (progidx != std::string::npos) { dialog.setLabelText(line.substr(progidx + 11).c_str()); @@ -5296,8 +5317,12 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &reportU std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); m_PluginList.addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str())); + } else if (std::tr1::regex_match(line, match, exIncompatible)) { + std::string modName(match[1].first, match[1].second); + std::string dependency(match[2].first, match[2].second); + m_PluginList.addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); } else { - qDebug("%s", line.c_str()); + qDebug("[loot] %s", line.c_str()); } } } @@ -5350,6 +5375,65 @@ HANDLE MainWindow::startApplication(const QString &executable, const QStringList return spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID); } + +bool MainWindow::waitForProcessOrJob(HANDLE handle, LPDWORD exitCode) +{ + LockedDialog *dialog = new LockedDialog(this); + dialog->show(); + setEnabled(false); + ON_BLOCK_EXIT([&] () { dialog->hide(); dialog->deleteLater(); this->setEnabled(true); }); + + DWORD retLen; + JOBOBJECT_BASIC_PROCESS_ID_LIST info; + + bool isJobHandle = true; + + ULONG lastProcessID = ULONG_MAX; + HANDLE processHandle = handle; + + DWORD res = ::MsgWaitForMultipleObjects(1, &handle, false, 500, QS_KEY | QS_MOUSE); + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0) && !dialog->unlockClicked()) { + if (isJobHandle) { + if (::QueryInformationJobObject(handle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (info.NumberOfProcessIdsInList == 0) { + // fake signaled state + res = WAIT_OBJECT_0; + break; + } else { + // this is indeed a job handle. Figure out one of the process handles as well. + if (lastProcessID != info.ProcessIdList[0]) { + lastProcessID = info.ProcessIdList[0]; + if (processHandle != handle) { + ::CloseHandle(processHandle); + } + processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); + } + } + } else { + // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there + // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. + // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without + // the right to break out. + if (::GetLastError() != ERROR_MORE_DATA) { + isJobHandle = false; + } + } + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); + + res = ::MsgWaitForMultipleObjects(1, &handle, false, 500, QS_KEY | QS_MOUSE); + } + + if (exitCode != NULL) { + ::GetExitCodeProcess(processHandle, exitCode); + } + ::CloseHandle(processHandle); + + return res == WAIT_OBJECT_0; +} + void MainWindow::on_bossButton_clicked() { std::string reportURL; @@ -5368,18 +5452,21 @@ void MainWindow::on_bossButton_clicked() dialog.show(); QStringList parameters; - parameters << "--game" << ToQString(GameInfo::instance().getGameShortName()) + parameters << "--unattended" + << "--stdout" + << "--noreport" + << "--game" << ToQString(GameInfo::instance().getGameShortName()) << "--gamePath" << QString("\"%1\"").arg(ToQString(GameInfo::instance().getGameDirectory())); - if (!m_DidUpdateMasterList) { - parameters << "--updateMasterlist"; + if (m_DidUpdateMasterList) { + parameters << "--skipUpdateMasterlist"; + } else { m_DidUpdateMasterList = true; } - HANDLE stdOutWrite = INVALID_HANDLE_VALUE; HANDLE stdOutRead = INVALID_HANDLE_VALUE; createStdoutPipe(&stdOutRead, &stdOutWrite); - HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), + HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/LOOT.exe"), parameters.join(" "), m_CurrentProfile->getName(), m_Settings.logLevel(), @@ -5395,6 +5482,8 @@ void MainWindow::on_bossButton_clicked() DWORD retLen; JOBOBJECT_BASIC_PROCESS_ID_LIST info; bool isJobHandle = true; + ULONG lastProcessID; + HANDLE processHandle = loot; if (loot != INVALID_HANDLE_VALUE) { DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, QS_KEY | QS_MOUSE); @@ -5404,6 +5493,14 @@ void MainWindow::on_bossButton_clicked() if (info.NumberOfProcessIdsInList == 0) { qDebug("no more processes in job"); break; + } else { + if (lastProcessID != info.ProcessIdList[0]) { + lastProcessID = info.ProcessIdList[0]; + if (processHandle != loot) { + ::CloseHandle(processHandle); + } + processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); + } } } else { // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there @@ -5437,7 +5534,8 @@ void MainWindow::on_bossButton_clicked() processLOOTOut(remainder, reportURL, errorMessages, dialog); } DWORD exitCode = 0UL; - ::GetExitCodeProcess(loot, &exitCode); + ::GetExitCodeProcess(processHandle, &exitCode); + ::CloseHandle(processHandle); if (exitCode != 0UL) { reportError(tr("loot failed. Exit code was: %1").arg(exitCode)); return; @@ -5460,11 +5558,12 @@ void MainWindow::on_bossButton_clicked() if (reportURL.length() > 0) { m_IntegratedBrowser.setWindowTitle("LOOT Report"); QString report(reportURL.c_str()); - if (QFile::exists(report)) { - m_IntegratedBrowser.openUrl(QUrl::fromLocalFile(report)); - } else { - qWarning("report file missing"); + QStringList temp = report.split("?"); + QUrl url = QUrl::fromLocalFile(temp.at(0)); + if (temp.size() > 1) { + url.setEncodedQuery(temp.at(1).toUtf8()); } + m_IntegratedBrowser.openUrl(url); } refreshESPList(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 62503062..124dde5f 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -134,9 +134,11 @@ public: HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", const QString &profile = ""); + bool waitForProcessOrJob(HANDLE processHandle, LPDWORD exitCode = NULL); + void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); - void waitForProcessOrJob(HANDLE processHandle); + public slots: void refreshLists(); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index cb20567e..60df0137 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -359,67 +359,72 @@ std::vector ModInfoWithConflictInfo::getFlags() const } -ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const +void ModInfoWithConflictInfo::doConflictCheck() const { - // this is costy so cache the result - QTime now = QTime::currentTime(); - if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { - m_OverwriteList.clear(); - m_OverwrittenList.clear(); - bool regular = false; + m_OverwriteList.clear(); + m_OverwrittenList.clear(); + bool regular = false; - int dataID = 0; - if ((*m_DirectoryStructure)->originExists(L"data")) { - dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); - } + int dataID = 0; + if ((*m_DirectoryStructure)->originExists(L"data")) { + dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); + } - std::wstring name = ToWString(this->name()); - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); - std::vector files = origin.getFiles(); - // for all files in this origin - for (auto iter = files.begin(); iter != files.end(); ++iter) { - const std::vector &alternatives = (*iter)->getAlternatives(); - if ((alternatives.size() == 0) - || (alternatives[0] == dataID)) { - // no alternatives -> no conflict - regular = true; - } else { - if ((*iter)->getOrigin() != origin.getID()) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID((*iter)->getOrigin()); + std::wstring name = ToWString(this->name()); + if ((*m_DirectoryStructure)->originExists(name)) { + FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + std::vector files = origin.getFiles(); + // for all files in this origin + for (auto iter = files.begin(); iter != files.end(); ++iter) { + const std::vector &alternatives = (*iter)->getAlternatives(); + if ((alternatives.size() == 0) + || (alternatives[0] == dataID)) { + // no alternatives -> no conflict + regular = true; + } else { + if ((*iter)->getOrigin() != origin.getID()) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID((*iter)->getOrigin()); + unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); + m_OverwrittenList.insert(altIndex); + } + // for all non-providing alternative origins + for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { + if ((*altIter != dataID) && (*altIter != origin.getID())) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(*altIter); unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - m_OverwrittenList.insert(altIndex); - } - // for all non-providing alternative origins - for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { - if ((*altIter != dataID) && (*altIter != origin.getID())) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(*altIter); - unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - if (origin.getPriority() > altOrigin.getPriority()) { - m_OverwriteList.insert(altIndex); - } else { - m_OverwrittenList.insert(altIndex); - } + if (origin.getPriority() > altOrigin.getPriority()) { + m_OverwriteList.insert(altIndex); + } else { + m_OverwrittenList.insert(altIndex); } } } } } + } - m_LastConflictCheck = QTime::currentTime(); + m_LastConflictCheck = QTime::currentTime(); - if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) - m_CurrentConflictState = CONFLICT_MIXED; - else if (!m_OverwriteList.empty()) - m_CurrentConflictState = CONFLICT_OVERWRITE; - else if (!m_OverwrittenList.empty()) { - if (!regular) { - m_CurrentConflictState = CONFLICT_REDUNDANT; - } else { - m_CurrentConflictState = CONFLICT_OVERWRITTEN; - } + if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) + m_CurrentConflictState = CONFLICT_MIXED; + else if (!m_OverwriteList.empty()) + m_CurrentConflictState = CONFLICT_OVERWRITE; + else if (!m_OverwrittenList.empty()) { + if (!regular) { + m_CurrentConflictState = CONFLICT_REDUNDANT; + } else { + m_CurrentConflictState = CONFLICT_OVERWRITTEN; } - else m_CurrentConflictState = CONFLICT_NONE; + } + else m_CurrentConflictState = CONFLICT_NONE; +} + +ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const +{ + // this is costy so cache the result + QTime now = QTime::currentTime(); + if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { + doConflictCheck(); } return m_CurrentConflictState; diff --git a/src/modinfo.h b/src/modinfo.h index e2121828..1097e5ba 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -498,17 +498,20 @@ public: virtual void saveMeta() {} /** - * @brief retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed - * @return + * @return retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed */ virtual std::set getModOverwrite() { return std::set(); } /** - * @brief retrieve list of mods (as mod index) that overwrite this one. Updates may be delayed - * @return + * @return list of mods (as mod index) that overwrite this one. Updates may be delayed */ virtual std::set getModOverwritten() { return std::set(); } + /** + * @brief update conflict information + */ + virtual void doConflictCheck() const {} + signals: /** @@ -567,6 +570,8 @@ public: virtual std::set getModOverwritten() { return m_OverwrittenList; } + virtual void doConflictCheck() const; + private: enum EConflictType { diff --git a/src/modlist.cpp b/src/modlist.cpp index ca173b18..681d880c 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -224,7 +224,6 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return QString(); } } else { - //return tr("None"); return QVariant(); } } @@ -887,6 +886,8 @@ void ModList::removeRow(int row, const QModelIndex&) void ModList::notifyChange(int rowStart, int rowEnd) { if (rowStart < 0) { + m_Overwrite.clear(); + m_Overwritten.clear(); beginResetModel(); endResetModel(); } else { @@ -1021,6 +1022,7 @@ bool ModList::eventFilter(QObject *obj, QEvent *event) notifyChange(idx.row()); } } + emit modorder_changed(); return true; } else if (keyEvent->key() == Qt::Key_Delete) { QItemSelectionModel *selectionModel = itemView->selectionModel(); diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index d790e277..ecd1755d 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -34,6 +34,7 @@ ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) , m_CurrentFilter() , m_FilterActive(false) , m_FilterMode(FILTER_AND) + , m_BeingInvalidated(false) { m_EnabledColumns.set(ModList::COL_FLAGS); m_EnabledColumns.set(ModList::COL_NAME); @@ -43,7 +44,6 @@ ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) // but I don't know why. This should be necessary } - void ModListSortProxy::setProfile(Profile *profile) { m_Profile = profile; @@ -72,7 +72,6 @@ Qt::ItemFlags ModListSortProxy::flags(const QModelIndex &modelIndex) const return flags; } - void ModListSortProxy::displayColumnSelection(const QPoint &pos) { QMenu menu; @@ -103,7 +102,6 @@ void ModListSortProxy::displayColumnSelection(const QPoint &pos) emit layoutChanged(); } - void ModListSortProxy::enableAllVisible() { if (m_Profile == NULL) return; @@ -115,7 +113,6 @@ void ModListSortProxy::enableAllVisible() invalidate(); } - void ModListSortProxy::disableAllVisible() { if (m_Profile == NULL) return; @@ -127,7 +124,6 @@ void ModListSortProxy::disableAllVisible() invalidate(); } - bool ModListSortProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { @@ -208,15 +204,16 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, return lt; } - void ModListSortProxy::updateFilter(const QString &filter) { m_CurrentFilter = filter; updateFilterActive(); + // workaround because qt throws a fit if, as a result of this invalidation, another invalidate is called + m_BeingInvalidated = true; invalidateFilter(); + m_BeingInvalidated = false; } - bool ModListSortProxy::hasConflictFlag(const std::vector &flags) const { foreach (ModInfo::EFlag flag, flags) { @@ -231,7 +228,6 @@ bool ModListSortProxy::hasConflictFlag(const std::vector &flags) return false; } - bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const { for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { @@ -306,8 +302,6 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const return false; } - - bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const { if (!m_CurrentFilter.isEmpty() && @@ -330,7 +324,6 @@ void ModListSortProxy::setFilterMode(ModListSortProxy::FilterMode mode) } } - bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) const { if (m_Profile == NULL) { @@ -362,7 +355,6 @@ bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) cons } } - bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index fc7edcd5..cb474200 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -83,6 +83,7 @@ public: return rowCount(parent) > 0; } + bool beingInvalidated() const { return m_BeingInvalidated; } public slots: @@ -116,6 +117,8 @@ private: bool m_FilterActive; FilterMode m_FilterMode; + bool m_BeingInvalidated; + }; #endif // MODLISTSORTPROXY_H diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 2789afe1..50a13a8c 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -38,7 +38,6 @@ ModListView::ModListView(QWidget *parent) : QTreeView(parent) , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) { -// setStyle(new ModListViewStyle(style(), indentation())); setVerticalScrollBar(m_Scrollbar); } diff --git a/src/organizer.pro b/src/organizer.pro index 19d3a3a0..994da396 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -88,7 +88,7 @@ SOURCES += \ json.cpp \ safewritefile.cpp \ modflagicondelegate.cpp \ - pluginflagicondelegate.cpp \ + genericicondelegate.cpp \ organizerproxy.cpp \ viewmarkingscrollbar.cpp @@ -168,7 +168,7 @@ HEADERS += \ safewritefile.h\ pdll.h \ modflagicondelegate.h \ - pluginflagicondelegate.h \ + genericicondelegate.h \ organizerproxy.h \ viewmarkingscrollbar.h diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index c48bc765..8adfda7e 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -104,6 +104,11 @@ HANDLE OrganizerProxy::startApplication(const QString &executable, const QString return m_Proxied->startApplication(executable, args, cwd, profile); } +bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const +{ + return m_Proxied->waitForProcessOrJob(handle, exitCode); +} + bool OrganizerProxy::onAboutToRun(const std::function &func) { auto conn = m_Proxied->m_AboutToRun.connect(func); diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 017908e5..7e49c99c 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -35,6 +35,7 @@ public: virtual MOBase::IPluginList *pluginList(); virtual MOBase::IModList *modList(); virtual HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", const QString &profile = ""); + virtual bool waitForApplication(HANDLE handle, LPDWORD exitCode = NULL) const; virtual void refreshModList(bool saveChanges); virtual bool onAboutToRun(const std::function &func); diff --git a/src/pluginflagicondelegate.cpp b/src/pluginflagicondelegate.cpp deleted file mode 100644 index a4f66c04..00000000 --- a/src/pluginflagicondelegate.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "pluginflagicondelegate.h" -#include "pluginlist.h" -#include - - -GenericIconDelegate::GenericIconDelegate(QObject *parent, int role, int logicalIndex, int compactSize) - : IconDelegate(parent) - , m_Role(role) - , m_LogicalIndex(logicalIndex) - , m_CompactSize(compactSize) - , m_Compact(false) -{ -} - -void GenericIconDelegate::columnResized(int logicalIndex, int, int newSize) -{ - if (logicalIndex == m_LogicalIndex) { - m_Compact = newSize < m_CompactSize; - } -} - -QList GenericIconDelegate::getIcons(const QModelIndex &index) const -{ - QList result; - if (index.isValid()) { - foreach (const QVariant &var, index.data(m_Role).toList()) { - QIcon icon = var.value(); - if (!m_Compact || !icon.isNull()) { - result.append(icon); - } - } - } - return result; -} - -size_t GenericIconDelegate::getNumIcons(const QModelIndex &index) const -{ - return index.data(m_Role).toList().count(); -} diff --git a/src/pluginflagicondelegate.h b/src/pluginflagicondelegate.h deleted file mode 100644 index 3c05db72..00000000 --- a/src/pluginflagicondelegate.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef PLUGINFLAGICONDELEGATE_H -#define PLUGINFLAGICONDELEGATE_H - -#include "icondelegate.h" - -/** - * @brief an icon delegate that takes the list of icons from a user-defines data role - */ -class GenericIconDelegate : public IconDelegate -{ -Q_OBJECT -public: - /** - * @brief constructor - * @param parent parent object - * @param role role of the itemmodel from which the icon list can be queried (as a QVariantList) - * @param logicalIndex logical index within the model. This is part of a "hack". Normally "empty" icons will be allocated the same - * space as a regular icon. This way the model can use empty icons as spacers and thus align same icons horizontally. - * Now, if you set the logical Index to a valid column and connect the columnResized slot to the sectionResized signal - * of the view, the delegate will turn off this behaviour if the column is smaller than "compactSize" - * @param compactSize see explanation of logicalIndex - */ - GenericIconDelegate(QObject *parent = NULL, int role = Qt::UserRole + 1, int logicalIndex = -1, int compactSize = 150); -public slots: - void columnResized(int logicalIndex, int oldSize, int newSize); -private: - virtual QList getIcons(const QModelIndex &index) const; - virtual size_t getNumIcons(const QModelIndex &index) const; -private: - int m_Role; - int m_LogicalIndex; - int m_CompactSize; - bool m_Compact; -}; - -#endif // PLUGINFLAGICONDELEGATE_H diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 5159871d..3532397d 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1089,6 +1089,13 @@ bool PluginList::eventFilter(QObject *obj, QEvent *event) diff = 1; } QModelIndexList rows = selectionModel->selectedRows(); + // remove elements that aren't supposed to be movable + QMutableListIterator iter(rows); + while (iter.hasNext()) { + if ((iter.next().flags() & Qt::ItemIsDragEnabled) == 0) { + iter.remove(); + } + } if (keyEvent->key() == Qt::Key_Down) { for (int i = 0; i < rows.size() / 2; ++i) { rows.swap(i, rows.size() - i - 1); diff --git a/src/viewmarkingscrollbar.cpp b/src/viewmarkingscrollbar.cpp index eca37562..f1b1ba34 100644 --- a/src/viewmarkingscrollbar.cpp +++ b/src/viewmarkingscrollbar.cpp @@ -14,6 +14,9 @@ ViewMarkingScrollBar::ViewMarkingScrollBar(QAbstractItemModel *model, QWidget *p void ViewMarkingScrollBar::paintEvent(QPaintEvent *event) { + if (m_Model == NULL) { + return; + } QScrollBar::paintEvent(event); QStyleOptionSlider styleOption; -- cgit v1.3.1 From d08e0ebab09b0a71c3042cd20b8994ec1c10a525 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 1 Mar 2015 11:28:49 +0100 Subject: - some fixes to how file changes are signaled and delayed --- src/iuserinterface.h | 4 +- src/mainwindow.cpp | 31 ++++------ src/mainwindow.h | 8 ++- src/modlist.cpp | 153 ++++++++++++++++++++++++++---------------------- src/modlist.h | 8 +++ src/modlistview.cpp | 1 + src/modlistview.h | 1 - src/organizercore.cpp | 26 ++++---- src/organizercore.h | 5 ++ src/plugincontainer.cpp | 3 +- src/pluginlist.cpp | 42 +++---------- src/pluginlist.h | 10 +--- src/profile.cpp | 51 ++++------------ src/profile.h | 17 ++---- src/version.rc | 4 +- 15 files changed, 162 insertions(+), 202 deletions(-) (limited to 'src/modlistview.cpp') diff --git a/src/iuserinterface.h b/src/iuserinterface.h index ff683bce..bcf800b7 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -5,6 +5,7 @@ #include "modinfo.h" #include #include +#include class IUserInterface @@ -27,12 +28,13 @@ public: virtual void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives) = 0; - virtual bool saveArchiveList() = 0; + virtual MOBase::DelayedFileWriterBase &archivesWriter() = 0; virtual void lock() = 0; virtual void unlock() = 0; virtual bool unlockClicked() = 0; + }; #endif // IUSERINTERFACE_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index af654e2f..9aa26db6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -160,6 +160,7 @@ MainWindow::MainWindow(const QString &exeName , m_OrganizerCore(organizerCore) , m_PluginContainer(pluginContainer) , m_DidUpdateMasterList(false) + , m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)) { ui->setupUi(this); this->setWindowTitle(ToQString(GameInfo::instance().getGameName()) + " Mod Organizer v" + m_OrganizerCore.getVersion().displayString()); @@ -1846,8 +1847,8 @@ void MainWindow::modorder_changed() } } m_OrganizerCore.refreshBSAList(); - m_OrganizerCore.currentProfile()->writeModlist(); - saveArchiveList(); + m_OrganizerCore.currentProfile()->modlistWriter().write(); + m_ArchiveListWriter.write(); m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins(); { // refresh selection @@ -1981,13 +1982,6 @@ void MainWindow::modRenamed(const QString &oldName, const QString &newName) } } - -void MainWindow::modlistChanged(int) -{ - m_ModListSortProxy->invalidate(); -} - - void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName) { const FileEntry::Ptr filePtr = m_OrganizerCore.directoryStructure()->findFile(ToWString(filePath)); @@ -2139,7 +2133,7 @@ void MainWindow::restoreBackup_clicked() void MainWindow::modlistChanged(const QModelIndex&, int) { - m_OrganizerCore.currentProfile()->writeModlist(); + m_OrganizerCore.currentProfile()->modlistWriter().write(); } void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&) @@ -2150,10 +2144,10 @@ void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QMode } else { m_OrganizerCore.modList()->setOverwriteMarkers(std::set(), std::set()); } - if ((m_ModListSortProxy != nullptr) +/* if ((m_ModListSortProxy != nullptr) && !m_ModListSortProxy->beingInvalidated()) { m_ModListSortProxy->invalidate(); - } + }*/ ui->modList->verticalScrollBar()->repaint(); } @@ -2727,7 +2721,7 @@ void MainWindow::savePrimaryCategory() } } -bool MainWindow::saveArchiveList() +void MainWindow::saveArchiveList() { if (m_OrganizerCore.isArchivesInit()) { SafeWriteFile archiveFile(m_OrganizerCore.currentProfile()->getArchivesFileName()); @@ -2746,12 +2740,10 @@ bool MainWindow::saveArchiveList() } if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { qDebug("%s saved", qPrintable(QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName()))); - return true; } } else { qWarning("archive list not initialised"); } - return false; } void MainWindow::checkModsForUpdates() @@ -3191,7 +3183,7 @@ void MainWindow::fixMods_clicked() } } - m_OrganizerCore.currentProfile()->writeModlist(); + m_OrganizerCore.currentProfile()->modlistWriter().write(); m_OrganizerCore.refreshLists(); std::set espsToActivate = dialog.getESPsToActivate(); @@ -4005,14 +3997,14 @@ void MainWindow::on_bsaList_customContextMenuRequested(const QPoint &pos) void MainWindow::bsaList_itemMoved() { - saveArchiveList(); + m_ArchiveListWriter.write(); m_CheckBSATimer.start(500); } void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) { - saveArchiveList(); + m_ArchiveListWriter.write(); m_CheckBSATimer.start(500); } @@ -4528,12 +4520,13 @@ void MainWindow::on_restoreButton_clicked() void MainWindow::on_saveModsButton_clicked() { - m_OrganizerCore.currentProfile()->writeModlistNow(true); + m_OrganizerCore.currentProfile()->modlistWriter().writeImmediately(true); QDateTime now = QDateTime::currentDateTime(); if (createBackup(m_OrganizerCore.currentProfile()->getModlistFileName(), now)) { MessageDialog::showMessage(tr("Backup of modlist created"), this); } } + void MainWindow::on_restoreModsButton_clicked() { QString modlistName = m_OrganizerCore.currentProfile()->getModlistFileName(); diff --git a/src/mainwindow.h b/src/mainwindow.h index d0e3aaf5..88419594 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -53,6 +53,7 @@ along with Mod Organizer. If not, see . #include "iuserinterface.h" #include #include +#include #ifndef Q_MOC_RUN #include #endif @@ -97,7 +98,7 @@ public: void setModListSorting(int index); void setESPListSorting(int index); - bool saveArchiveList(); + void saveArchiveList(); void registerPluginTool(MOBase::IPluginTool *tool); void registerModPage(MOBase::IPluginModPage *modPage); @@ -121,6 +122,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); @@ -315,6 +318,8 @@ private: LockedDialog *m_LockDialog { nullptr }; + MOBase::DelayedFileWriter m_ArchiveListWriter; + private slots: void showMessage(const QString &message); @@ -389,7 +394,6 @@ private slots: void addPrimaryCategoryCandidates(); void modDetailsUpdated(bool success); - void modlistChanged(int row); void modInstalled(const QString &modName); diff --git a/src/modlist.cpp b/src/modlist.cpp index 250b5caf..c6beee8b 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -458,6 +458,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) emit modlist_changed(index, role); } result = true; + emit dataChanged(index, index); } else if (role == Qt::EditRole) { switch (index.column()) { case COL_NAME: { @@ -469,7 +470,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) if (ok) { m_Profile->setModPriority(modID, newPriority); - emit modlist_changed(index, role); + emit modorder_changed(); result = true; } else { result = false; @@ -859,13 +860,14 @@ void ModList::removeRowForce(int row, const QModelIndex &parent) bool wasEnabled = m_Profile->modEnabled(row); - beginRemoveRows(parent, row, row); m_Profile->setModEnabled(row, false); - m_Profile->cancelWriteModlist(); // don't write modlist while we're changing it + + m_Profile->modlistWriter().cancel(); + beginRemoveRows(parent, row, row); ModInfo::removeMod(row); - m_Profile->refreshModStatus(); // removes the mod from the status list endRemoveRows(); - m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed + m_Profile->refreshModStatus(); // removes the mod from the status list + m_Profile->modlistWriter().write(); // this ensures the modified list gets written back before new mods can be installed if (wasEnabled) { emit removeOrigin(modInfo->name()); @@ -874,7 +876,6 @@ void ModList::removeRowForce(int row, const QModelIndex &parent) emit modUninstalled(modInfo->getInstallationFile()); } - bool ModList::removeRows(int row, int count, const QModelIndex &parent) { if (static_cast(row) >= ModInfo::getNumMods()) { @@ -903,6 +904,7 @@ bool ModList::removeRows(int row, int count, const QModelIndex &parent) removeRowForce(row + i, parent); } } + return success; } @@ -997,6 +999,76 @@ QString ModList::getColumnToolTip(int column) } +bool ModList::moveSelection(QAbstractItemView *itemView, int direction) +{ + QItemSelectionModel *selectionModel = itemView->selectionModel(); + + const QAbstractProxyModel *proxyModel = qobject_cast(selectionModel->model()); + const QSortFilterProxyModel *filterModel = nullptr; + + while ((filterModel == nullptr) && (proxyModel != nullptr)) { + filterModel = qobject_cast(proxyModel); + if (filterModel == nullptr) { + proxyModel = qobject_cast(proxyModel->sourceModel()); + } + } + if (filterModel == nullptr) { + return true; + } + + int offset = -1; + if (((direction < 0) && (filterModel->sortOrder() == Qt::DescendingOrder)) || + ((direction > 0) && (filterModel->sortOrder() == Qt::AscendingOrder))) { + offset = 1; + } + + QModelIndexList rows = selectionModel->selectedRows(); + if (direction > 0) { + for (int i = 0; i < rows.size() / 2; ++i) { + rows.swap(i, rows.size() - i - 1); + } + } + for (QModelIndex idx : rows) { + if (filterModel != nullptr) { + idx = filterModel->mapToSource(idx); + } + int newPriority = m_Profile->getModPriority(idx.row()) + offset; + if ((newPriority >= 0) && (newPriority < static_cast(m_Profile->numRegularMods()))) { + m_Profile->setModPriority(idx.row(), newPriority); + notifyChange(idx.row()); + } + } + emit modorder_changed(); + return true; +} + +bool ModList::deleteSelection(QAbstractItemView *itemView) +{ + QItemSelectionModel *selectionModel = itemView->selectionModel(); + + QModelIndexList rows = selectionModel->selectedRows(); + if (rows.count() > 1) { + emit removeSelectedMods(); + } else if (rows.count() == 1) { + removeRow(rows[0].data(Qt::UserRole + 1).toInt(), QModelIndex()); + } + return true; +} + +bool ModList::toggleSelection(QAbstractItemView *itemView) +{ + QAbstractItemModel *model = itemView->model(); + QItemSelectionModel *selectionModel = itemView->selectionModel(); + + for (QModelIndex idx : selectionModel->selectedRows()) { + int oldState = idx.data(Qt::CheckStateRole).toInt(); + model->setData(idx, oldState == Qt::Unchecked ? Qt::Checked + : Qt::Unchecked, + Qt::CheckStateRole); + } + return true; +} + bool ModList::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::ContextMenu) { @@ -1010,74 +1082,15 @@ bool ModList::eventFilter(QObject *obj, QEvent *event) } else if ((event->type() == QEvent::KeyPress) && (m_Profile != nullptr)) { QAbstractItemView *itemView = qobject_cast(obj); QKeyEvent *keyEvent = static_cast(event); + if ((itemView != nullptr) && (keyEvent->modifiers() == Qt::ControlModifier) - && ((keyEvent->key() == Qt::Key_Up) - || (keyEvent->key() == Qt::Key_Down))) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - const QAbstractProxyModel *proxyModel = qobject_cast(selectionModel->model()); - const QSortFilterProxyModel *filterModel = nullptr; - while ((filterModel == nullptr) && (proxyModel != nullptr)) { - filterModel = qobject_cast(proxyModel); - if (filterModel == nullptr) { - proxyModel = qobject_cast(proxyModel->sourceModel()); - } - } - if (filterModel == nullptr) { - return true; - } - int diff = -1; - if (((keyEvent->key() == Qt::Key_Up) && (filterModel->sortOrder() == Qt::DescendingOrder)) || - ((keyEvent->key() == Qt::Key_Down) && (filterModel->sortOrder() == Qt::AscendingOrder))) { - diff = 1; - } - QModelIndexList rows = selectionModel->selectedRows(); - if (keyEvent->key() == Qt::Key_Down) { - for (int i = 0; i < rows.size() / 2; ++i) { - rows.swap(i, rows.size() - i - 1); - } - } - foreach (QModelIndex idx, rows) { - if (filterModel != nullptr) { - idx = filterModel->mapToSource(idx); - } - int newPriority = m_Profile->getModPriority(idx.row()) + diff; - if ((newPriority >= 0) && (newPriority < static_cast(m_Profile->numRegularMods()))) { - m_Profile->setModPriority(idx.row(), newPriority); - notifyChange(idx.row()); - } - } - emit modorder_changed(); - return true; + && ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { + return moveSelection(itemView, keyEvent->key() == Qt::Key_Up ? -1 : 1); } else if (keyEvent->key() == Qt::Key_Delete) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - QModelIndexList rows = selectionModel->selectedRows(); - if (rows.count() > 1) { - emit removeSelectedMods(); - } else if (rows.count() == 1) { - removeRow(rows[0].data(Qt::UserRole + 1).toInt(), QModelIndex()); - } - return true; + return deleteSelection(itemView); } else if (keyEvent->key() == Qt::Key_Space) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - const QSortFilterProxyModel *proxyModel = qobject_cast(selectionModel->model()); - - QModelIndex minRow, maxRow; - foreach (QModelIndex idx, selectionModel->selectedRows()) { - if (proxyModel != nullptr) { - idx = proxyModel->mapToSource(idx); - } - if (!minRow.isValid() || (idx.row() < minRow.row())) { - minRow = idx; - } - if (!maxRow.isValid() || (idx.row() > maxRow.row())) { - maxRow = idx; - } - int oldState = idx.data(Qt::CheckStateRole).toInt(); - setData(idx, oldState == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); - } - emit dataChanged(minRow, maxRow); - return true; + return toggleSelection(itemView); } } return QObject::eventFilter(obj, event); diff --git a/src/modlist.h b/src/modlist.h index 73a49745..6a10cb3e 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -40,6 +40,8 @@ along with Mod Organizer. If not, see . #include +class QSortFilterProxyModel; + /** * Model presenting an overview of the installed mod * This is used in a view in the main window of MO. It combines general information about @@ -261,6 +263,12 @@ private: ModStates state(unsigned int modIndex) const; + bool moveSelection(QAbstractItemView *itemView, int direction); + + bool deleteSelection(QAbstractItemView *itemView); + + bool toggleSelection(QAbstractItemView *itemView); + private slots: private: diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 50a13a8c..fe2d3e57 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -53,3 +53,4 @@ void ModListView::setModel(QAbstractItemModel *model) QTreeView::setModel(model); setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); } + diff --git a/src/modlistview.h b/src/modlistview.h index 4907225f..6cd114b0 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -19,7 +19,6 @@ public slots: private: ViewMarkingScrollBar *m_Scrollbar; - }; #endif // MODLISTVIEW_H diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 0ea3ccb1..6a5e08d0 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -24,6 +24,7 @@ #include #include #include +#include using namespace MOShared; @@ -126,6 +127,7 @@ OrganizerCore::OrganizerCore(const QSettings &initSettings) , m_AskForNexusPW(false) , m_DirectoryUpdate(false) , m_ArchivesInit(false) + , m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) { m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); @@ -140,13 +142,14 @@ OrganizerCore::OrganizerCore(const QSettings &initSettings) connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this, SLOT(directory_refreshed())); connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); - connect(&m_PluginList, SIGNAL(saveTimer()), this, SLOT(savePluginList())); connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool))); connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame*)), &m_Settings, SLOT(managedGameChanged(MOBase::IPluginGame*))); + connect(&m_PluginList, &PluginList::writePluginsList, &m_PluginListsWriter, &DelayedFileWriterBase::write); + // make directory refresher run in a separate thread m_RefresherThread.start(); m_DirectoryRefresher.moveToThread(&m_RefresherThread); @@ -319,11 +322,11 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, QWidget *wid m_UserInterface = userInterface; if (widget != nullptr) { - connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex,int)), widget, SLOT(modorder_changed())); +// connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), widget, SLOT(modorder_changed())); + connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), widget, SLOT(modlistChanged(QModelIndex, int))); connect(&m_ModList, SIGNAL(showMessage(QString)), widget, SLOT(showMessage(QString))); connect(&m_ModList, SIGNAL(modRenamed(QString,QString)), widget, SLOT(modRenamed(QString,QString))); connect(&m_ModList, SIGNAL(modUninstalled(QString)), widget, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), widget, SLOT(modlistChanged(QModelIndex, int))); connect(&m_ModList, SIGNAL(removeSelectedMods()), widget, SLOT(removeMod_clicked())); connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget, SLOT(displayColumnSelection(QPoint))); connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget, SLOT(fileMoved(QString, QString, QString))); @@ -920,7 +923,7 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, const QString & // need to make sure all data is saved before we start the application if (m_CurrentProfile != nullptr) { - m_CurrentProfile->writeModlistNow(true); + m_CurrentProfile->modlistWriter().writeImmediately(true); } // TODO: should also pass arguments @@ -1066,7 +1069,7 @@ void OrganizerCore::refreshModList(bool saveChanges) { // don't lose changes! if (saveChanges) { - m_CurrentProfile->writeModlistNow(true); + m_CurrentProfile->modlistWriter().writeImmediately(true); } ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); @@ -1079,7 +1082,7 @@ void OrganizerCore::refreshModList(bool saveChanges) void OrganizerCore::refreshESPList() { - m_CurrentProfile->writeModlist(); + m_CurrentProfile->modlistWriter().write(); // clear list try { @@ -1151,8 +1154,7 @@ void OrganizerCore::updateModActiveState(int index, bool active) } m_PluginList.refreshLoadOrder(); // immediately save affected lists - savePluginList(); -// refreshBSAList(); + m_PluginListsWriter.writeImmediately(false); } void OrganizerCore::updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo) @@ -1172,7 +1174,7 @@ void OrganizerCore::updateModInDirectoryStructure(unsigned int index, ModInfo::P // 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->saveArchiveList(); + m_UserInterface->archivesWriter().write(); } m_DirectoryRefresher.setMods(m_CurrentProfile->getActiveMods(), enabledArchives()); @@ -1257,7 +1259,7 @@ std::set OrganizerCore::enabledArchives() void OrganizerCore::refreshDirectoryStructure() { if (!m_DirectoryUpdate) { - m_CurrentProfile->writeModlistNow(true); + m_CurrentProfile->modlistWriter().writeImmediately(true); m_DirectoryUpdate = true; std::vector > activeModList = m_CurrentProfile->getActiveMods(); @@ -1447,7 +1449,7 @@ bool OrganizerCore::saveCurrentLists() try { savePluginList(); if (m_UserInterface != nullptr) { - m_UserInterface->saveArchiveList(); + m_UserInterface->archivesWriter().write(); } } catch (const std::exception &e) { reportError(tr("failed to save load order: %1").arg(e.what())); @@ -1470,7 +1472,7 @@ void OrganizerCore::prepareStart() { if (m_CurrentProfile == nullptr) { return; } - m_CurrentProfile->writeModlist(); + m_CurrentProfile->modlistWriter().write(); m_CurrentProfile->createTweakedIniFile(); saveCurrentLists(); m_Settings.setupLoadMechanism(); diff --git a/src/organizercore.h b/src/organizercore.h index 3191e2a9..d8a3e232 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -124,6 +125,8 @@ public: void createDefaultProfile(); + MOBase::DelayedFileWriter &pluginsWriter() { return m_PluginListsWriter; } + public: MOBase::IGameInfo &gameInfo() const; MOBase::IModRepositoryBridge *createNexusBridge() const; @@ -255,6 +258,8 @@ private: bool m_DirectoryUpdate; bool m_ArchivesInit; + MOBase::DelayedFileWriter m_PluginListsWriter; + }; #endif // ORGANIZERCORE_H diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 1b06d1a6..6bc9d12b 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -317,7 +317,8 @@ QString PluginContainer::fullDescription(unsigned int key) const { switch (key) { case PROBLEM_PLUGINSNOTLOADED: { - QString result = tr("The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version:") + "
    "; + QString result = tr("The following plugins could not be loaded. The reason may be missing " + "dependencies (i.e. python) or an outdated version:") + "
      "; for (const QString &plugin : m_FailedPlugins) { result += "
    • " + plugin + "
    • "; } diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index f72ab690..f8a27ef4 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -69,23 +69,12 @@ static bool ByPriority(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo static bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { return QFileInfo(LHS.m_FullPath).lastModified() < QFileInfo(RHS.m_FullPath).lastModified(); -/* QString lhsExtension = LHS.m_Name.right(3).toLower(); - QString rhsExtension = RHS.m_Name.right(3).toLower(); - if (lhsExtension != rhsExtension) { - return lhsExtension == "esm"; - } - - return ::CompareFileTime(&LHS.m_Time, &RHS.m_Time) < 0;*/ } PluginList::PluginList(QObject *parent) : QAbstractItemModel(parent) , m_FontMetrics(QFont()) - , m_SaveTimer(this) { - m_SaveTimer.setSingleShot(true); - connect(&m_SaveTimer, SIGNAL(timeout()), this, SIGNAL(saveTimer())); - m_Utf8Codec = QTextCodec::codecForName("utf-8"); m_LocalCodec = QTextCodec::codecForName("Windows-1252"); @@ -238,7 +227,8 @@ void PluginList::enableESP(const QString &name, bool enable) if (iter != m_ESPsByName.end()) { m_ESPs[iter->second].m_Enabled = enable; - startSaveTime(); + + emit writePluginsList(); } else { reportError(tr("esp not found: %1").arg(name)); } @@ -252,7 +242,7 @@ void PluginList::enableAll() for (std::vector::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { iter->m_Enabled = true; } - startSaveTime(); + emit writePluginsList(); } } @@ -266,7 +256,7 @@ void PluginList::disableAll() iter->m_Enabled = false; } } - startSaveTime(); + emit writePluginsList(); } } @@ -382,7 +372,7 @@ void PluginList::readEnabledFrom(const QString &fileName) m_ESPs[iter->second].m_Enabled = true; } else { qWarning("plugin %s not found", modName.toUtf8().constData()); - startSaveTime(); + emit writePluginsList(); } } } @@ -503,8 +493,6 @@ void PluginList::saveTo(const QString &pluginFileName } else if (QFile::exists(deleterFileName)) { shellDelete(QStringList() << deleterFileName); } - - m_SaveTimer.stop(); } @@ -581,7 +569,8 @@ void PluginList::lockESPIndex(int index, bool lock) m_LockedOrder.erase(iter); } } - startSaveTime(); +qDebug(__FUNCTION__); + emit writePluginsList(); } @@ -631,7 +620,7 @@ void PluginList::refreshLoadOrder() setPluginPriority(index, temp); m_ESPs[index].m_LoadOrder = iter->first; syncLoadOrder(); - startSaveTime(); + emit writePluginsList(); } } } @@ -897,7 +886,7 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int emit dataChanged(modIndex, modIndex); refreshLoadOrder(); - startSaveTime(); + emit writePluginsList(); result = true; } else if (role == Qt::EditRole) { @@ -1034,21 +1023,8 @@ void PluginList::changePluginPriority(std::vector rows, int newPriority) layoutChange.finish(); refreshLoadOrder(); - - startSaveTime(); -} - - -void PluginList::startSaveTime() -{ - testMasters(); - - if (!m_SaveTimer.isActive()) { - m_SaveTimer.start(2000); - } } - bool PluginList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) { if (action == Qt::IgnoreAction) { diff --git a/src/pluginlist.h b/src/pluginlist.h index e0913840..80fcc25b 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -262,10 +262,7 @@ signals: **/ void esplist_changed(); - /** - * @brief emitted when the plugin list should be saved - */ - void saveTimer(); + void writePluginsList(); private: @@ -310,8 +307,6 @@ private: void setPluginPriority(int row, int &newPriority); void changePluginPriority(std::vector rows, int newPriority); - void startSaveTime(); - void testMasters(); private: @@ -336,14 +331,11 @@ private: QTextCodec *m_Utf8Codec; QTextCodec *m_LocalCodec; - mutable QTimer m_SaveTimer; - SignalRefreshed m_Refreshed; SignalPluginMoved m_PluginMoved; QTemporaryFile m_TempFile; - }; #endif // PLUGINLIST_H diff --git a/src/profile.cpp b/src/profile.cpp index 5d31533a..c1e6e5fc 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -34,6 +34,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #define WIN32_LEAN_AND_MEAN #include @@ -43,10 +44,10 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; + Profile::Profile() - : m_SaveTimer(nullptr) + : m_ModListWriter(std::bind(&Profile::writeModlistNow, this)) { - initTimer(); } void Profile::touchFile(QString fileName) @@ -58,10 +59,9 @@ void Profile::touchFile(QString fileName) } Profile::Profile(const QString &name, IPluginGame *gamePlugin, bool useDefaultSettings) - : m_SaveTimer(nullptr) + : m_ModListWriter(std::bind(&Profile::writeModlistNow, this)) , m_GamePlugin(gamePlugin) { - initTimer(); QString profilesDir = qApp->property("dataPath").toString() + "/" + ToQString(AppConfig::profilesPath()); QDir profileBase(profilesDir); @@ -100,10 +100,10 @@ Profile::Profile(const QString &name, IPluginGame *gamePlugin, bool useDefaultSe Profile::Profile(const QDir &directory, IPluginGame *gamePlugin) - : m_Directory(directory), m_SaveTimer(nullptr) + : m_Directory(directory) + , m_ModListWriter(std::bind(&Profile::writeModlistNow, this)) { assert(gamePlugin != nullptr); - initTimer(); if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { qWarning("missing modlist.txt in %s", qPrintable(directory.path())); @@ -124,52 +124,24 @@ Profile::Profile(const QDir &directory, IPluginGame *gamePlugin) Profile::Profile(const Profile &reference) : m_Directory(reference.m_Directory) - , m_SaveTimer(nullptr) + , m_ModListWriter(std::bind(&Profile::writeModlistNow, this)) { - initTimer(); refreshModStatus(); } Profile::~Profile() { - writeModlistNow(); -} - - -void Profile::initTimer() -{ - m_SaveTimer = new QTimer(this); - m_SaveTimer->setSingleShot(true); - connect(m_SaveTimer, SIGNAL(timeout()), this, SLOT(writeModlistNow())); + m_ModListWriter.writeImmediately(true); } - bool Profile::exists() const { return m_Directory.exists(); } - -void Profile::writeModlist() const -{ - if (!m_SaveTimer->isActive()) { - m_SaveTimer->start(2000); - } -} - - -void Profile::cancelWriteModlist() const +void Profile::writeModlistNow() { - m_SaveTimer->stop(); -} - - -void Profile::writeModlistNow(bool onlyOnTimer) const -{ - if (onlyOnTimer && !m_SaveTimer->isActive()) return; - - m_SaveTimer->stop(); if (!m_Directory.exists()) return; try { @@ -366,7 +338,7 @@ void Profile::refreshModStatus() file.close(); updateIndices(); if (modStatusModified) { - writeModlist(); + m_ModListWriter.write(); } } @@ -456,7 +428,6 @@ void Profile::setModEnabled(unsigned int index, bool enabled) } } - bool Profile::modEnabled(unsigned int index) const { if (index >= m_ModStatus.size()) { @@ -508,7 +479,7 @@ void Profile::setModPriority(unsigned int index, int &newPriority) m_ModStatus.at(index).m_Priority = newPriorityTemp; updateIndices(); - writeModlist(); + m_ModListWriter.write(); } Profile *Profile::createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame *gamePlugin) diff --git a/src/profile.h b/src/profile.h index 5eaa1ea1..342b6fa0 100644 --- a/src/profile.h +++ b/src/profile.h @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "modinfo.h" #include +#include #include #include #include @@ -89,16 +90,7 @@ public: **/ static Profile *createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame *gamePlugin); - /** - * @brief write out the modlist.txt - **/ - void writeModlist() const; - - /** - * cancel any potential request to write modlist.txt. This is used to ensure no invalid modlist is - * saved while it's being modified - */ - void cancelWriteModlist() const; + MOBase::DelayedFileWriter &modlistWriter() { return m_ModListWriter; } /** * @brief test if this profile uses archive invalidation @@ -283,7 +275,7 @@ signals: public slots: - void writeModlistNow(bool onlyOnTimer = false) const; + void writeModlistNow(); private: @@ -310,6 +302,7 @@ private: void mergeTweak(const QString &tweakName, const QString &tweakedIni) const; void mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const; void touchFile(QString fileName); + void finishChangeStatus() const; private: @@ -322,7 +315,7 @@ private: std::vector m_ModIndexByPriority; unsigned int m_NumRegularMods; - QTimer *m_SaveTimer; + MOBase::DelayedFileWriter m_ModListWriter; }; diff --git a/src/version.rc b/src/version.rc index 68d7cd23..d6fe2f85 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,7 +1,7 @@ #include "Winver.h" -#define VER_FILEVERSION 1,3,0,0 -#define VER_FILEVERSION_STR "1,3,0,0\0" +#define VER_FILEVERSION 1,3,1,0 +#define VER_FILEVERSION_STR "1,3,1,0\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1