Compare commits

...
29 Commits
Author SHA1 Message Date
isanae 1412befe1b Merge pull request #1198 from isanae/2_3_0
Remove RC
2020-08-17 01:48:22 -04:00
isanae e6e88074ea removed rc 2020-08-17 01:42:11 -04:00
isanae 2388f2471b Merge pull request #1195 from isanae/2_3_0
Cherry pick: die after dump
2020-08-13 14:48:35 -04:00
isanae 2c6c223cb0 bumped to 2.3.2rc2 2020-08-13 14:47:35 -04:00
isanae f16738ffd6 always call the previous terminate handler, which should abort
without it, execution just resumes and dumps are created endlessly
2020-08-13 14:46:47 -04:00
isanae f2dc4ddd30 Merge pull request #1189 from isanae/2_3_0
switched to 2.3.2-rc1
2020-08-12 06:23:19 -04:00
isanae 322b5d59f0 switched to 2.3.2-rc1 2020-08-12 06:22:28 -04:00
isanae 06d356e48f Merge pull request #1188 from isanae/2_3_0
bumped to 2.3.2
2020-08-12 04:26:19 -04:00
isanae 8db55c451c bumped to 2.3.2 2020-08-12 04:24:48 -04:00
isanae 768e6291b3 Merge pull request #1183 from AnyOldName3/cherry-pick-NexusBridgeFix
Cherry pick NexusBridge fix
2020-08-10 18:34:24 -04:00
AnyOldName3 2cdb7c5b13 Don't use reference as class member
This particular QString was only ever constructed from a temporary,
so it was literally never going to work. We only hadn't noticed this
earlier as no one had tried using the mod repository bridge from a
plugin before (which is fairly bad given that three people have
theoretically tested it while working on its Python bindings in the
past).
2020-08-08 14:18:14 +01:00
isanae b9f9e6af94 Merge pull request #1178 from isanae/2_3_0
Merge 2.3.1 changes
2020-08-01 11:33:35 -04:00
isanae b9078ffefd bump to 2.3.1 2020-08-01 11:29:09 -04:00
isanae 04c5986aa6 added startSafeThread() to get core dumps for threads other than the main thread
added terminate handler
2020-08-01 11:29:09 -04:00
isanae 1cb18b3c00 fixed warning when trying to get the file type of files without extensions 2020-08-01 11:29:09 -04:00
isanae e34a9e7315 Merge pull request #1172 from isanae/remove-prerelease
Chang release type to 0
2020-07-28 20:15:42 -04:00
isanae 4d546ea262 changed release type to 0 2020-07-28 20:15:12 -04:00
isanae 2ca7c1c9ea Merge pull request #1171 from isanae/bump-2.3
Bump to 2.3
2020-07-28 18:35:38 -04:00
isanae 4b0ba5c579 bumped to 2.3 2020-07-28 18:33:55 -04:00
isanae 7a10a74766 Merge pull request #1162 from isanae/bump-2.3rc2
Bump to 2.3rc2
2020-07-21 19:08:56 -04:00
isanae 7db0841a22 bumped to 2.3rc2
updated translations
2020-07-21 19:07:26 -04:00
isanae 2c082d6a3b Merge pull request #1161 from isanae/2.3rc2-fixes
2.3rc2 fixes (wip)
2020-07-21 18:13:42 -04:00
isanae a965a9dc41 added confirmation for sorting, some users click it by mistake 2020-07-20 20:05:15 -04:00
isanae 82cc488a18 conflict model didn't change persistent indexes when sorting 2020-07-20 20:01:46 -04:00
isanae 4adfeac9b7 shift+ and ctrl+double-click in mod list will now only open one mod instead of the whole selection
since these modifiers are also used for selecting, this is too error prone and users have reported opening a crapload of tabs in their browser or explorer windows
2020-07-20 19:26:33 -04:00
isanae c7a90f9506 added turkish translator 2020-07-20 19:09:12 -04:00
isanae 80645bacc1 Merge pull request #1160 from isanae/filetree-bugs
Filetree bugs and improvements
2020-07-20 19:07:58 -04:00
isanae 0c7265be4f only sort once at the end when fully loading for search
temporarily disable filtering completely when fully loading instead of just disabling recursive filtering, this could close already expanded nodes if their parent directories didn't match
2020-07-20 19:02:14 -04:00
isanae 6c4e237d4b fixed crash because items were sorted while being expanded
when expanding all or updating the tree, only sort once at the end
cache file types
2020-07-19 16:48:31 -04:00
20 changed files with 610 additions and 373 deletions
+5
View File
@@ -395,6 +395,11 @@
<string>Nubbie (Swedish)</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">Hakan &quot;Nyks45&quot; Albayrak (Turkish)</string>
</property>
</item>
<item>
<property name="text">
<string>...and all other contributors!</string>
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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();
}
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+33 -4
View File
@@ -127,9 +127,10 @@ bool bootstrap()
return true;
}
LPTOP_LEVEL_EXCEPTION_FILTER prevUnhandledExceptionFilter = nullptr;
thread_local LPTOP_LEVEL_EXCEPTION_FILTER prevUnhandledExceptionFilter = nullptr;
thread_local std::terminate_handler prevTerminateHandler = 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 +140,39 @@ 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
)
{
}
if (prevTerminateHandler) {
prevTerminateHandler();
} else {
std::abort();
}
}
void setUnhandledExceptionHandler()
{
prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
prevTerminateHandler = 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 +199,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 +976,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
View File
@@ -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
View File
@@ -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);
+60 -12
View File
@@ -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
+3
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -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");
+1 -1
View File
@@ -65,7 +65,7 @@ private:
OrganizerCore *m_Proxied;
PluginContainer *m_PluginContainer;
const QString &m_PluginName;
QString m_PluginName;
};
+22 -3
View File
@@ -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
View File
@@ -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,2
#define VER_FILEVERSION_STR "2.3.2\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)