diff options
| author | Project579 <star579avatar@gmail.com> | 2018-12-29 11:56:42 +0100 |
|---|---|---|
| committer | Project579 <star579avatar@gmail.com> | 2018-12-29 11:56:42 +0100 |
| commit | d6f9e6d900f4a92da6bb252f0f8a223d59819e45 (patch) | |
| tree | 1e39b666e3d52632a471152630d2f07d4e73125d | |
| parent | 4436d376a8d426867f217b03838570a424b09c5f (diff) | |
| parent | eb50d0b44773f17fd8edccd557b7a2135c2cdb76 (diff) | |
Merge branch 'Develop' into archive_conflicts_2
Updating to 2.1.7alpha1
34 files changed, 860 insertions, 174 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3f004ddc..a084bae0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -92,6 +92,7 @@ SET(organizer_SRCS eventfilter.cpp moshortcut.cpp listdialog.cpp + lcdnumber.cpp shared/windows_error.cpp shared/error_report.cpp @@ -187,6 +188,7 @@ SET(organizer_HDRS descriptionpage.h moshortcut.h listdialog.h + lcdnumber.h shared/windows_error.h shared/error_report.h diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 34a90534..c62d774c 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -120,8 +120,8 @@ void DownloadListWidgetDelegate::paintPendingDownload(int downloadIndex) const void DownloadListWidgetDelegate::paintRegularDownload(int downloadIndex) const
{
QString name = m_MetaDisplay ? m_Manager->getDisplayName(downloadIndex) : m_Manager->getFileName(downloadIndex);
- if (name.length() > 53) {
- name.truncate(50);
+ if (name.length() > 120) {
+ name.truncate(120);
name.append("...");
}
m_NameLabel->setText(name);
diff --git a/src/downloadlistwidget.ui b/src/downloadlistwidget.ui index 9e238509..112ca231 100644 --- a/src/downloadlistwidget.ui +++ b/src/downloadlistwidget.ui @@ -53,7 +53,7 @@ </property>
<property name="maximumSize">
<size>
- <width>323</width>
+ <width>16777215</width>
<height>16777215</height>
</size>
</property>
@@ -69,7 +69,7 @@ </property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
+ <width>10</width>
<height>20</height>
</size>
</property>
@@ -84,12 +84,12 @@ </item>
<item>
<widget class="QLabel" name="label_2">
+ <property name="visible">
+ <bool>false</bool>
+ </property>
<property name="text">
<string notr="true">KB</string>
</property>
- <property name="visible">
- <bool>false</bool>
- </property>
</widget>
</item>
</layout>
diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index fcf93754..644578de 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -119,8 +119,8 @@ void DownloadListWidgetCompactDelegate::paintPendingDownload(int downloadIndex) void DownloadListWidgetCompactDelegate::paintRegularDownload(int downloadIndex) const
{
QString name = m_MetaDisplay ? m_Manager->getDisplayName(downloadIndex) : m_Manager->getFileName(downloadIndex);
- if (name.length() > 53) {
- name.truncate(50);
+ if (name.length() > 100) {
+ name.truncate(100);
name.append("...");
}
m_NameLabel->setText(name);
diff --git a/src/downloadlistwidgetcompact.ui b/src/downloadlistwidgetcompact.ui index a3fa958c..ab634fb5 100644 --- a/src/downloadlistwidgetcompact.ui +++ b/src/downloadlistwidgetcompact.ui @@ -38,7 +38,7 @@ <item>
<widget class="QLabel" name="nameLabel">
<property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
diff --git a/src/helper.cpp b/src/helper.cpp index b7fc866c..59a2d3d1 100644 --- a/src/helper.cpp +++ b/src/helper.cpp @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <Windows.h>
#include <QDir>
+#include <QApplication>
using MOBase::reportError;
@@ -33,7 +34,7 @@ using MOBase::reportError; namespace Helper {
-static bool helperExec(LPCWSTR moDirectory, LPCWSTR commandLine)
+static bool helperExec(LPCWSTR moDirectory, LPCWSTR commandLine, BOOL async)
{
wchar_t fileName[MAX_PATH];
_snwprintf(fileName, MAX_PATH, L"%ls\\helper.exe", moDirectory);
@@ -51,7 +52,16 @@ static bool helperExec(LPCWSTR moDirectory, LPCWSTR commandLine) ::ShellExecuteExW(&execInfo);
- if ((execInfo.hProcess == 0) || (::WaitForSingleObject(execInfo.hProcess, INFINITE) != WAIT_OBJECT_0)) {
+ if (execInfo.hProcess == 0) {
+ reportError(QObject::tr("helper failed"));
+ return false;
+ }
+
+ if (async) {
+ return true;
+ }
+
+ if (::WaitForSingleObject(execInfo.hProcess, INFINITE) != WAIT_OBJECT_0) {
reportError(QObject::tr("helper failed"));
return false;
}
@@ -76,7 +86,7 @@ bool init(const std::wstring &moPath, const std::wstring &dataPath) _snwprintf(commandLine, 32768, L"init \"%ls\" \"%ls\"",
dataPath.c_str(), userName);
- bool res = helperExec(moPath.c_str(), commandLine);
+ bool res = helperExec(moPath.c_str(), commandLine, FALSE);
delete [] commandLine;
return res;
@@ -89,7 +99,23 @@ bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath) _snwprintf(commandLine, 32768, L"backdateBSA \"%ls\"",
dataPath.c_str());
- bool res = helperExec(moPath.c_str(), commandLine);
+ bool res = helperExec(moPath.c_str(), commandLine, FALSE);
+ delete [] commandLine;
+
+ return res;
+}
+
+
+bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir)
+{
+ wchar_t *commandLine = new wchar_t[32768];
+ _snwprintf(commandLine, 32768, L"adminLaunch %d \"%ls\" \"%ls\"",
+ ::GetCurrentProcessId(),
+ moFile.c_str(),
+ workingDir.c_str()
+ );
+
+ bool res = helperExec(moPath.c_str(), commandLine, TRUE);
delete [] commandLine;
return res;
diff --git a/src/helper.h b/src/helper.h index cd4b7883..f6667a84 100644 --- a/src/helper.h +++ b/src/helper.h @@ -26,7 +26,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. /**
* @brief Convenience functions to work with the external helper program.
- *
+ *
* The mo_helper program is used to make changes on the system that require administrative
* rights, so that ModOrganizer itself can run without special privileges
**/
@@ -34,7 +34,7 @@ namespace Helper { /**
* @brief initialise the specified directory for use with mod organizer.
- *
+ *
* This will create all required sub-directories and give the user running ModOrganizer
* write-access
*
@@ -50,6 +50,14 @@ bool init(const std::wstring &moPath, const std::wstring &dataPath); **/
bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath);
+/**
+ * @brief waits for the current process to exit and restarts it as an administrator
+ * @param moPath absolute path to the modOrganizer base directory
+ * @param moFile file name of modOrganizer
+ * @param workingDir current working directory
+ **/
+bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir);
+
}
diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 64e58358..76b1e086 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -677,7 +677,14 @@ void InstallationManager::postInstallCleanup() // TODO: this doesn't yet remove directories. Also, the files may be left there if this point isn't reached for (const QString &tempFile : m_TempFilesToDelete) { QFileInfo fileInfo(QDir::tempPath() + "/" + tempFile); - QFile::remove(fileInfo.absoluteFilePath()); + if (fileInfo.exists()) { + if (!fileInfo.isReadable() || !fileInfo.isWritable()) { + QFile::setPermissions(fileInfo.absoluteFilePath(), QFile::ReadOther | QFile::WriteOther); + } + if (!QFile::remove(fileInfo.absoluteFilePath())) { + qWarning() << "Unable to delete " << fileInfo.absoluteFilePath(); + } + } directoriesToRemove.insert(fileInfo.absolutePath()); } diff --git a/src/lcdnumber.cpp b/src/lcdnumber.cpp new file mode 100644 index 00000000..3191b434 --- /dev/null +++ b/src/lcdnumber.cpp @@ -0,0 +1,37 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ + +#include "lcdnumber.h" + +#include <QToolTip> +#include <QTimer> + +LCDNumber::LCDNumber(QWidget *parent) + : QLCDNumber(parent) +{ +} + +void LCDNumber::mousePressEvent(QMouseEvent *event) +{ + m_toolTipPosition = mapToGlobal(event->pos()); + QTimer::singleShot(100, this, SLOT(showToolTip())); +} + +void LCDNumber::showToolTip() +{ + QToolTip::showText(m_toolTipPosition, toolTip()); +} diff --git a/src/lcdnumber.h b/src/lcdnumber.h new file mode 100644 index 00000000..2f8e0454 --- /dev/null +++ b/src/lcdnumber.h @@ -0,0 +1,35 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ + +#include <QLCDNumber> +#include <QMouseEvent> +#include <QWidget> + +class LCDNumber : public QLCDNumber +{ + Q_OBJECT + +public: + LCDNumber(QWidget *parent = nullptr); + void mousePressEvent(QMouseEvent *event); + +public slots: + void showToolTip(); + +private: + QPoint m_toolTipPosition; +}; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 73a40f46..6f49822a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -245,8 +245,6 @@ MainWindow::MainWindow(QSettings &initSettings statusBar()->clearMessage();
statusBar()->hide();
- ui->actionEndorseMO->setVisible(false);
-
updateProblemsButton();
// Setup toolbar
@@ -262,6 +260,10 @@ MainWindow::MainWindow(QSettings &initSettings actionToToolButton(ui->actionHelp);
createHelpWidget();
+ actionToToolButton(ui->actionEndorseMO);
+ createEndorseWidget();
+ ui->actionEndorseMO->setVisible(false);
+
for (QAction *action : ui->toolBar->actions()) {
if (action->isSeparator()) {
// insert spacers
@@ -285,14 +287,17 @@ MainWindow::MainWindow(QSettings &initSettings ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList));
ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate);
ui->modList->header()->installEventFilter(m_OrganizerCore.modList());
+ connect(ui->modList->header(), SIGNAL(sectionResized(int, int, int)), this, SLOT(modListSectionResized(int, int, int)));
bool modListAdjusted = registerWidgetState(ui->modList->objectName(), ui->modList->header(), "mod_list_state");
if (modListAdjusted) {
// hack: force the resize-signal to be triggered because restoreState doesn't seem to do that
- int sectionSize = ui->modList->header()->sectionSize(ModList::COL_CONTENT);
- ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize + 1);
- ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize);
+ for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) {
+ int sectionSize = ui->modList->header()->sectionSize(column);
+ ui->modList->header()->resizeSection(column, sectionSize + 1);
+ ui->modList->header()->resizeSection(column, sectionSize);
+ }
} else {
// hide these columns by default
ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true);
@@ -362,6 +367,7 @@ MainWindow::MainWindow(QSettings &initSettings connect(ui->modList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(modlistSelectionChanged(QModelIndex,QModelIndex)));
connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool)));
+ connect(m_ModListSortProxy, SIGNAL(layoutChanged()), this, SLOT(updateModCount()));
connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString)));
connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString)));
@@ -468,17 +474,25 @@ MainWindow::MainWindow(QSettings &initSettings // set the name of the widget to the name of the action to allow styling
ui->toolBar->widgetForAction(action)->setObjectName(action->objectName());
}
+ emit updatePluginCount();
+ emit updateModCount();
}
MainWindow::~MainWindow()
{
- cleanup();
+ try {
+ cleanup();
- m_PluginContainer.setUserInterface(nullptr, nullptr);
- m_OrganizerCore.setUserInterface(nullptr, nullptr);
- m_IntegratedBrowser.close();
- delete ui;
+ m_PluginContainer.setUserInterface(nullptr, nullptr);
+ m_OrganizerCore.setUserInterface(nullptr, nullptr);
+ m_IntegratedBrowser.close();
+ delete ui;
+ } catch (std::exception &e) {
+ QMessageBox::critical(nullptr, tr("Crash on exit"),
+ tr("MO crashed while exiting. Some settings may not be saved.\n\nError: %1").arg(e.what()),
+ QMessageBox::Ok);
+ }
}
@@ -709,6 +723,25 @@ void MainWindow::about() }
+void MainWindow::createEndorseWidget()
+{
+ QToolButton *toolBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionEndorseMO));
+ QMenu *buttonMenu = toolBtn->menu();
+ if (buttonMenu == nullptr) {
+ return;
+ }
+ buttonMenu->clear();
+
+ QAction *endorseAction = new QAction(tr("Endorse"), buttonMenu);
+ connect(endorseAction, SIGNAL(triggered()), this, SLOT(on_actionEndorseMO_triggered()));
+ buttonMenu->addAction(endorseAction);
+
+ QAction *wontEndorseAction = new QAction(tr("Won't Endorse"), buttonMenu);
+ connect(wontEndorseAction, SIGNAL(triggered()), this, SLOT(wontEndorse()));
+ buttonMenu->addAction(wontEndorseAction);
+}
+
+
void MainWindow::createHelpWidget()
{
QToolButton *toolBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionHelp));
@@ -775,13 +808,17 @@ void MainWindow::createHelpWidget() void MainWindow::modFilterActive(bool filterActive)
{
+ ui->clearFiltersButton->setVisible(filterActive);
if (filterActive) {
// m_OrganizerCore.modList()->setOverwriteMarkers(std::set<unsigned int>(), std::set<unsigned int>());
ui->modList->setStyleSheet("QTreeView { border: 2px ridge #f00; }");
+ ui->activeModsCounter->setStyleSheet("QLCDNumber { border: 2px ridge #f00; }");
} else if (ui->groupCombo->currentIndex() != 0) {
ui->modList->setStyleSheet("QTreeView { border: 2px ridge #337733; }");
+ ui->activeModsCounter->setStyleSheet("");
} else {
ui->modList->setStyleSheet("");
+ ui->activeModsCounter->setStyleSheet("");
}
}
@@ -789,9 +826,12 @@ void MainWindow::espFilterChanged(const QString &filter) {
if (!filter.isEmpty()) {
ui->espList->setStyleSheet("QTreeView { border: 2px ridge #f00; }");
+ ui->activePluginsCounter->setStyleSheet("QLCDNumber { border: 2px ridge #f00; }");
} else {
ui->espList->setStyleSheet("");
+ ui->activePluginsCounter->setStyleSheet("");
}
+ updatePluginCount();
}
void MainWindow::downloadFilterChanged(const QString &filter)
@@ -1167,6 +1207,8 @@ void MainWindow::activateSelectedProfile() refreshSaveList();
m_OrganizerCore.refreshModList();
+ updateModCount();
+ updatePluginCount();
}
void MainWindow::on_profileBox_currentIndexChanged(int index)
@@ -1211,9 +1253,11 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) }
}
-void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly)
+void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly, QIcon *fileIcon, QIcon *folderIcon)
{
bool isDirectory = true;
+ //QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder);
+ //QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File);
std::wostringstream temp;
temp << directorySoFar << "\\" << directoryEntry.getName();
@@ -1226,11 +1270,11 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director columns.append("");
if (!(*current)->isEmpty()) {
QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns);
- directoryChild->setData(0, Qt::DecorationRole, (new QFileIconProvider())->icon(QFileIconProvider::Folder));
+ directoryChild->setData(0, Qt::DecorationRole, *folderIcon);
directoryChild->setData(0, Qt::UserRole + 3, isDirectory);
if (conflictsOnly || !m_showArchiveData) {
- updateTo(directoryChild, temp.str(), **current, conflictsOnly);
+ updateTo(directoryChild, temp.str(), **current, conflictsOnly, fileIcon, folderIcon);
if (directoryChild->childCount() != 0) {
subTree->addChild(directoryChild);
}
@@ -1249,7 +1293,7 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director }
else {
QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns);
- directoryChild->setData(0, Qt::DecorationRole, (new QFileIconProvider())->icon(QFileIconProvider::Folder));
+ directoryChild->setData(0, Qt::DecorationRole, *folderIcon);
directoryChild->setData(0, Qt::UserRole + 3, isDirectory);
subTree->addChild(directoryChild);
}
@@ -1298,7 +1342,7 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director fileChild->setFont(1, font);
}
fileChild->setData(0, Qt::UserRole, ToQString(current->getFullPath()));
- fileChild->setData(0, Qt::DecorationRole, (new QFileIconProvider())->icon(QFileIconProvider::File));
+ fileChild->setData(0, Qt::DecorationRole, *fileIcon);
fileChild->setData(0, Qt::UserRole + 3, isDirectory);
fileChild->setData(0, Qt::UserRole + 1, isArchive);
fileChild->setData(1, Qt::UserRole, source);
@@ -1348,7 +1392,9 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) std::wstring virtualPath = (path + L"\\").substr(6) + ToWString(item->text(0));
DirectoryEntry *dir = m_OrganizerCore.directoryStructure()->findSubDirectoryRecursive(virtualPath);
if (dir != nullptr) {
- updateTo(item, path, *dir, conflictsOnly);
+ QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder);
+ QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File);
+ updateTo(item, path, *dir, conflictsOnly, &fileIcon, &folderIcon);
} else {
qWarning("failed to update view of %ls", path.c_str());
}
@@ -1423,12 +1469,14 @@ void MainWindow::refreshDataTree() {
QCheckBox *conflictsBox = findChild<QCheckBox*>("conflictsCheckBox");
QTreeWidget *tree = findChild<QTreeWidget*>("dataTree");
+ QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder);
+ QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File);
tree->clear();
QStringList columns("data");
columns.append("");
QTreeWidgetItem *subTree = new QTreeWidgetItem(columns);
subTree->setData(0, Qt::DecorationRole, (new QFileIconProvider())->icon(QFileIconProvider::Folder));
- updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked());
+ updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked(), &fileIcon, &folderIcon);
tree->insertTopLevelItem(0, subTree);
subTree->setExpanded(true);
}
@@ -1437,7 +1485,8 @@ void MainWindow::refreshDataTreeKeepExpandedNodes() {
QCheckBox *conflictsBox = findChild<QCheckBox*>("conflictsCheckBox");
QTreeWidget *tree = findChild<QTreeWidget*>("dataTree");
-
+ QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder);
+ QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File);
QStringList expandedNodes;
QTreeWidgetItemIterator it1(tree, QTreeWidgetItemIterator::NotHidden | QTreeWidgetItemIterator::HasChildren);
while (*it1) {
@@ -1453,7 +1502,7 @@ void MainWindow::refreshDataTreeKeepExpandedNodes() columns.append("");
QTreeWidgetItem *subTree = new QTreeWidgetItem(columns);
subTree->setData(0, Qt::DecorationRole, (new QFileIconProvider())->icon(QFileIconProvider::Folder));
- updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked());
+ updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked(), &fileIcon, &folderIcon);
tree->insertTopLevelItem(0, subTree);
subTree->setExpanded(true);
QTreeWidgetItemIterator it2(tree, QTreeWidgetItemIterator::HasChildren);
@@ -2187,6 +2236,11 @@ void MainWindow::directory_refreshed() statusBar()->hide();
}
+void MainWindow::esplist_changed()
+{
+ emit updatePluginCount();
+}
+
void MainWindow::modorder_changed()
{
for (unsigned int i = 0; i < m_OrganizerCore.currentProfile()->numMods(); ++i) {
@@ -2458,6 +2512,7 @@ void MainWindow::restoreBackup_clicked() void MainWindow::modlistChanged(const QModelIndex&, int)
{
m_OrganizerCore.currentProfile()->writeModlist();
+ emit updateModCount();
}
void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&)
@@ -2496,6 +2551,12 @@ void MainWindow::modListSortIndicatorChanged(int, Qt::SortOrder) ui->modList->verticalScrollBar()->repaint();
}
+void MainWindow::modListSectionResized(int logicalIndex, int oldSize, int newSize)
+{
+ bool enabled = (newSize != 0);
+ qobject_cast<ModListSortProxy *>(ui->modList->model())->setColumnVisible(logicalIndex, enabled);
+}
+
void MainWindow::removeMod_clicked()
{
try {
@@ -2612,16 +2673,49 @@ void MainWindow::endorseMod(ModInfo::Ptr mod) void MainWindow::endorse_clicked()
{
- endorseMod(ModInfo::getByIndex(m_ContextRow));
+ QItemSelectionModel *selection = ui->modList->selectionModel();
+ if (selection->hasSelection() && selection->selectedRows().count() > 1) {
+ if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->loggedIn()) {
+ MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), this);
+ for (QModelIndex idx : selection->selectedRows()) {
+ ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(true);
+ }
+ }
+ else {
+ QString username, password;
+ if (m_OrganizerCore.settings().getNexusLogin(username, password)) {
+ MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), this);
+ for (QModelIndex idx : selection->selectedRows()) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt());
+ m_OrganizerCore.doAfterLogin(boost::bind(&MainWindow::endorseMod, this, modInfo));
+ }
+ NexusInterface::instance(&m_PluginContainer)->getAccessManager()->login(username, password);
+ } else {
+ MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this);
+ return;
+ }
+ }
+ }
+ else {
+ endorseMod(ModInfo::getByIndex(m_ContextRow));
+ }
}
void MainWindow::dontendorse_clicked()
{
- ModInfo::getByIndex(m_ContextRow)->setNeverEndorse();
+ QItemSelectionModel *selection = ui->modList->selectionModel();
+ if (selection->hasSelection() && selection->selectedRows().count() > 1) {
+ for (QModelIndex idx : selection->selectedRows()) {
+ ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->setNeverEndorse();
+ }
+ }
+ else {
+ ModInfo::getByIndex(m_ContextRow)->setNeverEndorse();
+ }
}
-void MainWindow::unendorse_clicked()
+void MainWindow::unendorseMod(ModInfo::Ptr mod)
{
QString username, password;
if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->loggedIn()) {
@@ -2636,6 +2730,37 @@ void MainWindow::unendorse_clicked() }
}
+
+void MainWindow::unendorse_clicked()
+{
+ QItemSelectionModel *selection = ui->modList->selectionModel();
+ if (selection->hasSelection() && selection->selectedRows().count() > 1) {
+ if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->loggedIn()) {
+ MessageDialog::showMessage(tr("Unendorsing multiple mods will take a while. Please wait..."), this);
+ for (QModelIndex idx : selection->selectedRows()) {
+ ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(false);
+ }
+ }
+ else {
+ QString username, password;
+ if (m_OrganizerCore.settings().getNexusLogin(username, password)) {
+ MessageDialog::showMessage(tr("Unendorsing multiple mods will take a while. Please wait..."), this);
+ for (QModelIndex idx : selection->selectedRows()) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt());
+ m_OrganizerCore.doAfterLogin(boost::bind(&MainWindow::unendorseMod, this, modInfo));
+ }
+ NexusInterface::instance(&m_PluginContainer)->getAccessManager()->login(username, password);
+ } else {
+ MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this);
+ return;
+ }
+ }
+ }
+ else {
+ unendorseMod(ModInfo::getByIndex(m_ContextRow));
+ }
+}
+
void MainWindow::loginFailed(const QString &error)
{
qDebug("login failed: %s", qPrintable(error));
@@ -2870,12 +2995,33 @@ void MainWindow::markConverted_clicked() void MainWindow::visitOnNexus_clicked()
{
- int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole).toInt();
- QString gameName = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole + 4).toString();
- if (modID > 0) {
- linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName));
- } else {
- MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this);
+ QItemSelectionModel *selection = ui->modList->selectionModel();
+ if (selection->hasSelection() && selection->selectedRows().count() > 1) {
+ int count = selection->selectedRows().count();
+ if (count > 10) {
+ if (QMessageBox::question(this, tr("Opening Nexus Links"),
+ tr("You are trying to open %1 links to Nexus Mods. Are you sure you want to do this?").arg(count),
+ QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+ }
+
+ for (QModelIndex idx : selection->selectedRows()) {
+ int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(idx.data(Qt::UserRole + 1).toInt(), 0), Qt::UserRole).toInt();
+ QString gameName = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(idx.data(Qt::UserRole + 1).toInt(), 0), Qt::UserRole + 4).toString();
+ if (modID > 0) {
+ linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName));
+ }
+ }
+ }
+ else {
+ int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole).toInt();
+ QString gameName = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole + 4).toString();
+ if (modID > 0) {
+ linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName));
+ } else {
+ MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this);
+ }
}
}
@@ -2977,23 +3123,165 @@ void MainWindow::search_activated() ui->modFilterEdit->setSelection(0, INT_MAX);
}
- if (ui->espList->hasFocus() || ui->espFilterEdit->hasFocus()) {
+ else if (ui->espList->hasFocus() || ui->espFilterEdit->hasFocus()) {
ui->espFilterEdit->setFocus();
ui->espFilterEdit->setSelection(0, INT_MAX);
}
+
+ else if (ui->downloadView->hasFocus() || ui->downloadFilterEdit->hasFocus()) {
+ ui->downloadFilterEdit->setFocus();
+ ui->downloadFilterEdit->setSelection(0, INT_MAX);
+ }
}
void MainWindow::searchClear_activated()
{
- if (ui->modFilterEdit->hasFocus()) {
+ if (ui->modList->hasFocus() || ui->modFilterEdit->hasFocus()) {
ui->modFilterEdit->clear();
ui->modList->setFocus();
}
- if (ui->espFilterEdit->hasFocus()) {
+ else if (ui->espList->hasFocus() || ui->espFilterEdit->hasFocus()) {
ui->espFilterEdit->clear();
ui->espList->setFocus();
}
+
+ else if (ui->downloadView->hasFocus() || ui->downloadFilterEdit->hasFocus()) {
+ ui->downloadFilterEdit->clear();
+ ui->downloadView->setFocus();
+ }
+}
+
+void MainWindow::updateModCount()
+{
+ int activeCount = 0;
+ int visActiveCount = 0;
+ int backupCount = 0;
+ int visBackupCount = 0;
+ int foreignCount = 0;
+ int visForeignCount = 0;
+ int separatorCount = 0;
+ int visSeparatorCount = 0;
+ int regularCount = 0;
+ int visRegularCount = 0;
+
+ QStringList allMods = m_OrganizerCore.modList()->allMods();
+
+ auto hasFlag = [](std::vector<ModInfo::EFlag> flags, ModInfo::EFlag filter) {
+ return std::find(flags.begin(), flags.end(), filter) != flags.end();
+ };
+
+ bool isEnabled;
+ bool isVisible;
+ for (QString mod : allMods) {
+ int modIndex = ModInfo::getIndex(mod);
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+ std::vector<ModInfo::EFlag> modFlags = modInfo->getFlags();
+ isEnabled = m_OrganizerCore.currentProfile()->modEnabled(modIndex);
+ isVisible = m_ModListSortProxy->filterMatchesMod(modInfo, isEnabled);
+
+ for (auto flag : modFlags) {
+ switch (flag) {
+ case ModInfo::FLAG_BACKUP: backupCount++;
+ if (isVisible)
+ visBackupCount++;
+ break;
+ case ModInfo::FLAG_FOREIGN: foreignCount++;
+ if (isVisible)
+ visForeignCount++;
+ break;
+ case ModInfo::FLAG_SEPARATOR: separatorCount++;
+ if (isVisible)
+ visSeparatorCount++;
+ break;
+ }
+ }
+
+ if (!hasFlag(modFlags, ModInfo::FLAG_BACKUP) &&
+ !hasFlag(modFlags, ModInfo::FLAG_FOREIGN) &&
+ !hasFlag(modFlags, ModInfo::FLAG_SEPARATOR) &&
+ !hasFlag(modFlags, ModInfo::FLAG_OVERWRITE)) {
+ if (isEnabled) {
+ activeCount++;
+ if (isVisible)
+ visActiveCount++;
+ }
+ if (isVisible)
+ visRegularCount++;
+ regularCount++;
+ }
+ }
+
+ ui->activeModsCounter->display(visActiveCount);
+ ui->activeModsCounter->setToolTip(tr("<table cellspacing=\"5\">"
+ "<tr><th>Type</th><th>All</th><th>Visible</th>"
+ "<tr><td>Enabled mods: </td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr>"
+ "<tr><td>Unmanaged/DLCs: </td><td align=right>%5</td><td align=right>%6</td></tr>"
+ "<tr><td>Mod backups: </td><td align=right>%7</td><td align=right>%8</td></tr>"
+ "<tr><td>Separators: </td><td align=right>%9</td><td align=right>%10</td></tr>"
+ "</table>")
+ .arg(activeCount)
+ .arg(regularCount)
+ .arg(visActiveCount)
+ .arg(visRegularCount)
+ .arg(foreignCount)
+ .arg(visForeignCount)
+ .arg(backupCount)
+ .arg(visBackupCount)
+ .arg(separatorCount)
+ .arg(visSeparatorCount)
+ );
+}
+
+void MainWindow::updatePluginCount()
+{
+ int activeMasterCount = 0;
+ int activeLightMasterCount = 0;
+ int activeRegularCount = 0;
+ int masterCount = 0;
+ int lightMasterCount = 0;
+ int regularCount = 0;
+ int activeVisibleCount = 0;
+
+ PluginList *list = m_OrganizerCore.pluginList();
+ QString filter = ui->espFilterEdit->text();
+
+ for (QString plugin : list->pluginNames()) {
+ bool active = list->isEnabled(plugin);
+ bool visible = m_PluginListSortProxy->filterMatchesPlugin(plugin);
+ if (list->isMaster(plugin)) {
+ masterCount++;
+ activeMasterCount += active;
+ activeVisibleCount += visible && active;
+ } else if (list->isLight(plugin) || list->isLightFlagged(plugin)) {
+ lightMasterCount++;
+ activeLightMasterCount += active;
+ activeVisibleCount += visible && active;
+ } else {
+ regularCount++;
+ activeRegularCount += active;
+ activeVisibleCount += visible && active;
+ }
+ }
+
+ int activeCount = activeMasterCount + activeLightMasterCount + activeRegularCount;
+ int totalCount = masterCount + lightMasterCount + regularCount;
+
+ ui->activePluginsCounter->display(activeVisibleCount);
+ ui->activePluginsCounter->setToolTip(tr("<table cellspacing=\"4\">"
+ "<tr><th>Type</th><th>Active</th><th>Total</th></tr>"
+ "<tr><td>Active plugins:</td><td align=right>%1</td><td align=right>%2</td></tr>"
+ "<tr><td>Active ESMs:</td><td align=right>%3</td><td align=right>%4</td></tr>"
+ "<tr><td>Active ESPs:</td><td align=right>%7</td><td align=right>%8</td></tr>"
+ "<tr><td>Active ESMs+ESPs:</td><td align=right>%9</td><td align=right>%10</td></tr>"
+ "<tr><td>Active ESLs:</td><td align=right>%5</td><td align=right>%6</td></tr>"
+ "</table>")
+ .arg(activeCount).arg(totalCount)
+ .arg(activeMasterCount).arg(masterCount)
+ .arg(activeLightMasterCount).arg(lightMasterCount)
+ .arg(activeRegularCount).arg(regularCount)
+ .arg(activeMasterCount+activeRegularCount).arg(masterCount+regularCount)
+ );
}
void MainWindow::information_clicked()
@@ -3242,7 +3530,17 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) else {
try {
m_ContextRow = m_ModListSortProxy->mapToSource(index).row();
- displayModInformation(sourceIdx.row());
+ sourceIdx.column();
+ int tab = -1;
+ switch (sourceIdx.column()) {
+ case ModList::COL_NOTES: tab = ModInfoDialog::TAB_NOTES; break;
+ case ModList::COL_VERSION: tab = ModInfoDialog::TAB_NEXUS; break;
+ case ModList::COL_MODID: tab = ModInfoDialog::TAB_NEXUS; break;
+ case ModList::COL_GAME: tab = ModInfoDialog::TAB_NEXUS; break;
+ case ModList::COL_CATEGORY: tab = ModInfoDialog::TAB_CATEGORIES; break;
+ default: tab = -1;
+ }
+ displayModInformation(sourceIdx.row(), tab);
// workaround to cancel the editor that might have opened because of
// selection-click
ui->modList->closePersistentEditor(index);
@@ -3262,7 +3560,9 @@ void MainWindow::openOriginInformation_clicked() {
try {
QItemSelectionModel *selection = ui->espList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 0) {
+ //we don't want to open multiple modinfodialogs.
+ /*if (selection->hasSelection() && selection->selectedRows().count() > 0) {
+
for (QModelIndex idx : selection->selectedRows()) {
QString fileName = idx.data().toString();
ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)));
@@ -3273,16 +3573,15 @@ void MainWindow::openOriginInformation_clicked() }
}
}
- else {
- QModelIndex idx = selection->currentIndex();
- QString fileName = idx.data().toString();
+ else {}*/
+ QModelIndex idx = selection->currentIndex();
+ QString fileName = idx.data().toString();
- ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)));
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)));
+ std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
- if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) {
- displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)));
- }
+ if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) {
+ displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)));
}
}
catch (const std::exception &e) {
@@ -3665,7 +3964,11 @@ void MainWindow::disableVisibleMods() void MainWindow::openInstanceFolder()
{
- ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getBaseDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+ QString dataPath = qApp->property("dataPath").toString();
+ ::ShellExecuteW(nullptr, L"explore", ToWString(dataPath).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+
+ //opens BaseDirectory instead
+ //::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getBaseDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
}
void MainWindow::openLogsFolder()
@@ -3691,6 +3994,17 @@ void MainWindow::openProfileFolder() ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.currentProfile()->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
}
+void MainWindow::openIniFolder()
+{
+ if (m_OrganizerCore.currentProfile()->localSettingsEnabled())
+ {
+ ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.currentProfile()->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+ }
+ else {
+ ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.managedGame()->documentsDirectory().absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+ }
+}
+
void MainWindow::openDownloadsFolder()
{
::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getDownloadDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
@@ -3890,6 +4204,8 @@ QMenu *MainWindow::openFolderMenu() FolderMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder()));
+ FolderMenu->addAction(tr("Open INIs folder"), this, SLOT(openIniFolder()));
+
FolderMenu->addSeparator();
FolderMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder()));
@@ -4033,14 +4349,14 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu->addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme()));
}
- if (info->updateAvailable() || info->downgradeAvailable()) {
- if (info->updateIgnored()) {
- menu->addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate()));
- } else {
- menu->addAction(tr("Ignore update"), this, SLOT(ignoreUpdate()));
+ if (info->updateIgnored()) {
+ menu->addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate()));
+ }
+ else {
+ if (info->updateAvailable() || info->downgradeAvailable()) {
+ menu->addAction(tr("Ignore update"), this, SLOT(ignoreUpdate()));
}
}
-
menu->addSeparator();
menu->addAction(tr("Enable selected"), this, SLOT(enableSelectedMods_clicked()));
@@ -4057,7 +4373,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu->addSeparator();
- if (info->getNexusID() > 0) {
+ if (info->getNexusID() > 0 && Settings::instance().endorsementIntegration()) {
switch (info->endorsedState()) {
case ModInfo::ENDORSED_TRUE: {
menu->addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked()));
@@ -4134,8 +4450,6 @@ void MainWindow::on_categoriesList_itemSelectionChanged() m_ModListSortProxy->setCategoryFilter(categories);
m_ModListSortProxy->setContentFilter(content);
ui->clickBlankButton->setEnabled(categories.size() > 0 || content.size() >0);
- //ui->clearFiltersButton->setStyleSheet("border:5px solid #ff0000;");
- ui->clearFiltersButton->setVisible(categories.size() > 0 || content.size() > 0);
if (indices.count() == 0) {
ui->currentCategoryLabel->setText(QString("(%1)").arg(tr("<All>")));
@@ -4423,11 +4737,9 @@ void MainWindow::installTranslator(const QString &name) QTranslator *translator = new QTranslator(this);
QString fileName = name + "_" + m_CurrentLanguage;
if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) {
- if ((m_CurrentLanguage != "en-US")
- && (m_CurrentLanguage != "en_US")
- && (m_CurrentLanguage != "en-GB")) {
+ if (m_CurrentLanguage.compare("en", Qt::CaseInsensitive)) {
qDebug("localization file %s not found", qPrintable(fileName));
- } // we don't actually expect localization files for english
+ } // we don't actually expect localization files for English
}
qApp->installTranslator(translator);
@@ -4820,7 +5132,16 @@ void MainWindow::motdReceived(const QString &motd) void MainWindow::notEndorsedYet()
{
- ui->actionEndorseMO->setVisible(true);
+ if (!Settings::instance().directInterface().value("wont_endorse_MO", false).toBool()) {
+ ui->actionEndorseMO->setVisible(true);
+ }
+}
+
+
+void MainWindow::wontEndorse()
+{
+ Settings::instance().directInterface().setValue("wont_endorse_MO", true);
+ ui->actionEndorseMO->setVisible(false);
}
@@ -4951,7 +5272,9 @@ void MainWindow::nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant us if (game
&& result["id"].toInt() == game->nexusModOrganizerID()
&& result["game_id"].toInt() == game->nexusGameID()) {
- if (!result["voted_by_user"].toBool()) {
+ if (!result["voted_by_user"].toBool() &&
+ Settings::instance().endorsementIntegration() &&
+ !Settings::instance().directInterface().value("wont_endorse_MO", false).toBool()) {
ui->actionEndorseMO->setVisible(true);
}
} else {
@@ -4975,7 +5298,8 @@ void MainWindow::nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant us (*iter)->setNewestVersion(result["version"].toString());
(*iter)->setNexusDescription(result["description"].toString());
if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->loggedIn() &&
- result.contains("voted_by_user")) {
+ result.contains("voted_by_user") &&
+ Settings::instance().endorsementIntegration()) {
// don't use endorsement info if we're not logged in or if the response doesn't contain it
(*iter)->setIsEndorsed(result["voted_by_user"].toBool());
}
@@ -5933,6 +6257,7 @@ void MainWindow::on_clickBlankButton_clicked() void MainWindow::on_clearFiltersButton_clicked()
{
+ ui->modFilterEdit->clear();
deselectFilters();
}
diff --git a/src/mainwindow.h b/src/mainwindow.h index ad88c42c..63d140e0 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -162,6 +162,7 @@ public slots: void displayColumnSelection(const QPoint &pos);
void modorder_changed();
+ void esplist_changed();
void refresher_progress(int percent);
void directory_refreshed();
@@ -211,7 +212,7 @@ private: void startSteam();
- void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly);
+ void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly, QIcon *fileIcon, QIcon *folderIcon);
bool refreshProfiles(bool selectProfile = true);
void refreshExecutablesList();
void installMod(QString fileName = "");
@@ -250,6 +251,7 @@ private: // remove invalid category-references from mods
void fixCategories();
+ void createEndorseWidget();
void createHelpWidget();
bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName);
@@ -483,6 +485,7 @@ private slots: void motdReceived(const QString &motd);
void notEndorsedYet();
+ void wontEndorse();
void originModified(int originID);
@@ -516,6 +519,7 @@ private slots: void resumeDownload(int downloadIndex);
void endorseMod(ModInfo::Ptr mod);
+ void unendorseMod(ModInfo::Ptr mod);
void cancelModListEditor();
void lockESPIndex();
@@ -531,6 +535,7 @@ private slots: void openDownloadsFolder();
void openModsFolder();
void openProfileFolder();
+ void openIniFolder();
void openGameFolder();
void openMyGamesFolder();
void startExeAction();
@@ -578,6 +583,7 @@ private slots: void modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous);
void modListSortIndicatorChanged(int column, Qt::SortOrder order);
+ void modListSectionResized(int logicalIndex, int oldSize, int newSize);
void modlistSelectionsChanged(const QItemSelection ¤t);
void esplistSelectionsChanged(const QItemSelection ¤t);
@@ -585,6 +591,9 @@ private slots: void search_activated();
void searchClear_activated();
+ void updateModCount();
+ void updatePluginCount();
+
private slots: // ui slots
// actions
void on_actionAdd_Profile_triggered();
diff --git a/src/mainwindow.ui b/src/mainwindow.ui index c6b43be0..972d9513 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -179,7 +179,7 @@ <number>2</number>
</property>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0,1,0,0,0,0,0">
+ <layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0,1,0,0,0,0,0,0,0">
<item>
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
@@ -295,6 +295,35 @@ p, li { white-space: pre-wrap; } </property>
</widget>
</item>
+ <item>
+ <widget class="QLabel" name="activeModslabel">
+ <property name="text">
+ <string>Active:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="LCDNumber" name="activeModsCounter">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="whatsThis">
+ <string>This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter.</string>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="digitCount">
+ <number>5</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
<item>
@@ -448,7 +477,7 @@ p, li { white-space: pre-wrap; } <bool>false</bool>
</property>
<attribute name="headerDefaultSectionSize">
- <number>20</number>
+ <number>35</number>
</attribute>
<attribute name="headerShowSortIndicator" stdset="0">
<bool>true</bool>
@@ -612,7 +641,7 @@ p, li { white-space: pre-wrap; } </size>
</property>
<property name="placeholderText">
- <string>Namefilter</string>
+ <string>Filter</string>
</property>
</widget>
</item>
@@ -890,6 +919,35 @@ p, li { white-space: pre-wrap; } </property>
</widget>
</item>
+ <item>
+ <widget class="QLabel" name="activePluginsLabel">
+ <property name="text">
+ <string>Active:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="LCDNumber" name="activePluginsCounter">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="whatsThis">
+ <string>This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter.</string>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
<item>
@@ -1002,7 +1060,7 @@ p, li { white-space: pre-wrap; } <string/>
</property>
<property name="placeholderText">
- <string>Namefilter</string>
+ <string>Filter</string>
</property>
</widget>
</item>
@@ -1220,7 +1278,7 @@ p, li { white-space: pre-wrap; } <html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;">
-<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p>
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html></string>
</property>
@@ -1361,7 +1419,7 @@ p, li { white-space: pre-wrap; } <item>
<widget class="MOBase::LineEditClear" name="downloadFilterEdit">
<property name="placeholderText">
- <string>Namefilter</string>
+ <string>Filter</string>
</property>
</widget>
</item>
@@ -1643,6 +1701,11 @@ Right now this has very limited functionality</string> <extends>QTreeView</extends>
<header>pluginlistview.h</header>
</customwidget>
+ <customwidget>
+ <class>LCDNumber</class>
+ <extends>QLCDNumber</extends>
+ <header>lcdnumber.h</header>
+ </customwidget>
</customwidgets>
<resources>
<include location="resources.qrc"/>
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index d1d8d82a..02ace33f 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -115,7 +115,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->commentsEdit->setText(modInfo->comments());
ui->notesEdit->setText(modInfo->notes());
- ui->descriptionView->setPage(new DescriptionPage);
+ ui->descriptionView->setPage(new DescriptionPage());
connect(&m_ThumbnailMapper, SIGNAL(mapped(const QString&)), this, SIGNAL(thumbnailClickedSignal(const QString&)));
connect(this, SIGNAL(thumbnailClickedSignal(const QString&)), this, SLOT(thumbnailClicked(const QString&)));
@@ -171,10 +171,8 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != nullptr);
- if (ui->tabWidget->currentIndex() == TAB_NEXUS) {
- activateNexusTab();
- }
+ ui->endorseBtn->setVisible(Settings::instance().endorsementIntegration());
ui->endorseBtn->setEnabled((m_ModInfo->endorsedState() == ModInfo::ENDORSED_FALSE) ||
(m_ModInfo->endorsedState() == ModInfo::ENDORSED_NEVER));
@@ -185,6 +183,10 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo break;
}
}
+
+ if (ui->tabWidget->currentIndex() == TAB_NEXUS) {
+ activateNexusTab();
+ }
}
@@ -936,7 +938,7 @@ void ModInfoDialog::activateNexusTab() void ModInfoDialog::on_tabWidget_currentChanged(int index)
{
- if (m_RealTabPos[index] == TAB_NEXUS) {
+ if (index == TAB_NEXUS || m_RealTabPos[index] == TAB_NEXUS) {
activateNexusTab();
}
}
diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 6a52d3df..548e3178 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -4,6 +4,7 @@ #include "messagedialog.h" #include "report.h" #include "scriptextender.h" +#include "settings.h" #include <QApplication> #include <QDirIterator> @@ -466,7 +467,9 @@ void ModInfoRegular::ignoreUpdate(bool ignore) std::vector<ModInfo::EFlag> ModInfoRegular::getFlags() const { std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags(); - if ((m_NexusID > 0) && (endorsedState() == ENDORSED_FALSE)) { + if ((m_NexusID > 0) && + (endorsedState() == ENDORSED_FALSE) && + Settings::instance().endorsementIntegration()) { result.push_back(ModInfo::FLAG_NOTENDORSED); } if (!isValid() && !m_Validated) { diff --git a/src/modlist.cpp b/src/modlist.cpp index 757fedb2..0b66a663 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -167,7 +167,7 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file");
case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return tr("Overwritten by another archive file");
case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten");
- case ModInfo::FLAG_ALTERNATE_GAME: return tr("Alternate game source");
+ case ModInfo::FLAG_ALTERNATE_GAME: return tr("This mod targets a different game");
default: return "";
}
}
@@ -981,7 +981,6 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa if (row == -1) {
row = parent.row();
}
-
ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
QDir modDirectory(modInfo->absolutePath());
QDir gameDirectory(Settings::instance().getOverwriteDirectory());
@@ -993,12 +992,14 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name();
for (const QUrl &url : mimeData->urls()) {
+ qDebug("URL drop requested: %s", qPrintable(url.toLocalFile()));
if (!url.isLocalFile()) {
+ qDebug("URL drop ignored: Not a local file.");
continue;
}
QString relativePath = gameDirectory.relativeFilePath(url.toLocalFile());
if (relativePath.startsWith("..")) {
- qDebug("%s drop ignored", qPrintable(url.toLocalFile()));
+ qDebug("URL drop ignored: relative path starts with ..");
continue;
}
source.append(url.toLocalFile());
@@ -1007,7 +1008,10 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa }
if (source.count() != 0) {
- shellMove(source, target);
+ if (!shellMove(source, target)) {
+ qDebug("Move failed %lu",::GetLastError());
+ return false;
+ }
}
return true;
diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 8cf4bb9c..aaf93f5a 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -39,10 +39,6 @@ ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) , m_FilterActive(false)
, m_FilterMode(FILTER_AND)
{
- m_EnabledColumns.set(ModList::COL_FLAGS);
- m_EnabledColumns.set(ModList::COL_NAME);
- m_EnabledColumns.set(ModList::COL_VERSION);
- m_EnabledColumns.set(ModList::COL_PRIORITY);
setDynamicSortFilter(true); // this seems to work without dynamicsortfilter
// but I don't know why. This should be necessary
}
@@ -218,9 +214,25 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, lt = comp < 0;
}
} break;
+ case ModList::COL_NOTES: {
+ QString leftComments = leftMod->comments();
+ QString rightComments = rightMod->comments();
+ if (leftComments != rightComments) {
+ if (leftComments.isEmpty()) {
+ lt = sortOrder() == Qt::DescendingOrder;
+ } else if (rightComments.isEmpty()) {
+ lt = sortOrder() == Qt::AscendingOrder;
+ } else {
+ lt = leftComments < rightComments;
+ }
+ }
+ } break;
case ModList::COL_PRIORITY: {
// nop, already compared by priority
} break;
+ default: {
+ qWarning() << "Sorting is not defined for column " << left.column();
+ } break;
}
return lt;
}
@@ -345,8 +357,53 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const
{
- if (!m_CurrentFilter.isEmpty() &&
- !info->name().contains(m_CurrentFilter, Qt::CaseInsensitive)) {
+ bool display = true;
+ if (!m_CurrentFilter.isEmpty()) {
+ display = false;
+
+ // Search by name
+ if (!display &&
+ m_EnabledColumns[ModList::COL_NAME] &&
+ info->name().contains(m_CurrentFilter, Qt::CaseInsensitive)) {
+ display = true;
+ }
+
+ // Search by notes
+ if (!display &&
+ m_EnabledColumns[ModList::COL_NOTES] &&
+ info->comments().contains(m_CurrentFilter, Qt::CaseInsensitive)) {
+ display = true;
+ }
+
+ // Search by Nexus ID
+ if (!display &&
+ m_EnabledColumns[ModList::COL_MODID]) {
+ bool ok;
+ int filterID = m_CurrentFilter.toInt(&ok);
+ if (ok) {
+ int modID = info->getNexusID();
+ while (modID > 0) {
+ if (modID == filterID) {
+ display = true;
+ break;
+ }
+ modID = (int)(modID / 10);
+ }
+ }
+ }
+
+ // Search by categories
+ if (!display &&
+ m_EnabledColumns[ModList::COL_CATEGORY]) {
+ for (auto category : info->categories()) {
+ if (category.contains(m_CurrentFilter, Qt::CaseInsensitive)) {
+ display = true;
+ break;
+ }
+ }
+ }
+ }
+ if (!display) {
return false;
}
@@ -357,6 +414,11 @@ bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const }
}
+void ModListSortProxy::setColumnVisible(int column, bool visible)
+{
+ m_EnabledColumns[column] = visible;
+}
+
void ModListSortProxy::setFilterMode(ModListSortProxy::FilterMode mode)
{
if (m_FilterMode != mode) {
diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index b3c01fea..5fe8d9d6 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -60,6 +60,7 @@ public: virtual void setSourceModel(QAbstractItemModel *sourceModel) override;
+
/**
* @brief enable all mods visible under the current filter
**/
@@ -94,6 +95,13 @@ public: return rowCount(parent) > 0;
}
+ /**
+ * @brief sets whether a column is visible
+ * @param column the index of the column
+ * @param visible the visibility of the column
+ */
+ void setColumnVisible(int column, bool visible);
+
public slots:
void updateFilter(const QString &filter);
diff --git a/src/organizer.pro b/src/organizer.pro index 582fde9f..df42db40 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -92,7 +92,8 @@ SOURCES += \ modinfobackup.cpp \
modinfooverwrite.cpp \
modinfoforeign.cpp \
- listdialog.cpp
+ listdialog.cpp \
+ lcdnumber.cpp
HEADERS += \
@@ -169,7 +170,8 @@ HEADERS += \ modinfobackup.h \
modinfooverwrite.h \
modinfoforeign.h \
- listdialog.h
+ listdialog.h \
+ lcdnumber.h
FORMS += \
transfersavesdialog.ui \
diff --git a/src/organizercore.cpp b/src/organizercore.cpp index acce25dc..80dad96b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -560,6 +560,10 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, SLOT(fileMoved(QString, QString, QString)));
connect(&m_ModList, SIGNAL(modorder_changed()), widget,
SLOT(modorder_changed()));
+ connect(&m_PluginList, SIGNAL(writePluginsList()), widget,
+ SLOT(esplist_changed()));
+ connect(&m_PluginList, SIGNAL(esplist_changed()), widget,
+ SLOT(esplist_changed()));
connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget,
SLOT(showMessage(QString)));
}
@@ -1093,8 +1097,9 @@ QString OrganizerCore::resolvePath(const QString &fileName) const QStringList OrganizerCore::listDirectories(const QString &directoryName) const
{
QStringList result;
- DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(
- ToWString(directoryName));
+ DirectoryEntry *dir = m_DirectoryStructure;
+ if (!directoryName.isEmpty())
+ dir = dir->findSubDirectoryRecursive(ToWString(directoryName));
if (dir != nullptr) {
std::vector<DirectoryEntry *>::iterator current, end;
dir->getSubDirectories(current, end);
@@ -1110,8 +1115,9 @@ QStringList OrganizerCore::findFiles( const std::function<bool(const QString &)> &filter) const
{
QStringList result;
- DirectoryEntry *dir
- = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path));
+ DirectoryEntry *dir = m_DirectoryStructure;
+ if (!path.isEmpty())
+ dir = dir->findSubDirectoryRecursive(ToWString(path));
if (dir != nullptr) {
std::vector<FileEntry::Ptr> files = dir->getFiles();
foreach (FileEntry::Ptr file, files) {
@@ -1150,8 +1156,9 @@ QList<MOBase::IOrganizer::FileInfo> OrganizerCore::findFileInfos( const
{
QList<IOrganizer::FileInfo> result;
- DirectoryEntry *dir
- = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path));
+ DirectoryEntry *dir = m_DirectoryStructure;
+ if (!path.isEmpty())
+ dir = dir->findSubDirectoryRecursive(ToWString(path));
if (dir != nullptr) {
std::vector<FileEntry::Ptr> files = dir->getFiles();
foreach (FileEntry::Ptr file, files) {
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 04e39abf..2f70cd27 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -181,15 +181,16 @@ void PluginList::refresh(const QString &profileName }
QString filename = ToQString(current->getName());
- availablePlugins.append(filename.toLower());
-
- if (m_ESPsByName.find(filename.toLower()) != m_ESPsByName.end()) {
- continue;
- }
-
QString extension = filename.right(3).toLower();
if ((extension == "esp") || (extension == "esm") || (extension == "esl")) {
+
+ availablePlugins.append(filename.toLower());
+
+ if (m_ESPsByName.find(filename.toLower()) != m_ESPsByName.end()) {
+ continue;
+ }
+
bool forceEnabled = Settings::instance().forceEnableCoreFiles() &&
std::find(primaryPlugins.begin(), primaryPlugins.end(), filename.toLower()) != primaryPlugins.end();
@@ -202,10 +203,12 @@ void PluginList::refresh(const QString &profileName bool hasIni = baseDirectory.findFile(ToWString(iniPath)).get() != nullptr;
std::set<QString> loadedArchives;
- QString originPath = QString::fromWCharArray(origin.getPath().c_str());
- QDir dir(QDir::toNativeSeparators(originPath));
- for (QString filename : dir.entryList(QStringList() << QFileInfo(filename).baseName() + "*.bsa" << QFileInfo(filename).baseName() + "*.ba2")) {
- loadedArchives.insert(filename);
+ for (FileEntry::Ptr archiveCandidate : files) {
+ QString candidateName = ToQString(archiveCandidate->getName());
+ if (candidateName.contains(QRegExp(QFileInfo(filename).baseName() + "*.bsa", Qt::CaseInsensitive, QRegExp::Wildcard)) ||
+ candidateName.contains(QRegExp(QFileInfo(filename).baseName() + "*.ba2", Qt::CaseInsensitive, QRegExp::Wildcard))) {
+ loadedArchives.insert(candidateName);
+ }
}
QString originName = ToQString(origin.getName());
@@ -846,6 +849,7 @@ void PluginList::generatePluginIndexes() m_ESPs[i].m_Index = QString("%1").arg(l - numESLs - numSkipped, 2, 16, QChar('0')).toUpper();
}
}
+ emit esplist_changed();
}
diff --git a/src/pluginlistsortproxy.cpp b/src/pluginlistsortproxy.cpp index e09e64a5..1e131c4d 100644 --- a/src/pluginlistsortproxy.cpp +++ b/src/pluginlistsortproxy.cpp @@ -72,8 +72,7 @@ void PluginListSortProxy::updateFilter(const QString &filter) bool PluginListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const
{
- return m_CurrentFilter.isEmpty() ||
- sourceModel()->data(sourceModel()->index(row, 0)).toString().contains(m_CurrentFilter, Qt::CaseInsensitive);
+ return filterMatchesPlugin(sourceModel()->data(sourceModel()->index(row, 0)).toString());
}
@@ -137,3 +136,10 @@ bool PluginListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction act sourceIndex.parent());
}
+
+bool PluginListSortProxy::filterMatchesPlugin(const QString &plugin) const
+{
+ return m_CurrentFilter.isEmpty() ||
+ plugin.contains(m_CurrentFilter, Qt::CaseInsensitive);
+}
+
diff --git a/src/pluginlistsortproxy.h b/src/pluginlistsortproxy.h index 89f60e70..03b079e2 100644 --- a/src/pluginlistsortproxy.h +++ b/src/pluginlistsortproxy.h @@ -46,6 +46,8 @@ public: virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
+ bool filterMatchesPlugin(const QString &plugin) const;
+
public slots:
void updateFilter(const QString &filter);
diff --git a/src/profile.cpp b/src/profile.cpp index 821be4b4..d1741311 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -29,6 +29,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <safewritefile.h> #include <bsainvalidation.h> #include <dataarchives.h> +#include "util.h" +#include "registry.h" #include <QApplication> #include <QFile> // for QFile @@ -85,7 +87,6 @@ Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDef m_Directory = QDir(fullPath); m_Settings = new QSettings(m_Directory.absoluteFilePath("settings.ini"), QSettings::IniFormat); - findProfileSettings(); try { // create files. Needs to happen after m_Directory was set! @@ -101,6 +102,7 @@ Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDef } gamePlugin->initializeProfile(fullPath, settings); + findProfileSettings(); } catch (...) { // clean up in case of an error shellDelete(QStringList(profileBase.absoluteFilePath(fixedName))); @@ -171,10 +173,8 @@ void Profile::findProfileSettings() if (m_Directory.exists(backupFile)) { storeSetting("LocalSettings", false); m_Directory.rename(backupFile, getIniFileName()); - } else if (m_Directory.exists(getIniFileName())) { - storeSetting("LocalSettings", true); } else { - storeSetting("LocalSettings", false); + storeSetting("LocalSettings", true); } } @@ -281,7 +281,7 @@ void Profile::createTweakedIniFile() mergeTweak(getProfileTweaks(), tweakedIni); bool error = false; - if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { + if (!MOBase::WriteRegistryValue(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { error = true; } @@ -683,7 +683,7 @@ void Profile::mergeTweak(const QString &tweakName, const QString &tweakedIni) co //TODO this treats everything as strings but how could I differentiate the type? ::GetPrivateProfileStringW(iter->c_str(), keyIter->c_str(), nullptr, buffer.data(), bufferSize, ToWString(tweakName).c_str()); - ::WritePrivateProfileStringW(iter->c_str(), keyIter->c_str(), + MOBase::WriteRegistryValue(iter->c_str(), keyIter->c_str(), buffer.data(), tweakedIniW.c_str()); } } @@ -750,9 +750,9 @@ bool Profile::enableLocalSaves(bool enable) } } else { QMessageBox::StandardButton res = QMessageBox::question( - QApplication::activeModalWidget(), tr("Delete savegames?"), - tr("Do you want to delete local savegames? (If you select \"No\", the " - "save games will show up again if you re-enable local savegames)"), + QApplication::activeModalWidget(), tr("Delete profile-specific save games?"), + tr("Do you want to delete the profile-specific save games? (If you select \"No\", the " + "save games will show up again if you re-enable profile-specific save games)"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Cancel); if (res == QMessageBox::Yes) { @@ -770,11 +770,50 @@ bool Profile::enableLocalSaves(bool enable) bool Profile::localSettingsEnabled() const { - return setting("LocalSettings", false).toBool(); + bool enabled = setting("LocalSettings", false).toBool(); + if (enabled) { + bool reinitConfig = false; + for (QString file : m_GamePlugin->iniFiles()) { + if (!QFile::exists(m_Directory.filePath(file))) { + qWarning("missing %s in %s", qPrintable(file), qPrintable(m_Directory.path())); + reinitConfig = true; + } + } + if (reinitConfig) { + QMessageBox::StandardButton res = QMessageBox::warning( + QApplication::activeModalWidget(), tr("Missing profile-specific game INI files!"), + tr("Some of your profile-specific game INI files were missing. They will now be copied " + "from the vanilla game folder. You might want double-check your settings.") + ); + m_GamePlugin->initializeProfile(m_Directory, IPluginGame::CONFIGURATION); + } + } + return enabled; } bool Profile::enableLocalSettings(bool enable) { + if (enable) { + m_GamePlugin->initializeProfile(m_Directory.absolutePath(), IPluginGame::CONFIGURATION); + } else { + QMessageBox::StandardButton res = QMessageBox::question( + QApplication::activeModalWidget(), tr("Delete profile-specific game INI files?"), + tr("Do you want to delete the profile-specific game INI files? (If you select \"No\", the " + "INI files will be used again if you re-enable profile-specific game INI files.)"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Cancel); + if (res == QMessageBox::Yes) { + QStringList filesToDelete; + for (QString file : m_GamePlugin->iniFiles()) { + filesToDelete << m_Directory.absoluteFilePath(file); + } + shellDelete(filesToDelete, true); + } else if (res == QMessageBox::No) { + // No action + } else { + return false; + } + } storeSetting("LocalSettings", enable); return true; } diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index ed54f33c..17844357 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -191,7 +191,7 @@ void ProfilesDialog::on_removeProfileButton_clicked() return;
}
- QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), tr("Are you sure you want to remove this profile (including local savegames if any)?"),
+ QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), tr("Are you sure you want to remove this profile (including profile-specific save games, if any)?"),
QMessageBox::Yes | QMessageBox::No, this);
if (confirmBox.exec() == QMessageBox::Yes) {
diff --git a/src/profilesdialog.ui b/src/profilesdialog.ui index d33e2283..f040faed 100644 --- a/src/profilesdialog.ui +++ b/src/profilesdialog.ui @@ -35,13 +35,13 @@ p, li { white-space: pre-wrap; } <item>
<widget class="QCheckBox" name="localSavesBox">
<property name="toolTip">
- <string><html><head/><body><p>If checked, savegames are stored locally to this profile and will not appear when starting with a different profile.</p></body></html></string>
+ <string><html><head/><body><p>If checked, save games are stored locally to this profile and will not appear when starting with a different profile.</p></body></html></string>
</property>
<property name="whatsThis">
- <string>If checked, savegames are local to this profile and will not appear when starting with a different profile.</string>
+ <string>If checked, save games are local to this profile and will not appear when starting with a different profile.</string>
</property>
<property name="text">
- <string>Use profile-specific Savegames</string>
+ <string>Use profile-specific Save Games</string>
</property>
</widget>
</item>
diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 7c0682bf..d26ca96c 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -327,10 +327,10 @@ void SelfUpdater::installUpdate() const QString mopath
= QDir::fromNativeSeparators(qApp->property("dataPath").toString());
- const std::wstring params = L"/DIR=" + qApp->applicationDirPath().toStdWString();
+ std::wstring parameters = ToWString("/DIR=\"" + qApp->applicationDirPath() + "\" ");
HINSTANCE res = ::ShellExecuteW(
- nullptr, L"open", m_UpdateFile.fileName().toStdWString().c_str(), params.c_str(),
+ nullptr, L"open", m_UpdateFile.fileName().toStdWString().c_str(), parameters.c_str(),
nullptr, SW_SHOW);
if (res > (HINSTANCE)32) {
diff --git a/src/settings.cpp b/src/settings.cpp index 74c95ebf..2bad749f 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -459,6 +459,11 @@ bool Settings::useProxy() const return m_Settings.value("Settings/use_proxy", false).toBool(); } +bool Settings::endorsementIntegration() const +{ + return m_Settings.value("Settings/endorsement_integration", true).toBool(); +} + bool Settings::displayForeign() const { return m_Settings.value("Settings/display_foreign", true).toBool(); @@ -946,6 +951,7 @@ Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) , m_knownServersList(dialog.findChild<QListWidget *>("knownServersList")) , m_preferredServersList( dialog.findChild<QListWidget *>("preferredServersList")) + , m_endorsementBox(dialog.findChild<QCheckBox *>("endorsementBox")) { if (parent->automaticLoginEnabled()) { m_loginCheckBox->setChecked(true); @@ -955,6 +961,7 @@ Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) m_offlineBox->setChecked(parent->offlineMode()); m_proxyBox->setChecked(parent->useProxy()); + m_endorsementBox->setChecked(parent->endorsementIntegration()); // display server preferences m_Settings.beginGroup("Servers"); @@ -996,6 +1003,7 @@ void Settings::NexusTab::update() } m_Settings.setValue("Settings/offline_mode", m_offlineBox->isChecked()); m_Settings.setValue("Settings/use_proxy", m_proxyBox->isChecked()); + m_Settings.setValue("Settings/endorsement_integration", m_endorsementBox->isChecked()); // store server preference m_Settings.beginGroup("Servers"); diff --git a/src/settings.h b/src/settings.h index 3694bfad..4a932da7 100644 --- a/src/settings.h +++ b/src/settings.h @@ -271,6 +271,11 @@ public: bool useProxy() const; /** + * @return true if endorsement integration is enabled + */ + bool endorsementIntegration() const; + + /** * @return true if the user wants to see non-official plugins installed outside MO in his mod list */ bool displayForeign() const; @@ -475,6 +480,7 @@ private: QCheckBox *m_proxyBox; QListWidget *m_knownServersList; QListWidget *m_preferredServersList; + QCheckBox *m_endorsementBox; }; /** Display/store the configuration in the 'steam' tab of the settings dialogue */ diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 51f35683..832fa71a 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -537,30 +537,44 @@ p, li { white-space: pre-wrap; } </widget>
</item>
<item>
- <widget class="QCheckBox" name="offlineBox">
- <property name="statusTip">
- <string>Disable automatic internet features</string>
- </property>
- <property name="whatsThis">
- <string>Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser)</string>
- </property>
- <property name="text">
- <string>Offline Mode</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="proxyBox">
- <property name="statusTip">
- <string>Use a proxy for network connections.</string>
- </property>
- <property name="whatsThis">
- <string>Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy.</string>
- </property>
- <property name="text">
- <string>Use HTTP Proxy (Uses System Settings)</string>
- </property>
- </widget>
+ <layout class="QGridLayout" name="gridLayout_8">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="offlineBox">
+ <property name="statusTip">
+ <string>Disable automatic internet features</string>
+ </property>
+ <property name="whatsThis">
+ <string>Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser)</string>
+ </property>
+ <property name="text">
+ <string>Offline Mode</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="proxyBox">
+ <property name="statusTip">
+ <string>Use a proxy for network connections.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy.</string>
+ </property>
+ <property name="text">
+ <string>Use HTTP Proxy (Uses System Settings)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QCheckBox" name="endorsementBox">
+ <property name="text">
+ <string>Endorsement Integration</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
@@ -1368,8 +1382,6 @@ programs you are intentionally running.</string> <tabstop>usernameEdit</tabstop>
<tabstop>passwordEdit</tabstop>
<tabstop>clearCacheButton</tabstop>
- <tabstop>offlineBox</tabstop>
- <tabstop>proxyBox</tabstop>
<tabstop>associateButton</tabstop>
<tabstop>knownServersList</tabstop>
<tabstop>preferredServersList</tabstop>
diff --git a/src/shared/util.cpp b/src/shared/util.cpp index b65ed071..ed7c434e 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -211,7 +211,7 @@ MOBase::VersionInfo createVersionInfo() {
VS_FIXEDFILEINFO version = GetFileVersion(QApplication::applicationFilePath().toStdWString());
- if (version.dwFileFlags | VS_FF_PRERELEASE)
+ if (version.dwFileFlags & VS_FF_PRERELEASE)
{
// Pre-release builds need annotating
QString versionString = QString::fromStdWString(GetFileVersionString(QApplication::applicationFilePath().toStdWString()));
diff --git a/src/spawn.cpp b/src/spawn.cpp index f92387d5..f8a7bb5f 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <Shellapi.h>
#include <appconfig.h>
#include <windows_error.h>
+#include "helper.h"
#include <QApplication>
#include <QMessageBox>
@@ -148,23 +149,31 @@ HANDLE startBinary(const QFileInfo &binary, }
} catch (const windows_error &e) {
if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) {
- // TODO: check if this is really correct. Are all settings updated that the secondary instance may use?
-
- if (QMessageBox::question(nullptr, QObject::tr("Elevation required"),
+ if (QMessageBox::question(QApplication::activeModalWidget(), QObject::tr("Elevation required"),
QObject::tr("This process requires elevation to run.\n"
"This is a potential security risk so I highly advice you to investigate if\n"
"\"%1\"\n"
"can be installed to work without elevation.\n\n"
- "Start elevated anyway? "
- "(you will be asked if you want to allow ModOrganizer.exe to make changes to the system)").arg(
+ "Restart Mod Organizer as an elevated process?\n"
+ "You will be asked if you want to allow helper.exe to make changes to the system. "
+ "You will need to relaunch the process above manually.").arg(
QDir::toNativeSeparators(binary.absoluteFilePath())),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- ::ShellExecuteW(nullptr, L"runas", ToWString(QCoreApplication::applicationFilePath()).c_str(),
- (std::wstring(L"\"") + binaryName + L"\" " + ToWString(arguments)).c_str(), currentDirectoryName.c_str(), SW_SHOWNORMAL);
- return INVALID_HANDLE_VALUE;
- } else {
- return INVALID_HANDLE_VALUE;
+ WCHAR cwd[MAX_PATH];
+ if (!GetCurrentDirectory(MAX_PATH, cwd)) {
+ reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(::GetLastError()));
+ cwd[0] = L'\0';
+ }
+ if (!Helper::adminLaunch(
+ qApp->applicationDirPath().toStdWString(),
+ qApp->applicationFilePath().toStdWString(),
+ std::wstring(cwd))) {
+ return INVALID_HANDLE_VALUE;
+ }
+ qApp->exit(0);
}
+ return INVALID_HANDLE_VALUE;
+
} else {
reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what()));
return INVALID_HANDLE_VALUE;
diff --git a/src/transfersavesdialog.ui b/src/transfersavesdialog.ui index 1fcad54d..8bc23088 100644 --- a/src/transfersavesdialog.ui +++ b/src/transfersavesdialog.ui @@ -11,7 +11,7 @@ </rect>
</property>
<property name="windowTitle">
- <string>Transfer Savegames</string>
+ <string>Transfer Save Games</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
diff --git a/src/version.rc b/src/version.rc index e1f80785..1061d112 100644 --- a/src/version.rc +++ b/src/version.rc @@ -3,8 +3,8 @@ // If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses VER_FILEVERSION to determine version number.
// Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser
// Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha
-#define VER_FILEVERSION 2,2,0
-#define VER_FILEVERSION_STR "2.2.0\0"
+#define VER_FILEVERSION 2,2,1
+#define VER_FILEVERSION_STR "2.2.1\0"
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
|
