Compare commits

..
Author SHA1 Message Date
Tannin b2a7c7c223 Merge 2014-06-23 21:43:40 +02:00
Tannin 7b4582c1b1 - adjusted mod load order in hook.dll to match the one from the UI
- bugfix: fnis checker took files in archives into consideration for hashing
2014-06-23 20:23:59 +02:00
Tannin 5d7b23acef - bugfix: networkaccessmanager of browser dialog wasn't destroyed correctly. As a result the cookie jar doesn't get saved 2014-06-21 12:16:57 +02:00
Tannin 3628ec930b - changed the url used to login on nexus (the previous page was disabled)
- bugfix: download manager might have tried to read from a closed connection
2014-06-21 10:39:28 +02:00
Tannin e22aeca3ff - download read buffer is now limited to 1MB. Might prevent out-of-memory errors during download
- bugfix: plugins are now disabled when deactivating a mod  before the files are removed from the vfs
2014-06-17 18:36:28 +02:00
Tannin cbd6b288eb Merge 2014-06-16 21:50:53 +02:00
Tannin 484749e612 - added pseudo-categories to filter for mo-managed vs. unmanaged mods
- deleted mods are now moved to the recycle bin instead of being deleted permanently
- reduced modinfo dialog for foreign mods
- foreign mods are now displayed with a pseudo-category
- added a signal when a plugin is moved
- bugfix: refreshing of directory structure for mods with bsas didn't work correctly when enabling a mod and on changing display of foreign mods
- bugfix: one mod in the list was assigned the same priority on the directory structure as the data directory
- bugfix: conflicts tab in the mod info dialog offered the hide option for files in bsas
2014-06-16 21:49:57 +02:00
Tannin 5b3cdaa83b bugfix: the warning dialog upon changing the mod directory didn't have an effect, the process couldn't be canceled 2014-06-14 09:17:57 +02:00
Tannin 6d3f88b330 Added tag release v1.2.3 for changeset c503e48f97aa 2014-06-08 15:10:21 +02:00
Tannin 0feb4b702d - loot client now only updates the masterlist once per MO session
- new event to notify plugins of changed mod priority
- overwrite now shows up in the "checked" category instead of "unchecked"
- display of "foreign" mods can now be limited to only official content
- bugfix: bsa extraction dialog showed up even if the plugin was disabled
- bugfix: after detection of foreign mods the priority of the overwrite folder could get messed up
- bugfix: when displaying only the context menu for the mod list as a whole, the menu didn't disappear
- bugfix: MO crashed when trying to download via the integrated browser
2014-06-08 15:10:08 +02:00
Tannin 5a4b6e70fd - plugin-list now displays loot messages 2014-06-02 19:15:23 +02:00
Tannin 18574c2ba8 - removed needless modlist-rewrite 2014-05-31 13:44:26 +02:00
Tannin 1eb783aae3 - added a new mod type that represents files handled externally (i.e. DLCs) as mods in MO
- hashes of file names in bsa files are no longer checked all the time
- author and description is now read from esp files
- rewrote the code that fixes modlists after a rename, should be a bit more robust
- fixes to qt 5 and msvc 2013 compatibility
- started to update the tutorial (not done yet!)
- bugfix: counter for the problems badge wasn't calculated correctly
2014-05-31 13:43:48 +02:00
Tannin 93a19799b8 Merge 2014-05-28 14:23:11 +02:00
Tannin 2a31eb40fb small bugfixes 2014-05-28 14:14:28 +02:00
Tannin cf0a1bc2be - files in bsas are now only displayed in the data tab if they are managed by mo
- number of problems detected by MO is now displayed as a badge on the icon
- rephrased the explanation text on the Archives tab. unchecked plugin-loaded bsas no longer prompt a warning
- bsa extraction is now handled in a plugin
- added a way for plugins to react to mod installation
- re-enabled the automatic fix for asset order problems
- bugfix: In some cases when a download wasn't started successfully the download urls weren't stored in the meta file so no resume was possible
- bugfix: MO tried to resume downloads when it didn't have and download urls
- bugfix: downloads couldn't be paused if the download was already broken on the network layer
- bugfix: download managear did not recognize a file as downloaded if the download completed before signals were hooked up
- bugfix: in-place file replacement was re-broken
2014-05-25 15:39:45 +02:00
Tannin 48c8cca578 - NCC will now report an error if a script tries to extract a non-existent file instead of creating an empty output file
- ncc installer plugin now uses a more reliable method to force the installer window to the foreground
- missing version on TESV.exe will no longer be reported as an error
- bugfix: loot client didn't read list of active mods
- bugfix: invalid free call in error reporting function
2014-05-15 19:11:19 +02:00
Tannin 977b407525 - mod list context menu split into two menus (one for whole list, one for selected mods)
- added option to combine category filters using "or"
- added context menu option for deselecting category filters
- slightly changed ui on the category filters
- added a sample plugin for cpp that can be built without building the rest of MO
- simple installer can now be configured to run without any user interaction
- extended interface for python plugins
- iorganizer implementation moved out of the main window
- nexus requests from plugins will now be identified in the user agent
- bugfix: shortcuts created from MO used the wrong working directory
- bugfix: deactivation of bsas didn't stick
- bugfix: file hiding mechanism wasn't active
- bugfix: executables linked on the toolbar couldn't be removed if the executable was removed first
- bugfix: the endorsement-filter couldn't be combined with other filters
- bugfix: python interface to repository bridge was broken
2014-05-13 21:20:44 +02:00
Tannin f4b1aba61a Added tag release v1.2.1 for changeset 366d0a6ac409 2014-05-06 21:07:34 +02:00
66 changed files with 2329 additions and 1262 deletions
+13
View File
@@ -18,5 +18,18 @@ staging_prepare/*
staging_trans/*
tools/python_zip/*
Makefile
html
*.vcxproj
*.pdb
*.dll
*.exp
*.tlog
*.user
*.obj
*.suo
*.sln
*.log
*.filters
*.lib
syntax: regexp
Makefile\.(Debug|Release)
+13 -6
View File
@@ -153,17 +153,24 @@ private:
"<a href=\"\\1\">\\1</a>");
m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"),
"<a href=\"\\1\">\\2</a>");
m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), "");
m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), "");
m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), " ");
m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), " ");
m_TagMap["email="] = std::make_pair(QRegExp("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"),
"<a href=\"mailto:\\1\">\\2</a>");
m_TagMap["youtube"] = std::make_pair(QRegExp("\\[youtube\\](.*)\\[/youtube\\]"),
"<a href=\"http://www.youtube.com/v/\\1\">http://www.youtube.com/v/\\1</a>");
m_ColorMap = boost::assign::map_list_of("red", "FF0000")("green", "00FF00")("blue", "0000FF")
("black", "000000")("gray", "7F7F7F")("white", "FFFFFF")
("yellow", "FFFF00")("cyan", "00FFFF")("magenta", "FF00FF")
("brown", "A52A2A")("orange", "FFCC00");
m_ColorMap.insert(std::make_pair<QString, QString>("red", "FF0000"));
m_ColorMap.insert(std::make_pair<QString, QString>("green", "00FF00"));
m_ColorMap.insert(std::make_pair<QString, QString>("blue", "0000FF"));
m_ColorMap.insert(std::make_pair<QString, QString>("black", "000000"));
m_ColorMap.insert(std::make_pair<QString, QString>("gray", "7F7F7F"));
m_ColorMap.insert(std::make_pair<QString, QString>("white", "FFFFFF"));
m_ColorMap.insert(std::make_pair<QString, QString>("yellow", "FFFF00"));
m_ColorMap.insert(std::make_pair<QString, QString>("cyan", "00FFFF"));
m_ColorMap.insert(std::make_pair<QString, QString>("magenta", "FF00FF"));
m_ColorMap.insert(std::make_pair<QString, QString>("brown", "A52A2A"));
m_ColorMap.insert(std::make_pair<QString, QString>("orange", "FFCC00"));
// make all patterns non-greedy and case-insensitive
for (TagMap::iterator iter = m_TagMap.begin(); iter != m_TagMap.end(); ++iter) {
+8 -8
View File
@@ -43,7 +43,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
BrowserDialog::BrowserDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::BrowserDialog)
, m_AccessManager(new QNetworkAccessManager)
, m_AccessManager(new QNetworkAccessManager(this))
{
ui->setupUi(this);
@@ -255,16 +255,16 @@ void BrowserDialog::on_searchEdit_returnPressed()
}
}
void BrowserDialog::on_browserTabWidget_currentChanged(QWidget *current)
void BrowserDialog::on_refreshBtn_clicked()
{
BrowserView *currentView = qobject_cast<BrowserView*>(current);
getCurrentView()->reload();
}
void BrowserDialog::on_browserTabWidget_currentChanged(int index)
{
BrowserView *currentView = qobject_cast<BrowserView*>(ui->browserTabWidget->widget(index));
if (currentView != NULL) {
ui->backBtn->setEnabled(currentView->history()->canGoBack());
ui->fwdBtn->setEnabled(currentView->history()->canGoForward());
}
}
void BrowserDialog::on_refreshBtn_clicked()
{
getCurrentView()->reload();
}
+2 -2
View File
@@ -99,10 +99,10 @@ private slots:
void startSearch();
void on_browserTabWidget_currentChanged(QWidget *arg1);
void on_refreshBtn_clicked();
void on_browserTabWidget_currentChanged(int index);
private:
QString guessFileName(const QString &url);
+2
View File
@@ -47,6 +47,8 @@ public:
static const int CATEGORY_SPECIAL_NOCATEGORY = 10003;
static const int CATEGORY_SPECIAL_CONFLICT = 10004;
static const int CATEGORY_SPECIAL_NOTENDORSED = 10005;
static const int CATEGORY_SPECIAL_MANAGED = 10006;
static const int CATEGORY_SPECIAL_UNMANAGED = 10007;
public:
+62 -18
View File
@@ -20,8 +20,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "directoryrefresher.h"
#include "utility.h"
#include "report.h"
#include "modinfo.h"
#include <gameinfo.h>
#include <QDir>
#include <QString>
using namespace MOBase;
@@ -46,12 +48,20 @@ DirectoryEntry *DirectoryRefresher::getDirectoryStructure()
return result;
}
void DirectoryRefresher::setMods(const std::vector<std::tuple<QString, QString, int> > &mods)
void DirectoryRefresher::setMods(const std::vector<std::tuple<QString, QString, int> > &mods
, const std::set<QString> &managedArchives)
{
QMutexLocker locker(&m_RefreshLock);
m_Mods = mods;
}
m_Mods.clear();
for (auto mod = mods.begin(); mod != mods.end(); ++mod) {
QString name = std::get<0>(*mod);
ModInfo::Ptr info = ModInfo::getByIndex(ModInfo::getIndex(name));
m_Mods.push_back(EntryInfo(name, std::get<1>(*mod), info->stealFiles(), info->archives(), std::get<2>(*mod)));
}
m_EnabledArchives = managedArchives;
}
void DirectoryRefresher::cleanStructure(DirectoryEntry *structure)
{
@@ -61,24 +71,59 @@ void DirectoryRefresher::cleanStructure(DirectoryEntry *structure)
}
static wchar_t *dirs[] = { L"fomod" };
for (int i = 0; i < sizeof(files) / sizeof(wchar_t*); ++i) {
structure->removeDir(dirs[i]);
for (int i = 0; i < sizeof(dirs) / sizeof(wchar_t*); ++i) {
structure->removeDir(std::wstring(dirs[i]));
}
}
void DirectoryRefresher::addModToStructure(DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory)
void DirectoryRefresher::addModBSAToStructure(DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &archives)
{
std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory));
directoryStructure->addFromOrigin(ToWString(modName), directoryW, priority);
QDir dir(directory);
QFileInfoList bsaFiles = dir.entryInfoList(QStringList("*.bsa"), QDir::Files);
foreach (QFileInfo file, bsaFiles) {
directoryStructure->addFromBSA(ToWString(modName), directoryW,
ToWString(QDir::toNativeSeparators(file.absoluteFilePath())), priority);
foreach (const QString &archive, archives) {
QFileInfo fileInfo(archive);
if (m_EnabledArchives.find(fileInfo.fileName()) != m_EnabledArchives.end()) {
directoryStructure->addFromBSA(ToWString(modName), directoryW,
ToWString(QDir::toNativeSeparators(fileInfo.absoluteFilePath())), priority);
}
}
}
void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &stealFiles)
{
std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory));
if (stealFiles.length() > 0) {
// instead of adding all the files of the target directory, we just change the root of the specified
// files to this mod
FilesOrigin origin = directoryStructure->createOrigin(ToWString(modName), directoryW, priority);
foreach (const QString &filename, stealFiles) {
QFileInfo fileInfo(filename);
FileEntry::Ptr file = directoryStructure->findFile(ToWString(fileInfo.fileName()));
if (file.get() != NULL) {
if (file->getOrigin() == 0) {
// replace data as the origin on this bsa
file->removeOrigin(0);
file->addOrigin(origin.getID(), file->getFileTime(), L"");
}
}
}
} else {
directoryStructure->addFromOrigin(ToWString(modName), directoryW, priority);
}
}
void DirectoryRefresher::addModToStructure(DirectoryEntry *directoryStructure
, const QString &modName
, int priority
, const QString &directory
, const QStringList &stealFiles
, const QStringList &archives)
{
addModFilesToStructure(directoryStructure, modName, priority, directory, stealFiles);
addModBSAToStructure(directoryStructure, modName, priority, directory, archives);
}
void DirectoryRefresher::refresh()
{
QMutexLocker locker(&m_RefreshLock);
@@ -87,23 +132,22 @@ void DirectoryRefresher::refresh()
m_DirectoryStructure = new DirectoryEntry(L"data", NULL, 0);
std::wstring dataDirectory = GameInfo::instance().getGameDirectory() + L"\\data";
m_DirectoryStructure->addFromOrigin(L"data", dataDirectory, 0);
// TODO what was the point of having the priority in this tuple? the list is already sorted by priority
std::vector<std::tuple<QString, QString, int> >::const_iterator iter = m_Mods.begin();
auto iter = m_Mods.begin();
//TODO i is the priority here, where higher = more important. the input vector is also sorted by priority but inverted!
for (int i = 1; iter != m_Mods.end(); ++iter, ++i) {
QString modName = std::get<0>(*iter);
try {
addModToStructure(m_DirectoryStructure, modName, i, std::get<1>(*iter));
addModToStructure(m_DirectoryStructure, iter->modName, i, iter->absolutePath, iter->stealFiles, iter->archives);
} catch (const std::exception &e) {
emit error(tr("failed to read bsa: %1").arg(e.what()));
}
emit progress((i * 100) / m_Mods.size() + 1);
}
std::wstring dataDirectory = GameInfo::instance().getGameDirectory() + L"\\data";
m_DirectoryStructure->addFromOrigin(L"data", dataDirectory, 0);
emit progress(100);
cleanStructure(m_DirectoryStructure);
+48 -9
View File
@@ -20,11 +20,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef DIRECTORYREFRESHER_H
#define DIRECTORYREFRESHER_H
#include <QObject>
#include <vector>
#include <QMutex>
#include <tuple>
#include <directoryentry.h>
#include <QObject>
#include <QMutex>
#include <QStringList>
#include <vector>
#include <set>
#include <tuple>
/**
@@ -60,7 +62,7 @@ public:
*
* @param mods list of the mods to include
**/
void setMods(const std::vector<std::tuple<QString, QString, int> > &mods);
void setMods(const std::vector<std::tuple<QString, QString, int> > &mods, const std::set<QString> &managedArchives);
/**
* @brief sets up the directory where mods are stored
@@ -77,12 +79,34 @@ public:
/**
* @brief add files for a mod to the directory structure, including bsas
* @param directoryStructure
* @param modName
* @param priorityBSA
* @param priority
* @param directory
* @param priorityDir
* @param stealFiles
* @param archives
*/
static void addModToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory);
void addModToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &stealFiles, const QStringList &archives);
/**
* @brief add only the bsas of a mod to the directory structure
* @param directoryStructure
* @param modName
* @param priority
* @param directory
* @param archives
*/
void addModBSAToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &archives);
/**
* @brief add only regular files ofr a mod to the directory structure
* @param directoryStructure
* @param modName
* @param priority
* @param directory
* @param stealFiles
*/
void addModFilesToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &stealFiles);
public slots:
@@ -99,7 +123,22 @@ signals:
private:
std::vector<std::tuple<QString, QString, int> > m_Mods;
struct EntryInfo {
EntryInfo(const QString &modName, const QString &absolutePath,
const QStringList &stealFiles, const QStringList &archives, int priority)
: modName(modName), absolutePath(absolutePath), stealFiles(stealFiles)
, archives(archives), priority(priority) {}
QString modName;
QString absolutePath;
QStringList stealFiles;
QStringList archives;
int priority;
};
private:
std::vector<EntryInfo> m_Mods;
std::set<QString> m_EnabledArchives;
MOShared::DirectoryEntry *m_DirectoryStructure;
QMutex m_RefreshLock;
+4 -4
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86"/>
<file name="icuin49.dll"/>
<file name="icuuc49.dll"/>
<file name="icudt49.dll"/>
<file name="icuin52.dll"/>
<file name="icuuc52.dll"/>
<file name="icudt52.dll"/>
<file name="Qt5Core.dll"/>
<file name="Qt5Declarative.dll"/>
<file name="Qt5Gui.dll"/>
@@ -14,4 +14,4 @@
<file name="Qt5Widgets.dll"/>
<file name="Qt5Xml.dll"/>
<file name="Qt5XmlPatterns.dll"/>
</assembly>
</assembly>
+122 -84
View File
@@ -26,8 +26,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "utility.h"
#include "json.h"
#include "selectiondialog.h"
#include "bbcode.h"
#include <utility.h>
#include <bbcode.h>
#include <QTimer>
#include <QFileInfo>
#include <QRegExp>
@@ -148,7 +148,10 @@ void DownloadManager::DownloadInfo::setName(QString newName, bool renameFile)
metaFile.rename(newName.mid(0).append(".meta"));
}
}
m_Output.setFileName(newName);
if (!m_Output.isOpen()) {
// can't set file name if it's open
m_Output.setFileName(newName);
}
}
bool DownloadManager::DownloadInfo::isPausedState()
@@ -253,67 +256,70 @@ void DownloadManager::setShowHidden(bool showHidden)
void DownloadManager::refreshList()
{
int downloadsBefore = m_ActiveDownloads.size();
try {
int downloadsBefore = m_ActiveDownloads.size();
// remove finished downloads
for (QVector<DownloadInfo*>::iterator Iter = m_ActiveDownloads.begin(); Iter != m_ActiveDownloads.end();) {
if (((*Iter)->m_State == STATE_READY) || ((*Iter)->m_State == STATE_INSTALLED) || ((*Iter)->m_State == STATE_UNINSTALLED)) {
delete *Iter;
Iter = m_ActiveDownloads.erase(Iter);
} else {
++Iter;
}
}
QStringList nameFilters(m_SupportedExtensions);
foreach (const QString &extension, m_SupportedExtensions) {
nameFilters.append("*." + extension);
}
nameFilters.append(QString("*").append(UNFINISHED));
QDir dir(QDir::fromNativeSeparators(m_OutputDirectory));
// find orphaned meta files and delete them (sounds cruel but it's better for everyone)
QStringList orphans;
QStringList metaFiles = dir.entryList(QStringList() << "*.meta");
foreach (const QString &metaFile, metaFiles) {
QString baseFile = metaFile.left(metaFile.length() - 5);
if (!QFile::exists(dir.absoluteFilePath(baseFile))) {
orphans.append(dir.absoluteFilePath(metaFile));
}
}
if (orphans.size() > 0) {
qDebug("%d orphaned meta files will be deleted", orphans.size());
shellDelete(orphans, true);
}
// add existing downloads to list
foreach (QString file, dir.entryList(nameFilters, QDir::Files, QDir::Time)) {
bool Exists = false;
for (QVector<DownloadInfo*>::const_iterator Iter = m_ActiveDownloads.begin(); Iter != m_ActiveDownloads.end() && !Exists; ++Iter) {
if (QString::compare((*Iter)->m_FileName, file, Qt::CaseInsensitive) == 0) {
Exists = true;
} else if (QString::compare(QFileInfo((*Iter)->m_Output.fileName()).fileName(), file, Qt::CaseInsensitive) == 0) {
Exists = true;
// remove finished downloads
for (QVector<DownloadInfo*>::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end();) {
if (((*iter)->m_State == STATE_READY) || ((*iter)->m_State == STATE_INSTALLED) || ((*iter)->m_State == STATE_UNINSTALLED)) {
delete *iter;
iter = m_ActiveDownloads.erase(iter);
} else {
++iter;
}
}
if (Exists) {
qDebug("%s exists", qPrintable(file));
continue;
QStringList nameFilters(m_SupportedExtensions);
foreach (const QString &extension, m_SupportedExtensions) {
nameFilters.append("*." + extension);
}
QString fileName = QDir::fromNativeSeparators(m_OutputDirectory) + "/" + file;
nameFilters.append(QString("*").append(UNFINISHED));
QDir dir(QDir::fromNativeSeparators(m_OutputDirectory));
DownloadInfo *info = DownloadInfo::createFromMeta(fileName, m_ShowHidden);
if (info != NULL) {
m_ActiveDownloads.push_front(info);
// find orphaned meta files and delete them (sounds cruel but it's better for everyone)
QStringList orphans;
QStringList metaFiles = dir.entryList(QStringList() << "*.meta");
foreach (const QString &metaFile, metaFiles) {
QString baseFile = metaFile.left(metaFile.length() - 5);
if (!QFile::exists(dir.absoluteFilePath(baseFile))) {
orphans.append(dir.absoluteFilePath(metaFile));
}
}
if (orphans.size() > 0) {
qDebug("%d orphaned meta files will be deleted", orphans.size());
shellDelete(orphans, true);
}
}
if (m_ActiveDownloads.size() != downloadsBefore) {
qDebug("downloads after refresh: %d", m_ActiveDownloads.size());
// add existing downloads to list
foreach (QString file, dir.entryList(nameFilters, QDir::Files, QDir::Time)) {
bool Exists = false;
for (QVector<DownloadInfo*>::const_iterator Iter = m_ActiveDownloads.begin(); Iter != m_ActiveDownloads.end() && !Exists; ++Iter) {
if (QString::compare((*Iter)->m_FileName, file, Qt::CaseInsensitive) == 0) {
Exists = true;
} else if (QString::compare(QFileInfo((*Iter)->m_Output.fileName()).fileName(), file, Qt::CaseInsensitive) == 0) {
Exists = true;
}
}
if (Exists) {
continue;
}
QString fileName = QDir::fromNativeSeparators(m_OutputDirectory) + "/" + file;
DownloadInfo *info = DownloadInfo::createFromMeta(fileName, m_ShowHidden);
if (info != NULL) {
m_ActiveDownloads.push_front(info);
}
}
if (m_ActiveDownloads.size() != downloadsBefore) {
qDebug("downloads after refresh: %d", m_ActiveDownloads.size());
}
emit update(-1);
} catch (const std::bad_alloc&) {
reportError(tr("Memory allocation error (in refreshing directory)."));
}
emit update(-1);
}
@@ -392,6 +398,7 @@ void DownloadManager::removePending(int modID, int fileID)
void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownload, bool resume)
{
reply->setReadBufferSize(1024 * 1024); // don't read more than 1MB at once to avoid memory troubles
newDownload->m_Reply = reply;
setState(newDownload, STATE_DOWNLOADING);
if (newDownload->m_Urls.count() == 0) {
@@ -404,6 +411,7 @@ void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownl
}
newDownload->m_StartTime.start();
createMetaFile(newDownload);
if (!newDownload->m_Output.open(mode)) {
reportError(tr("failed to download %1: could not open output file: %2")
@@ -413,6 +421,7 @@ void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownl
connect(newDownload->m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64)));
connect(newDownload->m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished()));
connect(newDownload->m_Reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
connect(newDownload->m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead()));
connect(newDownload->m_Reply, SIGNAL(metaDataChanged()), this, SLOT(metaDataChanged()));
@@ -421,11 +430,15 @@ void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownl
removePending(newDownload->m_FileInfo->modID, newDownload->m_FileInfo->fileID);
emit aboutToUpdate();
m_ActiveDownloads.append(newDownload);
emit update(-1);
emit downloadAdded();
if (reply->isFinished()) {
// it's possible the download has already finished before this function ran
downloadFinished();
}
}
}
@@ -448,7 +461,7 @@ void DownloadManager::addNXMDownload(const QString &url)
emit update(-1);
emit downloadAdded();
m_RequestIDs.insert(m_NexusInterface->requestFileInfo(nxmInfo.modId(), nxmInfo.fileId(), this, nxmInfo.fileId()));
m_RequestIDs.insert(m_NexusInterface->requestFileInfo(nxmInfo.modId(), nxmInfo.fileId(), this, nxmInfo.fileId(), ""));
}
@@ -593,8 +606,11 @@ void DownloadManager::pauseDownload(int index)
DownloadInfo *info = m_ActiveDownloads.at(index);
if (info->m_State == STATE_DOWNLOADING) {
setState(info, STATE_PAUSING);
qDebug("pausing %d - %s", index, info->m_FileName.toUtf8().constData());
if (info->m_Reply->isRunning()) {
setState(info, STATE_PAUSING);
} else {
setState(info, STATE_PAUSED);
}
} else if ((info->m_State == STATE_FETCHINGMODINFO) || (info->m_State == STATE_FETCHINGFILEINFO)) {
setState(info, STATE_READY);
}
@@ -619,6 +635,11 @@ void DownloadManager::resumeDownloadInt(int index)
}
DownloadInfo *info = m_ActiveDownloads[index];
if (info->isPausedState()) {
if ((info->m_Urls.size() == 0)
|| ((info->m_Urls.size() == 1) && (info->m_Urls[0].size() == 0))) {
emit showMessage(tr("No known download urls. Sorry, this download can't be resumed."));
return;
}
if (info->m_State == STATE_ERROR) {
info->m_CurrentUrl = (info->m_CurrentUrl + 1) % info->m_Urls.count();
}
@@ -919,10 +940,10 @@ void DownloadManager::setState(DownloadManager::DownloadInfo *info, DownloadMana
info->m_Reply->abort();
} break;
case STATE_FETCHINGMODINFO: {
m_RequestIDs.insert(m_NexusInterface->requestDescription(info->m_FileInfo->modID, this, info->m_DownloadID));
m_RequestIDs.insert(m_NexusInterface->requestDescription(info->m_FileInfo->modID, this, info->m_DownloadID, QString()));
} break;
case STATE_FETCHINGFILEINFO: {
m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_FileInfo->modID, this, info->m_DownloadID));
m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_FileInfo->modID, this, info->m_DownloadID, QString()));
} break;
case STATE_READY: {
createMetaFile(info);
@@ -955,32 +976,40 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
return;
}
int index = 0;
DownloadInfo *info = findDownload(this->sender(), &index);
if (info != NULL) {
if (info->m_State == STATE_CANCELING) {
setState(info, STATE_CANCELED);
} else if (info->m_State == STATE_PAUSING) {
setState(info, STATE_PAUSED);
} else {
if (bytesTotal > info->m_TotalSize) {
info->m_TotalSize = bytesTotal;
}
int oldProgress = info->m_Progress;
info->m_Progress = ((info->m_ResumePos + bytesReceived) * 100) / (info->m_ResumePos + bytesTotal);
TaskProgressManager::instance().updateProgress(info->m_TaskProgressId, bytesReceived, bytesTotal);
if (oldProgress != info->m_Progress) {
emit update(index);
try {
DownloadInfo *info = findDownload(this->sender(), &index);
if (info != NULL) {
if (info->m_State == STATE_CANCELING) {
setState(info, STATE_CANCELED);
} else if (info->m_State == STATE_PAUSING) {
setState(info, STATE_PAUSED);
} else {
if (bytesTotal > info->m_TotalSize) {
info->m_TotalSize = bytesTotal;
}
int oldProgress = info->m_Progress;
info->m_Progress = ((info->m_ResumePos + bytesReceived) * 100) / (info->m_ResumePos + bytesTotal);
TaskProgressManager::instance().updateProgress(info->m_TaskProgressId, bytesReceived, bytesTotal);
if (oldProgress != info->m_Progress) {
emit update(index);
}
}
}
} catch (const std::bad_alloc&) {
reportError(tr("Memory allocation error (in processing progress event)."));
}
}
void DownloadManager::downloadReadyRead()
{
DownloadInfo *info = findDownload(this->sender());
if (info != NULL) {
info->m_Output.write(info->m_Reply->readAll());
try {
DownloadInfo *info = findDownload(this->sender());
if (info != NULL) {
info->m_Output.write(info->m_Reply->readAll());
}
} catch (const std::bad_alloc&) {
reportError(tr("Memory allocation error (in processing downloaded data)."));
}
}
@@ -1167,7 +1196,7 @@ void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, QVariant userD
info->fileID = fileID;
QObject *test = info;
m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(test)));
m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(test), QString()));
}
@@ -1267,7 +1296,6 @@ void DownloadManager::nxmDownloadURLsAvailable(int modID, int fileID, QVariant u
foreach (const QVariant &server, resultList) {
URLs.append(server.toMap()["URI"].toString());
}
addDownload(URLs, modID, fileID, info);
}
@@ -1309,8 +1337,11 @@ void DownloadManager::downloadFinished()
DownloadInfo *info = findDownload(this->sender(), &index);
if (info != NULL) {
QNetworkReply *reply = info->m_Reply;
QByteArray data = info->m_Reply->readAll();
info->m_Output.write(data);
QByteArray data;
if (reply->isOpen()) {
data = reply->readAll();
info->m_Output.write(data);
}
info->m_Output.close();
TaskProgressManager::instance().forgetMe(info->m_TaskProgressId);
@@ -1356,7 +1387,6 @@ void DownloadManager::downloadFinished()
createMetaFile(info);
emit update(index);
} else {
QString url = info->m_Urls[info->m_CurrentUrl];
if (info->m_FileInfo->userData.contains("downloadMap")) {
foreach (const QVariant &server, info->m_FileInfo->userData["downloadMap"].toList()) {
@@ -1406,6 +1436,14 @@ void DownloadManager::downloadFinished()
}
void DownloadManager::downloadError(QNetworkReply::NetworkError error)
{
if (error != QNetworkReply::OperationCanceledError) {
qWarning("Download error occured: %d", error);
}
}
void DownloadManager::metaDataChanged()
{
int index = 0;
@@ -1416,7 +1454,7 @@ void DownloadManager::metaDataChanged()
if (!newName.isEmpty() && (newName != info->m_FileName)) {
info->setName(getDownloadFileName(newName), true);
refreshAlphabeticalTranslation();
if (!info->m_Output.open(QIODevice::WriteOnly | QIODevice::Append)) {
if (!info->m_Output.isOpen() && !info->m_Output.open(QIODevice::WriteOnly | QIODevice::Append)) {
reportError(tr("failed to re-open %1").arg(info->m_FileName));
setState(info, STATE_CANCELING);
}
+1
View File
@@ -417,6 +417,7 @@ private slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void downloadReadyRead();
void downloadFinished();
void downloadError(QNetworkReply::NetworkError error);
void metaDataChanged();
void directoryChanged(const QString &dirctory);
+41 -29
View File
@@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "report.h"
#include <QMutexLocker>
#include <QFile>
#include <QIcon>
#include <QDateTime>
#include <Windows.h>
@@ -108,21 +109,6 @@ void LogBuffer::init(int messageCount, QtMsgType minMsgType, const QString &outp
#endif
}
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
QMutexLocker guard(&s_Mutex);
if (!s_Instance.isNull()) {
s_Instance->logMessage(type, message);
}
fprintf(stdout, "(%s:%u) %s\n", context.file, context.line, qPrintable(message));
fflush(stdout);
}
#else
char LogBuffer::msgTypeID(QtMsgType type)
{
switch (type) {
@@ -134,6 +120,39 @@ char LogBuffer::msgTypeID(QtMsgType type)
}
}
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
QMutexLocker guard(&s_Mutex);
if (!s_Instance.isNull()) {
s_Instance->logMessage(type, message);
}
// fprintf(stdout, "(%s:%u) %s\n", context.file, context.line, qPrintable(message));
if (type == QtDebugMsg) {
fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), qPrintable(message));
} else {
fprintf(stdout, "%s [%c] (%s:%u) %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type),
context.file, context.line, qPrintable(message));
}
fflush(stdout);
}
#else
void LogBuffer::log(QtMsgType type, const char *message)
{
QMutexLocker guard(&s_Mutex);
if (!s_Instance.isNull()) {
s_Instance->logMessage(type, message);
}
fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), message);
fflush(stdout);
}
#endif
QModelIndex LogBuffer::index(int row, int column, const QModelIndex&) const
{
return createIndex(row, column, row);
@@ -162,13 +181,18 @@ QVariant LogBuffer::data(const QModelIndex &index, int role) const
{
unsigned offset = m_NumMessages < m_Messages.size() ? 0
: m_NumMessages - m_Messages.size();
unsigned int msgIndex = (offset + index.row()) % m_Messages.size();
unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size();
switch (role) {
case Qt::DisplayRole: {
if (index.column() == 0) {
return m_Messages.at(msgIndex).time;
} else if (index.column() == 1) {
return m_Messages.at(msgIndex).message;
const QString &msg = m_Messages.at(msgIndex).message;
if (msg.length() < 200) {
return msg;
} else {
return msg.mid(0, 200) + "...";
}
}
} break;
case Qt::DecorationRole: {
@@ -195,18 +219,6 @@ QVariant LogBuffer::data(const QModelIndex &index, int role) const
return QVariant();
}
void LogBuffer::log(QtMsgType type, const char *message)
{
QMutexLocker guard(&s_Mutex);
if (!s_Instance.isNull()) {
s_Instance->logMessage(type, message);
}
fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), message);
fflush(stdout);
}
#endif
void LogBuffer::writeNow()
{
QMutexLocker guard(&s_Mutex);
+2 -1
View File
@@ -310,6 +310,7 @@ bool HaveWriteAccess(const std::wstring &path)
return writable;
}
int main(int argc, char *argv[])
{
MOApplication application(argc, argv);
@@ -328,7 +329,7 @@ int main(int argc, char *argv[])
, ToWString(QDir::currentPath()).c_str(), SW_SHOWNORMAL);
return 1;
}
LogBuffer::init(200, QtDebugMsg, application.applicationDirPath() + "/logs/mo_interface.log");
LogBuffer::init(100, QtDebugMsg, application.applicationDirPath() + "/logs/mo_interface.log");
qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath())));
qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(application.applicationDirPath())));
+545 -577
View File
File diff suppressed because it is too large Load Diff
+48 -39
View File
@@ -64,11 +64,13 @@ class ModListSortProxy;
class ModListGroupCategoriesProxy;
class MainWindow : public QMainWindow, public MOBase::IOrganizer, public MOBase::IPluginDiagnose
class MainWindow : public QMainWindow, public MOBase::IPluginDiagnose
{
Q_OBJECT
Q_INTERFACES(MOBase::IPluginDiagnose)
friend class OrganizerProxy;
private:
struct SignalCombinerAnd
@@ -88,6 +90,7 @@ private:
};
typedef boost::signals2::signal<bool (const QString&), SignalCombinerAnd> SignalAboutToRunApplication;
typedef boost::signals2::signal<void (const QString&)> SignalModInstalled;
public:
explicit MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent = 0);
@@ -96,8 +99,6 @@ public:
void readSettings();
bool addProfile();
void refreshLists();
void refreshESPList();
void refreshBSAList();
void refreshDataTree();
void refreshSaveList();
@@ -117,34 +118,6 @@ public:
void loadPlugins();
virtual MOBase::IGameInfo &gameInfo() const;
virtual MOBase::IModRepositoryBridge *createNexusBridge() const;
virtual QString profileName() const;
virtual QString profilePath() const;
virtual QString downloadsPath() const;
virtual MOBase::VersionInfo appVersion() const;
virtual MOBase::IModInterface *getMod(const QString &name);
virtual MOBase::IModInterface *createMod(MOBase::GuessedValue<QString> &name);
virtual bool removeMod(MOBase::IModInterface *mod);
virtual void modDataChanged(MOBase::IModInterface *mod);
virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const;
virtual void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value);
virtual QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def = QVariant()) const;
virtual void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync = true);
virtual QString pluginDataPath() const;
virtual MOBase::IModInterface *installMod(const QString &fileName);
virtual QString resolvePath(const QString &fileName) const;
virtual QStringList listDirectories(const QString &directoryName) const;
virtual QStringList findFiles(const QString &path, const std::function<bool(const QString &)> &filter) const;
virtual QList<FileInfo> findFileInfos(const QString &path, const std::function<bool(const FileInfo&)> &filter) const;
virtual MOBase::IDownloadManager *downloadManager();
virtual MOBase::IPluginList *pluginList();
virtual MOBase::IModList *modList();
virtual HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", const QString &profile = "");
virtual bool onAboutToRun(const std::function<bool(const QString&)> &func);
virtual void refreshModList(bool saveChanges = true);
virtual std::vector<unsigned int> activeProblems() const;
virtual QString shortDescription(unsigned int key) const;
virtual QString fullDescription(unsigned int key) const;
@@ -153,13 +126,20 @@ public:
void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info);
void saveArchiveList();
bool saveArchiveList();
void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite);
std::string readFromPipe(HANDLE stdOutRead);
void processLOOTOut(const std::string &lootOut, std::string &reportURL, std::string &errorMessages, QProgressDialog &dialog);
HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", const QString &profile = "");
void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo);
public slots:
void refreshLists();
void displayColumnSelection(const QPoint &pos);
void externalMessage(const QString &message);
@@ -200,6 +180,9 @@ protected:
private:
void refreshESPList();
void refreshModList(bool saveChanges = true);
void actionToToolButton(QAction *&sourceAction);
bool verifyPlugin(MOBase::IPlugin *plugin);
void registerPluginTool(MOBase::IPluginTool *tool);
@@ -211,8 +194,6 @@ private:
void setExecutableIndex(int index);
bool nexusLogin();
bool testForSteam();
void startSteam();
@@ -224,6 +205,13 @@ private:
bool refreshProfiles(bool selectProfile = true);
void refreshExecutablesList();
void installMod();
MOBase::IModInterface *installMod(const QString &fileName);
MOBase::IModInterface *getMod(const QString &name);
MOBase::IModInterface *createMod(MOBase::GuessedValue<QString> &name);
bool removeMod(MOBase::IModInterface *mod);
QList<MOBase::IOrganizer::FileInfo> findFileInfos(const QString &path, const std::function<bool (const MOBase::IOrganizer::FileInfo &)> &filter) const;
bool modifyExecutablesDialog();
void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab);
void displayModInformation(int row, int tab = 0);
@@ -267,7 +255,7 @@ private:
bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName);
bool checkForProblems();
int checkForProblems();
int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments);
QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID);
@@ -275,8 +263,6 @@ private:
void setCategoryListVisible(bool visible);
void updateProblemsButton();
SaveGameGamebryo *getSaveGame(const QString &name);
SaveGameGamebryo *getSaveGame(QListWidgetItem *item);
@@ -298,6 +284,14 @@ private:
bool createBackup(const QString &filePath, const QDateTime &time);
QString queryRestore(const QString &filePath);
QMenu *modListContextMenu();
std::set<QString> enabledArchives();
void scheduleUpdateButton();
void updateModActiveState(int index, bool active);
private:
static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1;
@@ -367,6 +361,7 @@ private:
bool m_ArchivesInit;
QTimer m_CheckBSATimer;
QTimer m_SaveMetaTimer;
QTimer m_UpdateProblemsTimer;
QTime m_StartTime;
SaveGameInfoWidget *m_CurrentSaveView;
@@ -380,6 +375,7 @@ private:
QFile m_PluginsCheck;
SignalAboutToRunApplication m_AboutToRun;
SignalModInstalled m_ModInstalled;
QString m_CurrentLanguage;
std::vector<QTranslator*> m_Translators;
@@ -393,6 +389,8 @@ private:
uint m_ArchiveListHash;
bool m_DidUpdateMasterList;
private slots:
void showMessage(const QString &message);
@@ -458,6 +456,8 @@ private slots:
void linkClicked(const QString &url);
bool nexusLogin();
void loginSuccessful(bool necessary);
void loginSuccessfulUpdate(bool necessary);
void loginFailed(const QString &message);
@@ -484,11 +484,11 @@ private slots:
void modlistChanged(int row);
void nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant userData, QVariant resultData, int requestID);
// void nxmEndorsementToggled(int, QVariant, QVariant resultData, int);
void nxmDownloadURLs(int modID, int fileID, QVariant userData, QVariant resultData, int requestID);
void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString);
void editCategories();
void deselectFilters();
void displayModInformation(const QString &modName, int tab);
void modOpenNext();
@@ -515,6 +515,9 @@ private slots:
void startExeAction();
void checkBSAList();
void updateProblemsButton();
void saveModMetas();
void updateStyle(const QString &style);
@@ -558,6 +561,7 @@ private slots:
void requestDownload(const QUrl &url, QNetworkReply *reply);
private slots: // ui slots
void profileRefresh();
// actions
void on_actionAdd_Profile_triggered();
void on_actionInstallMod_triggered();
@@ -578,7 +582,6 @@ private slots: // ui slots
void on_modList_customContextMenuRequested(const QPoint &pos);
void on_modList_doubleClicked(const QModelIndex &index);
void on_profileBox_currentIndexChanged(int index);
void on_profileRefreshBtn_clicked();
void on_savegameList_customContextMenuRequested(const QPoint &pos);
void on_startButton_clicked();
void on_tabWidget_currentChanged(int index);
@@ -597,6 +600,12 @@ private slots: // ui slots
void on_restoreModsButton_clicked();
void on_saveModsButton_clicked();
void on_actionCopy_Log_to_Clipboard_triggered();
void on_categoriesAndBtn_toggled(bool checked);
void on_categoriesOrBtn_toggled(bool checked);
void on_managedArchiveLabel_linkHovered(const QString &link);
void on_manageArchivesBox_toggled(bool checked);
};
#endif // MAINWINDOW_H
+98 -24
View File
@@ -75,6 +75,9 @@
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="indentation">
<number>0</number>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
@@ -85,6 +88,54 @@
</column>
</widget>
</item>
<item>
<widget class="QLabel" name="clickBlankLabel">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Click blank area to deselect</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string/>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QRadioButton" name="categoriesAndBtn">
<property name="toolTip">
<string>If checked, only mods that match all selected categories are displayed.</string>
</property>
<property name="text">
<string>And</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="categoriesOrBtn">
<property name="toolTip">
<string>If checked, all mods that match at least one of the selected categories are displayed.</string>
</property>
<property name="text">
<string>Or</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
@@ -140,10 +191,23 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item>
<widget class="QPushButton" name="profileRefreshBtn">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="listOptionsBtn">
<property name="maximumSize">
<size>
<width>24</width>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
@@ -158,22 +222,15 @@ p, li { white-space: pre-wrap; }
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/MO/gui/resources/view-refresh.png</normaloff>:/MO/gui/resources/view-refresh.png</iconset>
<normaloff>:/MO/gui/settings</normaloff>:/MO/gui/settings</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<property name="iconSize">
<size>
<width>40</width>
<height>20</height>
<width>16</width>
<height>16</height>
</size>
</property>
</spacer>
</widget>
</item>
<item>
<widget class="QPushButton" name="restoreModsButton">
@@ -841,6 +898,33 @@ p, li { white-space: pre-wrap; }
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9" stretch="0,1">
<item>
<widget class="QCheckBox" name="manageArchivesBox">
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="managedArchiveLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:&lt;/p&gt;&lt;p&gt;If archives are &lt;span style=&quot; font-weight:600;&quot;&gt;managed&lt;/span&gt;, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If archives are &lt;span style=&quot; font-weight:600;&quot;&gt;not managed&lt;/span&gt; their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.&lt;/p&gt;&lt;p&gt;In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Have MO manage archives (&lt;a href=&quot;#&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;read more&lt;/span&gt;&lt;/a&gt;)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="MOBase::SortableTreeWidget" name="bsaList">
<property name="contextMenuPolicy">
@@ -901,16 +985,6 @@ BSAs checked here are loaded in such a way that your installation order is obeye
</column>
</widget>
</item>
<item>
<widget class="QLabel" name="bsaWarning">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Marked Archives (&lt;img src=&quot;:/MO/gui/warning_16&quot;/&gt;) are still loaded on Skyrim but the &lt;a href=&quot;http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;regular file override&lt;/span&gt;&lt;/a&gt; mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="dataTab">
+1
View File
@@ -81,6 +81,7 @@ void MessageDialog::resizeEvent(QResizeEvent *event)
void MessageDialog::showMessage(const QString &text, QWidget *reference, bool bringToFront)
{
qDebug("%s", qPrintable(text));
if (reference != NULL) {
if (bringToFront || (qApp->activeWindow() != NULL)) {
MessageDialog *dialog = new MessageDialog(text, reference);
+233 -117
View File
@@ -73,6 +73,16 @@ ModInfo::Ptr ModInfo::createFrom(const QDir &dir, DirectoryEntry **directoryStru
}
ModInfo::Ptr ModInfo::createFromPlugin(const QString &espName, const QStringList &bsaNames
, DirectoryEntry ** directoryStructure)
{
QMutexLocker locker(&s_Mutex);
ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(espName, bsaNames, directoryStructure));
s_Collection.push_back(result);
return result;
}
void ModInfo::createFromOverwrite()
{
QMutexLocker locker(&s_Mutex);
@@ -174,17 +184,37 @@ unsigned int ModInfo::findMod(const boost::function<bool (ModInfo::Ptr)> &filter
}
void ModInfo::updateFromDisc(const QString &modDirectory, DirectoryEntry **directoryStructure)
void ModInfo::updateFromDisc(const QString &modDirectory, DirectoryEntry **directoryStructure, bool displayForeign)
{
QMutexLocker lock(&s_Mutex);
s_Collection.clear();
s_NextID = 0;
// list all directories in the mod directory and make a mod out of each
QDir mods(QDir::fromNativeSeparators(modDirectory));
mods.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
QDirIterator modIter(mods);
while (modIter.hasNext()) {
createFrom(QDir(modIter.next()), directoryStructure);
{ // list all directories in the mod directory and make a mod out of each
QDir mods(QDir::fromNativeSeparators(modDirectory));
mods.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
QDirIterator modIter(mods);
while (modIter.hasNext()) {
createFrom(QDir(modIter.next()), directoryStructure);
}
}
{ // list plugins in the data directory and make a foreign-managed mod out of each
std::vector<std::wstring> dlcPlugins = GameInfo::instance().getDLCPlugins();
QDir dataDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data");
foreach (const QFileInfo &file, dataDir.entryInfoList(QStringList() << "*.esp" << "*.esm")) {
if ((file.baseName() != "Update") // hide update
&& (file.baseName() != ToQString(GameInfo::instance().getGameName())) // hide the game esp
&& (displayForeign // show non-dlc bundles only if the user wants them
|| std::find(dlcPlugins.begin(), dlcPlugins.end(), ToWString(file.fileName())) != dlcPlugins.end())) {
QStringList archives;
foreach (const QString archiveName, dataDir.entryList(QStringList() << file.baseName() + "*.bsa")) {
archives.append(dataDir.absoluteFilePath(archiveName));
}
createFromPlugin(file.fileName(), archives, directoryStructure);
}
}
}
createFromOverwrite();
@@ -219,7 +249,7 @@ ModInfo::ModInfo()
void ModInfo::checkChunkForUpdate(const std::vector<int> &modIDs, QObject *receiver)
{
if (modIDs.size() != 0) {
NexusInterface::instance()->requestUpdates(modIDs, receiver, QVariant());
NexusInterface::instance()->requestUpdates(modIDs, receiver, QVariant(), QString());
}
}
@@ -252,6 +282,12 @@ void ModInfo::setVersion(const VersionInfo &version)
m_Version = version;
}
bool ModInfo::hasFlag(ModInfo::EFlag flag) const
{
std::vector<EFlag> flags = getFlags();
return std::find(flags.begin(), flags.end(), flag) != flags.end();
}
bool ModInfo::categorySet(int categoryID) const
{
@@ -293,18 +329,136 @@ void ModInfo::testValid()
}
ModInfoWithConflictInfo::ModInfoWithConflictInfo(DirectoryEntry **directoryStructure)
: m_DirectoryStructure(directoryStructure) {}
void ModInfoWithConflictInfo::clearCaches()
{
m_LastConflictCheck = QTime();
}
std::vector<ModInfo::EFlag> ModInfoWithConflictInfo::getFlags() const
{
std::vector<ModInfo::EFlag> result;
switch (isConflicted()) {
case CONFLICT_MIXED: {
result.push_back(ModInfo::FLAG_CONFLICT_MIXED);
} break;
case CONFLICT_OVERWRITE: {
result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE);
} break;
case CONFLICT_OVERWRITTEN: {
result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN);
} break;
case CONFLICT_REDUNDANT: {
result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT);
} break;
default: { /* NOP */ }
}
return result;
}
ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const
{
// this is costy so cache the result
QTime now = QTime::currentTime();
if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) {
bool overwrite = false;
bool overwritten = false;
bool regular = false;
int dataID = 0;
if ((*m_DirectoryStructure)->originExists(L"data")) {
dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID();
}
std::wstring name = ToWString(this->name());
if ((*m_DirectoryStructure)->originExists(name)) {
FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
std::vector<FileEntry::Ptr> files = origin.getFiles();
for (auto iter = files.begin(); iter != files.end() && (!overwrite || !overwritten || !regular); ++iter) {
const std::vector<int> &alternatives = (*iter)->getAlternatives();
if (alternatives.size() == 0) {
// no alternatives -> no conflict
regular = true;
} else {
for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) {
// don't treat files overwritten in data as "conflict"
if (*altIter != dataID) {
bool ignore = false;
if ((*iter)->getOrigin(ignore) == origin.getID()) {
overwrite = true;
break;
} else {
overwritten = true;
break;
}
} else if (alternatives.size() == 1) {
// only alternative is data -> no conflict
regular = true;
}
}
}
}
}
m_LastConflictCheck = QTime::currentTime();
if (overwrite && overwritten) m_CurrentConflictState = CONFLICT_MIXED;
else if (overwrite) m_CurrentConflictState = CONFLICT_OVERWRITE;
else if (overwritten) {
if (!regular) {
m_CurrentConflictState = CONFLICT_REDUNDANT;
} else {
m_CurrentConflictState = CONFLICT_OVERWRITTEN;
}
}
else m_CurrentConflictState = CONFLICT_NONE;
}
return m_CurrentConflictState;
}
bool ModInfoWithConflictInfo::isRedundant() const
{
std::wstring name = ToWString(this->name());
if ((*m_DirectoryStructure)->originExists(name)) {
FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
std::vector<FileEntry::Ptr> files = origin.getFiles();
bool ignore = false;
for (auto iter = files.begin(); iter != files.end(); ++iter) {
if ((*iter)->getOrigin(ignore) == origin.getID()) {
return false;
}
}
return true;
} else {
return false;
}
}
ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure)
: ModInfo(), m_Name(path.dirName()), m_Path(path.absolutePath()), m_MetaInfoChanged(false),
m_EndorsedState(ENDORSED_UNKNOWN), m_DirectoryStructure(directoryStructure)
: ModInfoWithConflictInfo(directoryStructure)
, m_Name(path.dirName())
, m_Path(path.absolutePath())
, m_MetaInfoChanged(false)
, m_EndorsedState(ENDORSED_UNKNOWN)
{
testValid();
m_CreationTime = QFileInfo(path.absolutePath()).created();
// read out the meta-file for information
readMeta();
connect(&m_NexusBridge, SIGNAL(descriptionAvailable(int,QVariant,QVariant)), this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant)));
connect(&m_NexusBridge, SIGNAL(endorsementToggled(int,QVariant,QVariant)), this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant)));
connect(&m_NexusBridge, SIGNAL(requestFailed(int,int,QVariant,QString)), this, SLOT(nxmRequestFailed(int,int,QVariant,QString)));
connect(&m_NexusBridge, SIGNAL(descriptionAvailable(int,QVariant,QVariant))
, this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant)));
connect(&m_NexusBridge, SIGNAL(endorsementToggled(int,QVariant,QVariant))
, this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant)));
connect(&m_NexusBridge, SIGNAL(requestFailed(int,int,QVariant,QString))
, this, SLOT(nxmRequestFailed(int,int,QVariant,QString)));
}
@@ -602,7 +756,7 @@ void ModInfoRegular::setNeverEndorse()
bool ModInfoRegular::remove()
{
m_MetaInfoChanged = false;
return shellDelete(QStringList(absolutePath()));
return shellDelete(QStringList(absolutePath()), true);
}
void ModInfoRegular::endorse(bool doEndorse)
@@ -612,11 +766,6 @@ void ModInfoRegular::endorse(bool doEndorse)
}
}
void ModInfoRegular::clearCaches()
{
m_LastConflictCheck = QTime();
}
QString ModInfoRegular::absolutePath() const
{
@@ -636,22 +785,7 @@ void ModInfoRegular::ignoreUpdate(bool ignore)
std::vector<ModInfo::EFlag> ModInfoRegular::getFlags() const
{
std::vector<ModInfo::EFlag> result;
switch (isConflicted()) {
case CONFLICT_MIXED: {
result.push_back(ModInfo::FLAG_CONFLICT_MIXED);
} break;
case CONFLICT_OVERWRITE: {
result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE);
} break;
case CONFLICT_OVERWRITTEN: {
result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN);
} break;
case CONFLICT_REDUNDANT: {
result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT);
} break;
default: { /* NOP */ }
}
std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags();
if ((m_NexusID != -1) && (endorsedState() == ENDORSED_FALSE)) {
result.push_back(ModInfo::FLAG_NOTENDORSED);
}
@@ -712,92 +846,22 @@ ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const
return m_EndorsedState;
}
ModInfoRegular::EConflictType ModInfoRegular::isConflicted() const
{
// this is costy so cache the result
QTime now = QTime::currentTime();
if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) {
bool overwrite = false;
bool overwritten = false;
bool regular = false;
int dataID = 0;
if ((*m_DirectoryStructure)->originExists(L"data")) {
dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID();
}
std::wstring name = ToWString(m_Name);
if ((*m_DirectoryStructure)->originExists(name)) {
FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
std::vector<FileEntry::Ptr> files = origin.getFiles();
for (auto iter = files.begin(); iter != files.end() && (!overwrite || !overwritten || !regular); ++iter) {
const std::vector<int> &alternatives = (*iter)->getAlternatives();
if (alternatives.size() == 0) {
// no alternatives -> no conflict
regular = true;
} else {
for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) {
// don't treat files overwritten in data as "conflict"
if (*altIter != dataID) {
bool ignore = false;
if ((*iter)->getOrigin(ignore) == origin.getID()) {
overwrite = true;
break;
} else {
overwritten = true;
break;
}
} else if (alternatives.size() == 1) {
// only alternative is data -> no conflict
regular = true;
}
}
}
}
}
m_LastConflictCheck = QTime::currentTime();
if (overwrite && overwritten) m_CurrentConflictState = CONFLICT_MIXED;
else if (overwrite) m_CurrentConflictState = CONFLICT_OVERWRITE;
else if (overwritten) {
if (!regular) {
m_CurrentConflictState = CONFLICT_REDUNDANT;
} else {
m_CurrentConflictState = CONFLICT_OVERWRITTEN;
}
}
else m_CurrentConflictState = CONFLICT_NONE;
}
return m_CurrentConflictState;
}
bool ModInfoRegular::isRedundant() const
{
std::wstring name = ToWString(m_Name);
if ((*m_DirectoryStructure)->originExists(name)) {
FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
std::vector<FileEntry::Ptr> files = origin.getFiles();
bool ignore = false;
for (auto iter = files.begin(); iter != files.end(); ++iter) {
if ((*iter)->getOrigin(ignore) == origin.getID()) {
return false;
}
}
return true;
} else {
return false;
}
}
QDateTime ModInfoRegular::getLastNexusQuery() const
{
return m_LastNexusQuery;
}
QStringList ModInfoRegular::archives() const
{
QStringList result;
QDir dir(this->absolutePath());
foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) {
result.append(this->absolutePath() + "/" + archive);
}
return result;
}
std::vector<QString> ModInfoRegular::getIniTweaks() const
{
QString metaFileName = absolutePath().append("/meta.ini");
@@ -874,9 +938,61 @@ int ModInfoOverwrite::getHighlight() const
return (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER;
}
QString ModInfoOverwrite::getDescription() const
{
return tr("This pseudo mod contains files from the virtual data tree that got "
"modified (i.e. by the construction kit)");
}
QStringList ModInfoOverwrite::archives() const
{
QStringList result;
QDir dir(this->absolutePath());
foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) {
result.append(this->absolutePath() + "/" + archive);
}
return result;
}
QString ModInfoForeign::name() const
{
return m_Name;
}
QDateTime ModInfoForeign::creationTime() const
{
return m_CreationTime;
}
QString ModInfoForeign::absolutePath() const
{
return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data";
}
std::vector<ModInfo::EFlag> ModInfoForeign::getFlags() const
{
std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags();
result.push_back(FLAG_FOREIGN);
return result;
}
int ModInfoForeign::getHighlight() const
{
return 0;
}
QString ModInfoForeign::getDescription() const
{
return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO.");
}
ModInfoForeign::ModInfoForeign(const QString &referenceFile, const QStringList &archives,
DirectoryEntry **directoryStructure)
: ModInfoWithConflictInfo(directoryStructure)
, m_ReferenceFile(referenceFile)
, m_Archives(archives)
{
m_CreationTime = QFileInfo(referenceFile).created();
m_Name = QFileInfo(m_ReferenceFile).baseName();
}
+141 -34
View File
@@ -59,6 +59,7 @@ public:
FLAG_INVALID,
FLAG_BACKUP,
FLAG_OVERWRITE,
FLAG_FOREIGN,
FLAG_NOTENDORSED,
FLAG_NOTES,
FLAG_CONFLICT_OVERWRITE,
@@ -86,7 +87,7 @@ public:
/**
* @brief read the mod directory and Mod ModInfo objects for all subdirectories
**/
static void updateFromDisc(const QString &modDirectory, MOShared::DirectoryEntry **directoryStructure);
static void updateFromDisc(const QString &modDirectory, MOShared::DirectoryEntry **directoryStructure, bool displayForeign);
static void clear() { s_Collection.clear(); s_ModsByName.clear(); s_ModsByModID.clear(); }
@@ -160,6 +161,14 @@ public:
*/
static ModInfo::Ptr createFrom(const QDir &dir, MOShared::DirectoryEntry **directoryStructure);
/**
* @brief create a new "foreign-managed" mod from a tuple of plugin and archives
* @param espName name of the plugin
* @param bsaNames names of archives
* @return a new mod
*/
static ModInfo::Ptr createFromPlugin(const QString &espName, const QStringList &bsaNames, MOShared::DirectoryEntry **directoryStructure);
virtual bool isRegular() const { return false; }
virtual bool isEmpty() const { return false; }
@@ -339,6 +348,11 @@ public:
*/
virtual int getFixedPriority() const = 0;
/**
* @return true if the mod is always enabled
*/
virtual bool alwaysEnabled() const { return false; }
/**
* @return true if the mod can be updated
*/
@@ -354,6 +368,13 @@ public:
*/
virtual std::vector<EFlag> getFlags() const = 0;
/**
* @brief test if the specified flag is set for this mod
* @param flag the flag to test
* @return true if the flag is set, false otherwise
*/
bool hasFlag(EFlag flag) const;
/**
* @return an indicator if and how this mod should be highlighted by the UI
*/
@@ -389,6 +410,16 @@ public:
*/
virtual QDateTime getLastNexusQuery() const = 0;
/**
* @return a list of files that, if they exist in the data directory are treated as files in THIS mod
*/
virtual QStringList stealFiles() const { return QStringList(); }
/**
* @return a list of archives belonging to this mod (as absolute file paths)
*/
virtual QStringList archives() const = 0;
/**
* @brief test if the mod belongs to the specified category
*
@@ -481,6 +512,50 @@ private:
};
class ModInfoWithConflictInfo : public ModInfo
{
public:
ModInfoWithConflictInfo(MOShared::DirectoryEntry **directoryStructure);
std::vector<ModInfo::EFlag> getFlags() const;
/**
* @brief clear all caches held for this mod
*/
virtual void clearCaches();
private:
enum EConflictType {
CONFLICT_NONE,
CONFLICT_OVERWRITE,
CONFLICT_OVERWRITTEN,
CONFLICT_MIXED,
CONFLICT_REDUNDANT
};
private:
/**
* @return true if there is a conflict for files in this mod
*/
EConflictType isConflicted() const;
/**
* @return true if this mod is completely replaced by others
*/
bool isRedundant() const;
private:
MOShared::DirectoryEntry **m_DirectoryStructure;
mutable EConflictType m_CurrentConflictState;
mutable QTime m_LastConflictCheck;
};
/**
* @brief Represents meta information about a single mod.
@@ -489,7 +564,7 @@ private:
* to manage the mod collection
*
**/
class ModInfoRegular : public ModInfo
class ModInfoRegular : public ModInfoWithConflictInfo
{
Q_OBJECT
@@ -643,11 +718,6 @@ public:
*/
virtual void endorse(bool doEndorse);
/**
* @brief clear all caches held for this mod
*/
virtual void clearCaches();
/**
* @brief getter for the mod name
*
@@ -746,7 +816,9 @@ public:
/**
* @return last time nexus was queried for infos on this mod
*/
QDateTime getLastNexusQuery() const;
virtual QDateTime getLastNexusQuery() const;
virtual QStringList archives() const;
/**
* @brief stores meta information back to disk
@@ -754,15 +826,6 @@ public:
virtual void saveMeta();
void readMeta();
private:
enum EConflictType {
CONFLICT_NONE,
CONFLICT_OVERWRITE,
CONFLICT_OVERWRITTEN,
CONFLICT_MIXED,
CONFLICT_REDUNDANT
};
private slots:
@@ -770,18 +833,6 @@ private slots:
void nxmEndorsementToggled(int, QVariant userData, QVariant resultData);
void nxmRequestFailed(int modID, int fileID, QVariant userData, const QString &errorMessage);
private:
/**
* @return true if there is a conflict for files in this mod
*/
EConflictType isConflicted() const;
/**
* @return true if this mod is completely replaced by others
*/
bool isRedundant() const;
protected:
ModInfoRegular(const QDir &path, MOShared::DirectoryEntry **directoryStructure);
@@ -807,11 +858,6 @@ private:
NexusBridge m_NexusBridge;
MOShared::DirectoryEntry **m_DirectoryStructure;
mutable EConflictType m_CurrentConflictState;
mutable QTime m_LastConflictCheck;
};
@@ -872,6 +918,7 @@ public:
virtual void setNeverEndorse() {}
virtual bool remove() { return false; }
virtual void endorse(bool) {}
virtual bool alwaysEnabled() const { return true; }
virtual bool isEmpty() const;
virtual QString name() const { return "Overwrite"; }
virtual QString notes() const { return ""; }
@@ -887,6 +934,7 @@ public:
virtual QString getDescription() const;
virtual QDateTime getLastNexusQuery() const { return QDateTime(); }
virtual QString getNexusDescription() const { return QString(); }
virtual QStringList archives() const;
private:
@@ -898,4 +946,63 @@ private:
};
class ModInfoForeign : public ModInfoWithConflictInfo
{
Q_OBJECT
friend class ModInfo;
public:
virtual bool updateAvailable() const { return false; }
virtual bool updateIgnored() const { return false; }
virtual bool downgradeAvailable() const { return false; }
virtual bool updateNXMInfo() { return false; }
virtual void setCategory(int, bool) {}
virtual bool setName(const QString&) { return false; }
virtual void setNotes(const QString&) {}
virtual void setNexusID(int) {}
virtual void setNewestVersion(const MOBase::VersionInfo&) {}
virtual void ignoreUpdate(bool) {}
virtual void setNexusDescription(const QString&) {}
virtual void addNexusCategory(int) {}
virtual void setIsEndorsed(bool) {}
virtual void setNeverEndorse() {}
virtual bool remove() { return false; }
virtual void endorse(bool) {}
virtual bool isEmpty() const { return false; }
virtual QString name() const;
virtual QString notes() const { return ""; }
virtual QDateTime creationTime() const;
virtual QString absolutePath() const;
virtual MOBase::VersionInfo getNewestVersion() const { return ""; }
virtual QString getInstallationFile() const { return ""; }
virtual int getNexusID() const { return -1; }
virtual std::vector<QString> getIniTweaks() const { return std::vector<QString>(); }
virtual std::vector<ModInfo::EFlag> getFlags() const;
virtual int getHighlight() const;
virtual QString getDescription() const;
virtual QDateTime getLastNexusQuery() const { return QDateTime(); }
virtual QString getNexusDescription() const { return QString(); }
virtual int getFixedPriority() const { return INT_MIN; }
virtual QStringList archives() const { return m_Archives; }
virtual QStringList stealFiles() const { return m_Archives + QStringList(m_ReferenceFile); }
virtual bool alwaysEnabled() const { return true; }
protected:
ModInfoForeign(const QString &referenceFile, const QStringList &archives, MOShared::DirectoryEntry **directoryStructure);
private:
QString m_Name;
QString m_ReferenceFile;
QStringList m_Archives;
QDateTime m_CreationTime;
int m_Priority;
};
#endif // MODINFO_H
+122 -109
View File
@@ -60,7 +60,7 @@ bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS)
}
ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, QWidget *parent)
ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, QWidget *parent)
: TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo),
m_ThumbnailMapper(this), m_RequestStarted(false),
m_DeleteAction(NULL), m_RenameAction(NULL), m_OpenAction(NULL),
@@ -69,54 +69,19 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo
ui->setupUi(this);
this->setWindowTitle(modInfo->name());
this->setWindowModality(Qt::WindowModal);
m_UTF8Codec = QTextCodec::codecForName("utf-8");
QListWidget *textFileList = findChild<QListWidget*>("textFileList");
QListWidget *iniTweaksList = findChild<QListWidget*>("iniTweaksList");
QListWidget *activeESPList = findChild<QListWidget*>("activeESPList");
QListWidget *inactiveESPList = findChild<QListWidget*>("inactiveESPList");
QTreeWidget *categoriesTree = findChild<QTreeWidget*>("categoriesTree");
QHBoxLayout *thumbnailArea = findChild<QHBoxLayout*>("thumbnailArea");
m_FileTree = findChild<QTreeView*>("fileTree");
m_RootPath = modInfo->absolutePath();
QString metaFileName = m_RootPath.mid(0).append("/meta.ini");
m_Settings = new QSettings(metaFileName, QSettings::IniFormat);
QLineEdit *modIDEdit = findChild<QLineEdit*>("modIDEdit");
modIDEdit->setValidator(new QIntValidator(modIDEdit));
modIDEdit->setText(QString("%1").arg(modInfo->getNexusID()));
ui->modIDEdit->setValidator(new QIntValidator(modIDEdit));
ui->modIDEdit->setText(QString("%1").arg(modInfo->getNexusID()));
ui->notesEdit->setText(modInfo->notes());
m_FileSystemModel = new QFileSystemModel(this);
m_FileSystemModel->setReadOnly(false);
m_FileSystemModel->setRootPath(m_RootPath);
m_FileTree->setModel(m_FileSystemModel);
m_FileTree->setRootIndex(m_FileSystemModel->index(m_RootPath));
m_FileTree->setColumnWidth(0, 300);
connect(&m_ThumbnailMapper, SIGNAL(mapped(const QString&)), this, SIGNAL(thumbnailClickedSignal(const QString&)));
connect(this, SIGNAL(thumbnailClickedSignal(const QString&)), this, SLOT(thumbnailClicked(const QString&)));
m_DeleteAction = new QAction(tr("&Delete"), m_FileTree);
m_RenameAction = new QAction(tr("&Rename"), m_FileTree);
m_HideAction = new QAction(tr("&Hide"), m_FileTree);
m_UnhideAction = new QAction(tr("&Unhide"), m_FileTree);
m_OpenAction = new QAction(tr("&Open"), m_FileTree);
m_NewFolderAction = new QAction(tr("&New Folder"), m_FileTree);
QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered()));
QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered()));
QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered()));
QObject::connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered()));
QObject::connect(m_HideAction, SIGNAL(triggered()), this, SLOT(hideTriggered()));
connect(m_UnhideAction, SIGNAL(triggered()), this, SLOT(unhideTriggered()));
connect(m_ModInfo.data(), SIGNAL(modDetailsUpdated(bool)), this, SLOT(modDetailsUpdated(bool)));
// ui->descriptionView->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
// QObject::connect(ui->descriptionView, SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl)));
connect(ui->descriptionView, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl)));
if (directory->originExists(ToWString(modInfo->name()))) {
@@ -126,32 +91,39 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo
}
}
addCategories(CategoryFactory::instance(), modInfo->getCategories(), categoriesTree->invisibleRootItem(), 0);
refreshPrimaryCategoriesBox();
if (unmanaged) {
ui->tabWidget->setTabEnabled(TAB_INIFILES, false);
ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false);
ui->tabWidget->setTabEnabled(TAB_NEXUS, false);
ui->tabWidget->setTabEnabled(TAB_FILETREE, false);
ui->tabWidget->setTabEnabled(TAB_NOTES, false);
} else {
initFiletree(modInfo);
initINITweaks();
addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0);
refreshPrimaryCategoriesBox();
}
refreshLists();
int numTweaks = m_Settings->beginReadArray("INI Tweaks");
for (int i = 0; i < numTweaks; ++i) {
m_Settings->setArrayIndex(i);
QList<QListWidgetItem*> items = iniTweaksList->findItems(m_Settings->value("name").toString(), Qt::MatchFixedString);
if (items.size() != 0) {
items.at(0)->setCheckState(Qt::Checked);
}
}
m_Settings->endArray();
ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0);
ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0);
ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0));
ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != NULL);
QTabWidget *tabWidget = findChild<QTabWidget*>("tabWidget");
tabWidget->setTabEnabled(TAB_TEXTFILES, textFileList->count() != 0);
tabWidget->setTabEnabled(TAB_IMAGES, thumbnailArea->count() != 0);
tabWidget->setTabEnabled(TAB_ESPS, (inactiveESPList->count() != 0) || (activeESPList->count() != 0));
tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != NULL);
if (tabWidget->currentIndex() == TAB_NEXUS) {
if (ui->tabWidget->currentIndex() == TAB_NEXUS) {
activateNexusTab();
}
ui->endorseBtn->setEnabled((m_ModInfo->endorsedState() == ModInfo::ENDORSED_FALSE) ||
(m_ModInfo->endorsedState() == ModInfo::ENDORSED_NEVER));
// activate first enabled tab
for (int i = 0; i < ui->tabWidget->count(); ++i) {
if (ui->tabWidget->isTabEnabled(i)) {
ui->tabWidget->setCurrentIndex(i);
break;
}
}
}
@@ -165,6 +137,46 @@ ModInfoDialog::~ModInfoDialog()
}
void ModInfoDialog::initINITweaks()
{
int numTweaks = m_Settings->beginReadArray("INI Tweaks");
for (int i = 0; i < numTweaks; ++i) {
m_Settings->setArrayIndex(i);
QList<QListWidgetItem*> items = ui->iniTweaksList->findItems(m_Settings->value("name").toString(), Qt::MatchFixedString);
if (items.size() != 0) {
items.at(0)->setCheckState(Qt::Checked);
}
}
m_Settings->endArray();
}
void ModInfoDialog::initFiletree(ModInfo::Ptr modInfo)
{
m_RootPath = modInfo->absolutePath();
ui->fileTree = findChild<QTreeView*>("fileTree");
m_FileSystemModel = new QFileSystemModel(this);
m_FileSystemModel->setReadOnly(false);
m_FileSystemModel->setRootPath(m_RootPath);
ui->fileTree->setModel(m_FileSystemModel);
ui->fileTree->setRootIndex(m_FileSystemModel->index(m_RootPath));
ui->fileTree->setColumnWidth(0, 300);
m_DeleteAction = new QAction(tr("&Delete"), ui->fileTree);
m_RenameAction = new QAction(tr("&Rename"), ui->fileTree);
m_HideAction = new QAction(tr("&Hide"), ui->fileTree);
m_UnhideAction = new QAction(tr("&Unhide"), ui->fileTree);
m_OpenAction = new QAction(tr("&Open"), ui->fileTree);
m_NewFolderAction = new QAction(tr("&New Folder"), ui->fileTree);
QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered()));
QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered()));
QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered()));
QObject::connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered()));
QObject::connect(m_HideAction, SIGNAL(triggered()), this, SLOT(hideTriggered()));
connect(m_UnhideAction, SIGNAL(triggered()), this, SLOT(unhideTriggered()));
}
int ModInfoDialog::tabIndex(const QString &tabId)
{
for (int i = 0; i < ui->tabWidget->count(); ++i) {
@@ -271,51 +283,52 @@ void ModInfoDialog::refreshLists()
}
}
if (m_RootPath.length() > 0) {
QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories);
while (dirIterator.hasNext()) {
QString fileName = dirIterator.next();
QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories);
while (dirIterator.hasNext()) {
QString fileName = dirIterator.next();
if (fileName.endsWith(".txt", Qt::CaseInsensitive)) {
ui->textFileList->addItem(fileName.mid(m_RootPath.length() + 1));
} else if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) &&
!fileName.endsWith("meta.ini")) {
QString namePart = fileName.mid(m_RootPath.length() + 1);
if (namePart.startsWith("INI Tweaks", Qt::CaseInsensitive)) {
QListWidgetItem *newItem = new QListWidgetItem(namePart.mid(11), ui->iniTweaksList);
newItem->setData(Qt::UserRole, namePart);
newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable);
newItem->setCheckState(Qt::Unchecked);
ui->iniTweaksList->addItem(newItem);
} else {
ui->iniFileList->addItem(namePart);
}
} else if (fileName.endsWith(".esp", Qt::CaseInsensitive) ||
fileName.endsWith(".esm", Qt::CaseInsensitive)) {
QString relativePath = fileName.mid(m_RootPath.length() + 1);
if (relativePath.contains('/')) {
QFileInfo fileInfo(fileName);
QListWidgetItem *newItem = new QListWidgetItem(fileInfo.fileName());
newItem->setData(Qt::UserRole, relativePath);
ui->inactiveESPList->addItem(newItem);
} else {
ui->activeESPList->addItem(relativePath);
}
} else if ((fileName.endsWith(".png", Qt::CaseInsensitive)) ||
(fileName.endsWith(".jpg", Qt::CaseInsensitive))) {
QImage image = QImage(fileName);
if (!image.isNull()) {
if (static_cast<float>(image.width()) / static_cast<float>(image.height()) > 1.34) {
image = image.scaledToWidth(128);
if (fileName.endsWith(".txt", Qt::CaseInsensitive)) {
ui->textFileList->addItem(fileName.mid(m_RootPath.length() + 1));
} else if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) &&
!fileName.endsWith("meta.ini")) {
QString namePart = fileName.mid(m_RootPath.length() + 1);
if (namePart.startsWith("INI Tweaks", Qt::CaseInsensitive)) {
QListWidgetItem *newItem = new QListWidgetItem(namePart.mid(11), ui->iniTweaksList);
newItem->setData(Qt::UserRole, namePart);
newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable);
newItem->setCheckState(Qt::Unchecked);
ui->iniTweaksList->addItem(newItem);
} else {
image = image.scaledToHeight(96);
ui->iniFileList->addItem(namePart);
}
} else if (fileName.endsWith(".esp", Qt::CaseInsensitive) ||
fileName.endsWith(".esm", Qt::CaseInsensitive)) {
QString relativePath = fileName.mid(m_RootPath.length() + 1);
if (relativePath.contains('/')) {
QFileInfo fileInfo(fileName);
QListWidgetItem *newItem = new QListWidgetItem(fileInfo.fileName());
newItem->setData(Qt::UserRole, relativePath);
ui->inactiveESPList->addItem(newItem);
} else {
ui->activeESPList->addItem(relativePath);
}
} else if ((fileName.endsWith(".png", Qt::CaseInsensitive)) ||
(fileName.endsWith(".jpg", Qt::CaseInsensitive))) {
QImage image = QImage(fileName);
if (!image.isNull()) {
if (static_cast<float>(image.width()) / static_cast<float>(image.height()) > 1.34) {
image = image.scaledToWidth(128);
} else {
image = image.scaledToHeight(96);
}
QPushButton *thumbnailButton = new QPushButton(QPixmap::fromImage(image), "");
thumbnailButton->setIconSize(QSize(image.width(), image.height()));
connect(thumbnailButton, SIGNAL(clicked()), &m_ThumbnailMapper, SLOT(map()));
m_ThumbnailMapper.setMapping(thumbnailButton, fileName);
ui->thumbnailArea->addWidget(thumbnailButton);
QPushButton *thumbnailButton = new QPushButton(QPixmap::fromImage(image), "");
thumbnailButton->setIconSize(QSize(image.width(), image.height()));
connect(thumbnailButton, SIGNAL(clicked()), &m_ThumbnailMapper, SLOT(map()));
m_ThumbnailMapper.setMapping(thumbnailButton, fileName);
ui->thumbnailArea->addWidget(thumbnailButton);
}
}
}
}
@@ -460,7 +473,8 @@ void ModInfoDialog::openIniFile(const QString &fileName)
QFile iniFile(fileName);
iniFile.open(QIODevice::ReadOnly);
QByteArray buffer = iniFile.readAll();
QTextCodec *codec = QTextCodec::codecForUtfText(buffer, m_UTF8Codec);
QTextCodec *codec = QTextCodec::codecForUtfText(buffer, QTextCodec::codecForName("utf-8"));
QTextEdit *iniFileView = findChild<QTextEdit*>("iniFileView");
iniFileView->setText(codec->toUnicode(buffer));
iniFileView->setProperty("currentFile", fileName);
@@ -929,7 +943,7 @@ void ModInfoDialog::renameTriggered()
return;
}
m_FileTree->edit(index);
ui->fileTree->edit(index);
}
@@ -999,18 +1013,18 @@ void ModInfoDialog::createDirectoryTriggered()
return;
}
m_FileTree->setCurrentIndex(newIndex);
m_FileTree->edit(newIndex);
ui->fileTree->setCurrentIndex(newIndex);
ui->fileTree->edit(newIndex);
}
void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos)
{
QItemSelectionModel *selectionModel = m_FileTree->selectionModel();
QItemSelectionModel *selectionModel = ui->fileTree->selectionModel();
m_FileSelection = selectionModel->selectedRows(0);
// m_FileSelection = m_FileTree->indexAt(pos);
QMenu menu(m_FileTree);
// m_FileSelection = ui->fileTree->indexAt(pos);
QMenu menu(ui->fileTree);
menu.addAction(m_NewFolderAction);
@@ -1038,7 +1052,7 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos)
m_FileSelection.clear();
m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0));
}
menu.exec(m_FileTree->mapToGlobal(pos));
menu.exec(ui->fileTree->mapToGlobal(pos));
}
@@ -1164,18 +1178,17 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po
{
m_ConflictsContextItem = ui->overwriteTree->itemAt(pos.x(), pos.y());
QMenu menu;
if (m_ConflictsContextItem != NULL) {
// offer to hide/unhide file, but not for files from archives
// if (!m_ConflictsContextItem->data(0, Qt::UserRole + 1).toBool()) {
if (!m_ConflictsContextItem->data(0, Qt::UserRole + 2).toBool()) {
if (!m_ConflictsContextItem->data(1, Qt::UserRole + 2).toBool()) {
QMenu menu;
if (m_ConflictsContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) {
menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFile()));
} else {
menu.addAction(tr("Hide"), this, SLOT(hideConflictFile()));
}
menu.exec(ui->overwriteTree->mapToGlobal(pos));
}
menu.exec(ui->overwriteTree->mapToGlobal(pos));
}
}
+5 -2
View File
@@ -65,6 +65,7 @@ public:
TAB_CONFLICTS,
TAB_CATEGORIES,
TAB_NEXUS,
TAB_NOTES,
TAB_FILETREE
};
@@ -76,7 +77,7 @@ public:
* @param modInfo info structure about the mod to display
* @param parent parend widget
**/
explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry *directory, QWidget *parent = 0);
explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry *directory, bool unmanaged, QWidget *parent = 0);
~ModInfoDialog();
/**
@@ -121,6 +122,9 @@ public slots:
private:
void initFiletree(ModInfo::Ptr modInfo);
void initINITweaks();
void refreshLists();
void addCategories(const CategoryFactory &factory, const std::set<int> &enabledCategories, QTreeWidgetItem *root, int rootLevel);
@@ -225,7 +229,6 @@ private:
const MOShared::DirectoryEntry *m_Directory;
MOShared::FilesOrigin *m_Origin;
QTextCodec *m_UTF8Codec;
std::map<int, int> m_RealTabPos;

Some files were not shown because too many files have changed in this diff Show More