diff options
Diffstat (limited to 'src/mainwindow.cpp')
| -rw-r--r-- | src/mainwindow.cpp | 1440 |
1 files changed, 832 insertions, 608 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 625b406d..1e8d512a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -57,8 +57,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "savetextasdialog.h" #include "problemsdialog.h" #include "previewdialog.h" +#include "browserdialog.h" #include "aboutdialog.h" #include "safewritefile.h" +#include "organizerproxy.h" #include <gameinfo.h> #include <appconfig.h> #include <utility.h> @@ -94,6 +96,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QDesktopWidget> #include <QtPlugin> #include <QIdentityProxyModel> +#include <QClipboard> #include <boost/bind.hpp> #include <boost/foreach.hpp> #include <boost/assign.hpp> @@ -109,8 +112,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QtConcurrentRun> #endif #include <QCoreApplication> +#include <QProgressDialog> #include <scopeguard.h> #include <boost/thread.hpp> +#include <boost/algorithm/string.hpp> #ifdef TEST_MODELS @@ -157,12 +162,21 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget m_PluginList(this), m_OldExecutableIndex(-1), m_GamePath(ToQString(GameInfo::instance().getGameDirectory())), m_DownloadManager(NexusInterface::instance(), this), m_InstallationManager(this), m_Updater(NexusInterface::instance(), this), m_CategoryFactory(CategoryFactory::instance()), - m_CurrentProfile(NULL), m_AskForNexusPW(false), m_LoginAttempted(false), + m_CurrentProfile(NULL), m_AskForNexusPW(false), m_ArchivesInit(false), m_ContextItem(NULL), m_ContextAction(NULL), m_CurrentSaveView(NULL), - m_GameInfo(new GameInfoImpl()), m_AboutToRun() + m_GameInfo(new GameInfoImpl()), m_AboutToRun(), m_ModInstalled() { ui->setupUi(this); this->setWindowTitle(ToQString(GameInfo::instance().getGameName()) + " Mod Organizer v" + m_Updater.getVersion().displayString()); + ui->logList->setModel(LogBuffer::instance()); + ui->logList->setColumnWidth(0, 100); + ui->logList->setAutoScroll(true); + ui->logList->scrollToBottom(); + ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); + int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value + ui->topLevelSplitter->setSizes(QList<int>() << splitterSize - 100 << 100); + connect(ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), ui->logList, SLOT(scrollToBottom())); + connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), ui->logList, SLOT(scrollToBottom())); m_RefreshProgress = new QProgressBar(statusBar()); m_RefreshProgress->setTextVisible(true); @@ -232,6 +246,8 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); ui->linkButton->setMenu(linkMenu); + ui->listOptionsBtn->setMenu(modListContextMenu()); + m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); @@ -269,7 +285,6 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget connect(&m_PluginList, SIGNAL(saveTimer()), this, SLOT(savePluginList())); connect(ui->bsaList, SIGNAL(itemsMoved()), this, SLOT(bsaList_itemMoved())); - connect(ui->bsaWarning, SIGNAL(linkActivated(QString)), this, SLOT(linkClicked(QString))); connect(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*))); @@ -292,11 +307,14 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); connect(NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); connect(NexusInterface::instance(), SIGNAL(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(int,int,QVariant,QVariant,int))); + connect(NexusInterface::instance(), SIGNAL(needLogin()), this, SLOT(nexusLogin())); connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); + connect(&m_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), this, SLOT(requestDownload(QUrl,QNetworkReply*))); + connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString))); m_CheckBSATimer.setSingleShot(true); @@ -309,7 +327,6 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget m_DirectoryRefresher.moveToThread(&m_RefresherThread); m_RefresherThread.start(); - setCompactDownloads(initSettings.value("compact_downloads", false).toBool()); m_AskForNexusPW = initSettings.value("ask_for_nexuspw", true).toBool(); setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); FileDialogMemory::restore(initSettings); @@ -337,6 +354,7 @@ MainWindow::~MainWindow() { m_RefresherThread.exit(); m_RefresherThread.wait(); + m_IntegratedBrowser.close(); delete ui; delete m_GameInfo; delete m_DirectoryStructure; @@ -514,14 +532,26 @@ void MainWindow::updateToolBar() void MainWindow::updateProblemsButton() { - if (checkForProblems()) { + int numProblems = checkForProblems(); + if (numProblems > 0) { ui->actionProblems->setEnabled(true); ui->actionProblems->setIconText(tr("Problems")); ui->actionProblems->setToolTip(tr("There are potential problems with your setup")); + +// QPixmap mergedIcon(64, 64); + QPixmap mergedIcon = QPixmap(":/MO/gui/warning").scaled(64, 64); + { + QPainter painter(&mergedIcon); +// painter.setBrush(QBrush(Qt::transparent)); + std::string badgeName = std::string(":/MO/gui/badge_") + (numProblems < 10 ? std::to_string(static_cast<long long>(numProblems)) : "more"); + painter.drawPixmap(32, 32, 32, 32, QPixmap(badgeName.c_str())); + } + ui->actionProblems->setIcon(QIcon(mergedIcon)); } else { ui->actionProblems->setEnabled(false); ui->actionProblems->setIconText(tr("No Problems")); ui->actionProblems->setToolTip(tr("Everything seems to be in order")); + ui->actionProblems->setIcon(QIcon(":/MO/gui/warning")); } } @@ -556,15 +586,13 @@ bool MainWindow::errorReported(QString &logFile) } -bool MainWindow::checkForProblems() +int MainWindow::checkForProblems() { foreach (IPluginDiagnose *diagnose, m_DiagnosisPlugins) { std::vector<unsigned int> activeProblems = diagnose->activeProblems(); - if (activeProblems.size() > 0) { - return true; - } + return activeProblems.size(); } - return false; + return 0; } void MainWindow::about() @@ -648,8 +676,9 @@ void MainWindow::saveArchiveList() } } } - archiveFile.commit(); - qDebug("%s saved", qPrintable(QDir::toNativeSeparators(m_CurrentProfile->getArchivesFileName()))); + if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(m_CurrentProfile->getArchivesFileName()))); + } } else { qWarning("archive list not initialised"); } @@ -665,10 +694,12 @@ void MainWindow::savePluginList() m_PluginList.saveLoadOrder(*m_DirectoryStructure); } -void MainWindow::modFilterActive(bool active) +void MainWindow::modFilterActive(bool filterActive) { - if (active) { + if (filterActive) { ui->modList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); + } else if (ui->groupCombo->currentIndex() != 0) { + ui->modList->setStyleSheet("QTreeView { border: 2px ridge #337733; }"); } else { ui->modList->setStyleSheet(""); } @@ -824,6 +855,8 @@ void MainWindow::closeEvent(QCloseEvent* event) setCursor(Qt::WaitCursor); + m_IntegratedBrowser.close(); + storeSettings(); // profile has to be cleaned up before the modinfo-buffer is cleared @@ -846,6 +879,12 @@ void MainWindow::createFirstProfile() } +void MainWindow::setBrowserGeometry(const QByteArray &geometry) +{ + m_IntegratedBrowser.restoreGeometry(geometry); +} + + SaveGameGamebryo *MainWindow::getSaveGame(const QString &name) { return new SaveGameGamebryo(this, name); @@ -968,7 +1007,7 @@ bool MainWindow::verifyPlugin(IPlugin *plugin) { if (plugin == NULL) { return false; - } else if (!plugin->init(this)) { + } else if (!plugin->init(new OrganizerProxy(this, plugin->name()))) { qWarning("plugin failed to initialize"); return false; } @@ -990,6 +1029,50 @@ void MainWindow::toolPluginInvoke() } +void MainWindow::requestDownload(const QUrl &url, QNetworkReply *reply) +{ + QToolButton *browserBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionNexus)); + + QList<QAction*> browserActions = browserBtn->menu()->actions(); + foreach (QAction *action, browserActions) { + // the nexus action doesn't have a plugin connected currently + if (action->data().isValid()) { + IPluginModPage *plugin = qobject_cast<IPluginModPage*>(qvariant_cast<QObject*>(action->data())); + if (plugin == NULL) { + qCritical("invalid mod page. This is a bug"); + continue; + } + ModRepositoryFileInfo *fileInfo = new ModRepositoryFileInfo(); + if (plugin->handlesDownload(url, reply->url(), *fileInfo)) { + fileInfo->repository = plugin->name(); + m_DownloadManager.addDownload(reply, fileInfo); + return; + } + } + } + + if (QMessageBox::question(this, tr("Download?"), + tr("A download has been started but no installed page plugin recognizes it.\n" + "If you download anyway no information (i.e. version) will be associated with the download.\n" + "Continue?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo()); + } +} + + +void MainWindow::modPagePluginInvoke() +{ + QAction *triggeredAction = qobject_cast<QAction*>(sender()); + IPluginModPage *plugin = qobject_cast<IPluginModPage*>(triggeredAction->data().value<QObject*>()); + if (plugin->useIntegratedBrowser()) { + m_IntegratedBrowser.setWindowTitle(plugin->displayName()); + m_IntegratedBrowser.openUrl(plugin->pageURL()); + } else { + ::ShellExecuteW(NULL, L"open", ToWString(plugin->pageURL().toString()).c_str(), NULL, NULL, SW_SHOWNORMAL); + } +} + void MainWindow::registerPluginTool(IPluginTool *tool) { QAction *action = new QAction(tool->icon(), tool->displayName(), ui->toolBar); @@ -1002,6 +1085,34 @@ void MainWindow::registerPluginTool(IPluginTool *tool) } +void MainWindow::registerModPage(IPluginModPage *modPage) +{ + QToolButton *browserBtn = NULL; + // turn the browser action into a drop-down menu if necessary + if (ui->actionNexus->menu() == NULL) { + QAction *nexusAction = ui->actionNexus; + // TODO: use a different icon for nexus! + ui->actionNexus = new QAction(nexusAction->icon(), tr("Browse Mod Page"), ui->toolBar); + ui->toolBar->insertAction(nexusAction, ui->actionNexus); + ui->toolBar->removeAction(nexusAction); + actionToToolButton(ui->actionNexus); + + browserBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionNexus)); + browserBtn->menu()->addAction(nexusAction); + } else { + browserBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionNexus)); + } + + QAction *action = new QAction(modPage->icon(), modPage->displayName(), ui->toolBar); + modPage->setParentWidget(this); + action->setData(qVariantFromValue(reinterpret_cast<QObject*>(modPage))); + + connect(action, SIGNAL(triggered()), this, SLOT(modPagePluginInvoke()), Qt::QueuedConnection); + QToolButton *toolBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionNexus)); + toolBtn->menu()->addAction(action); +} + + bool MainWindow::registerPlugin(QObject *plugin, const QString &fileName) { { // generic treatment for all plugins @@ -1021,6 +1132,13 @@ bool MainWindow::registerPlugin(QObject *plugin, const QString &fileName) diagnose->onInvalidated([&] () { this->updateProblemsButton(); }); } } + { // mod page plugin + IPluginModPage *modPage = qobject_cast<IPluginModPage*>(plugin); + if (verifyPlugin(modPage)) { + registerModPage(modPage); + return true; + } + } { // tool plugins IPluginTool *tool = qobject_cast<IPluginTool*>(plugin); if (verifyPlugin(tool)) { @@ -1147,131 +1265,6 @@ void MainWindow::loadPlugins() m_DiagnosisPlugins.push_back(this); } -IGameInfo &MainWindow::gameInfo() const -{ - return *m_GameInfo; -} - - -IModRepositoryBridge *MainWindow::createNexusBridge() const -{ - return new NexusBridge(); -} - - -QString MainWindow::profileName() const -{ - if (m_CurrentProfile != NULL) { - return m_CurrentProfile->getName(); - } else { - return ""; - } -} - -QString MainWindow::profilePath() const -{ - if (m_CurrentProfile != NULL) { - return m_CurrentProfile->getPath(); - } else { - return ""; - } -} - -QString MainWindow::downloadsPath() const -{ - return QDir::fromNativeSeparators(m_Settings.getDownloadDirectory()); -} - -VersionInfo MainWindow::appVersion() const -{ - return m_Updater.getVersion(); -} - - -IModInterface *MainWindow::getMod(const QString &name) -{ - unsigned int index = ModInfo::getIndex(name); - if (index == UINT_MAX) { - return NULL; - } else { - return ModInfo::getByIndex(index).data(); - } -} - - -IModInterface *MainWindow::createMod(GuessedValue<QString> &name) -{ - if (!m_InstallationManager.testOverwrite(name)) { - return NULL; - } - - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - -/* QString fixedName = name; - fixDirectoryName(fixedName); - unsigned int index = ModInfo::getIndex(fixedName); - if (index != UINT_MAX) { - ModInfo::Ptr result = ModInfo::getByIndex(index); - if (!result->isEmpty()) { - throw MyException(tr("The mod \"%1\" already exists!").arg(fixedName)); - } - return result.data(); - } else {*/ - QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name); - - QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat); - - settingsFile.setValue("modid", 0); - settingsFile.setValue("version", ""); - settingsFile.setValue("newestVersion", ""); - settingsFile.setValue("category", 0); - settingsFile.setValue("installationFile", ""); - return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure).data(); -// } -} - -bool MainWindow::removeMod(IModInterface *mod) -{ - unsigned int index = ModInfo::getIndex(mod->name()); - if (index == UINT_MAX) { - return mod->remove(); - } else { - return ModInfo::removeMod(index); - } -} - - -void MainWindow::modDataChanged(IModInterface*) -{ - refreshModList(); -} - -QVariant MainWindow::pluginSetting(const QString &pluginName, const QString &key) const -{ - return m_Settings.pluginSetting(pluginName, key); -} - -void MainWindow::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) -{ - m_Settings.setPluginSetting(pluginName, key, value); -} - -QVariant MainWindow::persistent(const QString &pluginName, const QString &key, const QVariant &def) const -{ - return m_Settings.pluginPersistent(pluginName, key, def); -} - -void MainWindow::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) -{ - m_Settings.setPluginPersistent(pluginName, key, value, sync); -} - -QString MainWindow::pluginDataPath() const -{ - QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath()); - return pluginPath + "/data"; -} - void MainWindow::startSteam() { @@ -1318,11 +1311,12 @@ HANDLE MainWindow::spawnBinaryDirect(const QFileInfo &binary, const QString &arg } } - while (m_RefreshProgress->isVisible()) { + while (m_DirectoryUpdate) { ::Sleep(100); QCoreApplication::processEvents(); } + // need to make sure all data is saved before we start the application if (m_CurrentProfile != nullptr) { m_CurrentProfile->writeModlistNow(true); } @@ -1336,28 +1330,6 @@ HANDLE MainWindow::spawnBinaryDirect(const QFileInfo &binary, const QString &arg } } -/* -void MainWindow::spawnProgram(const QString &fileName, const QString &argumentsArg, - const QString &profileName, const QDir ¤tDirectory) -{ - QFileInfo binary; - QString arguments = argumentsArg; - QString steamAppID; - try { - const Executable &exe = m_ExecutablesList.find(fileName); - steamAppID = exe.m_SteamAppID; - if (arguments == "") { - arguments = exe.m_Arguments; - } - binary = exe.m_BinaryInfo; - } catch (const std::runtime_error&) { - qWarning("\"%s\" not set up as executable", fileName.toUtf8().constData()); - binary = QFileInfo(fileName); - } - spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID); -} -*/ - void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool closeAfterStart, const QString &steamAppID) { @@ -1411,21 +1383,6 @@ void MainWindow::startExeAction() } -void MainWindow::refreshModList(bool saveChanges) -{ - // don't lose changes! - if (saveChanges) { - m_CurrentProfile->writeModlistNow(true); - } - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure); - m_CurrentProfile->refreshModStatus(); - - m_ModList.notifyChange(-1); - - refreshDirectoryStructure(); -} - - void MainWindow::setExecutablesList(const ExecutablesList &executablesList) { m_ExecutablesList = executablesList; @@ -1668,11 +1625,27 @@ bool MainWindow::refreshProfiles(bool selectProfile) } +std::set<QString> MainWindow::managedArchives() +{ + std::set<QString> result; + + QFile archiveFile(m_CurrentProfile->getArchivesFileName()); + if (archiveFile.open(QIODevice::ReadOnly)) { + while (!archiveFile.atEnd()) { + result.insert(QString::fromUtf8(archiveFile.readLine()).trimmed()); + } + archiveFile.close(); + } + return result; +} + + void MainWindow::refreshDirectoryStructure() { m_DirectoryUpdate = true; std::vector<std::tuple<QString, QString, int> > activeModList = m_CurrentProfile->getActiveMods(); - m_DirectoryRefresher.setMods(activeModList); + + m_DirectoryRefresher.setMods(activeModList, managedArchives()); statusBar()->show(); m_RefreshProgress->setRange(0, 100); @@ -1807,6 +1780,20 @@ void MainWindow::refreshESPList() } } +void MainWindow::refreshModList(bool saveChanges) +{ + // don't lose changes! + if (saveChanges) { + m_CurrentProfile->writeModlistNow(true); + } + ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure); + m_CurrentProfile->refreshModStatus(); + + m_ModList.notifyChange(-1); + + refreshDirectoryStructure(); +} + static bool BySortValue(const std::pair<UINT32, QTreeWidgetItem*> &LHS, const std::pair<UINT32, QTreeWidgetItem*> &RHS) { @@ -1814,6 +1801,17 @@ static bool BySortValue(const std::pair<UINT32, QTreeWidgetItem*> &LHS, const st } +template <typename InputIterator> +QStringList toStringList(InputIterator current, InputIterator end) +{ + QStringList result; + for (; current != end; ++current) { + result.append(*current); + } + return result; +} + + void MainWindow::refreshBSAList() { m_ArchivesInit = false; @@ -1849,17 +1847,9 @@ void MainWindow::refreshBSAList() m_ActiveArchives.clear(); - QFile archiveFile(m_CurrentProfile->getArchivesFileName()); - if (archiveFile.open(QIODevice::ReadOnly)) { - while (!archiveFile.atEnd()) { - m_ActiveArchives.append(QString::fromUtf8(archiveFile.readLine())); - } - archiveFile.close(); - - for (int i = 0; i < m_ActiveArchives.count(); ++i) { - m_ActiveArchives[i] = m_ActiveArchives[i].trimmed(); - } - } else { + auto iter = managedArchives(); + m_ActiveArchives = toStringList(iter.begin(), iter.end()); + if (m_ActiveArchives.isEmpty()) { m_ActiveArchives = m_DefaultArchives; } @@ -1878,6 +1868,9 @@ void MainWindow::refreshBSAList() continue; } int index = m_ActiveArchives.indexOf(filename); + if (index == -1) { + index = 0xFFFF; + } QStringList strings(filename); bool isArchive = false; int origin = current->getOrigin(isArchive); @@ -1886,13 +1879,12 @@ void MainWindow::refreshBSAList() newItem->setData(0, Qt::UserRole, index); newItem->setData(1, Qt::UserRole, origin); newItem->setFlags(newItem->flags() & ~Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable); -// newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); if (m_Settings.forceEnableCoreFiles() && m_DefaultArchives.contains(filename)) { newItem->setCheckState(0, Qt::Checked); newItem->setDisabled(true); } else { - newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); + newItem->setCheckState(0, (index != 0xFFFF) ? Qt::Checked : Qt::Unchecked); } if (index < 0) index = 0; @@ -1917,7 +1909,6 @@ void MainWindow::refreshBSAList() ui->bsaList->addTopLevelItem(subItem); } subItem->addChild(iter->second); - //ui->bsaList->addTopLevelItem(iter->second); } checkBSAList(); @@ -1945,7 +1936,7 @@ void MainWindow::checkBSAList() item->setIcon(0, QIcon(":/MO/gui/warning")); item->setToolTip(0, tr("This bsa is enabled in the ini file so it may be required!")); modWarning = true; - } else { +/* } else { QString espName = filename.mid(0, filename.length() - 3).append("esp").toLower(); QString esmName = filename.mid(0, filename.length() - 3).append("esm").toLower(); if (m_PluginList.isEnabled(espName) || m_PluginList.isEnabled(esmName)) { @@ -1953,7 +1944,7 @@ void MainWindow::checkBSAList() item->setToolTip(0, tr("This archive will still be loaded since there is a plugin of the same name but " "its files will not follow installation order!")); modWarning = true; - } + }*/ } } } @@ -2061,51 +2052,74 @@ void MainWindow::storeSettings() m_CurrentProfile->createTweakedIniFile(); saveCurrentLists(); m_Settings.setupLoadMechanism(); - QSettings settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat); - if (m_CurrentProfile != NULL) { - settings.setValue("selected_profile", m_CurrentProfile->getName().toUtf8().constData()); - } else { - settings.remove("selected_profile"); - } - settings.setValue("mod_list_state", ui->modList->header()->saveState()); - settings.setValue("plugin_list_state", ui->espList->header()->saveState()); + QString iniFile = ToQString(GameInfo::instance().getIniFilename()); + shellCopy(iniFile, iniFile + ".new", true, this); - settings.setValue("group_state", ui->groupCombo->currentIndex()); + QSettings::Status result = QSettings::NoError; + { + QSettings settings(iniFile + ".new", QSettings::IniFormat); + if (m_CurrentProfile != NULL) { + settings.setValue("selected_profile", m_CurrentProfile->getName().toUtf8().constData()); + } else { + settings.remove("selected_profile"); + } - settings.setValue("compact_downloads", ui->compactBox->isChecked()); - settings.setValue("ask_for_nexuspw", m_AskForNexusPW); + settings.setValue("mod_list_state", ui->modList->header()->saveState()); + settings.setValue("plugin_list_state", ui->espList->header()->saveState()); - settings.setValue("window_geometry", saveGeometry()); - settings.setValue("window_split", ui->splitter->saveState()); + settings.setValue("group_state", ui->groupCombo->currentIndex()); - settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); + settings.setValue("ask_for_nexuspw", m_AskForNexusPW); - settings.remove("customExecutables"); - settings.beginWriteArray("customExecutables"); - std::vector<Executable>::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); - int count = 0; - for (; current != end; ++current) { - const Executable &item = *current; - if (item.m_Custom || item.m_Toolbar) { - settings.setArrayIndex(count++); - settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath()); - settings.setValue("title", item.m_Title); - settings.setValue("arguments", item.m_Arguments); - settings.setValue("workingDirectory", item.m_WorkingDirectory); - settings.setValue("closeOnStart", item.m_CloseMO == DEFAULT_CLOSE); - settings.setValue("steamAppID", item.m_SteamAppID); - settings.setValue("custom", item.m_Custom); - settings.setValue("toolbar", item.m_Toolbar); + settings.setValue("window_geometry", saveGeometry()); + settings.setValue("window_split", ui->splitter->saveState()); + + settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); + + settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); + + settings.remove("customExecutables"); + settings.beginWriteArray("customExecutables"); + std::vector<Executable>::const_iterator current, end; + m_ExecutablesList.getExecutables(current, end); + int count = 0; + for (; current != end; ++current) { + const Executable &item = *current; + if (item.m_Custom || item.m_Toolbar) { + settings.setArrayIndex(count++); + settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath()); + settings.setValue("title", item.m_Title); + settings.setValue("arguments", item.m_Arguments); + settings.setValue("workingDirectory", item.m_WorkingDirectory); + settings.setValue("closeOnStart", item.m_CloseMO == DEFAULT_CLOSE); + settings.setValue("steamAppID", item.m_SteamAppID); + settings.setValue("custom", item.m_Custom); + settings.setValue("toolbar", item.m_Toolbar); + } } - } - settings.endArray(); + settings.endArray(); - QComboBox *executableBox = findChild<QComboBox*>("executablesListBox"); - settings.setValue("selected_executable", executableBox->currentIndex()); + QComboBox *executableBox = findChild<QComboBox*>("executablesListBox"); + settings.setValue("selected_executable", executableBox->currentIndex()); - FileDialogMemory::save(m_Settings.directInterface()); + FileDialogMemory::save(settings); + + settings.sync(); + result = settings.status(); + } + if (result == QSettings::NoError) { + if (!shellRename(iniFile + ".new", iniFile, true, this)) { + QMessageBox::critical(this, tr("Failed to write settings"), + tr("An error occured trying to write back MO settings: %1").arg(windowsErrorString(::GetLastError()))); + } + } else { + QString reason = result == QSettings::AccessError ? tr("File is write protected") + : result == QSettings::FormatError ? tr("Invalid file format (probably a bug)") + : tr("Unknown error %1").arg(result); + QMessageBox::critical(this, tr("Failed to write settings"), + tr("An error occured trying to write back MO settings: %1").arg(reason)); + } } @@ -2133,172 +2147,6 @@ void MainWindow::on_tabWidget_currentChanged(int index) } } - -void MainWindow::installMod(const QString &fileName) -{ - if (m_CurrentProfile == NULL) { - return; - } - - bool hasIniTweaks = false; - GuessedValue<QString> modName; - m_CurrentProfile->writeModlistNow(); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { - MessageDialog::showMessage(tr("Installation successful"), this); - refreshModList(); - - QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast<const QString&>(modName)); - if (posList.count() == 1) { - ui->modList->scrollTo(posList.at(0)); - } - int modIndex = ModInfo::getIndex(modName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (hasIniTweaks && - (QMessageBox::question(this, tr("Configure Mod"), - tr("This mod contains ini tweaks. Do you want to configure them now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES); - } - testExtractBSA(modIndex); - } else { - reportError(tr("mod \"%1\" not found").arg(modName)); - } - } else if (m_InstallationManager.wasCancelled()) { - QMessageBox::information(this, tr("Installation cancelled"), tr("The mod was not installed completely."), QMessageBox::Ok); - } -} - -QString MainWindow::resolvePath(const QString &fileName) const -{ - if (m_DirectoryStructure == NULL) { - return QString(); - } - const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(fileName), NULL); - if (file.get() != NULL) { - return ToQString(file->getFullPath()); - } else { - return QString(); - } -} - -QStringList MainWindow::listDirectories(const QString &directoryName) const -{ - QStringList result; - DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(directoryName)); - if (dir != NULL) { - std::vector<DirectoryEntry*>::iterator current, end; - dir->getSubDirectories(current, end); - for (; current != end; ++current) { - result.append(ToQString((*current)->getName())); - } - } - return result; -} - -QStringList MainWindow::findFiles(const QString &path, const std::function<bool(const QString&)> &filter) const -{ - QStringList result; - DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); - if (dir != NULL) { - std::vector<FileEntry::Ptr> files = dir->getFiles(); - foreach (FileEntry::Ptr file, files) { - if (filter(ToQString(file->getFullPath()))) { - result.append(ToQString(file->getFullPath())); - } - } - } else { - qWarning("directory %s not found", qPrintable(path)); - } - return result; -} - -QList<IOrganizer::FileInfo> MainWindow::findFileInfos(const QString &path, const std::function<bool (const IOrganizer::FileInfo &)> &filter) const -{ - QList<IOrganizer::FileInfo> result; - DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); - if (dir != NULL) { - std::vector<FileEntry::Ptr> files = dir->getFiles(); - foreach (FileEntry::Ptr file, files) { - FileInfo info; - info.filePath = ToQString(file->getFullPath()); - bool fromArchive = false; - info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)).getName())); - info.archive = fromArchive ? ToQString(file->getArchive()) : ""; - foreach (int idx, file->getAlternatives()) { - info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(idx).getName())); - } - - if (filter(info)) { - result.append(info); - } - } - } else { - qWarning("directory %s not found", qPrintable(path)); - } - return result; -} - -IDownloadManager *MainWindow::downloadManager() -{ - return &m_DownloadManager; -} - -IPluginList *MainWindow::pluginList() -{ - return &m_PluginList; -} - -IModList *MainWindow::modList() -{ - return &m_ModList; -} - -HANDLE MainWindow::startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile) -{ - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; - QString profileName = (profile.length() > 0) ? profile : m_CurrentProfile->getName(); - QString steamAppID; - if (executable.contains('\\') || executable.contains('/')) { - // file path - binary = QFileInfo(executable); - if (binary.isRelative()) { - // relative path, should be relative to game directory - binary = QFileInfo(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/" + executable); - } - if (cwd.length() == 0) { - currentDirectory = binary.absolutePath(); - } - } else { - // only a file name, search executables list - try { - const Executable &exe = m_ExecutablesList.find(executable); - steamAppID = exe.m_SteamAppID; - if (arguments == "") { - arguments = exe.m_Arguments; - } - binary = exe.m_BinaryInfo; - if (cwd.length() == 0) { - currentDirectory = exe.m_WorkingDirectory; - } - } catch (const std::runtime_error&) { - qWarning("\"%s\" not set up as executable", executable.toUtf8().constData()); - binary = QFileInfo(executable); - } - } - - return spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID); -} - -bool MainWindow::onAboutToRun(const std::function<bool (const QString &)> &func) -{ - auto conn = m_AboutToRun.connect(func); - return conn.connected(); -} - std::vector<unsigned int> MainWindow::activeProblems() const { std::vector<unsigned int> problems; @@ -2377,6 +2225,109 @@ void MainWindow::installMod() } } +IModInterface *MainWindow::installMod(const QString &fileName) +{ + if (m_CurrentProfile == NULL) { + return NULL; + } + + bool hasIniTweaks = false; + GuessedValue<QString> modName; + m_CurrentProfile->writeModlistNow(); + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { + MessageDialog::showMessage(tr("Installation successful"), this); + refreshModList(); + + QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast<const QString&>(modName)); + if (posList.count() == 1) { + ui->modList->scrollTo(posList.at(0)); + } + int modIndex = ModInfo::getIndex(modName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + if (hasIniTweaks && + (QMessageBox::question(this, tr("Configure Mod"), + tr("This mod contains ini tweaks. Do you want to configure them now?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { + displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES); + } + m_ModInstalled(modName); + return modInfo.data(); + } else { + reportError(tr("mod \"%1\" not found").arg(modName)); + } + } else if (m_InstallationManager.wasCancelled()) { + QMessageBox::information(this, tr("Installation cancelled"), tr("The mod was not installed completely."), QMessageBox::Ok); + } + return NULL; +} + +IModInterface *MainWindow::getMod(const QString &name) +{ + unsigned int index = ModInfo::getIndex(name); + if (index == UINT_MAX) { + return NULL; + } else { + return ModInfo::getByIndex(index).data(); + } +} + +IModInterface *MainWindow::createMod(GuessedValue<QString> &name) +{ + if (!m_InstallationManager.testOverwrite(name)) { + return NULL; + } + + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + + QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name); + + QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat); + + settingsFile.setValue("modid", 0); + settingsFile.setValue("version", ""); + settingsFile.setValue("newestVersion", ""); + settingsFile.setValue("category", 0); + settingsFile.setValue("installationFile", ""); + return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure).data(); +} + +bool MainWindow::removeMod(IModInterface *mod) +{ + unsigned int index = ModInfo::getIndex(mod->name()); + if (index == UINT_MAX) { + return mod->remove(); + } else { + return ModInfo::removeMod(index); + } +} + +QList<IOrganizer::FileInfo> MainWindow::findFileInfos(const QString &path, const std::function<bool (const IOrganizer::FileInfo &)> &filter) const +{ + QList<IOrganizer::FileInfo> result; + DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); + if (dir != NULL) { + std::vector<FileEntry::Ptr> files = dir->getFiles(); + foreach (FileEntry::Ptr file, files) { + IOrganizer::FileInfo info; + info.filePath = ToQString(file->getFullPath()); + bool fromArchive = false; + info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)).getName())); + info.archive = fromArchive ? ToQString(file->getArchive()) : ""; + foreach (int idx, file->getAlternatives()) { + info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(idx).getName())); + } + + if (filter(info)) { + result.append(info); + } + } + } else { + qDebug("directory %s not found", qPrintable(path)); + } + return result; +} void MainWindow::on_startButton_clicked() { @@ -2580,12 +2531,6 @@ void MainWindow::setESPListSorting(int index) } -void MainWindow::setCompactDownloads(bool compact) -{ - ui->compactBox->setChecked(compact); -} - - bool MainWindow::queryLogin(QString &username, QString &password) { CredentialsDialog dialog(this); @@ -2640,16 +2585,12 @@ void MainWindow::refresher_progress(int percent) void MainWindow::directory_refreshed() { - statusBar()->hide(); - DirectoryEntry *newStructure = m_DirectoryRefresher.getDirectoryStructure(); + Q_ASSERT(newStructure != m_DirectoryStructure); if (newStructure != NULL) { - DirectoryEntry *oldStructure = m_DirectoryStructure; - m_DirectoryStructure = newStructure; - delete oldStructure; - + std::swap(m_DirectoryStructure, newStructure); + delete newStructure; refreshDataTree(); - refreshLists(); } else { // TODO: don't know why this happens, this slot seems to get called twice with only one emit return; @@ -2668,6 +2609,7 @@ void MainWindow::directory_refreshed() ModInfo::Ptr modInfo = ModInfo::getByIndex(i); modInfo->clearCaches(); } + statusBar()->hide(); } @@ -2685,7 +2627,10 @@ void MainWindow::modStatusChanged(unsigned int index) try { ModInfo::Ptr modInfo = ModInfo::getByIndex(index); if (m_CurrentProfile->modEnabled(index)) { - DirectoryRefresher::addModToStructure(m_DirectoryStructure, modInfo->name(), m_CurrentProfile->getModPriority(index), modInfo->absolutePath()); + m_DirectoryRefresher.addModToStructure(m_DirectoryStructure + , modInfo->name() + , m_CurrentProfile->getModPriority(index) + , modInfo->absolutePath()); DirectoryRefresher::cleanStructure(m_DirectoryStructure); } else { if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { @@ -2744,11 +2689,10 @@ void MainWindow::procFinished(int, QProcess::ExitStatus) } -void MainWindow::on_profileRefreshBtn_clicked() +void MainWindow::profileRefresh() { m_CurrentProfile->writeModlist(); -// m_ModList.updateModCollection(); refreshModList(); } @@ -3103,6 +3047,22 @@ void MainWindow::reinstallMod_clicked() } +void MainWindow::resumeDownload(int downloadIndex) +{ + if (NexusInterface::instance()->getAccessManager()->loggedIn()) { + m_DownloadManager.resumeDownload(downloadIndex); + } else { + QString username, password; + if (m_Settings.getNexusLogin(username, password)) { + m_PostLoginTasks.push_back(boost::bind(&MainWindow::resumeDownload, _1, downloadIndex)); + NexusInterface::instance()->getAccessManager()->login(username, password); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus to resume a download"), this); + } + } +} + + void MainWindow::endorseMod(ModInfo::Ptr mod) { if (NexusInterface::instance()->getAccessManager()->loggedIn()) { @@ -3199,9 +3159,9 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); origin.enable(false); - DirectoryRefresher::addModToStructure(m_DirectoryStructure, - modInfo->name(), m_CurrentProfile->getModPriority(index), - modInfo->absolutePath()); + m_DirectoryRefresher.addModToStructure(m_DirectoryStructure, + modInfo->name(), m_CurrentProfile->getModPriority(index), + modInfo->absolutePath()); DirectoryRefresher::cleanStructure(m_DirectoryStructure); refreshLists(); } @@ -3266,54 +3226,6 @@ void MainWindow::displayModInformation(int row, int tab) } -void MainWindow::testExtractBSA(int modIndex) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - QDir dir(modInfo->absolutePath()); - - QFileInfoList archives = dir.entryInfoList(QStringList("*.bsa")); - if (archives.length() != 0 && - (QuestionBoxMemory::query(this, "unpackBSA", tr("Extract BSA"), - tr("This mod contains at least one BSA. Do you want to unpack it?\n" - "(This removes the BSA after completion. If you don't know about BSAs, just select no)"), - QDialogButtonBox::Yes | QDialogButtonBox::No, QDialogButtonBox::No) == QMessageBox::Yes)) { - - - foreach (QFileInfo archiveInfo, archives) { - BSA::Archive archive; - BSA::EErrorCode result = archive.read(archiveInfo.absoluteFilePath().toLocal8Bit().constData()); - if ((result != BSA::ERROR_NONE) && (result != BSA::ERROR_INVALIDHASHES)) { - reportError(tr("failed to read %1: %2").arg(archiveInfo.fileName()).arg(result)); - return; - } - - QProgressDialog progress(this); - progress.setMaximum(100); - progress.setValue(0); - progress.show(); - - archive.extractAll(modInfo->absolutePath().toLocal8Bit().constData(), - boost::bind(&MainWindow::extractProgress, this, boost::ref(progress), _1, _2), - false); - - if (result == BSA::ERROR_INVALIDHASHES) { - reportError(tr("This archive contains invalid hashes. Some files may be broken.")); - } - - archive.close(); - - if (!QFile::remove(archiveInfo.absoluteFilePath())) { - qCritical("failed to remove archive %s", archiveInfo.absoluteFilePath().toUtf8().constData()); - } else { - m_DirectoryStructure->removeFile(ToWString(archiveInfo.fileName())); - } - } - - refreshBSAList(); - } -} - - void MainWindow::ignoreMissingData_clicked() { ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); @@ -3478,13 +3390,13 @@ void MainWindow::replaceCategoriesFromMenu(QMenu *menu, int modRow) } } -void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow) +void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow) { - if (m_ContextRow != -1 && m_ContextRow != modRow) { - ModInfo::Ptr editedModInfo = ModInfo::getByIndex(m_ContextRow); + if (referenceRow != -1 && referenceRow != modRow) { + ModInfo::Ptr editedModInfo = ModInfo::getByIndex(referenceRow); foreach (QAction* action, menu->actions()) { if (action->menu() != NULL) { - addRemoveCategoriesFromMenu(action->menu(), modRow); + addRemoveCategoriesFromMenu(action->menu(), modRow, referenceRow); } else { QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action); if (widgetAction != NULL) { @@ -3501,7 +3413,6 @@ void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow) } } } else { - //This block shouldn't be reached, but if it is then fall back to replace (context row is invalid or replacing edited mod) replaceCategoriesFromMenu(menu, modRow); } } @@ -3513,37 +3424,26 @@ void MainWindow::addRemoveCategories_MenuHandler() { return; } - QModelIndexList selected = ui->modList->selectionModel()->selectedRows(); + QModelIndexList selectedTemp = ui->modList->selectionModel()->selectedRows(); + QList<QPersistentModelIndex> selected; + foreach (const QModelIndex &idx, selectedTemp) { + selected.append(QPersistentModelIndex(idx)); + } if (selected.size() > 0) { - int min = INT_MAX; - int max = INT_MIN; - - QStringList selectedMods; - for (int i = 0; i < selected.size(); ++i) { - QModelIndex temp = mapToModel(&m_ModList, selected.at(i)); - selectedMods.append(temp.data().toString()); - if (temp.row() < min) min = temp.row(); - if (temp.row() > max) max = temp.row(); - // save the currently selected mod for last... then we can use it as a pattern for what is changing... - int modRow = m_ModListSortProxy->mapToSource(selected.at(i)).row(); - if (modRow != m_ContextRow) { - addRemoveCategoriesFromMenu(menu,modRow); + foreach (const QPersistentModelIndex &idx, selected) { + qDebug("change categories on: %s (ref: %s)", qPrintable(idx.data().toString()), qPrintable(m_ContextIdx.data().toString())); + QModelIndex modIdx = mapToModel(&m_ModList, idx); + if (modIdx.row() != m_ContextIdx.row()) { + addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); } } - //come back to the currently selected mod, after the others have been set - replaceCategoriesFromMenu(menu, m_ContextRow); + replaceCategoriesFromMenu(menu, m_ContextIdx.row()); m_ModList.notifyChange(-1); - // find mods by their name because indices are invalidated - QAbstractItemModel *model = ui->modList->model(); - Q_FOREACH(const QString &mod, selectedMods) { - QModelIndexList matches = model->match(model->index(0, 0), Qt::DisplayRole, mod, 1, - Qt::MatchFixedString | Qt::MatchCaseSensitive | Qt::MatchRecursive); - if (matches.size() > 0) { - ui->modList->selectionModel()->select(matches.at(0), QItemSelectionModel::Select | QItemSelectionModel::Rows); - } + foreach (const QPersistentModelIndex &idx, selected) { + ui->modList->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); } } else { //For single mod selections, just do a replace @@ -3564,15 +3464,10 @@ void MainWindow::replaceCategories_MenuHandler() { QModelIndexList selected = ui->modList->selectionModel()->selectedRows(); if (selected.size() > 0) { - int min = INT_MAX; - int max = INT_MIN; - QStringList selectedMods; for (int i = 0; i < selected.size(); ++i) { QModelIndex temp = mapToModel(&m_ModList, selected.at(i)); selectedMods.append(temp.data().toString()); - if (temp.row() < min) min = temp.row(); - if (temp.row() > max) max = temp.row(); replaceCategoriesFromMenu(menu, mapToModel(&m_ModList, selected.at(i)).row()); } @@ -3789,102 +3684,113 @@ void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) menu->addAction(action); } -void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) +QMenu *MainWindow::modListContextMenu() { - try { - QTreeView *modList = findChild<QTreeView*>("modList"); + QMenu *menu = new QMenu(); + menu->addAction(tr("Install Mod..."), this, SLOT(installMod_clicked())); - QModelIndex index = mapToModel(&m_ModList, modList->indexAt(pos)); - m_ContextRow = index.row(); + menu->addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods())); + menu->addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods())); - QMenu menu; + menu->addAction(tr("Check all for update"), this, SLOT(checkModsForUpdates())); - menu.addAction(tr("Install Mod..."), this, SLOT(installMod_clicked())); + menu->addAction(tr("Refresh"), this, SLOT(profileRefresh())); - menu.addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods())); - menu.addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods())); - - menu.addAction(tr("Check all for update"), this, SLOT(checkModsForUpdates())); + menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV())); + return menu; +} - menu.addAction(tr("Refresh"), this, SLOT(on_profileRefreshBtn_clicked())); +void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) +{ + try { + QTreeView *modList = findChild<QTreeView*>("modList"); - menu.addAction(tr("Export to csv..."), this, SLOT(exportModListCSV())); + m_ContextIdx = mapToModel(&m_ModList, modList->indexAt(pos)); + m_ContextRow = m_ContextIdx.row(); - if (m_ContextRow != -1) { - menu.addSeparator(); + QMenu *menu = NULL; + QMenu *allMods = modListContextMenu(); + if (m_ContextRow == -1) { + // no selection + menu = allMods; + menu->setParent(this); + } else { + menu = new QMenu(this); + allMods->setTitle(tr("All Mods")); + menu->addMenu(allMods); ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); std::vector<ModInfo::EFlag> flags = info->getFlags(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { if (QDir(info->absolutePath()).count() > 2) { - menu.addAction(tr("Sync to Mods..."), this, SLOT(syncOverwrite())); - menu.addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite())); + menu->addAction(tr("Sync to Mods..."), this, SLOT(syncOverwrite())); + menu->addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite())); } } else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { - menu.addAction(tr("Restore Backup"), this, SLOT(restoreBackup_clicked())); - menu.addAction(tr("Remove Backup..."), this, SLOT(removeMod_clicked())); + menu->addAction(tr("Restore Backup"), this, SLOT(restoreBackup_clicked())); + menu->addAction(tr("Remove Backup..."), this, SLOT(removeMod_clicked())); } else { QMenu *addRemoveCategoriesMenu = new QMenu(tr("Add/Remove Categories")); populateMenuCategories(addRemoveCategoriesMenu, 0); connect(addRemoveCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(addRemoveCategories_MenuHandler())); - addMenuAsPushButton(&menu, addRemoveCategoriesMenu); + addMenuAsPushButton(menu, addRemoveCategoriesMenu); QMenu *replaceCategoriesMenu = new QMenu(tr("Replace Categories")); populateMenuCategories(replaceCategoriesMenu, 0); connect(replaceCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(replaceCategories_MenuHandler())); - addMenuAsPushButton(&menu, replaceCategoriesMenu); + addMenuAsPushButton(menu, replaceCategoriesMenu); QMenu *primaryCategoryMenu = new QMenu(tr("Primary Category")); connect(primaryCategoryMenu, SIGNAL(aboutToShow()), this, SLOT(addPrimaryCategoryCandidates())); connect(primaryCategoryMenu, SIGNAL(aboutToHide()), this, SLOT(savePrimaryCategory())); - addMenuAsPushButton(&menu, primaryCategoryMenu); + addMenuAsPushButton(menu, primaryCategoryMenu); - menu.addSeparator(); + menu->addSeparator(); if (info->downgradeAvailable()) { - menu.addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme())); + 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())); + menu->addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate())); } else { - menu.addAction(tr("Ignore update"), this, SLOT(ignoreUpdate())); + menu->addAction(tr("Ignore update"), this, SLOT(ignoreUpdate())); } } - menu.addSeparator(); + menu->addSeparator(); - menu.addAction(tr("Rename Mod..."), this, SLOT(renameMod_clicked())); - menu.addAction(tr("Remove Mod..."), this, SLOT(removeMod_clicked())); - menu.addAction(tr("Reinstall Mod"), this, SLOT(reinstallMod_clicked())); + menu->addAction(tr("Rename Mod..."), this, SLOT(renameMod_clicked())); + menu->addAction(tr("Remove Mod..."), this, SLOT(removeMod_clicked())); + menu->addAction(tr("Reinstall Mod"), this, SLOT(reinstallMod_clicked())); switch (info->endorsedState()) { case ModInfo::ENDORSED_TRUE: { - menu.addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked())); + menu->addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked())); } break; case ModInfo::ENDORSED_FALSE: { - menu.addAction(tr("Endorse"), this, SLOT(endorse_clicked())); - menu.addAction(tr("Won't endorse"), this, SLOT(dontendorse_clicked())); + menu->addAction(tr("Endorse"), this, SLOT(endorse_clicked())); + menu->addAction(tr("Won't endorse"), this, SLOT(dontendorse_clicked())); } break; case ModInfo::ENDORSED_NEVER: { - menu.addAction(tr("Endorse"), this, SLOT(endorse_clicked())); + menu->addAction(tr("Endorse"), this, SLOT(endorse_clicked())); } break; default: { - QAction *action = new QAction(tr("Endorsement state unknown"), &menu); + QAction *action = new QAction(tr("Endorsement state unknown"), menu); action->setEnabled(false); - menu.addAction(action); + menu->addAction(action); } break; } std::vector<ModInfo::EFlag> flags = info->getFlags(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - menu.addAction(tr("Ignore missing data"), this, SLOT(ignoreMissingData_clicked())); + menu->addAction(tr("Ignore missing data"), this, SLOT(ignoreMissingData_clicked())); } - menu.addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked())); - menu.addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked())); + menu->addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked())); + menu->addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked())); } - QAction *infoAction = menu.addAction(tr("Information..."), this, SLOT(information_clicked())); - menu.setDefaultAction(infoAction); + QAction *infoAction = menu->addAction(tr("Information..."), this, SLOT(information_clicked())); + menu->setDefaultAction(infoAction); } - menu.exec(modList->mapToGlobal(pos)); + menu->exec(modList->mapToGlobal(pos)); } catch (const std::exception &e) { reportError(tr("Exception: ").arg(e.what())); } catch (...) { @@ -3895,7 +3801,6 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) void MainWindow::on_categoriesList_itemSelectionChanged() { -// int filter = current->data(0, Qt::UserRole).toInt(); QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows(); std::vector<int> categories; foreach (const QModelIndex &index, indices) { @@ -3906,7 +3811,7 @@ void MainWindow::on_categoriesList_itemSelectionChanged() } m_ModListSortProxy->setCategoryFilter(categories); -// ui->currentCategoryLabel->setText(QString("(%1)").arg(current->text(0))); + ui->clickBlankLabel->setEnabled(categories.size() > 0); if (indices.count() == 0) { ui->currentCategoryLabel->setText(QString("(%1)").arg(tr("<All>"))); } else if (indices.count() > 1) { @@ -4066,16 +3971,18 @@ void MainWindow::linkDesktop() reportError(tr("failed to remove %1").arg(linkName)); } } else { - QFileInfo exeInfo(m_ExeName); + QFileInfo exeInfo(qApp->arguments().at(0)); // create link std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); - std::wstring parameter = ToWString(QString("\"") + selectedExecutable.m_BinaryInfo.absoluteFilePath() + "\" " + selectedExecutable.m_Arguments); + std::wstring parameter = ToWString(QString("\"%1\" %2").arg(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())) + .arg(selectedExecutable.m_Arguments)); std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName()); - std::wstring currentDirectory = ToWString(selectedExecutable.m_BinaryInfo.absolutePath()); - - if (CreateShortcut(targetFile.c_str(), parameter.c_str(), - linkName.toUtf8().constData(), - description.c_str(), currentDirectory.c_str()) != E_INVALIDARG) { + std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(exeInfo.absolutePath())); + if (CreateShortcut(targetFile.c_str() + , parameter.c_str() + , linkName.toUtf8().constData() + , description.c_str() + , currentDirectory.c_str()) != E_INVALIDARG) { ui->linkButton->menu()->actions().at(0)->setIcon(QIcon(":/MO/gui/remove")); } else { reportError(tr("failed to create %1").arg(linkName)); @@ -4097,12 +4004,13 @@ void MainWindow::linkMenu() reportError(tr("failed to remove %1").arg(linkName)); } } else { - QFileInfo exeInfo(m_ExeName); + QFileInfo exeInfo(qApp->arguments().at(0)); // create link std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); - std::wstring parameter = ToWString(QString("\"") + selectedExecutable.m_BinaryInfo.absoluteFilePath() + "\" " + selectedExecutable.m_Arguments); + std::wstring parameter = ToWString(QString("\"%1\" %2").arg(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())) + .arg(selectedExecutable.m_Arguments)); std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName()); - std::wstring currentDirectory = ToWString(selectedExecutable.m_BinaryInfo.absolutePath()); + std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(exeInfo.absolutePath())); if (CreateShortcut(targetFile.c_str(), parameter.c_str(), linkName.toUtf8().constData(), @@ -4153,6 +4061,8 @@ void MainWindow::on_actionSettings_triggered() } NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion()); + + updateDownloadListDelegate(); } @@ -4175,17 +4085,30 @@ void MainWindow::linkClicked(const QString &url) } -void MainWindow::downloadRequestedNXM(const QString &url) +bool MainWindow::nexusLogin() { QString username, password; - qDebug("download requested: %s", qPrintable(url)); - if (!m_LoginAttempted && !NexusInterface::instance()->getAccessManager()->loggedIn() && - (m_Settings.getNexusLogin(username, password) || - (m_AskForNexusPW && queryLogin(username, password)))) { + NXMAccessManager *accessManager = NexusInterface::instance()->getAccessManager(); + + if (!accessManager->loginAttempted() + && !accessManager->loggedIn() + && (m_Settings.getNexusLogin(username, password) + || (m_AskForNexusPW + && queryLogin(username, password)))) { + accessManager->login(username, password); + return true; + } else { + return false; + } +} + + +void MainWindow::downloadRequestedNXM(const QString &url) +{ + qDebug("download requested: %s", qPrintable(url)); + if (nexusLogin()) { m_PendingDownloads.append(url); - NexusInterface::instance()->getAccessManager()->login(username, password); - m_LoginAttempted = true; } else { m_DownloadManager.addNXMDownload(url); } @@ -4195,7 +4118,7 @@ void MainWindow::downloadRequestedNXM(const QString &url) void MainWindow::downloadRequested(QNetworkReply *reply, int modID, const QString &fileName) { try { - if (m_DownloadManager.addDownload(reply, QStringList(), fileName, modID)) { + if (m_DownloadManager.addDownload(reply, QStringList(), fileName, modID, 0, new ModRepositoryFileInfo(modID))) { MessageDialog::showMessage(tr("Download started"), this); } } catch (const std::exception &e) { @@ -4210,7 +4133,9 @@ void MainWindow::installTranslator(const QString &name) QTranslator *translator = new QTranslator(this); QString fileName = name + "_" + m_CurrentLanguage; if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { - qWarning("localization file %s not found", qPrintable(fileName)); + if (m_CurrentLanguage != "en-US") { + qWarning("localization file %s not found", qPrintable(fileName)); + } // we don't actually expect localization files for english } qApp->installTranslator(translator); m_Translators.push_back(translator); @@ -4291,7 +4216,8 @@ void MainWindow::installDownload(int index) QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES); } - testExtractBSA(modIndex); + + m_ModInstalled(modName); } else { reportError(tr("mod \"%1\" not found").arg(modName)); } @@ -4642,13 +4568,7 @@ void MainWindow::on_conflictsCheckBox_toggled(bool) void MainWindow::on_actionUpdate_triggered() { - QString username, password; - - if (!m_LoginAttempted && !NexusInterface::instance()->getAccessManager()->loggedIn() && - (m_Settings.getNexusLogin(username, password) || - (m_AskForNexusPW && queryLogin(username, password)))) { - NexusInterface::instance()->getAccessManager()->login(username, password); - m_LoginAttempted = true; + if (nexusLogin()) { m_PostLoginTasks.push_back([&](MainWindow*) { m_Updater.startUpdate(); }); } else { m_Updater.startUpdate(); @@ -4661,17 +4581,19 @@ void MainWindow::on_actionEndorseMO_triggered() if (QMessageBox::question(this, tr("Endorse Mod Organizer"), tr("Do you want to endorse Mod Organizer on %1 now?").arg(ToQString(GameInfo::instance().getNexusPage())), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - NexusInterface::instance()->requestToggleEndorsement(GameInfo::instance().getNexusModID(), true, this, QVariant()); + NexusInterface::instance()->requestToggleEndorsement(GameInfo::instance().getNexusModID(), true, this, QVariant(), QString()); } } void MainWindow::updateDownloadListDelegate() { - if (ui->compactBox->isChecked()) { - ui->downloadView->setItemDelegate(new DownloadListWidgetCompactDelegate(&m_DownloadManager, ui->downloadView, ui->downloadView)); + if (m_Settings.compactDownloads()) { + ui->downloadView->setItemDelegate(new DownloadListWidgetCompactDelegate(&m_DownloadManager, m_Settings.metaDownloads(), + ui->downloadView, ui->downloadView)); } else { - ui->downloadView->setItemDelegate(new DownloadListWidgetDelegate(&m_DownloadManager, ui->downloadView, ui->downloadView)); + ui->downloadView->setItemDelegate(new DownloadListWidgetDelegate(&m_DownloadManager, m_Settings.metaDownloads(), + ui->downloadView, ui->downloadView)); } DownloadListSortProxy *sortProxy = new DownloadListSortProxy(&m_DownloadManager, ui->downloadView); @@ -4689,13 +4611,7 @@ void MainWindow::updateDownloadListDelegate() connect(ui->downloadView->itemDelegate(), SIGNAL(restoreDownload(int)), &m_DownloadManager, SLOT(restoreDownload(int))); connect(ui->downloadView->itemDelegate(), SIGNAL(cancelDownload(int)), &m_DownloadManager, SLOT(cancelDownload(int))); connect(ui->downloadView->itemDelegate(), SIGNAL(pauseDownload(int)), &m_DownloadManager, SLOT(pauseDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(resumeDownload(int)), &m_DownloadManager, SLOT(resumeDownload(int))); -} - - -void MainWindow::on_compactBox_toggled(bool) -{ - updateDownloadListDelegate(); + connect(ui->downloadView->itemDelegate(), SIGNAL(resumeDownload(int)), this, SLOT(resumeDownload(int))); } @@ -4755,19 +4671,6 @@ void MainWindow::nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant us } } -/* -void MainWindow::nxmEndorsementToggled(int, QVariant, QVariant resultData, int) -{ - if (resultData.toBool()) { - ui->actionEndorseMO->setVisible(false); - QMessageBox::question(this, tr("Thank you!"), tr("Thank you for your endorsement!")); - } - - if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(int, QVariant, QVariant, int)), - this, SLOT(nxmEndorsementToggled(int, QVariant, QVariant, int)))) { - qCritical("failed to disconnect endorsement slot"); - } -}*/ void MainWindow::nxmDownloadURLs(int, int, QVariant, QVariant resultData, int) { @@ -4813,6 +4716,7 @@ void MainWindow::loginSuccessful(bool necessary) } m_PostLoginTasks.clear(); + NexusInterface::instance()->loginCompleted(); } @@ -4838,6 +4742,7 @@ void MainWindow::loginFailed(const QString &message) m_PostLoginTasks.clear(); statusBar()->hide(); } + NexusInterface::instance()->loginCompleted(); } @@ -5024,10 +4929,16 @@ void MainWindow::editCategories() } } +void MainWindow::deselectFilters() +{ + ui->categoriesList->clearSelection(); +} + void MainWindow::on_categoriesList_customContextMenuRequested(const QPoint &pos) { QMenu menu; menu.addAction(tr("Edit Categories..."), this, SLOT(editCategories())); + menu.addAction(tr("Deselect filter"), this, SLOT(deselectFilters())); menu.exec(ui->categoriesList->mapToGlobal(pos)); } @@ -5060,8 +4971,13 @@ void MainWindow::unlockESPIndex() void MainWindow::removeFromToolbar() { - Executable &exe = m_ExecutablesList.find(m_ContextAction->text()); - exe.m_Toolbar = false; + try { + Executable &exe = m_ExecutablesList.find(m_ContextAction->text()); + exe.m_Toolbar = false; + } catch (const std::runtime_error&) { + qDebug("executable doesn't exist any more"); + } + updateToolBar(); } @@ -5069,11 +4985,13 @@ void MainWindow::removeFromToolbar() void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) { QAction *action = ui->toolBar->actionAt(point); - if (action->objectName().startsWith("custom_")) { - m_ContextAction = action; - QMenu menu; - menu.addAction(tr("Remove"), this, SLOT(removeFromToolbar())); - menu.exec(ui->toolBar->mapToGlobal(point)); + if (action != NULL) { + if (action->objectName().startsWith("custom_")) { + m_ContextAction = action; + QMenu menu; + menu.addAction(tr("Remove"), this, SLOT(removeFromToolbar())); + menu.exec(ui->toolBar->mapToGlobal(point)); + } } } @@ -5143,10 +5061,10 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index) connect(ui->modList, SIGNAL(expanded(QModelIndex)),newModel, SLOT(expanded(QModelIndex))); connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); - } else { m_ModListSortProxy->setSourceModel(&m_ModList); } + modFilterActive(m_ModListSortProxy->isFilterActive()); } void MainWindow::on_linkButton_pressed() @@ -5169,20 +5087,326 @@ void MainWindow::on_showHiddenBox_toggled(bool checked) m_DownloadManager.setShowHidden(checked); } + +void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) +{ + SECURITY_ATTRIBUTES secAttributes; + secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttributes.bInheritHandle = TRUE; + secAttributes.lpSecurityDescriptor = NULL; + + if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { + qCritical("failed to create stdout reroute"); + } + + if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { + qCritical("failed to correctly set up the stdout reroute"); + *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; + } +} + +std::string MainWindow::readFromPipe(HANDLE stdOutRead) +{ + static const int chunkSize = 128; + std::string result; + + char buffer[chunkSize + 1]; + buffer[chunkSize] = '\0'; + + DWORD read = 1; + while (read > 0) { + if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, NULL)) { + break; + } + if (read > 0) { + result.append(buffer, read); + if (read < chunkSize) { + break; + } + } + } + return result; +} + +void MainWindow::processLOOTOut(const std::string &lootOut, std::string &reportURL, std::string &errorMessages, QProgressDialog &dialog) +{ + std::vector<std::string> lines; + boost::split(lines, lootOut, boost::is_any_of("\r\n")); + foreach (const std::string &line, lines) { + if (line.length() > 0) { + size_t progidx = line.find("[progress]"); + 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()); + } else if (reportidx != std::string::npos) { + reportURL = line.substr(reportidx + 9); + } else if (erroridx != std::string::npos) { + qWarning("%s", line.c_str()); + errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); + } else { + qDebug("%s", line.c_str()); + } + } + } +} + + +HANDLE MainWindow::startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile) +{ + QFileInfo binary; + QString arguments = args.join(" "); + QString currentDirectory = cwd; + QString profileName = profile; + if (profile.length() == 0) { + if (m_CurrentProfile != NULL) { + profileName = m_CurrentProfile->getName(); + } else { + throw MyException(tr("No profile set")); + } + } + QString steamAppID; + if (executable.contains('\\') || executable.contains('/')) { + // file path + binary = QFileInfo(executable); + if (binary.isRelative()) { + // relative path, should be relative to game directory + binary = QFileInfo(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/" + executable); + } + if (cwd.length() == 0) { + currentDirectory = binary.absolutePath(); + } + } else { + // only a file name, search executables list + try { + const Executable &exe = m_ExecutablesList.find(executable); + steamAppID = exe.m_SteamAppID; + if (arguments == "") { + arguments = exe.m_Arguments; + } + binary = exe.m_BinaryInfo; + if (cwd.length() == 0) { + currentDirectory = exe.m_WorkingDirectory; + } + } catch (const std::runtime_error&) { + qWarning("\"%s\" not set up as executable", executable.toUtf8().constData()); + binary = QFileInfo(executable); + } + } + + return spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID); +} + void MainWindow::on_bossButton_clicked() { + std::string reportURL; + std::string errorMessages; + + m_CurrentProfile->writeModlistNow(); + + bool success = false; + try { this->setEnabled(false); ON_BLOCK_EXIT([&] () { this->setEnabled(true); }); - LockedDialog dialog(this, tr("BOSS working"), false); + QProgressDialog dialog(this); + dialog.setLabelText(tr("LOOT working")); + dialog.setMaximum(0); dialog.show(); - qApp->processEvents(); - m_PluginList.bossSort(); - savePluginList(); - dialog.hide(); + QStringList parameters; + parameters << "--game" << ToQString(GameInfo::instance().getGameShortName()) + << "--gamePath" << QString("\"%1\"").arg(ToQString(GameInfo::instance().getGameDirectory())); + + HANDLE stdOutWrite = INVALID_HANDLE_VALUE; + HANDLE stdOutRead = INVALID_HANDLE_VALUE; + createStdoutPipe(&stdOutRead, &stdOutWrite); + HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), + parameters.join(" "), + m_CurrentProfile->getName(), + m_Settings.logLevel(), + qApp->applicationDirPath() + "/loot", + true, + stdOutWrite); + + // we don't use the write end + ::CloseHandle(stdOutWrite); + + if (loot != INVALID_HANDLE_VALUE) { + while (::WaitForSingleObject(loot, 100) == WAIT_TIMEOUT) { + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); + if (dialog.wasCanceled()) { + ::TerminateProcess(loot, 1); + } + std::string lootOut = readFromPipe(stdOutRead); + processLOOTOut(lootOut, reportURL, errorMessages, dialog); + } + std::string remainder = readFromPipe(stdOutRead).c_str(); + if (remainder.length() > 0) { + processLOOTOut(remainder, reportURL, errorMessages, dialog); + } + DWORD exitCode = 0UL; + ::GetExitCodeProcess(loot, &exitCode); + if (exitCode != 0UL) { + reportError(tr("loot failed. Exit code was: %1").arg(exitCode)); + return; + } else { + success = true; + } + } } catch (const std::exception &e) { - reportError(tr("failed to run boss: %1").arg(e.what())); - ui->bossButton->setEnabled(false); + reportError(tr("failed to run loot: %1").arg(e.what())); + } + if (errorMessages.length() > 0) { + QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occured"), errorMessages.c_str(), QMessageBox::Ok, this); + warn->setModal(false); + warn->show(); + } + + if (success) { + 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"); + } + } + refreshESPList(); + + if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { + QFile::remove(m_CurrentProfile->getLoadOrderFileName()); + } + } +} + + +const char *MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??"; +const char *MainWindow::PATTERN_BACKUP_REGEX = "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)"; +const char *MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss"; + + +bool MainWindow::createBackup(const QString &filePath, const QDateTime &time) +{ + QString outPath = filePath + "." + time.toString(PATTERN_BACKUP_DATE); + if (shellCopy(QStringList(filePath), QStringList(outPath), this)) { + QFileInfo fileInfo(filePath); + removeOldFiles(fileInfo.absolutePath(), fileInfo.fileName() + PATTERN_BACKUP_GLOB, 3, QDir::Name); + return true; + } else { + return false; + } +} + +void MainWindow::on_saveButton_clicked() +{ + savePluginList(); + QDateTime now = QDateTime::currentDateTime(); + if (createBackup(m_CurrentProfile->getPluginsFileName(), now) + && createBackup(m_CurrentProfile->getLoadOrderFileName(), now) + && createBackup(m_CurrentProfile->getLockedOrderFileName(), now)) { + MessageDialog::showMessage(tr("Backup of load order created"), this); } } + +QString MainWindow::queryRestore(const QString &filePath) +{ + QFileInfo pluginFileInfo(filePath); + QString pattern = pluginFileInfo.fileName() + ".*"; + QFileInfoList files = pluginFileInfo.absoluteDir().entryInfoList(QStringList(pattern), QDir::Files, QDir::Name); + + SelectionDialog dialog(tr("Choose backup to restore"), this); + QRegExp exp(pluginFileInfo.fileName() + PATTERN_BACKUP_REGEX); + QRegExp exp2(pluginFileInfo.fileName() + "\\.(.*)"); + foreach(const QFileInfo &info, files) { + if (exp.exactMatch(info.fileName())) { + QDateTime time = QDateTime::fromString(exp.cap(1), PATTERN_BACKUP_DATE); + dialog.addChoice(time.toString(), "", exp.cap(1)); + } else if (exp2.exactMatch(info.fileName())) { + dialog.addChoice(exp2.cap(1), "", exp2.cap(1)); + } + } + + if (dialog.numChoices() == 0) { + QMessageBox::information(this, tr("No Backups"), tr("There are no backups to restore")); + return QString(); + } + + if (dialog.exec() == QDialog::Accepted) { + return dialog.getChoiceData().toString(); + } else { + return QString(); + } +} + +void MainWindow::on_restoreButton_clicked() +{ + QString pluginName = m_CurrentProfile->getPluginsFileName(); + QString choice = queryRestore(pluginName); + if (!choice.isEmpty()) { + QString loadOrderName = m_CurrentProfile->getLoadOrderFileName(); + QString lockedName = m_CurrentProfile->getLockedOrderFileName(); + if (!shellCopy(pluginName + "." + choice, pluginName, true, this) || + !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || + !shellCopy(lockedName + "." + choice, lockedName, true, this)) { + QMessageBox::critical(this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + } + refreshESPList(); + } +} + +void MainWindow::on_saveModsButton_clicked() +{ + m_CurrentProfile->writeModlistNow(true); + QDateTime now = QDateTime::currentDateTime(); + if (createBackup(m_CurrentProfile->getModlistFileName(), now)) { + MessageDialog::showMessage(tr("Backup of modlist created"), this); + } +} +void MainWindow::on_restoreModsButton_clicked() +{ + QString modlistName = m_CurrentProfile->getModlistFileName(); + QString choice = queryRestore(modlistName); + if (!choice.isEmpty()) { + if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { + QMessageBox::critical(this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + } + refreshModList(false); + } +} + +void MainWindow::on_actionCopy_Log_to_Clipboard_triggered() +{ + QStringList lines; + QAbstractItemModel *model = ui->logList->model(); + for (int i = 0; i < model->rowCount(); ++i) { + lines.append(QString("%1 [%2] %3").arg(model->index(i, 0).data().toString()) + .arg(model->index(i, 1).data(Qt::UserRole).toString()) + .arg(model->index(i, 1).data().toString())); + } + QApplication::clipboard()->setText(lines.join("\n")); +} + +void MainWindow::on_categoriesAndBtn_toggled(bool checked) +{ + if (checked) { + m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_AND); + } +} + +void MainWindow::on_categoriesOrBtn_toggled(bool checked) +{ + if (checked) { + m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_OR); + } +} + +void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) +{ + QToolTip::showText(QCursor::pos(), + ui->managedArchiveLabel->toolTip()); +} |
