mirror of
https://github.com/ModOrganizer2/modorganizer.git
synced 2026-07-27 13:58:24 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9f9e6af94 | ||
|
|
b9078ffefd | ||
|
|
04c5986aa6 | ||
|
|
1cb18b3c00 | ||
|
|
e34a9e7315 | ||
|
|
4d546ea262 | ||
|
|
2ca7c1c9ea | ||
|
|
4b0ba5c579 | ||
|
|
7a10a74766 | ||
|
|
7db0841a22 | ||
|
|
2c082d6a3b | ||
|
|
a965a9dc41 | ||
|
|
82cc488a18 | ||
|
|
4adfeac9b7 | ||
|
|
c7a90f9506 | ||
|
|
80645bacc1 | ||
|
|
0c7265be4f | ||
|
|
6c4e237d4b |
@@ -395,6 +395,11 @@
|
||||
<string>Nubbie (Swedish)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Hakan "Nyks45" Albayrak (Turkish)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>...and all other contributors!</string>
|
||||
|
||||
+2
-2
@@ -118,9 +118,9 @@ void DataTab::updateTree()
|
||||
void DataTab::ensureFullyLoaded()
|
||||
{
|
||||
if (!m_filetree->fullyLoaded()) {
|
||||
m_filter.proxyModel()->setRecursiveFilteringEnabled(false);
|
||||
m_filter.setFilteringEnabled(false);
|
||||
m_filetree->ensureFullyLoaded();
|
||||
m_filter.proxyModel()->setRecursiveFilteringEnabled(true);
|
||||
m_filter.setFilteringEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
#ifndef ENV_ENVFS_H
|
||||
#define ENV_ENVFS_H
|
||||
|
||||
#include "thread_utils.h"
|
||||
#include <thread>
|
||||
|
||||
namespace env
|
||||
@@ -125,7 +126,7 @@ private:
|
||||
ThreadInfo()
|
||||
: busy(true), ready(false), stop(false)
|
||||
{
|
||||
thread = std::thread([&]{ run(); });
|
||||
thread = MOShared::startSafeThread([&]{ run(); });
|
||||
}
|
||||
|
||||
~ThreadInfo()
|
||||
|
||||
+14
-2
@@ -802,11 +802,11 @@ void FileTree::addCommonMenus(QMenu& menu)
|
||||
.addTo(menu);
|
||||
|
||||
MenuItem(tr("Ex&pand All"))
|
||||
.callback([&]{ m_tree->expandAll(); })
|
||||
.callback([&]{ expandAll(); })
|
||||
.addTo(menu);
|
||||
|
||||
MenuItem(tr("&Collapse All"))
|
||||
.callback([&]{ m_tree->collapseAll(); })
|
||||
.callback([&]{ collapseAll(); })
|
||||
.addTo(menu);
|
||||
}
|
||||
|
||||
@@ -820,3 +820,15 @@ QModelIndex FileTree::proxiedIndex(const QModelIndex& index)
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
void FileTree::collapseAll()
|
||||
{
|
||||
m_tree->collapseAll();
|
||||
}
|
||||
|
||||
void FileTree::expandAll()
|
||||
{
|
||||
m_model->aboutToExpandAll();
|
||||
m_tree->expandAll();
|
||||
m_model->expandedAll();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@ public:
|
||||
bool fullyLoaded() const;
|
||||
void ensureFullyLoaded();
|
||||
|
||||
void expandAll();
|
||||
void collapseAll();
|
||||
|
||||
void open(FileTreeItem* item=nullptr);
|
||||
void openHooked(FileTreeItem* item=nullptr);
|
||||
void preview(FileTreeItem* item=nullptr);
|
||||
|
||||
+119
-56
@@ -14,9 +14,7 @@ constexpr bool AlwaysSortDirectoriesFirst = true;
|
||||
|
||||
const QString& directoryFileType()
|
||||
{
|
||||
static QString name;
|
||||
|
||||
if (name.isEmpty()) {
|
||||
static const QString name = [] {
|
||||
const DWORD flags = SHGFI_TYPENAME;
|
||||
SHFILEINFOW sfi = {};
|
||||
|
||||
@@ -30,15 +28,88 @@ const QString& directoryFileType()
|
||||
"SHGetFileInfoW failed for folder file type, {}",
|
||||
formatSystemMessage(e));
|
||||
|
||||
name = "File folder";
|
||||
return QString("File folder");
|
||||
} else {
|
||||
name = QString::fromWCharArray(sfi.szTypeName);
|
||||
return QString::fromWCharArray(sfi.szTypeName);
|
||||
}
|
||||
}
|
||||
}();
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
const QString& cachedFileTypeNoExtension()
|
||||
{
|
||||
static const QString name = [] {
|
||||
const DWORD flags = SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES;
|
||||
SHFILEINFOW sfi = {};
|
||||
|
||||
// dummy filename with no extension
|
||||
const auto r = SHGetFileInfoW(L"file", 0, &sfi, sizeof(sfi), flags);
|
||||
|
||||
if (!r) {
|
||||
const auto e = GetLastError();
|
||||
|
||||
log::error(
|
||||
"SHGetFileInfoW failed for file without extension, {}",
|
||||
formatSystemMessage(e));
|
||||
|
||||
return QString("File");
|
||||
} else {
|
||||
return QString::fromWCharArray(sfi.szTypeName);
|
||||
}
|
||||
}();
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
const QString& cachedFileType(const std::wstring& file, bool isOnFilesystem)
|
||||
{
|
||||
static std::map<std::wstring, QString, std::less<>> map;
|
||||
static std::mutex mutex;
|
||||
|
||||
const auto dot = file.find_last_of(L'.');
|
||||
if (dot == std::wstring::npos) {
|
||||
return cachedFileTypeNoExtension();
|
||||
}
|
||||
|
||||
std::scoped_lock lock(mutex);
|
||||
const auto sv = std::wstring_view(file.c_str() + dot, file.size() - dot);
|
||||
|
||||
auto itor = map.find(sv);
|
||||
if (itor != map.end()) {
|
||||
return itor->second;
|
||||
}
|
||||
|
||||
|
||||
DWORD flags = SHGFI_TYPENAME;
|
||||
|
||||
if (!isOnFilesystem) {
|
||||
// files from archives are not on the filesystem; this flag forces
|
||||
// SHGetFileInfoW() to only work with the filename
|
||||
flags |= SHGFI_USEFILEATTRIBUTES;
|
||||
}
|
||||
|
||||
SHFILEINFOW sfi = {};
|
||||
const auto r = SHGetFileInfoW(file.c_str(), 0, &sfi, sizeof(sfi), flags);
|
||||
|
||||
QString s;
|
||||
|
||||
if (!r) {
|
||||
const auto e = GetLastError();
|
||||
|
||||
log::error(
|
||||
"SHGetFileInfoW failed for '{}', {}",
|
||||
file, formatSystemMessage(e));
|
||||
|
||||
s = cachedFileTypeNoExtension();
|
||||
} else {
|
||||
s = QString::fromWCharArray(sfi.szTypeName);
|
||||
}
|
||||
|
||||
return map.emplace(sv, s).first->second;
|
||||
}
|
||||
|
||||
|
||||
|
||||
FileTreeItem::FileTreeItem(
|
||||
FileTreeModel* model, FileTreeItem* parent,
|
||||
@@ -176,51 +247,60 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void FileTreeItem::sort()
|
||||
void FileTreeItem::queueSort()
|
||||
{
|
||||
if (!m_children.empty()) {
|
||||
m_model->sortItem(*this, true);
|
||||
m_model->queueSortItem(this);
|
||||
}
|
||||
}
|
||||
|
||||
void FileTreeItem::makeSortingStale()
|
||||
{
|
||||
m_sortingStale = true;
|
||||
|
||||
for (auto& c : m_children) {
|
||||
c->makeSortingStale();
|
||||
}
|
||||
}
|
||||
|
||||
void FileTreeItem::sort(int column, Qt::SortOrder order, bool force)
|
||||
{
|
||||
if (!force && !m_expanded) {
|
||||
if (!m_expanded) {
|
||||
m_sortingStale = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sortingStale) {
|
||||
if (m_sortingStale || force) {
|
||||
//log::debug("sorting is stale for {}, sorting now", debugName());
|
||||
m_sortingStale = false;
|
||||
|
||||
std::sort(m_children.begin(), m_children.end(), [&](auto&& a, auto&& b) {
|
||||
int r = 0;
|
||||
|
||||
if (a->isDirectory() && !b->isDirectory()) {
|
||||
if constexpr (AlwaysSortDirectoriesFirst) {
|
||||
return true;
|
||||
} else {
|
||||
r = -1;
|
||||
}
|
||||
} else if (!a->isDirectory() && b->isDirectory()) {
|
||||
if constexpr (AlwaysSortDirectoriesFirst) {
|
||||
return false;
|
||||
} else {
|
||||
r = 1;
|
||||
}
|
||||
} else {
|
||||
r = FileTreeItem::Sorter::compare(column, a.get(), b.get());
|
||||
}
|
||||
|
||||
if (order == Qt::AscendingOrder) {
|
||||
return (r < 0);
|
||||
} else {
|
||||
return (r > 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::sort(m_children.begin(), m_children.end(), [&](auto&& a, auto&& b) {
|
||||
int r = 0;
|
||||
|
||||
if (a->isDirectory() && !b->isDirectory()) {
|
||||
if constexpr (AlwaysSortDirectoriesFirst) {
|
||||
return true;
|
||||
} else {
|
||||
r = -1;
|
||||
}
|
||||
} else if (!a->isDirectory() && b->isDirectory()) {
|
||||
if constexpr (AlwaysSortDirectoriesFirst) {
|
||||
return false;
|
||||
} else {
|
||||
r = 1;
|
||||
}
|
||||
} else {
|
||||
r = FileTreeItem::Sorter::compare(column, a.get(), b.get());
|
||||
}
|
||||
|
||||
if (order == Qt::AscendingOrder) {
|
||||
return (r < 0);
|
||||
} else {
|
||||
return (r > 0);
|
||||
}
|
||||
});
|
||||
|
||||
for (auto& child : m_children) {
|
||||
child->sort(column, order, force);
|
||||
}
|
||||
@@ -321,28 +401,11 @@ void FileTreeItem::getFileType() const
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD flags = SHGFI_TYPENAME;
|
||||
|
||||
if (isFromArchive()) {
|
||||
// files from archives are not on the filesystem; this flag forces
|
||||
// SHGetFileInfoW() to only work with the filename
|
||||
flags |= SHGFI_USEFILEATTRIBUTES;
|
||||
}
|
||||
|
||||
SHFILEINFOW sfi = {};
|
||||
const auto r = SHGetFileInfoW(
|
||||
m_wsRealPath.c_str(), 0, &sfi, sizeof(sfi), flags);
|
||||
|
||||
if (!r) {
|
||||
const auto e = GetLastError();
|
||||
|
||||
log::error(
|
||||
"SHGetFileInfoW failed for '{}', {}",
|
||||
m_realPath, formatSystemMessage(e));
|
||||
|
||||
const auto& t = cachedFileType(m_wsRealPath, !isFromArchive());
|
||||
if (t.isEmpty()) {
|
||||
m_fileType.fail();
|
||||
} else {
|
||||
m_fileType.set(QString::fromWCharArray(sfi.szTypeName));
|
||||
m_fileType.set(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -93,6 +93,7 @@ public:
|
||||
}
|
||||
|
||||
void sort(int column, Qt::SortOrder order, bool force);
|
||||
void makeSortingStale();
|
||||
|
||||
FileTreeItem* parent()
|
||||
{
|
||||
@@ -223,7 +224,7 @@ public:
|
||||
m_expanded = b;
|
||||
|
||||
if (m_expanded && m_sortingStale) {
|
||||
sort();
|
||||
queueSort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +315,7 @@ private:
|
||||
std::wstring dataRelativeParentPath, bool isDirectory, std::wstring file);
|
||||
|
||||
void getFileType() const;
|
||||
void sort();
|
||||
void queueSort();
|
||||
};
|
||||
|
||||
#endif // MODORGANIZER_FILETREEITEM_INCLUDED
|
||||
|
||||
+50
-17
@@ -196,13 +196,13 @@ void* makeInternalPointer(FileTreeItem* item)
|
||||
FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) :
|
||||
QAbstractItemModel(parent), m_core(core), m_enabled(true),
|
||||
m_root(FileTreeItem::createDirectory(this, nullptr, L"", L"")),
|
||||
m_flags(NoFlags), m_fullyLoaded(false)
|
||||
m_flags(NoFlags), m_fullyLoaded(false), m_sortingEnabled(true)
|
||||
{
|
||||
m_root->setExpanded(true);
|
||||
m_sortTimer.setSingleShot(true);
|
||||
|
||||
connect(&m_removeTimer, &QTimer::timeout, [&]{ removeItems(); });
|
||||
connect(&m_sortTimer, &QTimer::timeout, [&]{ sortItems(); });
|
||||
|
||||
connect(&m_iconPendingTimer, &QTimer::timeout, [&]{ updatePendingIcons(); });
|
||||
}
|
||||
|
||||
@@ -212,6 +212,7 @@ void FileTreeModel::refresh()
|
||||
|
||||
m_fullyLoaded = false;
|
||||
update(*m_root, *m_core.directoryStructure(), L"", false);
|
||||
sortItem(*m_root, false);
|
||||
}
|
||||
|
||||
void FileTreeModel::clear()
|
||||
@@ -226,7 +227,7 @@ void FileTreeModel::clear()
|
||||
void FileTreeModel::recursiveFetchMore(const QModelIndex& m)
|
||||
{
|
||||
if (canFetchMore(m)) {
|
||||
doFetchMore(m, false);
|
||||
doFetchMore(m, false, false);
|
||||
}
|
||||
|
||||
for (int i=0; i<rowCount(m); ++i) {
|
||||
@@ -239,6 +240,7 @@ void FileTreeModel::ensureFullyLoaded()
|
||||
if (!m_fullyLoaded) {
|
||||
TimeThis tt("FileTreeModel:: fully loading for search");
|
||||
recursiveFetchMore(QModelIndex());
|
||||
sortItem(*m_root, false);
|
||||
m_fullyLoaded = true;
|
||||
}
|
||||
}
|
||||
@@ -253,6 +255,17 @@ void FileTreeModel::setEnabled(bool b)
|
||||
m_enabled = b;
|
||||
}
|
||||
|
||||
void FileTreeModel::aboutToExpandAll()
|
||||
{
|
||||
m_sortingEnabled = false;
|
||||
}
|
||||
|
||||
void FileTreeModel::expandedAll()
|
||||
{
|
||||
m_sortingEnabled = true;
|
||||
sortItem(*m_root, false);
|
||||
}
|
||||
|
||||
const FileTreeModel::SortInfo& FileTreeModel::sortInfo() const
|
||||
{
|
||||
return m_sort;
|
||||
@@ -338,10 +351,11 @@ bool FileTreeModel::canFetchMore(const QModelIndex& parent) const
|
||||
|
||||
void FileTreeModel::fetchMore(const QModelIndex& parent)
|
||||
{
|
||||
doFetchMore(parent, true);
|
||||
doFetchMore(parent, true, true);
|
||||
}
|
||||
|
||||
void FileTreeModel::doFetchMore(const QModelIndex& parent, bool forFetch)
|
||||
void FileTreeModel::doFetchMore(
|
||||
const QModelIndex& parent, bool forFetch, bool doSort)
|
||||
{
|
||||
FileTreeItem* item = itemFromIndex(parent);
|
||||
if (!item) {
|
||||
@@ -360,6 +374,10 @@ void FileTreeModel::doFetchMore(const QModelIndex& parent, bool forFetch)
|
||||
|
||||
const auto parentPath = item->dataRelativeParentPath();
|
||||
update(*item, *parentEntry, parentPath.toStdWString(), forFetch);
|
||||
|
||||
if (!forFetch && doSort) {
|
||||
sortItem(*item, false);
|
||||
}
|
||||
}
|
||||
|
||||
QVariant FileTreeModel::data(const QModelIndex& index, int role) const
|
||||
@@ -485,7 +503,7 @@ void FileTreeModel::sort(int column, Qt::SortOrder order)
|
||||
m_sort.column = column;
|
||||
m_sort.order = order;
|
||||
|
||||
sortItem(*m_root, false);
|
||||
sortItem(*m_root, true);
|
||||
}
|
||||
|
||||
FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const
|
||||
@@ -558,11 +576,15 @@ void FileTreeModel::update(
|
||||
}
|
||||
|
||||
if (added) {
|
||||
parentItem.makeSortingStale();
|
||||
|
||||
// see comment at the top of this file
|
||||
if (forFetching)
|
||||
queueSortItem(&parentItem);
|
||||
else
|
||||
sortItem(parentItem, true);
|
||||
if (forFetching) {
|
||||
// don't pass a specific item, this will start a timer and re-sort the
|
||||
// whole tree, which is faster than potentially queuing every single
|
||||
// node if the whole tree is expanded
|
||||
queueSortItem(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -873,21 +895,32 @@ void FileTreeModel::removeItems()
|
||||
|
||||
void FileTreeModel::queueSortItem(FileTreeItem* item)
|
||||
{
|
||||
m_sortItems.push_back(item);
|
||||
if (!m_sortingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item) {
|
||||
m_sortItems.push_back(item);
|
||||
}
|
||||
|
||||
m_sortTimer.start(1);
|
||||
}
|
||||
|
||||
void FileTreeModel::sortItems()
|
||||
{
|
||||
// see comment at the top of this file
|
||||
trace(log::debug("sort item timer: sorting {} items", m_sortItems.size()));
|
||||
|
||||
auto copy = std::move(m_sortItems);
|
||||
m_sortItems.clear();
|
||||
m_sortTimer.stop();
|
||||
if (m_sortItems.empty()) {
|
||||
sortItem(*m_root, false);
|
||||
} else {
|
||||
log::debug("sort item timer: sorting {} items", m_sortItems.size());
|
||||
|
||||
for (auto&& f : copy) {
|
||||
sortItem(*f, true);
|
||||
auto items = std::move(m_sortItems);
|
||||
m_sortItems.clear();
|
||||
|
||||
for (auto* item : items) {
|
||||
sortItem(*item, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-3
@@ -61,6 +61,10 @@ public:
|
||||
bool enabled() const;
|
||||
void setEnabled(bool b);
|
||||
|
||||
void aboutToExpandAll();
|
||||
void expandedAll();
|
||||
|
||||
|
||||
const SortInfo& sortInfo() const;
|
||||
|
||||
QModelIndex index(int row, int col, const QModelIndex& parent={}) const override;
|
||||
@@ -77,6 +81,7 @@ public:
|
||||
|
||||
FileTreeItem* itemFromIndex(const QModelIndex& index) const;
|
||||
void sortItem(FileTreeItem& item, bool force);
|
||||
void queueSortItem(FileTreeItem* item);
|
||||
|
||||
private:
|
||||
class Range;
|
||||
@@ -92,11 +97,12 @@ private:
|
||||
mutable QTimer m_iconPendingTimer;
|
||||
SortInfo m_sort;
|
||||
bool m_fullyLoaded;
|
||||
bool m_sortingEnabled;
|
||||
|
||||
// see top of filetreemodel.cpp
|
||||
std::vector<FileTreeItem*> m_removeItems;
|
||||
QTimer m_removeTimer;
|
||||
std::vector<FileTreeItem*> m_sortItems;
|
||||
QTimer m_removeTimer;
|
||||
QTimer m_sortTimer;
|
||||
|
||||
|
||||
@@ -113,12 +119,11 @@ private:
|
||||
FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
|
||||
const std::wstring& parentPath, bool forFetching);
|
||||
|
||||
void doFetchMore(const QModelIndex& parent, bool forFetch);
|
||||
void doFetchMore(const QModelIndex& parent, bool forFetch, bool doSort);
|
||||
|
||||
void queueRemoveItem(FileTreeItem* item);
|
||||
void removeItems();
|
||||
|
||||
void queueSortItem(FileTreeItem* item);
|
||||
void sortItems();
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
#include "iconfetcher.h"
|
||||
#include "thread_utils.h"
|
||||
#include "shared/util.h"
|
||||
|
||||
void IconFetcher::Waiter::wait()
|
||||
@@ -25,7 +26,7 @@ IconFetcher::IconFetcher()
|
||||
m_quickCache.file = getPixmapIcon(QFileIconProvider::File);
|
||||
m_quickCache.directory = getPixmapIcon(QFileIconProvider::Folder);
|
||||
|
||||
m_thread = std::thread([&]{ threadFun(); });
|
||||
m_thread = MOShared::startSafeThread([&]{ threadFun(); });
|
||||
}
|
||||
|
||||
IconFetcher::~IconFetcher()
|
||||
|
||||
+26
-4
@@ -127,9 +127,9 @@ bool bootstrap()
|
||||
return true;
|
||||
}
|
||||
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER prevUnhandledExceptionFilter = nullptr;
|
||||
thread_local LPTOP_LEVEL_EXCEPTION_FILTER prevUnhandledExceptionFilter = nullptr;
|
||||
|
||||
static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs)
|
||||
LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs)
|
||||
{
|
||||
const std::wstring& dumpPath = OrganizerCore::crashDumpsPath();
|
||||
int dumpRes =
|
||||
@@ -139,12 +139,33 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except
|
||||
else
|
||||
log::error("ModOrganizer has crashed, CreateMiniDump failed ({}, error {}).", dumpRes, GetLastError());
|
||||
|
||||
if (prevUnhandledExceptionFilter)
|
||||
if (prevUnhandledExceptionFilter && exceptionPtrs)
|
||||
return prevUnhandledExceptionFilter(exceptionPtrs);
|
||||
else
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
void terminateHandler() noexcept
|
||||
{
|
||||
__try
|
||||
{
|
||||
// force an exception to get a valid stack trace for this thread
|
||||
*(int*)0 = 42;
|
||||
}
|
||||
__except
|
||||
(
|
||||
MyUnhandledExceptionFilter(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void setUnhandledExceptionHandler()
|
||||
{
|
||||
prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
|
||||
std::set_terminate(terminateHandler);
|
||||
}
|
||||
|
||||
// Parses the first parseArgCount arguments of the current process command line and returns
|
||||
// them in parsedArgs, the rest of the command line is returned untouched.
|
||||
LPCWSTR UntouchedCommandLineArguments(int parseArgCount, std::vector<std::wstring>& parsedArgs)
|
||||
@@ -171,6 +192,7 @@ LPCWSTR UntouchedCommandLineArguments(int parseArgCount, std::vector<std::wstrin
|
||||
return cmd;
|
||||
}
|
||||
|
||||
|
||||
static int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine) {
|
||||
PROCESS_INFORMATION pi{ 0 };
|
||||
STARTUPINFO si{ 0 };
|
||||
@@ -947,7 +969,7 @@ int main(int argc, char *argv[])
|
||||
application.setProperty("dataPath", dataPath);
|
||||
|
||||
// initialize dump collection only after "dataPath" since the crashes are stored under it
|
||||
prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
|
||||
setUnhandledExceptionHandler();
|
||||
|
||||
const auto logFile =
|
||||
qApp->property("dataPath").toString() + "/logs/mo_interface.log";
|
||||
|
||||
+42
-34
@@ -3309,6 +3309,29 @@ void MainWindow::visitWebPage_clicked()
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::visitNexusOrWebPage(const QModelIndex& idx)
|
||||
{
|
||||
int row_idx = idx.data(Qt::UserRole + 1).toInt();
|
||||
|
||||
ModInfo::Ptr info = ModInfo::getByIndex(row_idx);
|
||||
if (!info) {
|
||||
log::error("mod {} not found", row_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
int modID = info->getNexusID();
|
||||
QString gameName = info->getGameName();
|
||||
const auto url = info->parseCustomURL();
|
||||
|
||||
if (modID > 0) {
|
||||
linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName));
|
||||
} else if (url.isValid()) {
|
||||
linkClicked(url.toString());
|
||||
} else {
|
||||
log::error("mod '{}' has no valid link", info->name());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::visitNexusOrWebPage_clicked() {
|
||||
QItemSelectionModel* selection = ui->modList->selectionModel();
|
||||
if (selection->hasSelection() && selection->selectedRows().count() > 1) {
|
||||
@@ -3320,43 +3343,14 @@ void MainWindow::visitNexusOrWebPage_clicked() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
int row_idx;
|
||||
ModInfo::Ptr info;
|
||||
QString gameName;
|
||||
|
||||
for (QModelIndex idx : selection->selectedRows()) {
|
||||
row_idx = idx.data(Qt::UserRole + 1).toInt();
|
||||
info = ModInfo::getByIndex(row_idx);
|
||||
int modID = info->getNexusID();
|
||||
gameName = info->getGameName();
|
||||
const auto url = info->parseCustomURL();
|
||||
if (modID > 0) {
|
||||
linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName));
|
||||
}
|
||||
else if (url.isValid()) {
|
||||
linkClicked(url.toString());
|
||||
}
|
||||
else {
|
||||
log::error("mod '{}' has no valid link", info->name());
|
||||
}
|
||||
visitNexusOrWebPage(idx);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole).toInt();
|
||||
QString gameName = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole + 4).toString();
|
||||
if (modID > 0) {
|
||||
linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName));
|
||||
}
|
||||
else {
|
||||
ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
|
||||
const auto url = info->parseCustomURL();
|
||||
if (url.isValid()) {
|
||||
linkClicked(url.toString());
|
||||
}
|
||||
else {
|
||||
MessageDialog::showMessage(tr("No valid Web Page for this mod"), this);
|
||||
}
|
||||
}
|
||||
QModelIndex idx = m_OrganizerCore.modList()->index(m_ContextRow, 0);
|
||||
visitNexusOrWebPage(idx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3865,7 +3859,10 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index)
|
||||
if (modifiers.testFlag(Qt::ControlModifier)) {
|
||||
try {
|
||||
m_ContextRow = m_ModListSortProxy->mapToSource(index).row();
|
||||
openExplorer_clicked();
|
||||
|
||||
ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
|
||||
shell::Explore(modInfo->absolutePath());
|
||||
|
||||
// workaround to cancel the editor that might have opened because of
|
||||
// selection-click
|
||||
ui->modList->closePersistentEditor(index);
|
||||
@@ -3877,7 +3874,8 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index)
|
||||
else if (modifiers.testFlag(Qt::ShiftModifier)) {
|
||||
try {
|
||||
m_ContextRow = m_ModListSortProxy->mapToSource(index).row();
|
||||
visitNexusOrWebPage_clicked();
|
||||
QModelIndex idx = m_OrganizerCore.modList()->index(m_ContextRow, 0);
|
||||
visitNexusOrWebPage(idx);
|
||||
ui->modList->closePersistentEditor(index);
|
||||
}
|
||||
catch (const std::exception & e) {
|
||||
@@ -6220,6 +6218,16 @@ void MainWindow::on_showHiddenBox_toggled(bool checked)
|
||||
|
||||
void MainWindow::on_bossButton_clicked()
|
||||
{
|
||||
const auto r = QMessageBox::question(
|
||||
this, tr("Sorting plugins"),
|
||||
tr("Are you sure you want to sort your plugins list?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (r != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
m_OrganizerCore.savePluginList();
|
||||
|
||||
setEnabled(false);
|
||||
|
||||
+2
-1
@@ -251,7 +251,7 @@ private:
|
||||
void fixCategories();
|
||||
|
||||
bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName);
|
||||
|
||||
|
||||
// Performs checks, sets the m_NumberOfProblems and signals checkForProblemsDone().
|
||||
void checkForProblemsImpl();
|
||||
|
||||
@@ -498,6 +498,7 @@ private slots:
|
||||
ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep);
|
||||
|
||||
void displayModInformation(const QString &modName, ModInfoTabIDs tabID);
|
||||
void visitNexusOrWebPage(const QModelIndex& idx);
|
||||
|
||||
void modRenamed(const QString &oldName, const QString &newName);
|
||||
void modRemoved(const QString &fileName);
|
||||
|
||||
@@ -96,6 +96,7 @@ ConflictListModel::ConflictListModel(QTreeView* tree, std::vector<Column> column
|
||||
|
||||
void ConflictListModel::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
m_items.clear();
|
||||
endResetModel();
|
||||
}
|
||||
@@ -129,16 +130,39 @@ int ConflictListModel::columnCount(const QModelIndex&) const
|
||||
return static_cast<int>(m_columns.size());
|
||||
}
|
||||
|
||||
const ConflictItem* ConflictListModel::itemFromIndex(
|
||||
const QModelIndex& index) const
|
||||
{
|
||||
const auto row = index.row();
|
||||
if (row < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto i = static_cast<std::size_t>(row);
|
||||
if (i >= m_items.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &m_items[i];
|
||||
}
|
||||
|
||||
QModelIndex ConflictListModel::indexFromItem(
|
||||
const ConflictItem* item, int col)
|
||||
{
|
||||
for (std::size_t i=0; i<m_items.size(); ++i) {
|
||||
if (&m_items[i] == item) {
|
||||
return createIndex(static_cast<int>(i), col);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariant ConflictListModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (role == Qt::DisplayRole || role == Qt::FontRole) {
|
||||
const auto row = index.row();
|
||||
if (row < 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto i = static_cast<std::size_t>(row);
|
||||
if (i >= m_items.size()) {
|
||||
const ConflictItem* item = itemFromIndex(index);
|
||||
if (!item) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -152,12 +176,10 @@ QVariant ConflictListModel::data(const QModelIndex& index, int role) const
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& item = m_items[i];
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
return (item.*m_columns[c].getText)();
|
||||
return (item->*m_columns[c].getText)();
|
||||
} else if (role == Qt::FontRole) {
|
||||
if (item.isArchive()) {
|
||||
if (item->isArchive()) {
|
||||
QFont f = m_tree->font();
|
||||
f.setItalic(true);
|
||||
return f;
|
||||
@@ -191,7 +213,31 @@ void ConflictListModel::sort(int colIndex, Qt::SortOrder order)
|
||||
m_sortColumn = colIndex;
|
||||
m_sortOrder = order;
|
||||
|
||||
emit layoutAboutToBeChanged({}, QAbstractItemModel::VerticalSortHint);
|
||||
|
||||
const auto oldList = persistentIndexList();
|
||||
std::vector<std::pair<const ConflictItem*, int>> oldItems;
|
||||
|
||||
const auto itemCount = oldList.size();
|
||||
oldItems.reserve(static_cast<std::size_t>(itemCount));
|
||||
|
||||
for (int i=0; i<itemCount; ++i) {
|
||||
const QModelIndex& index = oldList[i];
|
||||
oldItems.push_back({itemFromIndex(index), index.column()});
|
||||
}
|
||||
|
||||
doSort();
|
||||
|
||||
QModelIndexList newList;
|
||||
newList.reserve(itemCount);
|
||||
|
||||
for (int i=0; i<itemCount; ++i) {
|
||||
const auto& pair = oldItems[static_cast<std::size_t>(i)];
|
||||
newList.append(indexFromItem(pair.first, pair.second));
|
||||
}
|
||||
|
||||
changePersistentIndexList(oldList, newList);
|
||||
|
||||
emit layoutChanged({}, QAbstractItemModel::VerticalSortHint);
|
||||
}
|
||||
|
||||
@@ -202,8 +248,10 @@ void ConflictListModel::add(ConflictItem item)
|
||||
|
||||
void ConflictListModel::finished()
|
||||
{
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
doSort();
|
||||
|
||||
sort(m_sortColumn, m_sortOrder);
|
||||
}
|
||||
|
||||
const ConflictItem* ConflictListModel::getItem(std::size_t row) const
|
||||
|
||||
@@ -78,6 +78,9 @@ private:
|
||||
int m_sortColumn;
|
||||
Qt::SortOrder m_sortOrder;
|
||||
|
||||
const ConflictItem* itemFromIndex(const QModelIndex& index) const;
|
||||
QModelIndex indexFromItem(const ConflictItem* item, int col);
|
||||
|
||||
void doSort();
|
||||
};
|
||||
|
||||
|
||||
+232
-227
File diff suppressed because it is too large
Load Diff
@@ -221,7 +221,7 @@ void OrganizerCore::updateExecutablesList()
|
||||
void OrganizerCore::updateModInfoFromDisc() {
|
||||
ModInfo::updateFromDisc(
|
||||
m_Settings.paths().mods(), &m_DirectoryStructure,
|
||||
m_PluginContainer, m_Settings.interface().displayForeign(),
|
||||
m_PluginContainer, m_Settings.interface().displayForeign(),
|
||||
m_Settings.refreshThreadCount(), managedGame());
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@ void OrganizerCore::downloadRequestedNXM(const QString &url)
|
||||
}
|
||||
}
|
||||
|
||||
void OrganizerCore::userInterfaceInitialized()
|
||||
void OrganizerCore::userInterfaceInitialized()
|
||||
{
|
||||
m_UserInterfaceInitialized(m_UserInterface->mainWindow());
|
||||
}
|
||||
@@ -1139,7 +1139,7 @@ void OrganizerCore::refreshModList(bool saveChanges)
|
||||
|
||||
ModInfo::updateFromDisc(
|
||||
m_Settings.paths().mods(), &m_DirectoryStructure,
|
||||
m_PluginContainer, m_Settings.interface().displayForeign(),
|
||||
m_PluginContainer, m_Settings.interface().displayForeign(),
|
||||
m_Settings.refreshThreadCount(), managedGame());
|
||||
|
||||
m_CurrentProfile->refreshModStatus();
|
||||
@@ -1473,7 +1473,7 @@ void OrganizerCore::directory_refreshed()
|
||||
m_StructureDeleter.join();
|
||||
}
|
||||
|
||||
m_StructureDeleter = std::thread([=]{
|
||||
m_StructureDeleter = MOShared::startSafeThread([=]{
|
||||
log::debug("structure deleter thread start");
|
||||
delete newStructure;
|
||||
log::debug("structure deleter thread done");
|
||||
|
||||
+22
-3
@@ -1,12 +1,31 @@
|
||||
#ifndef MO2_THREAD_UTILS_H
|
||||
#define MO2_THREAD_UTILS_H
|
||||
|
||||
#include <log.h>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
// in main.cpp
|
||||
void setUnhandledExceptionHandler();
|
||||
LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs);
|
||||
|
||||
|
||||
namespace MOShared {
|
||||
|
||||
// starts an std::thread with an unhandled exception handler for core dumps
|
||||
// and a top-level catch
|
||||
//
|
||||
template <class F>
|
||||
std::thread startSafeThread(F&& f)
|
||||
{
|
||||
return std::thread([f=std::forward<F>(f)] {
|
||||
setUnhandledExceptionHandler();
|
||||
f();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Class that can be used to perform thread-safe memoization.
|
||||
*
|
||||
@@ -26,7 +45,7 @@ struct MemoizedLocked {
|
||||
template <class Callable>
|
||||
MemoizedLocked(Callable &&callable, T value = {}) :
|
||||
m_Fn{ std::forward<Callable>(callable) }, m_Value{ std::move(value) } { }
|
||||
|
||||
|
||||
template <class... Args>
|
||||
T& value(Args&&... args) const {
|
||||
if (m_NeedUpdating) {
|
||||
@@ -66,7 +85,7 @@ private:
|
||||
*
|
||||
*/
|
||||
template <class It, class Callable>
|
||||
void parallelMap(It begin, It end, Callable callable, std::size_t nThreads)
|
||||
void parallelMap(It begin, It end, Callable callable, std::size_t nThreads)
|
||||
{
|
||||
std::mutex m;
|
||||
std::vector<std::thread> threads(nThreads);
|
||||
@@ -75,7 +94,7 @@ void parallelMap(It begin, It end, Callable callable, std::size_t nThreads)
|
||||
// - The mutex is only used to fetch/increment the iterator.
|
||||
// - The callable is copied in each thread to avoid conflicts.
|
||||
for (auto &thread: threads) {
|
||||
thread = std::thread([&m, &begin, end, callable]() {
|
||||
thread = startSafeThread([&m, &begin, end, callable]() {
|
||||
while (true) {
|
||||
decltype(begin) it;
|
||||
{
|
||||
|
||||
+3
-3
@@ -3,14 +3,14 @@
|
||||
// If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses VER_FILEVERSION to determine version number.
|
||||
// Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser
|
||||
// Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha
|
||||
#define VER_FILEVERSION 2,3,0
|
||||
#define VER_FILEVERSION_STR "2.3.0rc1\0"
|
||||
#define VER_FILEVERSION 2,3,1
|
||||
#define VER_FILEVERSION_STR "2.3.1\0"
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VER_FILEVERSION
|
||||
PRODUCTVERSION VER_FILEVERSION
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
FILEFLAGS VS_FF_PRERELEASE
|
||||
FILEFLAGS (0)
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE (0)
|
||||
|
||||
Reference in New Issue
Block a user