Compare commits

...
Author SHA1 Message Date
Tannin ff78c6a014 - bugfix: extracting bsas did not work with non-ascii characters in the mod name
- another attempt to switch branches...
2013-12-07 17:41:45 +01:00
Tannin 340edcfbca - returned to default branch
- nexus interface will now use the nmm url
- nmm importer no longer cancels on a missing non-essential flag
2013-12-07 17:13:36 +01:00
Tannin c4f0bc39c7 Merge with default 2013-12-07 16:55:19 +01:00
Tannin e71c37df37 - bugfix: drag&drop on archive list didn't always work properly 2013-12-07 16:54:08 +01:00
Tannin b32457a7d2 - minor text fixes
- versions without a subminor version are now displayed without it (1.1 instead of 1.1.0)
- version compares now prefer the decimal comparison over the traditional
2013-12-07 16:07:28 +01:00
Tannin 6e1e8c37eb minor text changes 2013-12-07 14:23:51 +01:00
Tannin 469cc2d945 - download manager now saves the file times on nexus, for potential later use in version check
- nexus interface now supports 301 redirects
- now using the new nexus url format
- bugfix: "visit on nexus" used an outdated url scheme and thus caused unnecessary redirection
2013-12-07 12:48:18 +01:00
Tannin 0e90b9d233 - bugfix: changing nexus id directly via mod list did not cause the change to be saved 2013-12-04 19:54:28 +01:00
Tannin 0e19c84722 Added tag release v1.0.9 for changeset a61a930515ae 2013-12-01 23:52:42 +01:00
Tannin daf4ae44fa - added second context menu for changing categories that applies only changes to
the selected mods instead of replacing the existing set of categories (thanks to Ross!)
- bugfix: category filtering didn't work correctly when grouping was also active
2013-12-01 23:50:24 +01:00
Tannin ac29aa81f2 minor updates to readme and mod info ui 2013-12-01 21:43:16 +01:00
Tannin a0335d0a63 Added tag 1.0.8 for changeset a61a930515ae 2013-11-27 20:25:55 +01:00
Tannin f1515193ca - plugins can now be localized too
- fixes to the installer script
- extended build scripts to fetch translations from pootle server automatically
2013-11-27 20:25:24 +01:00
Tannin db0347212a - bugfix: installFile for mods was not a relative path if the downloads directory was non-default
- bugfix: crash after removing the last profile in the list (ordered alphabetically)
- improved subdirs ordering to ensure pythonrunner is compiled before pythonproxy
- translation files for plugins are now generated
- set some texts as un-translatable where it made no sense
- staging script now re-builds MO completely before packaging
- updated qt libraries for packaging to 4.8.5
- added python to the package
2013-11-22 17:45:34 +01:00
Tannin 5895a1a92d - bugfix: canceling creation of an ini tweak prompted an error message
- added missing files from earlier commit (wtf?)
2013-11-20 21:54:14 +01:00
Tannin c6101e34e1 - archive.dll now supports querying the crc value of files
- esptk now determines if a esp is a dummy (without records)
- hook.dll no longer creates a log file if noone wrote to it
- nexus id and installtime columns are now hidden by default
- modlist can now be refreshed without saving first (so plugins can replace the modlist.txt as a whole)
- plugins can now query more details about virtualised files
- added style options "plastique" and "cleanlooks"
- "overwrite" is no longer listed with a creation time
- a warning will now be displayed if the user has too many plugins active
- a warning will now be displayed if mods with scripts have an installation order that doesn't match the corresponding esp load order
- nmm importer now has select all/deselect all buttons
- nmm importer no longer tries to unpack missing files from archives (won't work anyway)
- initial support for importing from nmm 0.5 alpha
- removed some broken warning suppresions
- python runner now works with bundled python
- extended qbs build system (still fails to build the main gui application)
- implemented a nsis-based installer
2013-11-18 20:17:14 +01:00
Tannin 75475a7984 - started qbs based project files (not functional yet)
- modlist will now updated immediately after a change throw the modinfo dialog
- bugfix: configurator wasn't able to save after revision 137
2013-11-11 23:19:54 +01:00
Tannin 315ed6e41a Added tag release v1.0.7 for changeset 1973f6e00784 2013-11-06 18:40:09 +01:00
Tannin b2d613e9d1 Added tag release v1.0.6 for changeset 1973f6e00784 2013-11-06 18:40:03 +01:00
Tannin f010c22a3b - diagnosis plugins can now request to be re-evaluated
- main application now contains means for plugins to react on some changes (to be extended)
- plugins can now retrieve more information about a mod
- user-agent sent to nexus now automatically contains the current MO version
- included updated russian translation
- problems dialog now refreshes itself after a fix was applied
- fixed new esp/asset ordering diagnosis. May be fully functional now
- bugfix: restoring locked load order could leave MO in an endless loop (not sure if this fix is correct yet)
- bugfix: plugin list was not visually updated after some changes
- bugfix: nmm importer threw away mod ordering
2013-11-06 18:35:27 +01:00
Tannin d1594798e6 - added a new diagnosis to detect potential problems in regards to esp vs. asset ordering (not fully functional yet)
- new plugin interfaces to the mod list
- plugins can now query the origin (mod) of a esp/esm
- bugfix: potential access to profile before one was activated
- bugfix: regression in previous (not-yet-released) commit prevented changes to bsa list from being saved
2013-11-01 14:59:25 +01:00
Tannin 09bd3dbead - text files encoded in system encoding should now be correctly recognized in the mod info dialog
- archive-list can be user-sorted again (within one mod)
2013-10-26 15:02:31 +02:00
Tannin 8c0b309bde - Option to choose edition of the game. Currently only relevant for FO3 because FO3 GOTY is its own app
- applications that aren't in the executables list can again be started from the command line
- references to missing categories are now removed from mods
- bugfix: integrated fomod installer didn't update description and picture on some constellations of Windows version and theme
2013-10-25 14:49:42 +02:00
67 changed files with 8772 additions and 11359 deletions
+14 -11
View File
@@ -6,29 +6,31 @@ Requirements:
Visual C++ compiler 2010 (VC 10.0) or up
- Included in visual c++ express 2010 or windows sdk 7.0
Note: If you're having trouble installing the windows sdk, you may be affected by this bug: http://support.microsoft.com/kb/2717426
QtSDK 4.8.x (http://qt-project.org/downloads)
- Qt5 is not yet supported but WIP
- Install according to instruction
boost libraries (http://www.boost.org/)
- Compile according to their instructions (using vc++)
- As of time of writing boost_thread is the only compiled boost library used, you can
disable the others to safe yourself the compile time
- Compile according to their instructions (using vc++): http://www.boost.org/doc/libs/1_54_0/more/getting_started/windows.html
- A few of the boost libraries need to be built (the rest is header-only). The only compiled lib MO needs (at the time of writing) is
boost_thread. You can disable the others to safe yourself compile time (even on a modern system compiling boost can easily take an hour)
zlib (http://www.zlib.net/)
- Compile static library according to their instructions
- You should have a "zlibstatic.lib" under "build" afterwards
- Depending on the version of zlib and how you built you should have a file called zlibstatic.lib or zlibstat.lib in "build" or "x86\ZLibStatRelease".
- Please copy that lib file to "build\zlibstatic.lib" (if it doesn't exist) so MO finds it.
7z.dll (http://www.7-zip.org/download.html)
- Part of the 7-zip program
- Has to be the 32-bit dll! (approx. 893kb)
- If you don't want to install 7-zip (32-bit) you can open the 7z installer (i.e. 7z920.exe) in an archiving tool and unpack only the 7z.dll
Recommended:
------------
Qt Creator
- Included in QtSDK but there might be a newer version as a separate download
Set up (using Qt Creator):
--------------------------
@@ -40,14 +42,15 @@ Set up (using Qt Creator):
5. Switch the build configuration at the very top of the same page from "debug" to "release" (or vice versa) and repeat steps 3 and 4
6. Compile the configuration(s) you want to use (debug and/or release) (Build All). This should compile correctly.
7. return to the "projects" tab and switch to "Run Settings"
8. Determine the folder of the qt binaries in the package you downloaded. This could be "QtSDK\Desktop\Qt\4.8.5\msvc2010\bin" or "Qt4.8.5\bin"
For Release build:
8r. Add a "Run configuration" that points to <your working copy>\output\ModOrganizer.exe
9r. Copy "7z.dll" to <your working copy>\output\dlls
10r. From QtSDK\Desktop\Qt\4.8.0\msvc2010\bin copy the following files to <your working copy>\output\dlls: QtCore4.dll, QtDeclarative4.dll, QtGui4.dll, QtNetwork4.dll, QtScript4.dll, QtSql4.dll, QtWebkit4.dll, QtXml4.dll, QtXmlPatterns4.dll
9r. Add a "Run configuration" that points to <your working copy>\output\ModOrganizer.exe
10r. Copy "7z.dll" to <your working copy>\output\dlls
11r. From the qt binaries directory, copy the following files to <your working copy>\output\dlls: QtCore4.dll, QtDeclarative4.dll, QtGui4.dll, QtNetwork4.dll, QtScript4.dll, QtSql4.dll, QtWebkit4.dll, QtXml4.dll, QtXmlPatterns4.dll
For Debug build:
8d. Add a "Run configuration" that points to <your working copy>\outputd\ModOrganizer.exe
9d. Copy "7z.dll" to <your working copy>\outputd\dlls
10d. From QtSDK\Desktop\Qt\4.8.0\msvc2010\bin copy the following files to <your working copy>\outputd\dlls: QtCored4.dll, QtDeclaratived4.dll, QtGuid4.dll, QtNetworkd4.dll, QtScriptd4.dll, QtSqld4.dll, QtWebkitd4.dll, QtXmld4.dll, QtXmlPatternsd4.dll
9d. Add a "Run configuration" that points to <your working copy>\outputd\ModOrganizer.exe
10d. Copy "7z.dll" to <your working copy>\outputd\dlls
11d. From the qt binaries copy the following files to <your working copy>\outputd\dlls: QtCored4.dll, QtDeclaratived4.dll, QtGuid4.dll, QtNetworkd4.dll, QtScriptd4.dll, QtSqld4.dll, QtWebkitd4.dll, QtXmld4.dll, QtXmlPatternsd4.dll
Now you should be able to compile and run Mod Organizer.
Please note that when you change anything apart from the "organizer" subproject, qt creator may not pick up on the changes
+11 -11
View File
@@ -1,23 +1,23 @@
TEMPLATE = subdirs
CONFIG += ordered
SUBDIRS = bsatk \
shared \
uibase \
organizer \
hookdll \
archive \
helper \
shared \
uibase \
organizer \
hookdll \
archive \
helper \
plugins \
proxydll \
nxmhandler \
BossDummy \
pythonRunner \
esptk
BossDummy \
pythonRunner \
esptk
plugins.depends = pythonRunner
hookdll.depends = shared
organizer.depends = shared, uibase, plugins
organizer.depends = shared uibase plugins
CONFIG(debug, debug|release) {
DESTDIR = outputd
+3 -3
View File
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>315</width>
<height>75</height>
<height>81</height>
</rect>
</property>
<property name="contextMenuPolicy">
@@ -78,14 +78,14 @@
<item>
<widget class="QLabel" name="sizeLabel">
<property name="text">
<string>0</string>
<string notr="true">0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>KB</string>
<string notr="true">KB</string>
</property>
</widget>
</item>
+22 -4
View File
@@ -153,7 +153,8 @@ QString DownloadManager::DownloadInfo::currentURL()
DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent)
: IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false)
: IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false),
m_DateExpression("/Date\\((\\d+)\\)/")
{
connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
}
@@ -851,7 +852,6 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
setState(info, STATE_PAUSED);
} else {
if (bytesTotal > info->m_TotalSize) {
qDebug("file size %s: %lld", qPrintable(info->m_FileName), bytesTotal);
info->m_TotalSize = bytesTotal;
}
int oldProgress = info->m_Progress;
@@ -883,6 +883,7 @@ void DownloadManager::createMetaFile(DownloadInfo *info)
metaFile.setValue("name", info->m_NexusInfo.m_Name);
metaFile.setValue("modName", info->m_NexusInfo.m_ModName);
metaFile.setValue("version", info->m_NexusInfo.m_Version);
metaFile.setValue("fileTime", info->m_NexusInfo.m_FileTime);
metaFile.setValue("fileCategory", info->m_NexusInfo.m_FileCategory);
metaFile.setValue("newestVersion", info->m_NexusInfo.m_NewestVersion);
metaFile.setValue("category", info->m_NexusInfo.m_Category);
@@ -914,11 +915,9 @@ void DownloadManager::nxmDescriptionAvailable(int, QVariant userData, QVariant r
DownloadInfo *info = downloadInfoByID(userData.toInt());
if (info == NULL) return;
info->m_NexusInfo.m_Category = result["category_id"].toInt();
info->m_NexusInfo.m_ModName = result["name"].toString().trimmed();
info->m_NexusInfo.m_NewestVersion = result["version"].toString();
if (info->m_FileID != 0) {
setState(info, STATE_READY);
} else {
@@ -927,6 +926,17 @@ void DownloadManager::nxmDescriptionAvailable(int, QVariant userData, QVariant r
}
QDateTime DownloadManager::matchDate(const QString &timeString)
{
if (m_DateExpression.exactMatch(timeString)) {
return QDateTime::fromMSecsSinceEpoch(m_DateExpression.cap(1).toLongLong());
} else {
qWarning("date not matched: %s", qPrintable(timeString));
return QDateTime::currentDateTime();
}
}
void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID)
{
std::set<int>::iterator idIter = m_RequestIDs.find(requestID);
@@ -960,7 +970,11 @@ void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultD
(fileNameVariant == info->m_FileName) || (fileNameVariant == alternativeLocalName)) {
info->m_NexusInfo.m_Name = fileInfo["name"].toString();
info->m_NexusInfo.m_Version = fileInfo["version"].toString();
if (info->m_NexusInfo.m_Version.isEmpty()) {
info->m_NexusInfo.m_Version = info->m_NexusInfo.m_NewestVersion;
}
info->m_NexusInfo.m_FileCategory = fileInfo["category_id"].toInt();
info->m_NexusInfo.m_FileTime = matchDate(fileInfo["date"].toString());
info->m_FileID = fileInfo["id"].toInt();
found = true;
break;
@@ -1014,7 +1028,11 @@ void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, QVariant userD
info.m_Name = result["name"].toString();
info.m_Version = result["version"].toString();
if (info.m_Version.isEmpty()) {
info.m_Version = info.m_NewestVersion;
}
info.m_FileName = result["uri"].toString();
info.m_FileTime = matchDate(result["date"].toString());
if (userData.isValid()) {
m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(info), userData.toString()));
+5
View File
@@ -46,6 +46,7 @@ struct NexusInfo {
QString m_NewestVersion;
QString m_FileName;
QVariantList m_DownloadMap;
QDateTime m_FileTime;
bool m_Set;
};
Q_DECLARE_METATYPE(NexusInfo)
@@ -437,6 +438,8 @@ private:
DownloadInfo *downloadInfoByID(unsigned int id);
QDateTime matchDate(const QString &timeString);
private:
static const int AUTOMATIC_RETRIES = 3;
@@ -458,6 +461,8 @@ private:
bool m_ShowHidden;
QRegExp m_DateExpression;
};
#endif // DOWNLOADMANAGER_H
+1 -3
View File
@@ -506,7 +506,6 @@ bool InstallationManager::ensureValidModName(GuessedValue<QString> &name) const
return true;
}
bool InstallationManager::doInstall(GuessedValue<QString> &modName, int modID,
const QString &version, const QString &newestVersion, int categoryID)
{
@@ -528,7 +527,6 @@ bool InstallationManager::doInstall(GuessedValue<QString> &modName, int modID,
m_InstallationProgress.setValue(0);
m_InstallationProgress.setWindowModality(Qt::WindowModal);
m_InstallationProgress.show();
if (!m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(targetDirectory)).c_str(),
new MethodCallback<InstallationManager, void, float>(this, &InstallationManager::updateProgress),
new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::updateProgressFile),
@@ -649,7 +647,7 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString>
}
m_CurrentFile = fileInfo.absoluteFilePath();
if (fileInfo.dir() == QDir(ToQString(GameInfo::instance().getDownloadDir()))) {
if (fileInfo.dir() == QDir(m_DownloadsDirectory)) {
m_CurrentFile = fileInfo.fileName();
}
qDebug("using mod name \"%s\" (id %d) -> %s", modName->toUtf8().constData(), modID, qPrintable(m_CurrentFile));
+7
View File
@@ -64,6 +64,12 @@ public:
*/
void setModsDirectory(const QString &modsDirectory) { m_ModsDirectory = modsDirectory; }
/**
* @brief update the directory where downloads are stored
* @param downloadDirectory the download directory
*/
void setDownloadDirectory(const QString &downloadDirectory) { m_DownloadsDirectory = downloadDirectory; }
/**
* @brief install a mod from an archive
*
@@ -191,6 +197,7 @@ private:
QWidget *m_ParentWidget;
QString m_ModsDirectory;
QString m_DownloadsDirectory;
std::vector<MOBase::IPluginInstaller*> m_Installers;
std::set<QString, CaseInsensitive> m_SupportedExtensions;
+23 -2
View File
@@ -60,7 +60,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "selectiondialog.h"
#include "moapplication.h"
#include "tutorialmanager.h"
#include <QLibrary>
#include <iostream>
#include <QMessageBox>
#include <QSharedMemory>
@@ -140,7 +139,7 @@ bool bootstrap()
QObject::tr("The current user account doesn't have the required access rights to run "
"Mod Organizer. The neccessary changes can be made automatically (the MO directory "
"will be made writable for the current user account). You will be asked to run "
"\"helper.exe\" with administrative rights)."),
"\"helper.exe\" with administrative rights."),
QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) {
if (!Helper::init(GameInfo::instance().getOrganizerDirectory())) {
return false;
@@ -156,6 +155,7 @@ bool bootstrap()
// verify the hook-dll exists
QString dllName = qApp->applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName());
HMODULE dllMod = ::LoadLibraryW(ToWString(dllName).c_str());
if (dllMod == NULL) {
throw windows_error("hook.dll is missing or invalid");
@@ -400,6 +400,27 @@ int main(int argc, char *argv[])
settings.setValue("gamePath", gamePath.toUtf8().constData());
}
int edition = 0;
if (settings.contains("game_edition")) {
edition = settings.value("game_edition").toInt();
} else {
std::vector<std::wstring> editions = GameInfo::instance().getSteamVariants();
if (editions.size() < 2) {
edition = 0;
} else {
SelectionDialog selection(QObject::tr("Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!)"), NULL);
int index = 0;
for (auto iter = editions.begin(); iter != editions.end(); ++iter) {
selection.addChoice(ToQString(*iter), "", index++);
}
if (selection.exec() == QDialog::Rejected) {
return -1;
} else {
settings.setValue("game_edition", selection.getChoiceData().toInt());
}
}
}
qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(gamePath)));
ExecutablesList executablesList;
+218 -63
View File
File diff suppressed because it is too large Load Diff
+31 -9
View File
@@ -98,7 +98,6 @@ public:
void refreshBSAList();
void refreshDataTree();
void refreshSaveList();
void refreshModList();
void setExecutablesList(const ExecutablesList &executablesList);
@@ -135,10 +134,14 @@ public:
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;
@@ -194,7 +197,7 @@ private:
void actionToToolButton(QAction *&sourceAction);
bool verifyPlugin(MOBase::IPlugin *plugin);
void registerPluginTool(MOBase::IPluginTool *tool);
bool registerPlugin(QObject *pluginObj);
bool registerPlugin(QObject *pluginObj, const QString &fileName);
void updateToolBar();
void activateSelectedProfile();
@@ -227,9 +230,23 @@ private:
void refreshFilters();
void saveCategoriesFromMenu(QMenu *menu, int modRow);
/**
* Sets category selections from menu; for multiple mods, this will only apply
* the changes made in the menu (which is the delta between the current menu selection and the reference mod)
* @param menu the menu after editing by the user
* @param modRow index of the mod to edit
*/
void addRemoveCategoriesFromMenu(QMenu *menu, int modRow);
bool addCategories(QMenu *menu, int targetID);
/**
* Sets category selections from menu; for multiple mods, this will completely
* replace the current set of categories on each selected with those selected in the menu
* @param menu the menu after editing by the user
* @param modRow index of the mod to edit
*/
void replaceCategoriesFromMenu(QMenu *menu, int modRow);
bool populateMenuCategories(QMenu *menu, int targetID);
void updateDownloadListDelegate();
@@ -269,10 +286,12 @@ private:
static void setupNetworkProxy(bool activate);
void activateProxy(bool activate);
void installTranslator(const QString &name);
private:
static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1;
static const unsigned int PROBLEM_TOOMANYPLUGINS = 2;
private:
@@ -314,9 +333,6 @@ private:
DownloadManager m_DownloadManager;
InstallationManager m_InstallationManager;
QTranslator *m_Translator;
QTranslator *m_TranslatorQt;
SelfUpdater m_Updater;
CategoryFactory &m_CategoryFactory;
@@ -348,6 +364,9 @@ private:
SignalAboutToRunApplication m_AboutToRun;
QString m_CurrentLanguage;
std::vector<QTranslator*> m_Translators;
private slots:
void showMessage(const QString &message);
@@ -427,7 +446,9 @@ private slots:
void originModified(int originID);
void saveCategories();
void addRemoveCategories_MenuHandler();
void replaceCategories_MenuHandler();
void savePrimaryCategory();
void addPrimaryCategoryCandidates();
@@ -510,7 +531,7 @@ private slots: // ui slots
void on_actionEndorseMO_triggered();
void on_bsaList_customContextMenuRequested(const QPoint &pos);
void on_bsaList_itemChanged(QTreeWidgetItem *item, int column);
void bsaList_itemMoved();
void on_btnRefreshData_clicked();
void on_categoriesList_customContextMenuRequested(const QPoint &pos);
void on_compactBox_toggled(bool checked);
@@ -531,6 +552,7 @@ private slots: // ui slots
void on_categoriesList_itemSelectionChanged();
void on_linkButton_pressed();
void on_showHiddenBox_toggled(bool checked);
void on_bsaList_itemChanged(QTreeWidgetItem *item, int column);
};
#endif // MAINWINDOW_H
+15 -9
View File
@@ -725,7 +725,7 @@ p, li { white-space: pre-wrap; }
<number>6</number>
</property>
<item>
<widget class="QTreeWidget" name="bsaList">
<widget class="MOBase::SortableTreeWidget" name="bsaList">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
@@ -742,7 +742,7 @@ BSAs checked here are loaded in such a way that your installation order is obeye
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
<bool>true</bool>
</property>
<property name="dragEnabled">
<bool>false</bool>
@@ -751,10 +751,10 @@ BSAs checked here are loaded in such a way that your installation order is obeye
<bool>false</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::NoDragDrop</enum>
<enum>QAbstractItemView::DragDrop</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::IgnoreAction</enum>
<enum>Qt::MoveAction</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
@@ -768,6 +768,12 @@ BSAs checked here are loaded in such a way that your installation order is obeye
<property name="itemsExpandable">
<bool>true</bool>
</property>
<property name="columnCount">
<number>1</number>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>200</number>
</attribute>
@@ -776,11 +782,6 @@ BSAs checked here are loaded in such a way that your installation order is obeye
<string>File</string>
</property>
</column>
<column>
<property name="text">
<string>Mod</string>
</property>
</column>
</widget>
</item>
<item>
@@ -1224,6 +1225,11 @@ Right now this has very limited functionality</string>
<extends>QTreeView</extends>
<header>modlistview.h</header>
</customwidget>
<customwidget>
<class>MOBase::SortableTreeWidget</class>
<extends>QTreeWidget</extends>
<header>sortabletreewidget.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="resources.qrc"/>
+21 -10
View File
@@ -19,16 +19,19 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "moapplication.h"
#include "report.h"
#include "utility.h"
#include <utility.h>
#include <appconfig.h>
#include <QFile>
#include <QStringList>
#include <QPlastiqueStyle>
#include <QCleanlooksStyle>
MOApplication::MOApplication(int argc, char **argv)
: QApplication(argc, argv)
{
connect(&m_StyleWatcher, SIGNAL(fileChanged(QString)), SLOT(updateStyle(QString)));
m_DefaultStyle = style()->objectName();
}
@@ -42,11 +45,12 @@ bool MOApplication::setStyleFile(const QString &styleName)
// set new stylesheet or clear it
if (styleName.length() != 0) {
QString styleSheetName = applicationDirPath() + "/" + MOBase::ToQString(AppConfig::stylesheetsPath()) + "/" + styleName;
if (!QFile::exists(styleSheetName)) {
return false;
if (QFile::exists(styleSheetName)) {
m_StyleWatcher.addPath(styleSheetName);
updateStyle(styleSheetName);
} else {
updateStyle(styleName);
}
m_StyleWatcher.addPath(styleSheetName);
updateStyle(styleSheetName);
} else {
setStyleSheet("");
}
@@ -74,11 +78,18 @@ bool MOApplication::notify(QObject *receiver, QEvent *event)
void MOApplication::updateStyle(const QString &fileName)
{
QFile file(fileName);
if (file.open(QFile::ReadOnly)) {
setStyleSheet(file.readAll());
if (fileName == "Plastique") {
setStyle(new QPlastiqueStyle);
setStyleSheet("");
} else if (fileName == "Cleanlooks") {
setStyle(new QCleanlooksStyle);
setStyleSheet("");
} else {
qDebug("no stylesheet");
setStyle(m_DefaultStyle);
if (QFile::exists(fileName)) {
setStyleSheet(QString("file:///%1").arg(fileName));
} else {
qWarning("invalid stylesheet: %s", qPrintable(fileName));
}
}
file.close();
}
+2
View File
@@ -43,6 +43,8 @@ private slots:
private:
QFileSystemWatcher m_StyleWatcher;
QString m_DefaultStyle;
};
+16 -1
View File
@@ -541,6 +541,13 @@ bool ModInfoRegular::setName(const QString &name)
void ModInfoRegular::setNotes(const QString &notes)
{
m_Notes = notes;
m_MetaInfoChanged = true;
}
void ModInfoRegular::setNexusID(int modID)
{
m_NexusID = modID;
m_MetaInfoChanged = true;
}
void ModInfoRegular::setVersion(const VersionInfo &version)
@@ -560,7 +567,6 @@ void ModInfoRegular::addNexusCategory(int categoryID)
m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID));
}
void ModInfoRegular::setIsEndorsed(bool endorsed)
{
if (m_EndorsedState != ENDORSED_NEVER) {
@@ -828,6 +834,15 @@ ModInfoOverwrite::ModInfoOverwrite()
}
bool ModInfoOverwrite::isEmpty() const
{
QDirIterator iter(absolutePath(), QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
if (!iter.hasNext()) return true;
iter.next();
if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true;
return false;
}
QString ModInfoOverwrite::absolutePath() const
{
return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOverwriteDir()));
+2 -1
View File
@@ -570,7 +570,7 @@ public:
*
* @param modID the nexus mod id
**/
void setNexusID(int modID) { m_NexusID = modID; }
void setNexusID(int modID);
/**
* @brief set the version of this mod
@@ -866,6 +866,7 @@ public:
virtual void setNeverEndorse() {}
virtual bool remove() { return false; }
virtual void endorse(bool) {}
virtual bool isEmpty() const;
virtual QString name() const { return "Overwrite"; }
virtual QString notes() const { return ""; }
virtual QDateTime creationTime() const { return m_StartupTime; }
+22 -14
View File
@@ -254,17 +254,19 @@ void ModInfoDialog::refreshLists()
} else if ((fileName.endsWith(".png", Qt::CaseInsensitive)) ||
(fileName.endsWith(".jpg", Qt::CaseInsensitive))) {
QImage image = QImage(fileName);
if (static_cast<float>(image.width()) / static_cast<float>(image.height()) > 1.34) {
image = image.scaledToWidth(128);
} else {
image = image.scaledToHeight(96);
}
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);
}
}
}
@@ -400,7 +402,13 @@ void ModInfoDialog::openTextFile(const QString &fileName)
textFile.open(QIODevice::ReadOnly);
QByteArray buffer = textFile.readAll();
QTextCodec *codec = QTextCodec::codecForUtfText(buffer, m_UTF8Codec);
ui->textFileView->setText(codec->toUnicode(buffer));
QString text = codec->toUnicode(buffer);
if (codec->fromUnicode(text) != buffer) {
qDebug("conversion failed assuming local encoding");
codec = QTextCodec::codecForLocale();
text = codec->toUnicode(buffer);
}
ui->textFileView->setText(text);
ui->textFileView->setProperty("currentFile", fileName);
ui->textFileView->setProperty("encoding", codec->name());
ui->saveTXTButton->setEnabled(false);
@@ -1169,10 +1177,10 @@ void ModInfoDialog::on_prevButton_clicked()
void ModInfoDialog::createTweak()
{
QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name"));
if (!fixDirectoryName(name)) {
QMessageBox::critical(this, tr("Error"), tr("Invalid name. Must be a valid file name"));
if (name.isNull()) {
return;
} else if (name.isEmpty()) {
} else if (!fixDirectoryName(name)) {
QMessageBox::critical(this, tr("Error"), tr("Invalid name. Must be a valid file name"));
return;
} else if (ui->iniTweaksList->findItems(name, Qt::MatchFixedString).count() != 0) {
QMessageBox::critical(this, tr("Error"), tr("A tweak by that name exists"));
+30 -24
View File
@@ -209,7 +209,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>676</width>
<width>668</width>
<height>126</height>
</rect>
</property>
@@ -217,17 +217,22 @@
<string>Images located in the mod.</string>
</property>
<property name="whatsThis">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view.</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
@@ -254,14 +259,10 @@ p, li { white-space: pre-wrap; }
<string>List of esps and esms that can not be loaded by the game.</string>
</property>
<property name="whatsThis">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;List of esps and esms contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;They usually contain optional functionality, see the readme.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Most mods do not have optional esps, so chances are good you are looking at an empty list.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>List of esps and esms contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window.
They usually contain optional functionality, see the readme.
Most mods do not have optional esps, so chances are good you are looking at an empty list.</string>
</property>
</widget>
</item>
@@ -403,6 +404,9 @@ p, li { white-space: pre-wrap; }
<property name="textElideMode">
<enum>Qt::ElideLeft</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="animated">
<bool>true</bool>
</property>
@@ -413,13 +417,13 @@ p, li { white-space: pre-wrap; }
<number>2</number>
</property>
<attribute name="headerDefaultSectionSize">
<number>500</number>
<number>365</number>
</attribute>
<attribute name="headerHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="headerMinimumSectionSize">
<number>300</number>
<number>200</number>
</attribute>
<column>
<property name="text">
@@ -459,14 +463,17 @@ p, li { white-space: pre-wrap; }
<property name="textElideMode">
<enum>Qt::ElideLeft</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="animated">
<bool>true</bool>
</property>
<attribute name="headerDefaultSectionSize">
<number>500</number>
<number>365</number>
</attribute>
<attribute name="headerMinimumSectionSize">
<number>300</number>
<number>200</number>
</attribute>
<column>
<property name="text">
@@ -524,9 +531,9 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QLabel" name="label_9">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Primary Category</string>
</property>
@@ -659,9 +666,8 @@ p, li { white-space: pre-wrap; }
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
+96 -5
View File
@@ -169,10 +169,16 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
int category = modInfo->getPrimaryCategory();
if (category != -1) {
CategoryFactory &categoryFactory = CategoryFactory::instance();
try {
return categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(category));
} catch (const std::exception &e) {
qCritical("failed to retrieve category name: %s", e.what());
if (categoryFactory.categoryExists(category)) {
try {
return categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(category));
} catch (const std::exception &e) {
qCritical("failed to retrieve category name: %s", e.what());
return QString();
}
} else {
qWarning("category %d doesn't exist (may have been removed)", category);
modInfo->setCategory(category, false);
return QString();
}
} else {
@@ -180,7 +186,12 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
return QVariant();
}
} else if (column == COL_INSTALLTIME) {
return modInfo->creationTime();
// display installation time for mods that can be updated
if (modInfo->canBeUpdated()) {
return modInfo->creationTime();
} else {
return QVariant();
}
} else {
return tr("invalid");
}
@@ -545,6 +556,86 @@ void ModList::changeModPriority(int sourceIndex, int newPriority)
emit modorder_changed();
}
void ModList::modInfoAboutToChange(ModInfo::Ptr info)
{
m_ChangeInfo.name = info->name();
m_ChangeInfo.state = state(info->name());
}
void ModList::modInfoChanged(ModInfo::Ptr info)
{
if (info->name() == m_ChangeInfo.name) {
IModList::ModStates newState = state(info->name());
if (m_ChangeInfo.state != newState) {
m_ModStateChanged(info->name(), newState);
}
int row = ModInfo::getIndex(info->name());
info->testValid();
emit dataChanged(index(row, 0), index(row, columnCount()));
} else {
qCritical("modInfoChanged not called after modInfoAboutToChange");
}
}
IModList::ModStates ModList::state(const QString &name) const
{
ModStates result;
unsigned int modIndex = ModInfo::getIndex(name);
if (modIndex != UINT_MAX) {
result |= IModList::STATE_EXISTS;
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
if (modInfo->isEmpty()) {
result |= IModList::STATE_EMPTY;
}
if (modInfo->endorsedState() == ModInfo::ENDORSED_TRUE) {
result |= IModList::STATE_ENDORSED;
}
if (modInfo->isValid()) {
result |= IModList::STATE_VALID;
}
if (modInfo->canBeEnabled()) {
if (m_Profile->modEnabled(modIndex)) {
result |= IModList::STATE_ACTIVE;
}
} else {
result |= IModList::STATE_ESSENTIAL;
}
}
return result;
}
int ModList::priority(const QString &name) const
{
unsigned int modIndex = ModInfo::getIndex(name);
if (modIndex == UINT_MAX) {
return -1;
} else {
return m_Profile->getModPriority(modIndex);
}
}
bool ModList::setPriority(const QString &name, int newPriority)
{
if ((newPriority < 0) || (newPriority >= static_cast<int>(m_Profile->numRegularMods()))) {
return false;
}
unsigned int modIndex = ModInfo::getIndex(name);
if (modIndex == UINT_MAX) {
return false;
} else {
m_Profile->setModPriority(modIndex, newPriority);
notifyChange(modIndex);
return true;
}
}
bool ModList::onModStateChanged(const std::function<void (const QString &, IModList::ModStates)> &func)
{
auto conn = m_ModStateChanged.connect(func);
return conn.connected();
}
bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent)
{
+36 -2
View File
@@ -26,11 +26,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "modinfo.h"
#include "profile.h"
#include <imodlist.h>
#include <QFile>
#include <QListWidget>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <boost/signals2.hpp>
#include <set>
#include <vector>
#include <QVector>
@@ -41,7 +43,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
* This is used in a view in the main window of MO. It combines general information about
* the mods from ModInfo with status information from the Profile
**/
class ModList : public QAbstractItemModel
class ModList : public QAbstractItemModel, public MOBase::IModList
{
Q_OBJECT
@@ -59,6 +61,8 @@ public:
COL_LASTCOLUMN = COL_PRIORITY
};
typedef boost::signals2::signal<void (const QString &, ModStates)> SignalModStateChanged;
public:
/**
@@ -92,6 +96,23 @@ public:
void changeModPriority(int sourceIndex, int newPriority);
void modInfoAboutToChange(ModInfo::Ptr info);
void modInfoChanged(ModInfo::Ptr info);
public:
/// \copydoc MOBase::IModList::state
virtual ModStates state(const QString &name) const;
/// \copydoc MOBase::IModList::priority
virtual int priority(const QString &name) const;
/// \copydoc MOBase::IModList::setPriority
virtual bool setPriority(const QString &name, int newPriority);
/// \copydoc MOBase::IModList::onModStateChanged
virtual bool onModStateChanged(const std::function<void (const QString &, ModStates)> &func);
public: // implementation of virtual functions of QAbstractItemModel
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
@@ -232,6 +253,11 @@ private:
unsigned int categoryOrder;
};
struct TModInfoChange {
QString name;
QFlags<IModList::ModStates> state;
};
private:
Profile *m_Profile;
@@ -245,6 +271,14 @@ private:
bool m_DropOnItems;
TModInfoChange m_ChangeInfo;
SignalModStateChanged m_ModStateChanged;
// QAbstractItemModel interface
// IModList interface
};
#endif // MODLIST_H
+20 -4
View File
@@ -220,7 +220,7 @@ bool ModListSortProxy::hasConflictFlag(const std::vector<ModInfo::EFlag> &flags)
}
bool ModListSortProxy::filterMatches(ModInfo::Ptr info, bool enabled) const
bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const
{
if (!m_CurrentFilter.isEmpty() &&
!info->name().contains(m_CurrentFilter, Qt::CaseInsensitive)) {
@@ -258,7 +258,7 @@ bool ModListSortProxy::filterMatches(ModInfo::Ptr info, bool enabled) const
}
bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const
bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) const
{
if (m_Profile == NULL) {
return false;
@@ -268,9 +268,25 @@ bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const
qWarning("invalid row idx %d", row);
return false;
}
bool modEnabled = m_Profile->modEnabled(row);
return filterMatches(ModInfo::getByIndex(row), modEnabled);
QModelIndex idx = sourceModel()->index(row, 0, parent);
if (!idx.isValid()) {
qDebug("invalid index");
return false;
}
if (idx.isValid() && sourceModel()->hasChildren(idx)) {
for (int i = 0; i < sourceModel()->rowCount(idx); ++i) {
if (filterAcceptsRow(i, idx)) {
return true;
}
}
return false;
} else {
bool modEnabled = idx.sibling(row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked;
unsigned int index = idx.data(Qt::UserRole + 1).toInt();
return filterMatchesMod(ModInfo::getByIndex(index), modEnabled);
}
}

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