aboutsummaryrefslogtreecommitdiff
path: root/libs/uibase/include
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
commit7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch)
tree27fb39be241fdb5ac2734c574de678977d1856d0 /libs/uibase/include
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/uibase/include')
-rw-r--r--libs/uibase/include/uibase/delayedfilewriter.h74
-rw-r--r--libs/uibase/include/uibase/diagnosisreport.h51
-rw-r--r--libs/uibase/include/uibase/dllimport.h46
-rw-r--r--libs/uibase/include/uibase/errorcodes.h21
-rw-r--r--libs/uibase/include/uibase/eventfilter.h45
-rw-r--r--libs/uibase/include/uibase/exceptions.h58
-rw-r--r--libs/uibase/include/uibase/executableinfo.h67
-rw-r--r--libs/uibase/include/uibase/expanderwidget.h63
-rw-r--r--libs/uibase/include/uibase/filemapping.h17
-rw-r--r--libs/uibase/include/uibase/filesystemutilities.h36
-rw-r--r--libs/uibase/include/uibase/filterwidget.h148
-rw-r--r--libs/uibase/include/uibase/finddialog.h80
-rw-r--r--libs/uibase/include/uibase/formatters.h5
-rw-r--r--libs/uibase/include/uibase/formatters/enums.h16
-rw-r--r--libs/uibase/include/uibase/formatters/qt.h87
-rw-r--r--libs/uibase/include/uibase/formatters/strings.h89
-rw-r--r--libs/uibase/include/uibase/game_features/bsainvalidation.h26
-rw-r--r--libs/uibase/include/uibase/game_features/dataarchives.h35
-rw-r--r--libs/uibase/include/uibase/game_features/game_feature.h40
-rw-r--r--libs/uibase/include/uibase/game_features/gameplugins.h25
-rw-r--r--libs/uibase/include/uibase/game_features/igamefeatures.h166
-rw-r--r--libs/uibase/include/uibase/game_features/localsavegames.h21
-rw-r--r--libs/uibase/include/uibase/game_features/moddatachecker.h66
-rw-r--r--libs/uibase/include/uibase/game_features/moddatacontent.h122
-rw-r--r--libs/uibase/include/uibase/game_features/savegameinfo.h52
-rw-r--r--libs/uibase/include/uibase/game_features/scriptextender.h53
-rw-r--r--libs/uibase/include/uibase/game_features/unmanagedmods.h45
-rw-r--r--libs/uibase/include/uibase/guessedvalue.h252
-rw-r--r--libs/uibase/include/uibase/idownloadmanager.h102
-rw-r--r--libs/uibase/include/uibase/iexecutable.h84
-rw-r--r--libs/uibase/include/uibase/iexecutableslist.h75
-rw-r--r--libs/uibase/include/uibase/ifiletree.h1130
-rw-r--r--libs/uibase/include/uibase/ifiletree_utils.h82
-rw-r--r--libs/uibase/include/uibase/iinstallationmanager.h128
-rw-r--r--libs/uibase/include/uibase/iinstance.h61
-rw-r--r--libs/uibase/include/uibase/iinstancemanager.h66
-rw-r--r--libs/uibase/include/uibase/imodinterface.h343
-rw-r--r--libs/uibase/include/uibase/imodlist.h229
-rw-r--r--libs/uibase/include/uibase/imodrepositorybridge.h219
-rw-r--r--libs/uibase/include/uibase/imoinfo.h616
-rw-r--r--libs/uibase/include/uibase/iplugin.h146
-rw-r--r--libs/uibase/include/uibase/iplugindiagnose.h103
-rw-r--r--libs/uibase/include/uibase/ipluginfilemapper.h49
-rw-r--r--libs/uibase/include/uibase/iplugingame.h380
-rw-r--r--libs/uibase/include/uibase/iplugingamefeatures.h61
-rw-r--r--libs/uibase/include/uibase/iplugininstaller.h157
-rw-r--r--libs/uibase/include/uibase/iplugininstallercustom.h79
-rw-r--r--libs/uibase/include/uibase/iplugininstallersimple.h69
-rw-r--r--libs/uibase/include/uibase/ipluginlist.h268
-rw-r--r--libs/uibase/include/uibase/ipluginmodpage.h92
-rw-r--r--libs/uibase/include/uibase/ipluginpreview.h72
-rw-r--r--libs/uibase/include/uibase/ipluginproxy.h83
-rw-r--r--libs/uibase/include/uibase/iplugintool.h87
-rw-r--r--libs/uibase/include/uibase/iprofile.h41
-rw-r--r--libs/uibase/include/uibase/isavegame.h58
-rw-r--r--libs/uibase/include/uibase/isavegameinfowidget.h30
-rw-r--r--libs/uibase/include/uibase/json.h95
-rw-r--r--libs/uibase/include/uibase/lineeditclear.h40
-rw-r--r--libs/uibase/include/uibase/linklabel.h38
-rw-r--r--libs/uibase/include/uibase/log.h346
-rw-r--r--libs/uibase/include/uibase/memoizedlock.h61
-rw-r--r--libs/uibase/include/uibase/moassert.h36
-rw-r--r--libs/uibase/include/uibase/modrepositoryfileinfo.h56
-rw-r--r--libs/uibase/include/uibase/nxmurl.h87
-rw-r--r--libs/uibase/include/uibase/pluginrequirements.h193
-rw-r--r--libs/uibase/include/uibase/pluginsetting.h50
-rw-r--r--libs/uibase/include/uibase/questionboxmemory.h112
-rw-r--r--libs/uibase/include/uibase/registry.h39
-rw-r--r--libs/uibase/include/uibase/report.h117
-rw-r--r--libs/uibase/include/uibase/safewritefile.h72
-rw-r--r--libs/uibase/include/uibase/scopeguard.h296
-rw-r--r--libs/uibase/include/uibase/sortabletreewidget.h64
-rw-r--r--libs/uibase/include/uibase/steamutility.h47
-rw-r--r--libs/uibase/include/uibase/strings.h22
-rw-r--r--libs/uibase/include/uibase/taskprogressmanager.h60
-rw-r--r--libs/uibase/include/uibase/textviewer.h97
-rw-r--r--libs/uibase/include/uibase/tutorabledialog.h65
-rw-r--r--libs/uibase/include/uibase/tutorialcontrol.h83
-rw-r--r--libs/uibase/include/uibase/tutorialmanager.h109
-rw-r--r--libs/uibase/include/uibase/utility.h443
-rw-r--r--libs/uibase/include/uibase/versioninfo.h185
-rw-r--r--libs/uibase/include/uibase/versioning.h181
-rw-r--r--libs/uibase/include/uibase/widgetutility.h22
l---------libs/uibase/include/uibase/windows_compat.h1
84 files changed, 9733 insertions, 0 deletions
diff --git a/libs/uibase/include/uibase/delayedfilewriter.h b/libs/uibase/include/uibase/delayedfilewriter.h
new file mode 100644
index 0000000..ecdfd8d
--- /dev/null
+++ b/libs/uibase/include/uibase/delayedfilewriter.h
@@ -0,0 +1,74 @@
+#ifndef DELAYEDFILEWRITER_H
+#define DELAYEDFILEWRITER_H
+
+#include "dllimport.h"
+#include <QString>
+#include <QTimer>
+#include <functional>
+
+namespace MOBase
+{
+
+/**
+ * The purpose of this class is to aggregate changes to a file before writing it out
+ */
+class QDLLEXPORT DelayedFileWriterBase : public QObject
+{
+
+ Q_OBJECT
+
+public:
+ /**
+ * @brief constructor
+ * @param fileName
+ * @param delay delay (in milliseconds) before we call the actual write function
+ */
+ DelayedFileWriterBase(int delay = 200);
+ ~DelayedFileWriterBase();
+
+public slots:
+ /**
+ * @brief write with delay
+ */
+ void write();
+
+ /**
+ * @brief cancel a scheduled write (does nothing if no write is scheduled)
+ */
+ void cancel();
+
+ /**
+ * @brief write immediately without waiting for the timer to expire
+ * @param ifOnTimer only write if the timer is running
+ */
+ void writeImmediately(bool ifOnTimer);
+
+private slots:
+ void timerExpired();
+
+private:
+ virtual void doWrite() = 0;
+
+private:
+ int m_TimerDelay;
+ QTimer m_Timer;
+};
+
+class QDLLEXPORT DelayedFileWriter : public DelayedFileWriterBase
+{
+public:
+ typedef std::function<void()> WriterFunc;
+
+public:
+ DelayedFileWriter(WriterFunc func, int delay = 200);
+
+private:
+ void doWrite();
+
+private:
+ WriterFunc m_Func;
+};
+
+} // namespace MOBase
+
+#endif // DELAYEDFILEWRITER_H
diff --git a/libs/uibase/include/uibase/diagnosisreport.h b/libs/uibase/include/uibase/diagnosisreport.h
new file mode 100644
index 0000000..4b541ff
--- /dev/null
+++ b/libs/uibase/include/uibase/diagnosisreport.h
@@ -0,0 +1,51 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef DIAGNOSISREPORT_H
+#define DIAGNOSISREPORT_H
+
+#include <QString>
+
+namespace MOBase
+{
+
+/**
+ * @brief report for a single problem reported by a plugin
+ */
+struct ProblemReport
+{
+ QString key; // a plugin-defined unique key for the issue. This is used to refer to
+ // the problem
+ enum
+ {
+ SEVERITY_REPORT, // the issue should be reported but nothing more
+ SEVERITY_BREAKPLUGIN, // the issue breaks the plugin (the plugin has to disable
+ // itself)
+ SEVERITY_BREAKGAME // the issue will (likely) break the game. The user will be
+ // warned about this every time he tries to start
+ } severity;
+ bool guidedFix; // if true, the plugin provides a guide to fixing the issue
+ QString shortDescription; // short description text for the overview
+ QString longDescription; //
+};
+
+} // namespace MOBase
+
+#endif // DIAGNOSISREPORT_H
diff --git a/libs/uibase/include/uibase/dllimport.h b/libs/uibase/include/uibase/dllimport.h
new file mode 100644
index 0000000..577829d
--- /dev/null
+++ b/libs/uibase/include/uibase/dllimport.h
@@ -0,0 +1,46 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef DLLIMPORT_H
+#define DLLIMPORT_H
+
+namespace MOBase
+{
+
+#ifdef _WIN32
+ #if defined(UIBASE_EXPORT)
+ #define QDLLEXPORT __declspec(dllexport)
+ #elif defined(_NODLL)
+ #define QDLLEXPORT
+ #else
+ #undef DLLEXPORT
+ #define QDLLEXPORT __declspec(dllimport)
+ #endif
+#else
+ #if defined(UIBASE_EXPORT)
+ #define QDLLEXPORT __attribute__((visibility("default")))
+ #else
+ #define QDLLEXPORT
+ #endif
+#endif
+
+} // namespace MOBase
+
+#endif // DLLIMPORT_H
diff --git a/libs/uibase/include/uibase/errorcodes.h b/libs/uibase/include/uibase/errorcodes.h
new file mode 100644
index 0000000..e880ce0
--- /dev/null
+++ b/libs/uibase/include/uibase/errorcodes.h
@@ -0,0 +1,21 @@
+#ifndef UIBASE_ERRORCODES_H
+#define UIBASE_ERRORCODES_H
+
+#include "dllimport.h"
+#include <cstdint>
+
+#ifdef _WIN32
+#include <Windows.h>
+#else
+// POSIX: use uint32_t as DWORD equivalent
+using DWORD = uint32_t;
+#endif
+
+namespace MOBase
+{
+
+QDLLEXPORT const wchar_t* errorCodeName(DWORD code);
+
+} // namespace MOBase
+
+#endif // UIBASE_ERRORCODES_H
diff --git a/libs/uibase/include/uibase/eventfilter.h b/libs/uibase/include/uibase/eventfilter.h
new file mode 100644
index 0000000..3878a50
--- /dev/null
+++ b/libs/uibase/include/uibase/eventfilter.h
@@ -0,0 +1,45 @@
+/*
+Copyright (C) 2016 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include "dllimport.h"
+#include <QObject>
+#include <functional>
+
+namespace MOBase
+{
+
+class QDLLEXPORT EventFilter : public QObject
+{
+
+ Q_OBJECT
+
+ typedef std::function<bool(QObject*, QEvent*)> HandlerFunc;
+
+public:
+ EventFilter(QObject* parent, const HandlerFunc& handler);
+
+ virtual bool eventFilter(QObject* obj, QEvent* event) override;
+
+private:
+ HandlerFunc m_Handler;
+};
+
+} // namespace MOBase
diff --git a/libs/uibase/include/uibase/exceptions.h b/libs/uibase/include/uibase/exceptions.h
new file mode 100644
index 0000000..58b0d34
--- /dev/null
+++ b/libs/uibase/include/uibase/exceptions.h
@@ -0,0 +1,58 @@
+#ifndef UIBASE_EXCEPTIONS_H
+#define UIBASE_EXCEPTIONS_H
+
+#include <stdexcept>
+
+#include <QObject>
+#include <QString>
+
+#include "dllimport.h"
+
+namespace MOBase
+{
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4275) // non-dll interface
+#endif
+
+/**
+ * @brief exception class that takes a QString as the parameter
+ **/
+class QDLLEXPORT Exception : public std::exception
+{
+public:
+ Exception(const QString& text) : m_Message(text.toUtf8()) {}
+
+ virtual const char* what() const noexcept override { return m_Message.constData(); }
+
+private:
+ QByteArray m_Message;
+};
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+// Exception thrown in case of incompatibilities, i.e. between plugins.
+class QDLLEXPORT IncompatibilityException : public Exception
+{
+public:
+ using Exception::Exception;
+};
+
+// Exception thrown for invalid NXM links.
+class QDLLEXPORT InvalidNXMLinkException : public Exception
+{
+public:
+ InvalidNXMLinkException(const QString& link)
+ : Exception(QObject::tr("invalid nxm-link: %1").arg(link))
+ {}
+};
+
+// alias for backward-compatibility, should be removed when possible
+using MyException = Exception;
+
+} // namespace MOBase
+
+#endif
diff --git a/libs/uibase/include/uibase/executableinfo.h b/libs/uibase/include/uibase/executableinfo.h
new file mode 100644
index 0000000..1fe52b3
--- /dev/null
+++ b/libs/uibase/include/uibase/executableinfo.h
@@ -0,0 +1,67 @@
+#ifndef EXECUTABLEINFO_H
+#define EXECUTABLEINFO_H
+
+#include "dllimport.h"
+#include <QDir>
+#include <QFileInfo>
+#include <QList>
+#include <QString>
+
+namespace MOBase
+{
+
+class QDLLEXPORT ExecutableForcedLoadSetting
+{
+public:
+ ExecutableForcedLoadSetting(const QString& process, const QString& library);
+
+ ExecutableForcedLoadSetting& withForced(bool forced = true);
+
+ ExecutableForcedLoadSetting& withEnabled(bool enabled = true);
+
+ bool enabled() const;
+ bool forced() const;
+ QString library() const;
+ QString process() const;
+
+private:
+ bool m_Enabled;
+ QString m_Process;
+ QString m_Library;
+ bool m_Forced;
+};
+
+class QDLLEXPORT ExecutableInfo
+{
+public:
+ ExecutableInfo(const QString& title, const QFileInfo& binary);
+
+ ExecutableInfo& withArgument(const QString& argument);
+
+ ExecutableInfo& withWorkingDirectory(const QDir& workingDirectory);
+
+ ExecutableInfo& withSteamAppId(const QString& appId);
+
+ ExecutableInfo& asCustom();
+
+ bool isValid() const;
+
+ QString title() const;
+ QFileInfo binary() const;
+ QStringList arguments() const;
+ QDir workingDirectory() const;
+ QString steamAppID() const;
+ bool isCustom() const;
+
+private:
+ QString m_Title;
+ QFileInfo m_Binary;
+ QStringList m_Arguments;
+ QDir m_WorkingDirectory;
+ QString m_SteamAppID;
+ bool m_Custom{false};
+};
+
+} // namespace MOBase
+
+#endif // EXECUTABLEINFO_H
diff --git a/libs/uibase/include/uibase/expanderwidget.h b/libs/uibase/include/uibase/expanderwidget.h
new file mode 100644
index 0000000..0a5b07f
--- /dev/null
+++ b/libs/uibase/include/uibase/expanderwidget.h
@@ -0,0 +1,63 @@
+#ifndef EXPANDERWIDGET_H
+#define EXPANDERWIDGET_H
+
+#include "dllimport.h"
+#include <QToolButton>
+
+namespace MOBase
+{
+
+/* Takes a QToolButton and a widget and creates an expandable widget.
+ **/
+class QDLLEXPORT ExpanderWidget : public QObject
+{
+ Q_OBJECT;
+
+public:
+ /** empty expander, use set()
+ **/
+ ExpanderWidget();
+
+ /** see set()
+ **/
+ ExpanderWidget(QToolButton* button, QWidget* content);
+
+ /** @brief sets the button and content widgets to use
+ * the button will be given an arrow icon, clicking it will toggle the
+ * visibility of the given widget
+ * @param button the button that toggles the content
+ * @param content the widget that will be shown or hidden
+ * @param opened initial state, defaults to closed
+ **/
+ void set(QToolButton* button, QWidget* content, bool opened = false);
+
+ /** either opens or closes the expander depending on the current state
+ **/
+ void toggle();
+
+ /** sets the current state of the expander
+ **/
+ void toggle(bool b);
+
+ /** returns whether the expander is currently opened
+ **/
+ bool opened() const;
+
+ QByteArray saveState() const;
+ void restoreState(const QByteArray& a);
+
+ QToolButton* button() const;
+
+signals:
+ void aboutToToggle(bool b);
+ void toggled(bool b);
+
+private:
+ QToolButton* m_button;
+ QWidget* m_content;
+ bool opened_;
+};
+
+} // namespace MOBase
+
+#endif // EXPANDERWIDGET_H
diff --git a/libs/uibase/include/uibase/filemapping.h b/libs/uibase/include/uibase/filemapping.h
new file mode 100644
index 0000000..2498c10
--- /dev/null
+++ b/libs/uibase/include/uibase/filemapping.h
@@ -0,0 +1,17 @@
+#ifndef FILEMAPPING_H
+#define FILEMAPPING_H
+
+#include <QString>
+#include <vector>
+
+struct Mapping
+{
+ QString source;
+ QString destination;
+ bool isDirectory;
+ bool createTarget;
+};
+
+typedef std::vector<Mapping> MappingType;
+
+#endif // FILEMAPPING_H
diff --git a/libs/uibase/include/uibase/filesystemutilities.h b/libs/uibase/include/uibase/filesystemutilities.h
new file mode 100644
index 0000000..ae074c6
--- /dev/null
+++ b/libs/uibase/include/uibase/filesystemutilities.h
@@ -0,0 +1,36 @@
+#ifndef FILESYSTEMUTILIITES_H
+#define FILESYSTEMUTILITIES_H
+
+#include <QString>
+
+#include "dllimport.h"
+
+namespace MOBase
+{
+/**
+ * @brief fix a directory name so it can be dealt with by windows explorer
+ * @return false if there was no way to convert the name into a valid one
+ **/
+QDLLEXPORT bool fixDirectoryName(QString& name);
+
+/**
+ * @brief ensures a file name is valid
+ *
+ * @param name the file name being sanitized
+ * @param replacement invalid characters are replaced with this string
+ * @return the sanitized file name
+ **/
+QDLLEXPORT QString sanitizeFileName(const QString& name,
+ const QString& replacement = QString(""));
+
+/**
+ * @brief checks file name validity per sanitizeFileName
+ *
+ * @param name the file name being checked
+ * @return true if the given file name is valid
+ **/
+QDLLEXPORT bool validFileName(const QString& name);
+
+} // namespace MOBase
+
+#endif // FILESYSTEM_H
diff --git a/libs/uibase/include/uibase/filterwidget.h b/libs/uibase/include/uibase/filterwidget.h
new file mode 100644
index 0000000..b6f09cd
--- /dev/null
+++ b/libs/uibase/include/uibase/filterwidget.h
@@ -0,0 +1,148 @@
+#ifndef FILTERWIDGET_H
+#define FILTERWIDGET_H
+
+#include "dllimport.h"
+#include <QAbstractItemView>
+#include <QLineEdit>
+#include <QList>
+#include <QObject>
+#include <QRegularExpression>
+#include <QShortcut>
+#include <QSortFilterProxyModel>
+#include <QToolButton>
+
+namespace MOBase
+{
+
+class EventFilter;
+class FilterWidget;
+
+class QDLLEXPORT FilterWidgetProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT;
+
+public:
+ FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent = nullptr);
+ void refreshFilter() {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
+ beginFilterChange();
+#endif
+ invalidateFilter();
+ }
+
+protected:
+ bool filterAcceptsRow(int row, const QModelIndex& parent) const override;
+ void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
+ bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
+
+private:
+ FilterWidget& m_filter;
+
+ bool columnMatches(int sourceRow, const QModelIndex& sourceParent, int c,
+ const QRegularExpression& what) const;
+};
+
+class QDLLEXPORT FilterWidget : public QObject
+{
+ Q_OBJECT;
+
+public:
+ struct Options
+ {
+ bool useRegex = false;
+ bool regexCaseSensitive = false;
+ bool regexExtended = false;
+ bool scrollToSelection = false;
+ };
+
+ using predFun = std::function<bool(const QRegularExpression& what)>;
+ using sortFun = std::function<bool(const QModelIndex&, const QModelIndex&)>;
+
+ FilterWidget();
+
+ static void setOptions(const Options& o);
+ static Options options();
+
+ void setEdit(QLineEdit* edit);
+ void setList(QAbstractItemView* list);
+ void clear();
+ void scrollToSelection();
+ bool empty() const;
+
+ void setUpdateDelay(bool b);
+ bool hasUpdateDelay() const;
+
+ void setUseSourceSort(bool b);
+ bool useSourceSort() const;
+
+ void setSortPredicate(sortFun f);
+ const sortFun& sortPredicate() const;
+
+ void setFilterColumn(int i);
+ int filterColumn() const;
+
+ void setFilteringEnabled(bool b);
+ bool filteringEnabled() const;
+
+ void setFilteredBorder(bool b);
+ bool filteredBorder() const;
+
+ FilterWidgetProxyModel* proxyModel();
+ QAbstractItemModel* sourceModel();
+
+ QModelIndex mapFromSource(const QModelIndex& index) const;
+ QModelIndex mapToSource(const QModelIndex& index) const;
+ QItemSelection mapSelectionFromSource(const QItemSelection& sel) const;
+ QItemSelection mapSelectionToSource(const QItemSelection& sel) const;
+
+ bool matches(predFun pred) const;
+ bool matches(const QString& s) const;
+
+signals:
+ void aboutToChange(const QString& oldFilter, const QString& newFilter);
+ void changed(const QString& oldFilter, const QString& newFilter);
+
+private:
+ using Compiled = QList<QList<QRegularExpression>>;
+
+ QLineEdit* m_edit;
+ QAbstractItemView* m_list;
+ FilterWidgetProxyModel* m_proxy;
+ EventFilter* m_eventFilter;
+ QToolButton* m_clear;
+ QString m_text;
+ Compiled m_compiled;
+ QTimer* m_timer;
+ std::vector<QShortcut*> m_shortcuts;
+ bool m_useDelay;
+ bool m_valid;
+ bool m_useSourceSort;
+ sortFun m_lt;
+ int m_filterColumn;
+ bool m_filteringEnabled;
+ bool m_filteredBorder;
+
+ void hookEdit();
+ void unhookEdit();
+
+ void hookList();
+ void setShortcuts();
+ void unhookList();
+
+ void createClear();
+ void repositionClearButton();
+
+ void onTextChanged();
+ void onFind();
+ void onReset();
+ void onResized();
+ void onContextMenu(QObject*, QContextMenuEvent* e);
+
+ void set();
+ void update();
+ void compile();
+};
+
+} // namespace MOBase
+
+#endif // FILTERWIDGET_H
diff --git a/libs/uibase/include/uibase/finddialog.h b/libs/uibase/include/uibase/finddialog.h
new file mode 100644
index 0000000..77a8ac3
--- /dev/null
+++ b/libs/uibase/include/uibase/finddialog.h
@@ -0,0 +1,80 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef FINDDIALOG_H
+#define FINDDIALOG_H
+
+#include <QDialog>
+
+namespace Ui
+{
+class FindDialog;
+}
+
+namespace MOBase
+{
+
+/**
+ * @brief Find dialog used in the TextView dialog
+ **/
+class FindDialog : public QDialog
+{
+
+ Q_OBJECT
+
+public:
+ /**
+ * @brief constructor
+ *
+ * @param parent parent widget
+ **/
+ explicit FindDialog(QWidget* parent = 0);
+
+ ~FindDialog();
+
+signals:
+
+ /**
+ * @brief emitted when the user wants to jump to the next location matching the
+ *pattern
+ **/
+ void findNext();
+
+ /**
+ * @brief emitted when the user changes the pattern to search for
+ *
+ * @param pattern the new search pattern
+ **/
+ void patternChanged(const QString& pattern);
+
+private slots:
+ void on_nextBtn_clicked();
+
+ void on_patternEdit_textChanged(const QString& arg1);
+
+ void on_closeBtn_clicked();
+
+private:
+ Ui::FindDialog* ui;
+};
+
+} // namespace MOBase
+
+#endif // FINDDIALOG_H
diff --git a/libs/uibase/include/uibase/formatters.h b/libs/uibase/include/uibase/formatters.h
new file mode 100644
index 0000000..e33aa86
--- /dev/null
+++ b/libs/uibase/include/uibase/formatters.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "./formatters/enums.h"
+#include "./formatters/qt.h"
+#include "./formatters/strings.h"
diff --git a/libs/uibase/include/uibase/formatters/enums.h b/libs/uibase/include/uibase/formatters/enums.h
new file mode 100644
index 0000000..6538140
--- /dev/null
+++ b/libs/uibase/include/uibase/formatters/enums.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <format>
+#include <type_traits>
+
+template <class Enum, class CharT>
+ requires std::is_enum_v<Enum>
+struct std::formatter<Enum, CharT> : std::formatter<std::underlying_type_t<Enum>, CharT>
+{
+ template <class FmtContext>
+ FmtContext::iterator format(Enum v, FmtContext& ctx) const
+ {
+ return std::formatter<std::underlying_type_t<Enum>, CharT>::format(
+ static_cast<std::underlying_type_t<Enum>>(v), ctx);
+ }
+};
diff --git a/libs/uibase/include/uibase/formatters/qt.h b/libs/uibase/include/uibase/formatters/qt.h
new file mode 100644
index 0000000..de64aa8
--- /dev/null
+++ b/libs/uibase/include/uibase/formatters/qt.h
@@ -0,0 +1,87 @@
+#pragma once
+
+#include <format>
+
+#include <QColor>
+#include <QFlag>
+#include <QFlags>
+#include <QRect>
+#include <QSize>
+#include <QString>
+#include <QVariant>
+
+template <class CharT>
+struct std::formatter<QSize, CharT> : std::formatter<std::basic_string<CharT>, CharT>
+{
+ template <class FmtContext>
+ FmtContext::iterator format(QSize s, FmtContext& ctx) const
+ {
+ return std::format_to(ctx.out(), "QSize({}, {})", s.width(), s.height());
+ }
+};
+
+template <class CharT>
+struct std::formatter<QRect, CharT> : std::formatter<std::basic_string<CharT>, CharT>
+{
+ template <class FmtContext>
+ FmtContext::iterator format(QRect r, FmtContext& ctx) const
+ {
+ return std::format_to(ctx.out(), "QRect({},{}-{},{})", r.left(), r.top(), r.right(),
+ r.bottom());
+ }
+};
+
+template <class CharT>
+struct std::formatter<QColor, CharT> : std::formatter<std::basic_string<CharT>, CharT>
+{
+ template <class FmtContext>
+ FmtContext::iterator format(QColor c, FmtContext& ctx) const
+ {
+ return std::format_to(ctx.out(), "QColor({}, {}, {}, {})", c.red(), c.green(),
+ c.blue(), c.alpha());
+ }
+};
+
+template <class CharT>
+struct std::formatter<QByteArray, CharT>
+ : std::formatter<std::basic_string<CharT>, CharT>
+{
+ template <class FmtContext>
+ FmtContext::iterator format(QByteArray v, FmtContext& ctx) const
+ {
+ return std::format_to(ctx.out(), "QByteArray({} bytes)", v.size());
+ }
+};
+
+template <class CharT>
+struct std::formatter<QVariant, CharT> : std::formatter<std::basic_string<CharT>, CharT>
+{
+ template <class FmtContext>
+ FmtContext::iterator format(QVariant v, FmtContext& ctx) const
+ {
+ return std::format_to(
+ ctx.out(), "QVariant(type={}, value={})", v.typeName(),
+ (v.typeId() == QMetaType::Type::QByteArray ? "(binary)" : v.toString()));
+ }
+};
+
+template <class CharT>
+struct std::formatter<QFlag, CharT> : std::formatter<int, CharT>
+{
+ template <class FmtContext>
+ FmtContext::iterator format(QFlag v, FmtContext& ctx) const
+ {
+ return std::formatter<int, CharT>::format(static_cast<int>(v), ctx);
+ }
+};
+
+template <class T, class CharT>
+struct std::formatter<QFlags<T>, CharT> : std::formatter<int, CharT>
+{
+ template <class FmtContext>
+ FmtContext::iterator format(QFlags<T> v, FmtContext& ctx) const
+ {
+ // TODO: display flags has aa | bb | cc?
+ return std::formatter<int, CharT>::format(v.toInt(), ctx);
+ }
+};
diff --git a/libs/uibase/include/uibase/formatters/strings.h b/libs/uibase/include/uibase/formatters/strings.h
new file mode 100644
index 0000000..bc31ef9
--- /dev/null
+++ b/libs/uibase/include/uibase/formatters/strings.h
@@ -0,0 +1,89 @@
+
+#pragma once
+
+#include <format>
+#include <string>
+#include <string_view>
+
+#include <QString>
+#include <QStringView>
+
+namespace MOBase::details
+{
+template <class CharT>
+inline std::basic_string<CharT> toStdBasicString(QString const& qstring);
+
+template <>
+inline std::basic_string<char> toStdBasicString(QString const& qstring)
+{
+ return qstring.toStdString();
+}
+template <>
+inline std::basic_string<wchar_t> toStdBasicString(QString const& qstring)
+{
+ return qstring.toStdWString();
+}
+template <>
+inline std::basic_string<char16_t> toStdBasicString(QString const& qstring)
+{
+ return qstring.toStdU16String();
+}
+template <>
+inline std::basic_string<char32_t> toStdBasicString(QString const& qstring)
+{
+ return qstring.toStdU32String();
+}
+
+inline QString fromStdBasicString(std::string const& value)
+{
+ return QString::fromStdString(value);
+}
+inline QString fromStdBasicString(std::wstring const& value)
+{
+ return QString::fromStdWString(value);
+}
+inline QString fromStdBasicString(std::u16string const& value)
+{
+ return QString::fromStdU16String(value);
+}
+inline QString fromStdBasicString(std::u32string const& value)
+{
+ return QString::fromStdU32String(value);
+}
+} // namespace MOBase::details
+
+template <class CharT>
+struct std::formatter<QString, CharT> : std::formatter<std::basic_string<CharT>, CharT>
+{
+ template <class FmtContext>
+ FmtContext::iterator format(QString s, FmtContext& ctx) const
+ {
+ return std::formatter<std::basic_string<CharT>, CharT>::format(
+ MOBase::details::toStdBasicString<CharT>(s), ctx);
+ }
+};
+
+template <class CharT1, class CharT2>
+ requires(!std::is_same_v<CharT1, CharT2>)
+struct std::formatter<std::basic_string<CharT1>, CharT2>
+ : std::formatter<std::basic_string<CharT2>, CharT2>
+{
+ template <class FmtContext>
+ FmtContext::iterator format(std::basic_string<CharT1> s, FmtContext& ctx) const
+ {
+ return std::formatter<std::basic_string<CharT2>, CharT2>::format(
+ MOBase::details::toStdBasicString<CharT2>(
+ MOBase::details::fromStdBasicString(s)),
+ ctx);
+ }
+};
+
+template <class CharT>
+struct std::formatter<QStringView, CharT> : std::formatter<QString, CharT>
+{
+ template <class FmtContext>
+ FmtContext::iterator format(QStringView s, FmtContext& ctx) const
+ {
+ return std::formatter<QString, CharT>::format(s.toString(), ctx);
+ }
+};
diff --git a/libs/uibase/include/uibase/game_features/bsainvalidation.h b/libs/uibase/include/uibase/game_features/bsainvalidation.h
new file mode 100644
index 0000000..926753d
--- /dev/null
+++ b/libs/uibase/include/uibase/game_features/bsainvalidation.h
@@ -0,0 +1,26 @@
+#ifndef UIBASE_GAMEFEATURES_BSAINVALIDATION_H
+#define UIBASE_GAMEFEATURES_BSAINVALIDATION_H
+
+#include <QString>
+
+#include "./game_feature.h"
+
+namespace MOBase
+{
+class IProfile;
+
+class BSAInvalidation : public details::GameFeatureCRTP<BSAInvalidation>
+{
+public:
+ virtual bool isInvalidationBSA(const QString& bsaName) = 0;
+
+ virtual void deactivate(MOBase::IProfile* profile) = 0;
+
+ virtual void activate(MOBase::IProfile* profile) = 0;
+
+ virtual bool prepareProfile(MOBase::IProfile* profile) = 0;
+};
+
+} // namespace MOBase
+
+#endif // BSAINVALIDATION_H
diff --git a/libs/uibase/include/uibase/game_features/dataarchives.h b/libs/uibase/include/uibase/game_features/dataarchives.h
new file mode 100644
index 0000000..65c1419
--- /dev/null
+++ b/libs/uibase/include/uibase/game_features/dataarchives.h
@@ -0,0 +1,35 @@
+#ifndef UIBASE_GAMEFEATURES_DATAARCHIVES_H
+#define UIBASE_GAMEFEATURES_DATAARCHIVES_H
+
+#include <QString>
+#include <QStringList>
+
+#include "./game_feature.h"
+
+namespace MOBase
+{
+class IProfile;
+
+class DataArchives : public details::GameFeatureCRTP<DataArchives>
+{
+public:
+ virtual QStringList vanillaArchives() const = 0;
+
+ virtual QStringList archives(const MOBase::IProfile* profile) const = 0;
+
+ /**
+ * @brief add an archive to the archive list
+ * @param profile the profile for which to change the archive list
+ * @param index index to insert before. 0 is the beginning of the list, INT_MAX can be
+ * used for the end of the list
+ * @param archiveName the archive to add
+ */
+ virtual void addArchive(MOBase::IProfile* profile, int index,
+ const QString& archiveName) = 0;
+
+ virtual void removeArchive(MOBase::IProfile* profile, const QString& archiveName) = 0;
+};
+
+} // namespace MOBase
+
+#endif // DATAARCHIVES
diff --git a/libs/uibase/include/uibase/game_features/game_feature.h b/libs/uibase/include/uibase/game_features/game_feature.h
new file mode 100644
index 0000000..6e0dd4e
--- /dev/null
+++ b/libs/uibase/include/uibase/game_features/game_feature.h
@@ -0,0 +1,40 @@
+#ifndef UIBASE_GAMEFEATURES_GAMEFEATURE_H
+#define UIBASE_GAMEFEATURES_GAMEFEATURE_H
+
+#include <typeindex>
+
+namespace MOBase
+{
+
+/**
+ * Empty class that is inherit by all game features.
+ */
+class GameFeature
+{
+public:
+ GameFeature() = default;
+ virtual ~GameFeature() = 0;
+
+ /**
+ * @brief Retrieve the type index of the main game feature this feature extends.
+ */
+ virtual const std::type_info& typeInfo() const = 0;
+};
+
+// Pure virtual destructor must still have a definition
+inline GameFeature::~GameFeature() {}
+
+namespace details
+{
+
+ template <class T>
+ class GameFeatureCRTP : public GameFeature
+ {
+ const std::type_info& typeInfo() const final { return typeid(T); }
+ };
+
+} // namespace details
+
+} // namespace MOBase
+
+#endif
diff --git a/libs/uibase/include/uibase/game_features/gameplugins.h b/libs/uibase/include/uibase/game_features/gameplugins.h
new file mode 100644
index 0000000..3406d91
--- /dev/null
+++ b/libs/uibase/include/uibase/game_features/gameplugins.h
@@ -0,0 +1,25 @@
+#ifndef UIBASE_GAMEFEATURES_GAMEPLUGINS_H
+#define UIBASE_GAMEFEATURES_GAMEPLUGINS_H
+
+#include <QStringList>
+
+#include "./game_feature.h"
+
+namespace MOBase
+{
+class IPluginList;
+
+class GamePlugins : public details::GameFeatureCRTP<GamePlugins>
+{
+public:
+ virtual void writePluginLists(const MOBase::IPluginList* pluginList) = 0;
+ virtual void readPluginLists(MOBase::IPluginList* pluginList) = 0;
+ virtual QStringList getLoadOrder() = 0;
+ virtual bool lightPluginsAreSupported() { return false; }
+ virtual bool mediumPluginsAreSupported() { return false; }
+ virtual bool blueprintPluginsAreSupported() { return false; }
+};
+
+} // namespace MOBase
+
+#endif // GAMEPLUGINS_H
diff --git a/libs/uibase/include/uibase/game_features/igamefeatures.h b/libs/uibase/include/uibase/game_features/igamefeatures.h
new file mode 100644
index 0000000..2d89b3c
--- /dev/null
+++ b/libs/uibase/include/uibase/game_features/igamefeatures.h
@@ -0,0 +1,166 @@
+#ifndef UIBASE_GAMEFEATURES_IGAMEFEATURES_H
+#define UIBASE_GAMEFEATURES_IGAMEFEATURES_H
+
+#include <memory>
+#include <tuple>
+
+#include <QStringList>
+
+#include "game_feature.h"
+
+namespace MOBase
+{
+
+class IPluginGame;
+
+// top-level game features
+class BSAInvalidation;
+class DataArchives;
+class GamePlugins;
+class LocalSavegames;
+class ModDataChecker;
+class ModDataContent;
+class SaveGameInfo;
+class ScriptExtender;
+class UnmanagedMods;
+
+namespace details
+{
+
+ // use pointers in the tuple since are only forward-declaring the features here
+ using BaseGameFeaturesP =
+ std::tuple<BSAInvalidation*, DataArchives*, GamePlugins*, LocalSavegames*,
+ ModDataChecker*, ModDataContent*, SaveGameInfo*, ScriptExtender*,
+ UnmanagedMods*>;
+
+} // namespace details
+
+// simple concept that only restricting template function that should take a game
+// feature to actually viable game feature types
+//
+template <class T>
+concept BaseGameFeature = requires(T) {
+ {
+ std::get<T*>(std::declval<details::BaseGameFeaturesP>())
+ } -> std::convertible_to<T*>;
+};
+
+/**
+ * @brief Interface to game features.
+ *
+ */
+class IGameFeatures
+{
+public:
+ /**
+ * @brief Register game feature for the specified game.
+ *
+ * This method register a game feature to combine or replace the same feature provided
+ * by the game. Some features are merged (e.g., ModDataContent, ModDataChecker), while
+ * other override previous features (e.g., SaveGameInfo).
+ *
+ * For features that can be combined, the priority argument indicates the order of
+ * priority (e.g., the order of the checks for ModDataChecker). For other features,
+ * the feature with the highest priority will be used. The features provided by the
+ * game plugin itself always have lowest priority.
+ *
+ * The feature is associated to the plugin that registers it, if the plugin is
+ * disabled, the feature will not be available.
+ *
+ * This function will return True if the feature was registered, even if the feature
+ * is not used du to its low priority.
+ *
+ * @param games Names of the game to enable the feature for.
+ * @param feature Game feature to register.
+ * @param priority Priority of the game feature. If the plugin registering the feature
+ * is a game plugin, this parameter is ignored.
+ * @param replace If true, remove features of the same kind registered by the current
+ * plugin, otherwise add the feature alongside existing ones.
+ *
+ * @return true if the game feature was properly registered, false otherwise.
+ */
+ virtual bool registerFeature(QStringList const& games,
+ std::shared_ptr<GameFeature> feature, int priority,
+ bool replace = false) = 0;
+
+ /**
+ * @brief Register game feature for the specified game.
+ *
+ * See first overload for more details.
+ *
+ * @param game Game to enable the feature for.
+ * @param feature Game feature to register.
+ * @param priority Priority of the game feature.
+ * @param replace If true, remove features of the same kind registered by the current
+ * plugin, otherwise add the feature alongside existing ones.
+ *
+ * @return true if the game feature was properly registered, false otherwise.
+ */
+ virtual bool registerFeature(IPluginGame* game, std::shared_ptr<GameFeature> feature,
+ int priority, bool replace = false) = 0;
+
+ /**
+ * @brief Register game feature for all games.
+ *
+ * See first overload for more details.
+ *
+ * @param feature Game feature to register.
+ * @param priority Priority of the game feature.
+ * @param replace If true, remove features of the same kind registered by the current
+ * plugin, otherwise add the feature alongside existing ones.
+ *
+ * @return true if the game feature was properly registered, false otherwise.
+ */
+ virtual bool registerFeature(std::shared_ptr<GameFeature> feature, int priority,
+ bool replace = false) = 0;
+
+ /**
+ * @brief Unregister the given game feature.
+ *
+ * This function is safe to use even if the feature was not registered before.
+ *
+ * @param feature Feature to unregister.
+ */
+ virtual bool unregisterFeature(std::shared_ptr<GameFeature> feature) = 0;
+
+ /**
+ * @brief Unregister all features of the given type registered by the calling plugin.
+ *
+ * This function is safe to use even if the plugin has no feature of the given type
+ * register.
+ *
+ * @return the number of features unregistered.
+ *
+ * @tparam Feature Type of game feature to remove.
+ */
+ template <BaseGameFeature Feature>
+ int unregisterFeatures()
+ {
+ return unregisterFeaturesImpl(typeid(Feature));
+ }
+
+ /**
+ * Retrieve the given game feature, if one exists.
+ *
+ * @return the feature of the given type, if one exists, otherwise a null pointer.
+ */
+ template <BaseGameFeature T>
+ std::shared_ptr<T> gameFeature() const
+ {
+ // gameFeatureImpl ensure that the returned pointer is of the right type (or
+ // nullptr), so reinterpret_cast should be fine here
+ return std::dynamic_pointer_cast<T>(gameFeatureImpl(typeid(T)));
+ }
+
+public:
+ virtual ~IGameFeatures() = default;
+
+protected:
+ virtual std::shared_ptr<GameFeature>
+ gameFeatureImpl(std::type_info const& info) const = 0;
+ virtual int unregisterFeaturesImpl(std::type_info const& info) = 0;
+};
+
+} // namespace MOBase
+
+#endif
diff --git a/libs/uibase/include/uibase/game_features/localsavegames.h b/libs/uibase/include/uibase/game_features/localsavegames.h
new file mode 100644
index 0000000..f425431
--- /dev/null
+++ b/libs/uibase/include/uibase/game_features/localsavegames.h
@@ -0,0 +1,21 @@
+#ifndef UIBASE_GAMEFEATURES_LOCALSAVEGAMES_H
+#define UIBASE_GAMEFEATURES_LOCALSAVEGAMES_H
+
+#include <QDir>
+
+#include "../filemapping.h"
+#include "./game_feature.h"
+
+namespace MOBase
+{
+class IProfile;
+
+class LocalSavegames : public details::GameFeatureCRTP<LocalSavegames>
+{
+public:
+ virtual MappingType mappings(const QDir& profileSaveDir) const = 0;
+ virtual bool prepareProfile(MOBase::IProfile* profile) = 0;
+};
+} // namespace MOBase
+
+#endif // LOCALSAVEGAMES_H
diff --git a/libs/uibase/include/uibase/game_features/moddatachecker.h b/libs/uibase/include/uibase/game_features/moddatachecker.h
new file mode 100644
index 0000000..4c95f79
--- /dev/null
+++ b/libs/uibase/include/uibase/game_features/moddatachecker.h
@@ -0,0 +1,66 @@
+#ifndef UIBASE_GAMEFEATURES_MODDATACHECKER_H
+#define UIBASE_GAMEFEATURES_MODDATACHECKER_H
+
+#include <memory>
+
+#include "./game_feature.h"
+
+namespace MOBase
+{
+class IFileTree;
+
+class ModDataChecker : public details::GameFeatureCRTP<ModDataChecker>
+{
+public:
+ /**
+ *
+ */
+ enum class CheckReturn
+ {
+ INVALID,
+ FIXABLE,
+ VALID
+ };
+
+ /**
+ * @brief Check that the given filetree represent a valid mod layout, or can be easily
+ * fixed.
+ *
+ * This method is mainly used during installation (to find which installer should
+ * be used or to recurse into multi-level archives), or to quickly indicates to a
+ * user if a mod looks valid.
+ *
+ * This method does not have to be exact, it only has to indicate if the given tree
+ * looks like a valid mod or not by quickly checking the structure (heavy operations
+ * should be avoided).
+ *
+ * If the tree can be fixed by the `fix()` method, this method should return
+ * `FIXABLE`. `FIXABLE` should only be returned when it is guaranteed that `fix()` can
+ * fix the tree.
+ *
+ * @param tree The tree starting at the root of the "data" folder.
+ *
+ * @return whether the tree is invalid, fixable or valid.
+ */
+ virtual CheckReturn
+ dataLooksValid(std::shared_ptr<const MOBase::IFileTree> fileTree) const = 0;
+
+ /**
+ * @brief Try to fix the given tree.
+ *
+ * This method is used during installation to try to fix invalid archives and will
+ * only be called if dataLooksValid returned `FIXABLE`.
+ *
+ * @param tree The tree to try to fix. Can be modified during the process.
+ *
+ * @return the fixed tree, or a null pointer if the tree could not be fixed.
+ */
+ virtual std::shared_ptr<MOBase::IFileTree>
+ fix(std::shared_ptr<MOBase::IFileTree> fileTree) const
+ {
+ return nullptr;
+ }
+};
+} // namespace MOBase
+
+#endif
diff --git a/libs/uibase/include/uibase/game_features/moddatacontent.h b/libs/uibase/include/uibase/game_features/moddatacontent.h
new file mode 100644
index 0000000..b86527c
--- /dev/null
+++ b/libs/uibase/include/uibase/game_features/moddatacontent.h
@@ -0,0 +1,122 @@
+#ifndef UIBASE_GAMEFEATURES_MODDATACONTENT_H
+#define UIBASE_GAMEFEATURES_MODDATACONTENT_H
+
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+#include <QString>
+
+#include "./game_feature.h"
+
+namespace MOBase
+{
+class IFileTree;
+
+/**
+ * The ModDataContent feature is used (when available) to indicate to users the content
+ * of mods in the "Content" column.
+ *
+ * The feature exposes a list of possible content types, each associated with an ID, a
+ * name and an icon. The icon is the path to either:
+ * - A Qt resource or;
+ * - A file on the disk.
+ *
+ * In order to facilitate the implementation, MO2 already provides a set of icons that
+ * can be used. Those icons are all under :/MO/gui/content (e.g. :/MO/gui/content/plugin
+ * or :/MO/gui/content/music).
+ *
+ * The list of available icons is:
+ * - plugin:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/jigsaw-piece.png
+ * - skyproc:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/hand-of-god.png
+ * - texture:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/empty-chessboard.png
+ * - music:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/double-quaver.png
+ * - sound:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/lyre.png
+ * - interface:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/usable.png
+ * - skse:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/checkbox-tree.png
+ * - script:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/tinker.png
+ * - mesh:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/breastplate.png
+ * - string:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/conversation.png
+ * - bsa:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/locked-chest.png
+ * - menu:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/config.png
+ * - inifile:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/feather-and-scroll.png
+ * - modgroup:
+ * https://github.com/ModOrganizer2/modorganizer/blob/master/src/resources/contents/xedit.png
+ */
+class ModDataContent : public details::GameFeatureCRTP<ModDataContent>
+{
+public:
+ struct Content
+ {
+
+ /**
+ * @param id ID of this content.
+ * @param name Name of this content.
+ * @param icon Path to the icon for this content. Can be either a path
+ * to an image on the disk, or to a resource. Can be an empty string if
+ * filterOnly is true.
+ * @param filterOnly Indicates if the content should only be show in the filter
+ * criteria and not in the actual Content column.
+ */
+ Content(int id, QString name, QString icon, bool filterOnly = false)
+ : m_Id{id}, m_Name{name}, m_Icon{icon}, m_FilterOnly{filterOnly}
+ {}
+
+ /**
+ * @return the ID of this content.
+ */
+ int id() const { return m_Id; }
+
+ /**
+ * @return the name of this content.
+ */
+ QString name() const { return m_Name; }
+
+ /**
+ * @return the path to the icon of this content (can be a Qt resource path).
+ */
+ QString icon() const { return m_Icon; }
+
+ /**
+ * @return true if this content is only meant to be used as a filter criteria.
+ */
+ bool isOnlyForFilter() const { return m_FilterOnly; }
+
+ private:
+ int m_Id;
+ QString m_Name;
+ QString m_Icon;
+ bool m_FilterOnly;
+ };
+
+ /**
+ * @return the list of all possible contents for the corresponding game.
+ */
+ virtual std::vector<Content> getAllContents() const = 0;
+
+ /**
+ * @brief Retrieve the list of contents in the given tree.
+ *
+ * @param fileTree The tree corresponding to the mod to retrieve contents for.
+ *
+ * @return the IDs of the content in the given tree.
+ */
+ virtual std::vector<int>
+ getContentsFor(std::shared_ptr<const MOBase::IFileTree> fileTree) const = 0;
+};
+} // namespace MOBase
+
+#endif
diff --git a/libs/uibase/include/uibase/game_features/savegameinfo.h b/libs/uibase/include/uibase/game_features/savegameinfo.h
new file mode 100644
index 0000000..b5ea216
--- /dev/null
+++ b/libs/uibase/include/uibase/game_features/savegameinfo.h
@@ -0,0 +1,52 @@
+#ifndef UIBASE_GAMEFEATURES_SAVEGAMEINFO_H
+#define UIBASE_GAMEFEATURES_SAVEGAMEINFO_H
+
+#include <QMap>
+#include <QString>
+#include <QStringList>
+#include <QWidget>
+
+#include "./game_feature.h"
+
+namespace MOBase
+{
+class ISaveGame;
+class ISaveGameInfoWidget;
+
+/** Feature to get hold of stuff to do with save games */
+class SaveGameInfo : public details::GameFeatureCRTP<SaveGameInfo>
+{
+public:
+ virtual ~SaveGameInfo() {}
+
+ typedef QStringList ProvidingModules;
+ typedef QMap<QString, ProvidingModules> MissingAssets;
+
+ /**
+ * @brief Get missing items from a save.
+ *
+ * @param save The save to retrieve missing assets from.
+ *
+ * @returns a collection of missing assets and the modules that can supply those
+ * assets.
+ *
+ * Note that in the situation where 'module' and 'asset' are indistinguishable,
+ * both still have to be supplied.
+ */
+ virtual MissingAssets getMissingAssets(MOBase::ISaveGame const& save) const = 0;
+
+ /**
+ * @brief Get a widget to display over the save game list.
+ *
+ * @param parent The parent widget.
+ *
+ * @returns a Qt widget to display saves Widget.
+ *
+ * It is permitted to return a null pointer to indicate the plugin does not have a
+ * nice visual way of displaying save game contents.
+ */
+ virtual MOBase::ISaveGameInfoWidget* getSaveGameWidget(QWidget* parent = 0) const = 0;
+};
+} // namespace MOBase
+
+#endif // SAVEGAMEINFO_H
diff --git a/libs/uibase/include/uibase/game_features/scriptextender.h b/libs/uibase/include/uibase/game_features/scriptextender.h
new file mode 100644
index 0000000..d6028ae
--- /dev/null
+++ b/libs/uibase/include/uibase/game_features/scriptextender.h
@@ -0,0 +1,53 @@
+#ifndef UIBASE_GAMEFEATURES_SCRIPTEXTENDER
+#define UIBASE_GAMEFEATURES_SCRIPTEXTENDER
+
+#include <cstdint>
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+using WORD = uint16_t;
+#endif
+
+#include <QString>
+#include <QStringList>
+
+#include "./game_feature.h"
+
+namespace MOBase
+{
+
+class ScriptExtender : public details::GameFeatureCRTP<ScriptExtender>
+{
+
+public:
+ virtual ~ScriptExtender() {}
+
+ /** Get the name of the script extender binary */
+ virtual QString BinaryName() const = 0;
+
+ /** Get the script extender plugin path*/
+ virtual QString PluginPath() const = 0;
+
+ /** The loader to use to ensure the game runs with the script extender */
+ virtual QString loaderName() const = 0;
+
+ /** Full path of the loader */
+ virtual QString loaderPath() const = 0;
+
+ /** Extension of the script extender save game */
+ virtual QString savegameExtension() const = 0;
+
+ /** Returns true if the extender is installed */
+ virtual bool isInstalled() const = 0;
+
+ /** Get version of extender */
+ virtual QString getExtenderVersion() const = 0;
+
+ /** Get CPU platform of extender */
+ virtual WORD getArch() const = 0;
+};
+
+} // namespace MOBase
+
+#endif // SCRIPTEXTENDER
diff --git a/libs/uibase/include/uibase/game_features/unmanagedmods.h b/libs/uibase/include/uibase/game_features/unmanagedmods.h
new file mode 100644
index 0000000..6eeb8a7
--- /dev/null
+++ b/libs/uibase/include/uibase/game_features/unmanagedmods.h
@@ -0,0 +1,45 @@
+#ifndef UIBASE_GAMEFEATURES_UNMANAGEDMODS_H
+#define UIBASE_GAMEFEATURES_UNMANAGEDMODS_H
+
+#include <QFileInfo>
+#include <QString>
+#include <QStringList>
+
+#include "./game_feature.h"
+
+namespace MOBase
+{
+
+class UnmanagedMods : public details::GameFeatureCRTP<UnmanagedMods>
+{
+public:
+ virtual ~UnmanagedMods() {}
+
+ /**
+ * @param onlyOfficial if set, only official mods (dlcs) are returned
+ * @return the list of unmanaged mods (internal names)
+ */
+ virtual QStringList mods(bool onlyOfficial) const = 0;
+
+ /**
+ * @param modName (internal) name of the mod being requested
+ * @return display name of the mod
+ */
+ virtual QString displayName(const QString& modName) const = 0;
+
+ /**
+ * @param modName name of the mod being requested
+ * @return reference file info
+ */
+ virtual QFileInfo referenceFile(const QString& modName) const = 0;
+
+ /**
+ * @param modName name of the mod being requested
+ * @return list of file names (absolute paths)
+ */
+ virtual QStringList secondaryFiles(const QString& modName) const = 0;
+};
+
+} // namespace MOBase
+
+#endif // UNMANAGEDMODS_H
diff --git a/libs/uibase/include/uibase/guessedvalue.h b/libs/uibase/include/uibase/guessedvalue.h
new file mode 100644
index 0000000..9c0be51
--- /dev/null
+++ b/libs/uibase/include/uibase/guessedvalue.h
@@ -0,0 +1,252 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef GUESSEDVALUE_H
+#define GUESSEDVALUE_H
+
+#include <functional>
+#include <set>
+
+namespace MOBase
+{
+
+/**
+ * @brief describes how good the code considers a guess (i.e. for a mod name)
+ * this is used to determine if a name from another source should overwrite or
+ * not
+ */
+enum EGuessQuality
+{
+ GUESS_INVALID, /// no valid value has been set yet
+ GUESS_FALLBACK, /// the guess is very basic and should only be used if no other
+ /// source is available
+ GUESS_GOOD, /// considered a good guess
+ GUESS_META, /// the value comes from meta data and is usually what the author
+ /// intended
+ GUESS_PRESET, /// the value comes from a previous install of the same data and
+ /// usually represents what the user chose before
+ GUESS_USER /// the user selection. Always overrules other sources
+};
+
+/**
+ * Represents a value that may be set from different places. Each time the value is
+ * changed a "quality" is specified to say how probable it is the value is the best
+ * choice. Only the best choice should be used in the end but alternatives can be
+ * queried. This class also allows a filter to be set. If a "guess" doesn't pass the
+ * filter, it is ignored.
+ */
+template <typename T>
+class GuessedValue
+{
+public:
+public:
+ /**
+ * @brief default constructor
+ */
+ GuessedValue();
+
+ /**
+ * @brief constructor with initial value
+ *
+ * @param reference the initial value to set
+ * @param quality quality of the guess
+ */
+ GuessedValue(const T& reference, EGuessQuality quality = GUESS_USER);
+
+ /**
+ * @brief
+ *
+ * @param reference
+ * @return GuessedValue<T>
+ */
+ GuessedValue<T>& operator=(const GuessedValue<T>& reference);
+
+ /**
+ * install a filter function. This filter is applied on every update and can
+ * refuse the update altogether or modify the value.
+ * @param filterFunction the filter to apply
+ */
+ /**
+ * @brief
+ *
+ * @param filterFunction
+ */
+ void setFilter(std::function<bool(T&)> filterFunction);
+
+ /**
+ * @brief
+ *
+ * @return operator const T
+ */
+ operator const T&() const { return m_Value; }
+
+ /**
+ * @brief
+ *
+ * @return T *operator ->
+ */
+ T* operator->() { return &m_Value; }
+ /**
+ * @brief
+ *
+ * @return const T *operator ->
+ */
+ /**
+ * @brief
+ *
+ * @return const T *operator ->
+ */
+ const T* operator->() const { return &m_Value; }
+
+ /**
+ * @brief
+ *
+ * @param value
+ * @return GuessedValue<T>
+ */
+ GuessedValue<T>& update(const T& value);
+ /**
+ * @brief
+ *
+ * @param value
+ * @param quality
+ * @return GuessedValue<T>
+ */
+ GuessedValue<T>& update(const T& value, EGuessQuality quality);
+
+ /**
+ * @brief
+ *
+ * @return const std::set<T>
+ */
+ const std::set<T>& variants() const { return m_Variants; }
+
+private:
+ T m_Value; /**< TODO */
+ std::set<T> m_Variants; /**< TODO */
+ EGuessQuality m_Quality; /**< TODO */
+ std::function<bool(T&)> m_Filter;
+};
+
+template <typename T>
+/**
+ * @brief
+ *
+ * @param
+ * @return bool
+ */
+bool nullFilter(T&)
+{
+ return true;
+}
+
+/**
+ * @brief
+ *
+ */
+template <typename T>
+GuessedValue<T>::GuessedValue()
+ : m_Value(), m_Quality(GUESS_INVALID), m_Filter(nullFilter<T>)
+{}
+
+/**
+ * @brief
+ *
+ * @param reference
+ * @param quality
+ */
+template <typename T>
+GuessedValue<T>::GuessedValue(const T& reference, EGuessQuality quality)
+ : m_Value(reference), m_Variants{reference}, m_Quality(quality),
+ m_Filter(nullFilter<T>)
+{}
+
+/**
+ * @brief
+ *
+ * @param reference
+ * @return GuessedValue<T> &GuessedValue<T>
+ */
+template <typename T>
+GuessedValue<T>& GuessedValue<T>::operator=(const GuessedValue<T>& reference)
+{
+ if (this != &reference) {
+ if (reference.m_Quality >= m_Quality) {
+ m_Value = reference.m_Value;
+ m_Quality = reference.m_Quality;
+ m_Filter = reference.m_Filter;
+ m_Variants = reference.m_Variants;
+ }
+ }
+ return *this;
+}
+
+/**
+ * @brief
+ *
+ * @param filterFunction
+ */
+template <typename T>
+void GuessedValue<T>::setFilter(std::function<bool(T&)> filterFunction)
+{
+ m_Filter = filterFunction;
+}
+
+/**
+ * @brief
+ *
+ * @param value
+ * @return GuessedValue<T> &GuessedValue<T>
+ */
+template <typename T>
+GuessedValue<T>& GuessedValue<T>::update(const T& value)
+{
+ T temp = value;
+ if (m_Filter(temp)) {
+ m_Variants.insert(temp);
+ m_Value = temp;
+ }
+ return *this;
+}
+
+/**
+ * @brief
+ *
+ * @param value
+ * @param quality
+ * @return GuessedValue<T> &GuessedValue<T>
+ */
+template <typename T>
+GuessedValue<T>& GuessedValue<T>::update(const T& value, EGuessQuality quality)
+{
+ T temp = value;
+ if (m_Filter(temp)) {
+ m_Variants.insert(temp);
+ if (quality >= m_Quality) {
+ m_Value = temp;
+ m_Quality = quality;
+ }
+ }
+ return *this;
+}
+
+} // namespace MOBase
+
+#endif // GUESSEDVALUE_H
diff --git a/libs/uibase/include/uibase/idownloadmanager.h b/libs/uibase/include/uibase/idownloadmanager.h
new file mode 100644
index 0000000..f22dd0e
--- /dev/null
+++ b/libs/uibase/include/uibase/idownloadmanager.h
@@ -0,0 +1,102 @@
+#ifndef IDOWNLOADMANAGER_H
+#define IDOWNLOADMANAGER_H
+
+#include "dllimport.h"
+#include <QList>
+#include <QObject>
+#include <QString>
+#include <functional>
+
+namespace MOBase
+{
+
+class QDLLEXPORT IDownloadManager : public QObject
+{
+ Q_OBJECT
+
+public:
+ IDownloadManager(QObject* parent = nullptr) : QObject(parent) {}
+
+ /**
+ * @brief download a file by url. The list can contain alternative URLs to allow the
+ * download manager to switch in case of download problems
+ * @param urls list of urls to download from
+ * @return an id by which the download will be identified
+ */
+ virtual int startDownloadURLs(const QStringList& urls) = 0;
+
+ /**
+ * @brief download a file from www.nexusmods.com/<game>. <game> is always the game
+ * currently being managed
+ * @param modID id of the mod for which to download a file
+ * @param fileID id of the file to download
+ * @return an id by which the download will be identified
+ */
+ virtual int startDownloadNexusFile(int modID, int fileID) = 0;
+
+ /**
+ * @brief download a file from www.nexusmods.com/<gameName>.
+ * @param gameName 'short' name of the game the mod is for
+ * @param modID id of the mod for which to download a file
+ * @param fileID id of the file to download
+ * @return an id by which the download will be identified
+ */
+ virtual int startDownloadNexusFileForGame(const QString& gameName, int modID,
+ int fileID) = 0;
+
+ /**
+ * @brief get the (absolute) file path of the specified download.
+ * @param id id of the download as returned by the download... functions
+ * @return absoute path to the downloaded file. This file may not yet exist if the
+ * download is incomplete
+ */
+ virtual QString downloadPath(int id) = 0;
+
+ /**
+ * @brief Installs a handler to be called when a download complete.
+ *
+ * @param callback The function to be called when a download complete. The argument is
+ * the download ID.
+ *
+ * @return true if the handler was successfully installed (there is as of now no known
+ * reason this should fail).
+ */
+ virtual bool onDownloadComplete(const std::function<void(int)>& callback) = 0;
+
+ /**
+ * @brief Installs a handler to be called when a download is paused.
+ *
+ * @param callback The function to be called when a download is paused. The argument
+ * is the download ID.
+ *
+ * @return true if the handler was successfully installed (there is as of now no known
+ * reason this should fail).
+ */
+ virtual bool onDownloadPaused(const std::function<void(int)>& callback) = 0;
+
+ /**
+ * @brief Installs a handler to be called when a download fails.
+ *
+ * @param callback The function to be called when a download fails. The argument is
+ * the download ID.
+ *
+ * @return true if the handler was successfully installed (there is as of now no known
+ * reason this should fail).
+ */
+ virtual bool onDownloadFailed(const std::function<void(int)>& callback) = 0;
+
+ /**
+ * @brief Installs a handler to be called when a download is removed.
+ *
+ * @param callback The function to be called when a download is removed. The argument
+ * is the download ID (which is no longer valid).
+ *
+ * @return true if the handler was successfully installed (there is as of now no known
+ * reason this should fail).
+ */
+ virtual bool onDownloadRemoved(const std::function<void(int)>& callback) = 0;
+};
+
+} // namespace MOBase
+
+#endif // IDOWNLOADMANAGER_H
diff --git a/libs/uibase/include/uibase/iexecutable.h b/libs/uibase/include/uibase/iexecutable.h
new file mode 100644
index 0000000..7873b10
--- /dev/null
+++ b/libs/uibase/include/uibase/iexecutable.h
@@ -0,0 +1,84 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IEXECUTABLE_H
+#define IEXECUTABLE_H
+
+#include <QFileInfo>
+#include <QString>
+
+namespace MOBase
+{
+class IExecutable
+{
+public: // Information found in ExecutableInfo
+ /**
+ * @return the title of the executable.
+ */
+ virtual const QString& title() const = 0;
+
+ /**
+ * @return the file info of the executable binary.
+ */
+ virtual const QFileInfo& binaryInfo() const = 0;
+
+ /**
+ * @return the arguments to be passed to the executable.
+ *
+ * @note This API might be changed in the future to return a QStringList instead.
+ */
+ virtual const QString& arguments() const = 0;
+
+ /**
+ * @return the Steam App ID associated with this executable, or an empty string if
+ * there is none.
+ */
+ virtual const QString& steamAppID() const = 0;
+
+ /**
+ * @return the working directory for the executable.
+ */
+ virtual const QString& workingDirectory() const = 0;
+
+public: // Information found in flags
+ /**
+ * @return true if the executable is shown on the toolbar.
+ */
+ virtual bool isShownOnToolbar() const = 0;
+
+ /**
+ * @return true if the executable's application icon is used for desktop shortcuts.
+ */
+ virtual bool usesOwnIcon() const = 0;
+
+ /**
+ * @return true if Mod Organizer should minimize to the system tray while this
+ * executable is running.
+ */
+ virtual bool minimizeToSystemTray() const = 0;
+
+ /**
+ * @return true if this executable is hidden in the user interface.
+ */
+ virtual bool hide() const = 0;
+};
+} // namespace MOBase
+
+#endif // IEXECUTABLE_H
diff --git a/libs/uibase/include/uibase/iexecutableslist.h b/libs/uibase/include/uibase/iexecutableslist.h
new file mode 100644
index 0000000..f8f7a3f
--- /dev/null
+++ b/libs/uibase/include/uibase/iexecutableslist.h
@@ -0,0 +1,75 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IEXECUTABLESLIST_H
+#define IEXECUTABLESLIST_H
+
+#include <QFileInfo>
+#include <QString>
+
+#include <generator>
+
+#include "iexecutable.h"
+
+namespace MOBase
+{
+/**
+ * @brief Interface to the list of executables configured in Mod Organizer.
+ */
+class IExecutablesList
+{
+public:
+ /**
+ * @brief Retrieve all configured executables.
+ *
+ * @return a generator yielding all configured executables.
+ */
+ virtual std::generator<const IExecutable&> executables() const = 0;
+
+ /**
+ * @brief Retrieve an executable by its title.
+ *
+ * @param title Title of the executable to retrieve.
+ *
+ * @return the executable with the specified title, or nullptr if not found.
+ */
+ virtual const IExecutable* getByTitle(const QString& title) const = 0;
+
+ /**
+ * @brief Retrieve an executable by its binary file info.
+ *
+ * @param info File info of the executable binary to retrieve.
+ *
+ * @return the executable with the specified binary, or nullptr if not found.
+ */
+ virtual const IExecutable* getByBinary(const QFileInfo& info) const = 0;
+
+ /**
+ * @brief Check if an executable with the specified title exists.
+ *
+ * @param title Title of the executable to check.
+ *
+ * @return true if an executable with the specified title exists, false otherwise.
+ */
+ virtual bool contains(const QString& title) const = 0;
+};
+} // namespace MOBase
+
+#endif // IEXECUTABLESLIST_H
diff --git a/libs/uibase/include/uibase/ifiletree.h b/libs/uibase/include/uibase/ifiletree.h
new file mode 100644
index 0000000..65bc171
--- /dev/null
+++ b/libs/uibase/include/uibase/ifiletree.h
@@ -0,0 +1,1130 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2020 MO2 Team. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IFILETREE_H
+#define IFILETREE_H
+
+#include <atomic>
+#include <generator>
+#include <iterator>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <stdexcept>
+#include <vector>
+#include <version>
+
+#include <QDateTime>
+#include <QFlags>
+#include <QList>
+#include <QString>
+
+#include "dllimport.h"
+#include "utility.h"
+
+/**
+ * This header contains definition for the interface IFileTree and the FileTreeEntry
+ * class.
+ *
+ * The purpose of IFileTree is to expose a file tree in a user-friendly way. The
+ * IFileTree interface represent a "virtual" file tree: the tree may not exists on
+ * the disk or anywhere, it is just an abstract structure. The source of the tree
+ * is irrelevant to the IFileTree user and is an implementation details.
+ *
+ * IFileTree and FileTreeEntry are very intrically linked so it is not possible to
+ * use FileTreeEntry for something else, and the only way to create FileTreeEntry
+ * is through an existing IFileTree.
+ *
+ * IFileTree expose a mutable and a strict non-mutable interfaces based on
+ * const-qualification of methods, but all underlying implementation are obviously
+ * non-const.
+ *
+ * The IFileTree interface is implemented such that creating implementations should
+ * be fairly easy (two short methods to implement). Implementation can override other
+ * methods in order to reflect changes from the tree to the actual source (e.g.,
+ * override the beforeX() methods to actually rename or move file on the disk).
+ *
+ */
+
+namespace MOBase
+{
+
+/**
+ *
+ */
+class IFileTree;
+
+/**
+ * @brief Simple valid C++ comparator for QString that compare them case-insensitive,
+ * mostly useful to compare filenames on Windows.
+ */
+struct FileNameComparator
+{
+
+ /**
+ * @brief The case sensitivity of filenames.
+ */
+ static constexpr auto CaseSensitivity = Qt::CaseInsensitive;
+
+ /**
+ * @brief Compare the two given filenames.
+ *
+ * @param lhs, rhs Filenames to compare.
+ *
+ * @return -1, 0 or 1 if the first one is less, equal or greater than the second one.
+ */
+ static int compare(QString const& lhs, QString const& rhs)
+ {
+ return lhs.compare(rhs, CaseSensitivity);
+ }
+
+ /**
+ *
+ */
+ bool operator()(QString const& a, QString const& b) const
+ {
+ return compare(a, b) < 0;
+ }
+};
+
+/**
+ * @brief Exception thrown when an operation on the tree is not supported by the
+ * implementation or makes no sense (e.g., creation of a file in an archive).
+ */
+struct QDLLEXPORT UnsupportedOperationException : public Exception
+{
+ using Exception::Exception;
+};
+
+/**
+ * @brief Represent an entry in a file tree, either a file or a directory. This class
+ * inherited by IFileTree so that operations on entry are the same for a file or
+ * a directory.
+ *
+ * This class provides convenience methods to query information on the file, like its
+ * name or the its last modification time. It also provides a convenience astree()
+ * method that can be used to retrieve the tree corresponding to its entry in case the
+ * entry represent a directory.
+ *
+ */
+class QDLLEXPORT FileTreeEntry : public std::enable_shared_from_this<FileTreeEntry>
+{
+
+public: // Enums
+ /**
+ * @brief Enumeration of the different file type.
+ *
+ */
+ enum FileType
+ {
+ DIRECTORY = 0b01,
+ FILE = 0b10
+ };
+ Q_DECLARE_FLAGS(FileTypes, FileType);
+
+ constexpr static auto FILE_OR_DIRECTORY = FileTypes{DIRECTORY, FILE};
+
+public: // Deleted operators:
+ FileTreeEntry(FileTreeEntry const&) = delete;
+ FileTreeEntry(FileTreeEntry&&) = delete;
+
+ FileTreeEntry& operator=(FileTreeEntry const&) = delete;
+ FileTreeEntry& operator=(FileTreeEntry&&) = delete;
+
+public: // Methods
+ /**
+ * @brief Check if this entry is a file.
+ *
+ * @return true if this entry is a file, false otherwize.
+ */
+ bool isFile() const { return astree() == nullptr; }
+
+ /**
+ * @brief Check if this entry is a directory.
+ *
+ * @return true if this entry is a directory, false otherwize.
+ */
+ bool isDir() const { return astree() != nullptr; }
+
+ /**
+ * @brief Convert this entry to a tree. This method returns a null pointer
+ * if this entry corresponds to a file.
+ *
+ * @return this entry as a tree, or a null pointer if isDir() is false.
+ */
+ virtual std::shared_ptr<IFileTree> astree() { return nullptr; }
+
+ /**
+ * @brief Convert this entry to a tree. This method returns a null pointer
+ * if this entry corresponds to a file.
+ *
+ * @return this entry as a tree, or a null pointer if isDir() is false.
+ */
+ virtual std::shared_ptr<const IFileTree> astree() const { return nullptr; }
+
+ /**
+ * @brief Retrieve the type of this entry.
+ *
+ * @return the type of this entry.
+ */
+ FileType fileType() const { return isDir() ? DIRECTORY : FILE; }
+
+ /**
+ * @brief Retrieve the name of this entry.
+ *
+ * @return the name of this entry.
+ */
+ QString name() const { return m_Name; }
+
+ /**
+ * @brief Compare the name of this entry against the given string.
+ *
+ * This method only checks the name of the entry, not the full path.
+ *
+ * @param name Name to test.
+ *
+ * @return -1, 0 or 1 depending on the result of the comparison.
+ */
+ int compare(QString name) const { return FileNameComparator::compare(m_Name, name); }
+
+ /**
+ * @brief Retrieve the "last" extension of this entry.
+ *
+ * The "last" extension is everything after the last dot in the file name.
+ *
+ * @return the last extension of this entry, or an empty string if the file has no
+ * extension or is directory.
+ */
+ QString suffix() const;
+
+ /**
+ * @brief Check if this entry has the given suffix.
+ *
+ * @param suffix Suffix of to check.
+ *
+ * @return true if this entry is a file and has the given suffix.
+ */
+ bool hasSuffix(QString suffix) const;
+
+ /**
+ * @brief Check if this entry has one of the given suffixes.
+ *
+ * @param suffixes Suffixes of to check.
+ *
+ * @return true if this entry is a file and has the given suffix.
+ */
+ bool hasSuffix(QStringList suffixes) const;
+
+ /**
+ * @brief Retrieve the path from this entry up to the root of the tree.
+ *
+ * This method propagate up the tree so is not constant complexity as
+ * the full path is never stored.
+ *
+ * @param sep The type of separator to use to create the path.
+ *
+ * @return the path from this entry to the root, including the name
+ * of this entry.
+ */
+ QString path(QString sep = "\\") const { return pathFrom(nullptr, sep); }
+
+ /**
+ * @brief Retrieve the path from this entry to the given tree.
+ *
+ * @param tree The tree to reach, must be a parent of this entry.
+ * @param sep The type of separator to use to create the path.
+ *
+ * @return the path from this entry up to the given tree, including the name
+ * of this entry, or QString() if the given tree is not a parent of
+ * this one.
+ */
+ QString pathFrom(std::shared_ptr<const IFileTree> tree, QString sep = "\\") const;
+
+ /**
+ * @brief Detach this entry from its parent tree.
+ *
+ * @return true if the entry was removed correctly, false otherwize.
+ */
+ bool detach();
+
+ /**
+ * @brief Move this entry to the given tree.
+ *
+ * @param tree The tree to move this entry to.
+ *
+ * @return true if the entry was moved correctly, false otherwize.
+ */
+ bool moveTo(std::shared_ptr<IFileTree> tree);
+
+ /**
+ * @brief Retrieve the immediate parent tree of this entry.
+ *
+ * @return the parent tree containing this entry, or a null pointer
+ * if this entry is the root or the parent tree is unreachable.
+ */
+ std::shared_ptr<IFileTree> parent()
+ {
+ return std::const_pointer_cast<IFileTree>(
+ const_cast<const FileTreeEntry*>(this)->parent());
+ }
+
+ /**
+ * @brief Retrieve the immediate parent tree of this entry.
+ *
+ * @return the parent tree containing this entry, or a null pointer
+ * if this entry is the root or the parent tree is unreachable.
+ */
+ std::shared_ptr<const IFileTree> parent() const { return m_Parent.lock(); }
+
+public: // Destructor:
+ virtual ~FileTreeEntry() {}
+
+protected: // Constructors:
+ /**
+ * @brief Create a new entry corresponding.
+ *
+ * @param parent The tree containing this entry.
+ * @param name The name of this entry.
+ */
+ FileTreeEntry(std::shared_ptr<const IFileTree> parent, QString name);
+
+ /**
+ * @brief Creates a new orphan entry identical to this entry.
+ *
+ */
+ virtual std::shared_ptr<FileTreeEntry> clone() const;
+
+ /**
+ * @brief Creates a new FileTreeEntry corresponding to a file with the given
+ * parameters.
+ *
+ * The purpose of this methods is to allow child classes corresponding to tree (i.e.,
+ * that do not inherit directly FileTreeEntry) to create FileTreeEntry.
+ *
+ * @param parent The tree containing this file.
+ * @param name The name of this file.
+ */
+ static std::shared_ptr<FileTreeEntry>
+ createFileEntry(std::shared_ptr<const IFileTree> parent, QString name);
+
+private:
+ std::weak_ptr<const IFileTree> m_Parent;
+
+ QString m_Name;
+
+ friend class IFileTree;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeEntry::FileTypes);
+
+/**
+ * @brief Interface to classes that provides way to visualize and alter file trees. The
+ * tree may not correspond to an actual file tree on the disk (e.g., inside an archive,
+ * from a QTree Widget, ...).
+ *
+ * This interface already implements most of the usual methods for a file tree. Child
+ * classes only have to implement methods to populate the tree and to create child tree
+ * object.
+ *
+ * Read-only operations on the tree are thread-safe, even when the tree has not been
+ * populated yet.
+ *
+ * In order to prevent wrong usage of the tree, implementing classes may throw
+ * UnsupportedOperationException if an operation is not supported. By default, all
+ * operations are supported, but some may not make sense in many situations.
+ *
+ * The goal of this is not reflect the change made to a IFileTree to the disk, but child
+ * classes may override relevant methods to do so.
+ *
+ * The tree is built upon FileTreeEntry. A given tree holds shared pointers to its
+ * entries while each entry holds a weak pointer to its parent, this means that the
+ * descending link are strong (shared pointers) but the uplink are weaks.
+ *
+ * Accessing the parent is always done by locking the weak pointer so that returned
+ * pointer or either null or valid. This structure implies that as long as the initial
+ * root lives, entry should not be destroyed, unless the entry are detached from the
+ * root and no shared pointers are kept.
+ *
+ * However, it is not guarantee that one can go up the tree from a single node entry. If
+ * the root node is destroyed, it will not be possible to go up the tree, even if we
+ * still have a valid shared pointer.
+ *
+ * The inheritance is made virtual to provide a way for child classes to use both a
+ * custom FileTreeEntry and IFileTree implementations. This has no impact on the usage
+ * of the interface.
+ *
+ */
+class QDLLEXPORT IFileTree : public virtual FileTreeEntry
+{
+public: // Enumerations and aliases:
+ /**
+ *
+ */
+ enum class InsertPolicy
+ {
+ FAIL_IF_EXISTS,
+ REPLACE,
+ MERGE
+ };
+
+ /**
+ * @brief Special constant returns by merge when the merge failed.
+ */
+ constexpr static std::size_t MERGE_FAILED = (std::size_t)-1;
+
+ /**
+ *
+ */
+ using OverwritesType = std::map<std::shared_ptr<const FileTreeEntry>,
+ std::shared_ptr<const FileTreeEntry>>;
+
+public: // Iterators:
+ /**
+ * The standard iterator are constant, but the pointed value are not. Since
+ * we are storing a vector of shared pointer to non-const object, we need this
+ * wrapper to create iterators to shared pointer of const-object to have proper
+ * immutability when IFileTree is const-qualified.
+ *
+ * Note: convert_iterator satisfies std::forward_iterator concept but not
+ * ForwardIterator since dereferencing does not return an lvalue.
+ */
+ template <class U, class V>
+ struct convert_iterator
+ {
+
+ using reference = U;
+ using difference_type = typename V::difference_type;
+ using value_type = U;
+ using pointer = U;
+ using iterator_category = std::forward_iterator_tag;
+
+ friend bool operator==(convert_iterator a, convert_iterator b)
+ {
+ return a.v == b.v;
+ }
+ friend bool operator!=(convert_iterator a, convert_iterator b)
+ {
+ return a.v != b.v;
+ }
+
+ reference operator*() const { return U(*v); }
+ reference operator->() const { return U(*v); }
+
+ convert_iterator& operator++()
+ {
+ v++;
+ return *this;
+ }
+
+ convert_iterator operator++(int)
+ {
+ value_type value = *(*this);
+ (*this)++;
+ return *this;
+ }
+
+ public:
+ convert_iterator() = default;
+ convert_iterator(convert_iterator const&) = default;
+ convert_iterator(convert_iterator&&) = default;
+ convert_iterator& operator=(convert_iterator const&) = default;
+ convert_iterator& operator=(convert_iterator&&) = default;
+
+ protected:
+ V v;
+
+ convert_iterator(V v) : v{v} {}
+ friend class IFileTree;
+ };
+
+ using value_type = std::shared_ptr<FileTreeEntry>;
+ using reference = std::shared_ptr<FileTreeEntry>;
+ using const_reference = std::shared_ptr<const FileTreeEntry>;
+
+ using iterator = std::vector<std::shared_ptr<FileTreeEntry>>::const_iterator;
+ using const_iterator =
+ convert_iterator<std::shared_ptr<const FileTreeEntry>,
+ std::vector<std::shared_ptr<FileTreeEntry>>::const_iterator>;
+
+ using reverse_iterator =
+ std::vector<std::shared_ptr<FileTreeEntry>>::const_reverse_iterator;
+ using const_reverse_iterator = convert_iterator<
+ std::shared_ptr<const FileTreeEntry>,
+ std::vector<std::shared_ptr<FileTreeEntry>>::const_reverse_iterator>;
+
+#if __cplusplus > 201703L
+ static_assert(std::forward_iterator<iterator>);
+ static_assert(std::forward_iterator<const_iterator>);
+ static_assert(std::forward_iterator<reverse_iterator>);
+ static_assert(std::forward_iterator<const_reverse_iterator>);
+#endif
+
+public: // Access methods:
+ /**
+ *
+ */
+ iterator begin() { return {std::cbegin(entries())}; }
+ const_iterator begin() const { return {std::cbegin(entries())}; }
+ const_iterator cbegin() const { return {std::cbegin(entries())}; }
+
+ /**
+ *
+ */
+ reverse_iterator rbegin() { return {std::crbegin(entries())}; }
+ const_reverse_iterator rbegin() const { return {std::crbegin(entries())}; }
+ const_reverse_iterator crbegin() const { return {std::crbegin(entries())}; }
+
+ /**
+ *
+ */
+ iterator end() { return {std::cend(entries())}; }
+ const_iterator end() const { return {std::cend(entries())}; }
+ const_iterator cend() const { return {std::cend(entries())}; }
+
+ /**
+ *
+ */
+ reverse_iterator rend() { return {std::crend(entries())}; }
+ const_reverse_iterator rend() const { return {std::crend(entries())}; }
+ const_reverse_iterator crend() const { return {std::crend(entries())}; }
+
+ /**
+ * @brief Retrieve the number of entries in this tree.
+ *
+ * This is constant if the tree has already been populated.
+ *
+ * @return the number of entries in this tree.
+ */
+ std::size_t size() const { return entries().size(); }
+
+ /**
+ * @brief Retrieve the file entry at the given index.
+ *
+ * @param i Index of the entry to retrieve.
+ *
+ * @return the file entry at the given index.
+ *
+ * @throw std::out_of_range if the index is invalid.
+ */
+ std::shared_ptr<FileTreeEntry> at(std::size_t i)
+ {
+ if (i < size()) {
+ return entries()[i];
+ }
+ throw std::out_of_range("IFileTree::at");
+ }
+ std::shared_ptr<const FileTreeEntry> at(std::size_t i) const
+ {
+ if (i < size()) {
+ return entries()[i];
+ }
+ throw std::out_of_range("IFileTree::at");
+ }
+
+ /**
+ * @brief Check if this tree is empty, i.e., if it contains no entries.
+ *
+ * @return true if the tree is empty, false otherwize.
+ */
+ bool empty() const { return size() == 0; }
+
+ /**
+ * @brief Check if the given entry exists.
+ *
+ * @param path Path to the entry, separated by / or \.
+ * @param type The type of the entry to check.
+ *
+ * @return true if the entry was found, false otherwize.
+ */
+ bool exists(QString path,
+ FileTreeEntry::FileTypes type = FileTreeEntry::FILE_OR_DIRECTORY) const;
+
+ /**
+ * @brief Retrieve the given entry.
+ *
+ * If an entry is found at the given path but does not match the given type,
+ * a null pointer is returned.
+ *
+ * @param path Path to the entry, separated by / or \.
+ * @param type The type of the entry to find.
+ *
+ * @return the entry if found, a null pointer otherwize.
+ */
+ std::shared_ptr<FileTreeEntry> find(QString path, FileTypes type = FILE_OR_DIRECTORY);
+ std::shared_ptr<const FileTreeEntry> find(QString path,
+ FileTypes type = FILE_OR_DIRECTORY) const;
+
+ /**
+ * @brief Convenient method around find() that returns IFileTree instead of entries.
+ *
+ * @param path Path to the directory, separated by / or \.
+ *
+ * @return the directory if found, a null pointer otherwize.
+ */
+ std::shared_ptr<IFileTree> findDirectory(QString path)
+ {
+ auto entry = find(path, DIRECTORY);
+ return (entry != nullptr && entry->isDir()) ? entry->astree() : nullptr;
+ }
+ std::shared_ptr<const IFileTree> findDirectory(QString path) const
+ {
+ auto entry = find(path, DIRECTORY);
+ return (entry != nullptr && entry->isDir()) ? entry->astree() : nullptr;
+ }
+
+ /**
+ * @brief Retrieve the path from this tree to the given entry.
+ *
+ * @param entry The entry to reach, must be in this tree.
+ * @param sep The type of separator to use to create the path.
+ *
+ * @return the path from this tree to the given entry, including the name
+ * of the entry, or QString() if the given entry is not in this tree.
+ */
+ QString pathTo(std::shared_ptr<const FileTreeEntry> entry, QString sep = "\\") const
+ {
+ return entry->pathFrom(astree(), sep);
+ }
+
+public: // Walk & Glob operations
+ enum class WalkReturn
+ {
+
+ /**
+ * @brief Continue walking normally.
+ */
+ CONTINUE,
+
+ /**
+ * @brief Stop walking normally.
+ */
+ STOP,
+
+ /**
+ * @brief Skip this folder (no effect if the entry is a file).
+ */
+ SKIP
+
+ };
+
+ /**
+ * @brief Walk this tree, calling the given function for each entry in it.
+ *
+ * The given callback will be called with two parameters: the path from this tree to
+ * the given entry (with a trailing separator, not including the entry name), and the
+ * actual entry. The method returns a `WalkReturn` object to indicates what to do.
+ *
+ * During the walk, parent tree are guaranteed to be visited before their childrens.
+ * The given function is never called with the current tree.
+ *
+ * @param callback Method to call for each entry in the tree.
+ */
+ void
+ walk(std::function<WalkReturn(QString const&, std::shared_ptr<const FileTreeEntry>)>
+ callback,
+ QString sep = "\\") const;
+
+public: // Utility functions:
+ /**
+ * @brief Create a new orphan empty tree.
+ *
+ * @param name Name of the tree.
+ *
+ * @return a new tree without any parent.
+ */
+ std::shared_ptr<IFileTree> createOrphanTree(QString name = "") const;
+
+public: // Mutable operations:
+ /**
+ * @brief Create a new file directly under this tree.
+ *
+ * This method will return a null pointer if the file already exists and if
+ * replaceIfExists is false. This method invalidates iterators to this tree and
+ * all the subtrees present in the given path.
+ *
+ * @param name Name of the file.
+ * @param replaceIfExists If true and an entry already exists at the given path,
+ * it will be replaced by a new entry. This will replace both files and
+ * directories.
+ *
+ * @return the entry corresponding to the create file, or a null
+ * pointer if the file was not created.
+ */
+ virtual std::shared_ptr<FileTreeEntry> addFile(QString path,
+ bool replaceIfExists = false);
+
+ /**
+ * @brief Create a new directory tree under this tree.
+ *
+ * This method will create missing folders in the given path and will
+ * not fail if the directory already exists but will fail if the given
+ * path contains "." or "..".
+ * This method invalidates iterators to this tree and all the subtrees
+ * present in the given path.
+ *
+ * @param path Path to the directory.
+ *
+ * @return the entry corresponding to the created directory, or a null
+ * pointer if the directory was not created.
+ */
+ virtual std::shared_ptr<IFileTree> addDirectory(QString path);
+
+ /**
+ * @brief Insert the given entry in this tree, removing it from its
+ * previouis parent.
+ *
+ * The entry must not be this tree or a parent entry of this tree.
+ *
+ * - If the insert policy if FAIL_IF_EXISTS, the call will fail if an entry
+ * with the same name already exists.
+ * - If the policy is REPLACE, an existing entry will be replaced by the given entry.
+ * - If MERGE:
+ * - If there is no entry with the same name, the new entry is inserted.
+ * - If there is an entry with the same name:
+ * - If both entries are files, the old file is replaced by the given entry.
+ * - If both entries are directories, a merge is performed as if using merge().
+ * - Otherwize the insertion fails (two entries with different types).
+ *
+ * This method invalidates iterator to this tree, to the parent tree of the given
+ * entry, and to subtrees of this tree if the insert policy is MERGE.
+ *
+ * @param entry Entry to insert.
+ * @param insertPolicy Policy to use on conflict.
+ *
+ * @return an iterator to the inserted tree if it was inserted or if it
+ * already existed, or the end iterator if insertPolicy is FAIL_IF_EXISTS
+ * and an entry with the same name already exists.
+ */
+ iterator insert(std::shared_ptr<FileTreeEntry> entry,
+ InsertPolicy insertPolicy = InsertPolicy::FAIL_IF_EXISTS);
+
+ /**
+ * @brief Merge the given tree with this tree, i.e., insert all entries
+ * of the given tree into this tree.
+ *
+ * The tree must not be this tree or a parent entry of this tree. Files present in
+ * both tree will be replaced by files in the given tree. The overwrites parameter can
+ * be used to track the replaced files. After a merge, the source tree will be
+ * empty but still attached to its parent.
+ *
+ * Note that the merge process makes no distinction between files and directories
+ * when merging: if a directory is present in this tree and a file from source
+ * is in conflict with it, the tree will be removed and the file inserted; if a file
+ * is in this tree and a directory from source is in conflict with it, the file will
+ * be replaced with the directory.
+ *
+ * This method invalidates iterators to this tree, all the subtrees under this tree
+ * present in the given path, and all the subtrees of the given source.
+ *
+ * @param source Tree to merge.
+ * @param overwrites If not null, can be used to create a mapping from
+ * overriden file to new files.
+ *
+ * @return the number of overwritten entries, or MERGE_FAILED if the merge
+ * failed (e.g. because the source is a parent of this tree).
+ */
+ std::size_t merge(std::shared_ptr<IFileTree> source,
+ OverwritesType* overwrites = nullptr);
+
+ /**
+ * @brief Move the given entry to the given path under this tree.
+ *
+ * The entry must not be a parent tree of this tree. This method can also be used
+ * to rename entries.
+ *
+ * If the insert policy if FAIL_IF_EXISTS, the call will fail if an entry
+ * at the same location already exists. If the policy is REPLACE, an existing
+ * entry will be replaced. If MERGE, the entry will be merged with the existing
+ * one (if the entry is a file, and a file exists, the file will be replaced).
+ *
+ * This method invalidates iterator to this tree, to the parent tree of the given
+ * entry, and to subtrees of this tree if the insert policy is MERGE.
+ *
+ * @param entry Entry to insert.
+ * @param path The path to move the entry to. If the path ends with / or \,
+ * the entry will be inserted in the corresponding directory instead of replacing
+ * it. If the given path is empty (`""`), this is equivalent to `insert()`.
+ * @param insertPolicy Policy to use on conflict.
+ *
+ * @return true if the entry was moved correctly, false otherwize.
+ */
+ bool move(std::shared_ptr<FileTreeEntry> entry, QString path = "",
+ InsertPolicy insertPolicy = InsertPolicy::FAIL_IF_EXISTS);
+
+ /**
+ * @brief Copy the given entry to the given path under this tree.
+ *
+ * The entry must not be a parent tree of this tree.
+ *
+ * If the insert policy if FAIL_IF_EXISTS, the call will fail if an entry
+ * at the same location already exists. If the policy is REPLACE, an existing
+ * entry will be replaced. If MERGE, the entry will be merged with the existing
+ * one (if the entry is a file, and a file exists, the file will be replaced).
+ *
+ * This method invalidates iterator to this tree and to subtrees of this tree if
+ * the insert policy is MERGE. The given entry is left untouched
+ *
+ * @param entry Entry to copy.
+ * @param path The path to copy the entry to. If the path ends with / or \,
+ * the entry will be inserted in the corresponding directory instead of replacing
+ * it.
+ * @param insertPolicy Policy to use on conflict.
+ *
+ * @return the copy of the entry if it was copied correctly, a null pointer otherwise.
+ */
+ std::shared_ptr<FileTreeEntry>
+ copy(std::shared_ptr<const FileTreeEntry> entry, QString path = "",
+ InsertPolicy insertPolicy = InsertPolicy::FAIL_IF_EXISTS);
+
+ /**
+ * @brief Delete the given entry.
+ *
+ * @param entry Entry to delete. The entry must belongs to this tree (and
+ * not to a subtree).
+ *
+ * @return an iterator following the removed entry (might be the end
+ * iterator if the entry was not found or was the last).
+ */
+ iterator erase(std::shared_ptr<FileTreeEntry> entry);
+
+ /**
+ * @brief Delete the entry with the given name.
+ *
+ * This method does not recurse into subtrees, so the entry should be
+ * accessible directly from this tree.
+ *
+ * @param name Name of the entry to delete.
+ *
+ * @return a pair containing an iterator following the removed entry (might
+ * be the end iterator if the entry was not found or was the last) and
+ * the removed entry (or a null pointer if the entry was not found).
+ */
+ std::pair<iterator, std::shared_ptr<FileTreeEntry>> erase(QString name);
+
+ /**
+ * @brief Delete (detach) all the entries from this tree
+ *
+ * This method will go through the entries in this tree and stop at the first
+ * entry that cannot be deleted, this means that the tree can be partially cleared.
+ *
+ * @return true if all entries could be deleted, false otherwize.
+ */
+ bool clear();
+
+ /**
+ * @brief Delete the entries with the given names from the tree.
+ *
+ * This method does not recurse into subtrees, so the entry should be
+ * accessible directly from this tree. This method invalidates iterators.
+ *
+ * @param names Names of the entries to delete.
+ *
+ * @return the number of deleted entry.
+ */
+ std::size_t removeAll(QStringList names);
+
+ /**
+ * @brief Delete the entries that match the given predicate from the tree.
+ *
+ * This method does not recurse into subtrees, so the entry should be
+ * accessible directly from this tree. This method invalidates iterators.
+ *
+ * @param predicate Predicate that should return true for entries to delete.
+ *
+ * @return the number of deleted entry.
+ */
+ std::size_t
+ removeIf(std::function<bool(std::shared_ptr<FileTreeEntry> const& entry)> predicate);
+
+public: // Inherited methods:
+ /**
+ * @brief Retrieve the tree corresponding to this entry. Returns a null pointer
+ * if this entry corresponds to a file.
+ *
+ * @return the tree corresponding to this entry, or a null pointer if
+ * isDir() is false.
+ */
+ virtual std::shared_ptr<IFileTree> astree() override
+ {
+ return std::dynamic_pointer_cast<IFileTree>(shared_from_this());
+ }
+
+ /**
+ * @brief Retrieve the tree corresponding to this entry. Returns a null pointer
+ * if this entry corresponds to a file.
+ *
+ * @return the tree corresponding to this entry, or a null pointer if
+ * isDir() is false.
+ */
+ virtual std::shared_ptr<const IFileTree> astree() const override
+ {
+ return std::dynamic_pointer_cast<const IFileTree>(shared_from_this());
+ }
+
+public: // Destructor:
+ virtual ~IFileTree() {}
+
+public: // Deleted operators:
+ IFileTree(IFileTree const&) = delete;
+ IFileTree(IFileTree&&) = delete;
+
+ IFileTree& operator=(IFileTree const&) = delete;
+ IFileTree& operator=(IFileTree&&) = delete;
+
+ /**
+ * A few implementation details here for implementing classes. While there are
+ * multiple virtual public methods, most implementation should not have to
+ * re-implement them.
+ *
+ * There are three pure virtual methods that needs to be implemented by any child
+ * class:
+ * - makeDirectory(): used to create directories - this method serves to create
+ * directory that may or may not existing in the underlying source. Implementing class
+ * do not have to rely on this for `doPopulate()`. This method is also called when new
+ * directory needs to be created (addDirectory, insert, createOrphanTree,
+ * merge, etc.), and may return a null pointer to indicate that the operations failed
+ * or is not permitted.
+ * - doClone(): called when a tree needs to be cloned (e.g., for a copy) - this
+ * methods does not copy the subtrees, it should only create an empty tree equivalent
+ * to the current tree.
+ * - doPopulate(): called when a tree have to be populated.
+ *
+ * The other commons methods that can be re-implemented are:
+ * - makeFile(), this is used to create new file - this is very similar to
+ * makeDirectory() except that it has a default implementation that simply creates a
+ * FileTreeEntry.
+ * - beforeInsert(), beforeReplace() and beforeRemove(): these can be implemented to
+ * 1) prevent some operations, 2) perform operations on the actual tree (e.g., move a
+ * file on the disk).
+ */
+protected:
+ friend class FileTreeEntry;
+
+ /**
+ * Split the given path into parts.
+ *
+ * @param path The path to split.
+ *
+ * @return a list containing the section of the path.
+ */
+ static QStringList splitPath(QString path);
+
+ /**
+ * @brief Called before replacing an entry with another one.
+ *
+ * This is a pre-method meaning that it can be used to prevent an operation on
+ * the tree.
+ *
+ * This method is for internal usage only and is called when an entry is going to
+ * be replaced by another (because there is name conflict).
+ *
+ * The base implementation of this method does nothing (the actual replacement is
+ * made elsewhere). This method can be used to prevent a replacement by returning
+ * false.
+ *
+ * @param dstTree Tree containing the destination entry.
+ * @param destination Entry that will be replaced.
+ * @param source Entry that will replace the destination.
+ *
+ * @return true if the entry can be replaced, false otherwize.
+ */
+ virtual bool beforeReplace(IFileTree const* dstTree, FileTreeEntry const* destination,
+ FileTreeEntry const* source);
+
+ /**
+ * @brief Called before inserting an entry in a tree.
+ *
+ * This is a pre-method meaning that it can be used to prevent an operation on
+ * the tree.
+ *
+ * This method is for internal usage only and is called when an entry is going to
+ * be inserted in a tree. This method is not called after makeFile() or
+ * makeDirectory() so those should be used to prevent creation of files and
+ * directories.
+ *
+ * The base implementation of this method does nothing (the actual insertion is
+ * made elsewhere). This method can be used to prevent an insertion by returning
+ * false.
+ *
+ * @param tree Tree into which the entry will be inserted.
+ * @param source Entry that will be inserted the destination.
+ *
+ * @return true if the entry can be inserted, false otherwize.
+ */
+ virtual bool beforeInsert(IFileTree const* entry, FileTreeEntry const* name);
+
+ /**
+ * @brief Called before removing an entry.
+ *
+ * This is a pre-method meaning that it can be used to prevent an operation on
+ * the tree.
+ *
+ * This method is for internal usage only and is called when an entry is going to
+ * be removed from a tree.
+ *
+ * The base implementation of this method does nothing (the actual removal is
+ * made elsewhere). This method can be used to prevent it by returning false.
+ *
+ * @param tree Tree containing the entry.
+ * @param entry Entry that will be removed.
+ *
+ * @return true if the entry can be removed, false otherwize.
+ */
+ virtual bool beforeRemove(IFileTree const* entry, FileTreeEntry const* name);
+
+ /**
+ * @brief Create a new file under this tree.
+ *
+ * @param parent The current tree, without const-qualification.
+ * @param name Name of the file.
+ *
+ * @return the created file.
+ */
+ virtual std::shared_ptr<FileTreeEntry>
+ makeFile(std::shared_ptr<const IFileTree> parent, QString name) const;
+
+ /**
+ * @brief Create a new entry corresponding to a subtree under this tree.
+ *
+ * @param parent The current tree, without const-qualification.
+ * @param name Name of the directory.
+ *
+ * @return the entry for the created directory.
+ */
+ virtual std::shared_ptr<IFileTree>
+ makeDirectory(std::shared_ptr<const IFileTree> parent, QString name) const = 0;
+
+ /**
+ * @brief Method that child classes should implement.
+ *
+ * This method should populate the given entries of this tree by using the makeFile
+ * and makeTree method. The usage of the makeFile and makeTree method is not mandatory
+ * here.
+ *
+ * If the implementation can populate the vector of entries in order, it is possible
+ * to return false to tell IFileTree not to re-sort the vector. If sorted, directories
+ * should be before files, and both directories and files should be sorted by name in
+ * a case-insensitive way.
+ *
+ * @param parent The current tree, without const-qualification.
+ * @param entries Vector of entries to populate.
+ *
+ * @return true if the vector of entries is already sorted, false if it must be
+ * sorted.
+ */
+ virtual bool
+ doPopulate(std::shared_ptr<const IFileTree> parent,
+ std::vector<std::shared_ptr<FileTreeEntry>>& entries) const = 0;
+
+ /**
+ * @brief Creates a copy of this file tree.
+ *
+ * This methods is called by clone() in order to copy child class attributes. This
+ * method should basically returns a new copy of the tree with the attributes added by
+ * the implementation copied (and nothing else).
+ *
+ * @return the cloned tree.
+ */
+ virtual std::shared_ptr<IFileTree> doClone() const = 0;
+
+protected: // Constructor
+ /**
+ * @brief Creates a new tree. This method takes no parameter since, due to the virtual
+ * inheritance, child classes must directly call the FileTreeEntry constructor.
+ */
+ IFileTree();
+
+ /**
+ *
+ */
+ std::shared_ptr<FileTreeEntry> clone() const override;
+
+ /**
+ *
+ */
+private: // Private stuff, look away!
+ /**
+ * @brief Retrieve the entry corresponding to the given path.
+ *
+ * @param path Path to entry.
+ * @param matchType Type of file to check.
+ *
+ * @return the entry, or a null pointer if the entry did not exist.
+ */
+ std::shared_ptr<FileTreeEntry> fetchEntry(QStringList path, FileTypes matchType);
+ std::shared_ptr<const FileTreeEntry> fetchEntry(QStringList const& path,
+ FileTypes matchType) const;
+
+ /**
+ * @brief Merge the source tree into the destination tree. On conflict, the source
+ * entries are always chosen.
+ *
+ * @param destination Destination tree.
+ * @param source Source tree.
+ *
+ * @return the number of overwritten entries.
+ */
+ std::size_t mergeTree(std::shared_ptr<IFileTree> destination,
+ std::shared_ptr<IFileTree> source, OverwritesType* overwrites);
+
+ /**
+ * @brief Create a new subtree under the given tree.
+ *
+ * This method will create missing folders in the given path and will not fail if the
+ * directory already exists but will fail the given path contains "." or "..".
+ *
+ * @param begin, end Range of section of the path.
+ *
+ * @return the entry corresponding to the create tree, or a null pointer if the tree
+ * was not created.
+ */
+ std::shared_ptr<IFileTree> createTree(QStringList::const_iterator begin,
+ QStringList::const_iterator end);
+
+ // Indicate if this tree has been populated:
+ mutable std::atomic<bool> m_Populated{false};
+ mutable std::once_flag m_OnceFlag;
+ mutable std::vector<std::shared_ptr<FileTreeEntry>> m_Entries;
+
+ /**
+ * @brief Retrieve the vector of entries after populating it if required.
+ *
+ * @return the vector of entries.
+ */
+ std::vector<std::shared_ptr<FileTreeEntry>>& entries();
+ const std::vector<std::shared_ptr<FileTreeEntry>>& entries() const;
+
+ /**
+ * @brief Populate the internal vectors and update the flag.
+ */
+ void populate() const;
+};
+
+} // namespace MOBase
+
+// __has_cpp_attribute(__cpp_lib_generator) does not seem to work, maybe some conflict
+// with Qt?
+#ifdef __cpp_lib_generator
+
+#include "ifiletree_utils.h"
+
+#endif
+
+#endif
diff --git a/libs/uibase/include/uibase/ifiletree_utils.h b/libs/uibase/include/uibase/ifiletree_utils.h
new file mode 100644
index 0000000..9340788
--- /dev/null
+++ b/libs/uibase/include/uibase/ifiletree_utils.h
@@ -0,0 +1,82 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2020 MO2 Team. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef UIBASE_IFILETREE_UTILS_H
+#define UIBASE_IFILETREE_UTILS_H
+
+#include <generator>
+
+#include <QString>
+
+#include "dllimport.h"
+#include "exceptions.h"
+#include "ifiletree.h"
+
+namespace MOBase
+{
+
+/**
+ * @brief Exception thrown when an invalid glob pattern is specified.
+ */
+struct QDLLEXPORT InvalidGlobPatternException : public Exception
+{
+ using Exception::Exception;
+};
+
+enum class GlobPatternType
+{
+ /**
+ * @brief Glob mode, similar to python pathlib.Path.glob function
+ */
+ GLOB,
+
+ /**
+ * @brief Regex mode, each part of the pattern (between / or \) is considered a
+ * regex, except for ** which is still considered as glob.
+ */
+ REGEX
+};
+
+/**
+ * @brief Walk this tree, returning entries.
+ *
+ * During the walk, parent tree are guaranteed to be visited before their childrens.
+ * The current tree is not included in the return generator.
+ *
+ * @return a generator over the entries.
+ */
+QDLLEXPORT std::generator<std::shared_ptr<const FileTreeEntry>>
+walk(std::shared_ptr<const IFileTree> fileTree);
+
+/**
+ * @brief Glob entries matching the given pattern in this tree.
+ *
+ * @param pattern Glob pattern to match, using the same syntax as QRegularExpression.
+ * @param patternType Type of the pattern.
+ *
+ * @return a generator over the entries matching the given pattern.
+ */
+QDLLEXPORT std::generator<std::shared_ptr<const FileTreeEntry>>
+glob(std::shared_ptr<const IFileTree> fileTree, QString pattern,
+ GlobPatternType patternType = GlobPatternType::GLOB);
+
+} // namespace MOBase
+
+#endif
diff --git a/libs/uibase/include/uibase/iinstallationmanager.h b/libs/uibase/include/uibase/iinstallationmanager.h
new file mode 100644
index 0000000..ef02b29
--- /dev/null
+++ b/libs/uibase/include/uibase/iinstallationmanager.h
@@ -0,0 +1,128 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IINSTALLATIONMANAGER_H
+#define IINSTALLATIONMANAGER_H
+
+#include <QList>
+#include <QString>
+
+#include "ifiletree.h"
+#include "iplugininstaller.h"
+
+namespace MOBase
+{
+
+template <typename T>
+class GuessedValue;
+
+/**
+ * @brief The IInstallationManager class.
+ */
+class IInstallationManager
+{
+
+public:
+ virtual ~IInstallationManager() {}
+
+ /**
+ * @return the extensions of archives supported by this installation manager.
+ */
+ virtual QStringList getSupportedExtensions() const = 0;
+
+ /**
+ * @brief Extract the specified file from the currently opened archive to a temporary
+ * location.
+ *
+ * This method cannot be used to extract directory.
+ *
+ * @param entry Entry corresponding to the file to extract.
+ * @param silent If true, the dialog showing extraction progress will not be shown.
+ *
+ * @return the absolute path to the temporary file, or an empty string if the
+ * file was not extracted.
+ *
+ * @note The call will fail with an exception if no archive is open (plugins deriving
+ * from IPluginInstallerSimple can rely on that, custom installers should not).
+ * @note The temporary file is automatically cleaned up after the installation.
+ * @note This call can be very slow if the archive is large and "solid".
+ */
+ virtual QString extractFile(std::shared_ptr<const FileTreeEntry> entry,
+ bool silent = false) = 0;
+
+ /**
+ * @brief Extract the specified files from the currently opened archive to a temporary
+ * location.
+ *
+ * @param entres Entries corresponding to the files to extract.
+ * @param silent If true, the dialog showing extraction progress will not be shown.
+ *
+ * This method cannot be used to extract directory.
+ *
+ * @return the list of absolute paths to the temporary files.
+ *
+ * @note The call will fail with an exception if no archive is open (plugins deriving
+ * from IPluginInstallerSimple can rely on that, custom installers should not).
+ * @note The temporary file is automatically cleaned up after the installation.
+ * @note This call can be very slow if the archive is large and "solid".
+ *
+ * The flatten argument is not present here while it is present in the deprecated
+ * QStringList version for multiple reasons: 1) it was never used, 2) it is kind of
+ * fishy because there is no way to know if a file is going to be overriden, 3) it is
+ * quite easy to flatten a IFileTree and thus to given a list of entries flattened
+ * (this was not possible with the QStringList version since these were based on the
+ * name of the file inside the archive).
+ */
+ virtual QStringList
+ extractFiles(std::vector<std::shared_ptr<const FileTreeEntry>> const& entries,
+ bool silent = false) = 0;
+
+ /**
+ * @brief Create a new file on the disk corresponding to the given entry.
+ *
+ * This method can be used by installer that needs to create files that are not in the
+ * original archive. At the end of the installation, if there are entries in the final
+ * tree that were used to create files, the corresponding files will be moved to the
+ * mod folder.
+ *
+ * @param entry The entry for which a temporary file should be created.
+ *
+ * @return the path to the created file, or an empty QString() if the file could not
+ * be created.
+ */
+ virtual QString createFile(std::shared_ptr<const MOBase::FileTreeEntry> entry) = 0;
+
+ /**
+ * @brief Installs the given archive.
+ *
+ * @param modName Suggested name of the mod.
+ * @param archiveFile Path to the archive to install.
+ * @param modId ID of the mod, if available.
+ *
+ * @return the installation result.
+ */
+ virtual IPluginInstaller::EInstallResult
+ installArchive(MOBase::GuessedValue<QString>& modName, const QString& archiveFile,
+ int modID = 0) = 0;
+};
+
+} // namespace MOBase
+
+#endif // IINSTALLATIONMANAGER_H
diff --git a/libs/uibase/include/uibase/iinstance.h b/libs/uibase/include/uibase/iinstance.h
new file mode 100644
index 0000000..1c59d4a
--- /dev/null
+++ b/libs/uibase/include/uibase/iinstance.h
@@ -0,0 +1,61 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IINSTANCE_H
+#define IINSTANCE_H
+
+#include <QString>
+
+namespace MOBase
+{
+class IPluginGame;
+
+/**
+ * @brief Represents a Mod Organizer instance, either global or portable.
+ */
+class IInstance
+{
+public:
+ /**
+ * @return The instance name; this is the directory name or "Portable" for portable
+ * instances.
+ */
+ virtual QString displayName() const = 0;
+
+ /**
+ * @return The name of the game managed by this instance, or an empty string if the
+ * INI file could not be read.
+ */
+ virtual QString gameName() const = 0;
+
+ /**
+ * @return The directory where the game is installed, or an empty string if the INI
+ * file could not be read.
+ */
+ virtual QString gameDirectory() const = 0;
+
+ /**
+ * @return true if this is a portable instance, false if it is a global one.
+ */
+ virtual bool isPortable() const = 0;
+};
+} // namespace MOBase
+
+#endif // IINSTANCE_H
diff --git a/libs/uibase/include/uibase/iinstancemanager.h b/libs/uibase/include/uibase/iinstancemanager.h
new file mode 100644
index 0000000..c57479a
--- /dev/null
+++ b/libs/uibase/include/uibase/iinstancemanager.h
@@ -0,0 +1,66 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IINSTANCEMANAGER_H
+#define IINSTANCEMANAGER_H
+
+#include <QDir>
+#include <QString>
+
+#include <memory>
+#include <vector>
+
+namespace MOBase
+{
+class IInstance;
+
+/**
+ * @brief Interface to the instance manager of Mod Organizer.
+ */
+class IInstanceManager
+{
+public:
+ /**
+ * @return the current instance.
+ */
+ virtual std::shared_ptr<IInstance> currentInstance() const = 0;
+
+ /**
+ * @return The list of absolute paths to all global instances.
+ *
+ * @note This does not include portable instances.
+ */
+ virtual std::vector<QDir> globalInstancePaths() const = 0;
+
+ /**
+ * @brief Retrieve the global instance corresponding to the given name.
+ *
+ * @param instanceName Name of the global instance to retrieve. This is the directory
+ * name of the instance.
+ *
+ * @return the global instance corresponding to the given name, or nullptr if no such
+ * instance exists.
+ */
+ virtual std::shared_ptr<const IInstance>
+ getGlobalInstance(const QString& instanceName) const = 0;
+};
+} // namespace MOBase
+
+#endif // IINSTANCEMANAGER_H
diff --git a/libs/uibase/include/uibase/imodinterface.h b/libs/uibase/include/uibase/imodinterface.h
new file mode 100644
index 0000000..a2684b4
--- /dev/null
+++ b/libs/uibase/include/uibase/imodinterface.h
@@ -0,0 +1,343 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IMODINTERFACE_H
+#define IMODINTERFACE_H
+
+#include <memory>
+#include <set>
+#include <utility>
+
+#include <QColor>
+#include <QDateTime>
+#include <QList>
+#include <QString>
+
+namespace MOBase
+{
+
+class VersionInfo;
+class IFileTree;
+
+enum class EndorsedState
+{
+ ENDORSED_FALSE,
+ ENDORSED_TRUE,
+ ENDORSED_UNKNOWN,
+ ENDORSED_NEVER
+};
+
+enum class TrackedState
+{
+ TRACKED_FALSE,
+ TRACKED_TRUE,
+ TRACKED_UNKNOWN,
+};
+
+class IModInterface
+{
+public:
+ virtual ~IModInterface() {}
+
+public: // Non-meta related information:
+ /**
+ * @return the name of the mod.
+ */
+ virtual QString name() const = 0;
+
+ /**
+ * @return the absolute path to the mod to be used in file system operations.
+ */
+ virtual QString absolutePath() const = 0;
+
+public: // Meta-related information:
+ /**
+ * @return the comments for this mod, if any.
+ */
+ virtual QString comments() const = 0;
+
+ /**
+ * @return the notes for this mod, if any.
+ */
+ virtual QString notes() const = 0;
+
+ /**
+ * @brief Retrieve the short name of the game associated with this mod. This may
+ * differ from the current game plugin (e.g. you can install a Skyrim LE game in a SSE
+ * installation).
+ *
+ * @return the name of the game associated with this mod.
+ */
+ virtual QString gameName() const = 0;
+
+ /**
+ * @return the name of the repository from which this mod was installed.
+ */
+ virtual QString repository() const = 0;
+
+ /**
+ * @return the Nexus ID of this mod.
+ */
+ virtual int nexusId() const = 0;
+
+ /**
+ * @return the current version of this mod.
+ */
+ virtual VersionInfo version() const = 0;
+
+ /**
+ * @return the newest version of thid mod (as known by MO2). If this matches
+ * version(), then the mod is up-to-date.
+ */
+ virtual VersionInfo newestVersion() const = 0;
+
+ /**
+ * @return the ignored version of this mod (for update), or an invalid version if the
+ * user did not ignore version for this mod.
+ */
+ virtual VersionInfo ignoredVersion() const = 0;
+
+ /**
+ * @return the absolute path to the file that was used to install this mod.
+ */
+ virtual QString installationFile() const = 0;
+
+ virtual std::set<std::pair<int, int>> installedFiles() const = 0;
+
+ /**
+ * @return true if this mod was marked as converted by the user.
+ *
+ * @note When a mod is for a different game, a flag is shown to users to warn them,
+ * but they can mark mods as converted to remove this flag.
+ */
+ virtual bool converted() const = 0;
+
+ /**
+ * @return true if th is mod was marked as containing valid game data.
+ *
+ * @note MO2 uses ModDataChecker to check the content of mods, but sometimes these
+ * fail, in which case mods are incorrectly marked as 'not containing valid games
+ * data'. Users can choose to mark these mods as valid to hide the warning / flag.
+ */
+ virtual bool validated() const = 0;
+
+ /**
+ * @return the color of the 'Notes' column chosen by the user.
+ */
+ virtual QColor color() const = 0;
+
+ /**
+ * @return the URL of this mod, or an empty QString() if no URL is associated
+ * with this mod.
+ */
+ virtual QString url() const = 0;
+
+ /**
+ * @return the ID of the primary category of this mod.
+ */
+ virtual int primaryCategory() const = 0;
+
+ /**
+ * @return the list of categories this mod belongs to.
+ */
+ virtual QStringList categories() const = 0;
+
+ /**
+ * @return the mod author.
+ */
+ virtual QString author() const = 0;
+
+ /**
+ * @return the mod uploader.
+ */
+ virtual QString uploader() const = 0;
+
+ /**
+ * @return the URL of the uploader.
+ */
+ virtual QString uploaderUrl() const = 0;
+
+ /**
+ * @return the tracked state of this mod.
+ */
+ virtual TrackedState trackedState() const = 0;
+
+ /**
+ * @return the endorsement state of this mod.
+ */
+ virtual EndorsedState endorsedState() const = 0;
+
+ /**
+ * @brief Retrieve a file tree corresponding to the underlying disk content
+ * of this mod.
+ *
+ * The file tree should not be cached since it is already cached and updated when
+ * required.
+ *
+ * @return a file tree representing the content of this mod.
+ */
+ virtual std::shared_ptr<const MOBase::IFileTree> fileTree() const = 0;
+
+ /**
+ * @return true if this object represents the overwrite mod.
+ */
+ virtual bool isOverwrite() const = 0;
+
+ /**
+ * @return true if this object represents a backup.
+ */
+ virtual bool isBackup() const = 0;
+
+ /**
+ * @return true if this object represents a separator.
+ */
+ virtual bool isSeparator() const = 0;
+
+ /**
+ * @return true if this object represents a foreign mod.
+ */
+ virtual bool isForeign() const = 0;
+
+public: // Mutable operations:
+ /**
+ * @brief set/change the version of this mod
+ * @param version new version of the mod
+ */
+ virtual void setVersion(const VersionInfo& version) = 0;
+
+ /**
+ * @brief sets the installation file for this mod
+ * @param fileName archive file name
+ */
+ virtual void setInstallationFile(const QString& fileName) = 0;
+
+ /**
+ * @brief set/change the latest known version of this mod
+ * @param version newest known version of the mod
+ */
+ virtual void setNewestVersion(const VersionInfo& version) = 0;
+
+ /**
+ * @brief set endorsement state of the mod
+ * @param endorsed new endorsement state
+ */
+ virtual void setIsEndorsed(bool endorsed) = 0;
+
+ /**
+ * @brief sets the mod id on nexus for this mod
+ * @param the new id to set
+ */
+ virtual void setNexusID(int nexusID) = 0;
+
+ /**
+ * @brief sets the category id from a nexus category id. Conversion to MO id happens
+ * internally
+ * @param categoryID the nexus category id
+ * @note if a mapping is not possible, the category is set to the default value
+ */
+ virtual void addNexusCategory(int categoryID) = 0;
+
+ /**
+ * @brief assign a category to the mod. If the named category doesn't exist it is
+ * created
+ * @param categoryName name of the new category
+ */
+ virtual void addCategory(const QString& categoryName) = 0;
+
+ /**
+ * @brief unassign a category from this mod.
+ * @param categoryName name of the category to be removed
+ * @return true if the category was removed successfully, false if no such category
+ * was assigned
+ */
+ virtual bool removeCategory(const QString& categoryName) = 0;
+
+ /**
+ * @brief set/change the source game of this mod
+ *
+ * @param gameName the source game shortName
+ */
+ virtual void setGameName(const QString& gameName) = 0;
+
+ /**
+ * @brief Set a URL for this mod.
+ *
+ * @param url The URL of this mod.
+ */
+ virtual void setUrl(const QString& url) = 0;
+
+public: // Plugin operations:
+ /**
+ * @brief Retrieve the specified setting in this mod for a plugin.
+ *
+ * @param pluginName Name of the plugin for which to retrieve a setting. This should
+ * always be IPlugin::name() unless you have a really good reason to access
+ * settings of another plugin.
+ * @param key Identifier of the setting.
+ * @param defaultValue The default value to return if the setting does not exist.
+ *
+ * @return the setting, if found, or the default value.
+ */
+ virtual QVariant pluginSetting(const QString& pluginName, const QString& key,
+ const QVariant& defaultValue = QVariant()) const = 0;
+
+ /**
+ * @brief Retrieve the settings in this mod for a plugin.
+ *
+ * @param pluginName Name of the plugin for which to retrieve settings. This should
+ * always be IPlugin::name() unless you have a really good reason to access settings
+ * of another plugin.
+ *
+ * @return a map from setting key to value. The map is empty if there are not settings
+ * for this mod.
+ */
+ virtual std::map<QString, QVariant>
+ pluginSettings(const QString& pluginName) const = 0;
+
+ /**
+ * @brief Set the specified setting in this mod for a plugin.
+ *
+ * @param pluginName Name of the plugin for which to retrieve a setting. This should
+ * always be IPlugin::name() unless you have a really good reason to access settings
+ * of another plugin.
+ * @param key Identifier of the setting.
+ * @param value New value for the setting to set.
+ *
+ * @return true if the setting was set correctly, false otherwise.
+ */
+ virtual bool setPluginSetting(const QString& pluginName, const QString& key,
+ const QVariant& value) = 0;
+
+ /**
+ * @brief Remove all the settings of the specified plugin from th is mod.
+ *
+ * @param pluginName Name of the plugin for which settings should be removed. This
+ * should always be IPlugin::name() unless you have a really good reason to access
+ * settings of another plugin.
+ *
+ * @return the old settings from the given plugin, as returned by `pluginSettings()`.
+ */
+ virtual std::map<QString, QVariant>
+ clearPluginSettings(const QString& pluginName) = 0;
+};
+
+} // namespace MOBase
+
+#endif // IMODINTERFACE_H
diff --git a/libs/uibase/include/uibase/imodlist.h b/libs/uibase/include/uibase/imodlist.h
new file mode 100644
index 0000000..7f7a2a1
--- /dev/null
+++ b/libs/uibase/include/uibase/imodlist.h
@@ -0,0 +1,229 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2013 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IMODLIST_H
+#define IMODLIST_H
+
+#include <functional>
+#include <map>
+
+#include <QList>
+#include <QString>
+
+#include "imodinterface.h"
+#include "iprofile.h"
+
+namespace MOBase
+{
+
+/**
+ * @brief interface to the mod-list
+ * @note all api functions in this interface work need the internal name of a mod to
+ * find a mod. For regular mods (mods the user installed) the display name (as shown to
+ * the user) and internal name are identical. For other mods (non-MO mods) there is
+ * currently no way to translate from display name to internal name because the display
+ * name might not me un-ambiguous.
+ */
+class IModList
+{
+
+public:
+ enum ModState
+ {
+ STATE_EXISTS = 0x00000001,
+ STATE_ACTIVE = 0x00000002,
+ STATE_ESSENTIAL = 0x00000004,
+ STATE_EMPTY = 0x00000008,
+ STATE_ENDORSED = 0x00000010,
+ STATE_VALID = 0x00000020,
+ STATE_ALTERNATE = 0x00000040
+ };
+
+ Q_DECLARE_FLAGS(ModStates, ModState)
+
+public:
+ virtual ~IModList() {}
+
+ /**
+ * @brief retrieves the display name of a mod from it's internal name
+ * @param internalName the internal name
+ * @return a string intended to identify the name to the user
+ * @note If you received an internal name from an api (i.e. IPluginList::origin) then
+ * you should use that name to identify the mod to all other api calls but use this
+ * function to retrieve the name to show to the user.
+ */
+ virtual QString displayName(const QString& internalName) const = 0;
+
+ /**
+ * @brief Retrieve a list of all installed mod names.
+ *
+ * @return list of mods (internal names).
+ */
+ virtual QStringList allMods() const = 0;
+
+ /**
+ * @brief Retrieve the list of installed mod names, sorted by current profile
+ * priority.
+ *
+ * @param profile The profile to use for the priority. If nullptr, the current
+ * profile is used.
+ *
+ * @return list of mods (internal names), sorted by priority.
+ */
+ virtual QStringList
+ allModsByProfilePriority(MOBase::IProfile* profile = nullptr) const = 0;
+
+ /**
+ * @brief Retrieve the mod with the given name.
+ *
+ * @param name Name of the mod to retrieve.
+ *
+ * @return the mod with the given name, or a null pointer if there is no mod with this
+ * name.
+ */
+ virtual IModInterface* getMod(const QString& name) const = 0;
+
+ /**
+ * @brief Remove a mod (from disc and from the ui).
+ *
+ * @param mod The mod to remove.
+ *
+ * @return true on success, false on error.
+ */
+ virtual bool removeMod(MOBase::IModInterface* mod) = 0;
+
+ /**
+ * @brief Rename the given mod.
+ *
+ * This invalidate the mod so you should use the returned value afterwards.
+ *
+ * @param mod The mod to rename.
+ *
+ * @return the new mod (after renaming) on success, a null pointer on error.
+ */
+ virtual MOBase::IModInterface* renameMod(MOBase::IModInterface* mod,
+ const QString& name) = 0;
+
+ /**
+ * @brief retrieve the state of a mod
+ * @param name name of the mod
+ * @return a bitset of information about the mod
+ */
+ virtual ModStates state(const QString& name) const = 0;
+
+ /**
+ * @brief enable or disable a mod
+ *
+ * @param name name of the mod
+ * @param active if true the mod is enabled, otherwise it's disabled
+ *
+ * @return true on success, false if the mod name is not valid
+ *
+ * @note calling this will cause MO to re-evaluate its virtual file system so this is
+ * fairly expensive
+ */
+ virtual bool setActive(const QString& name, bool active) = 0;
+
+ /**
+ * @brief enable or disable a list of mod
+ *
+ * @param names names of the mod
+ * @param active if true mods are enabled, otherwise they are disabled
+ *
+ * @return the number of mods successfully enabled or disabled
+ *
+ * @note calling this will cause MO to re-evaluate its virtual file system so this is
+ * fairly expensive
+ */
+ virtual int setActive(const QStringList& names, bool active) = 0;
+
+ /**
+ * @brief retrieve the priority of a mod
+ * @param name name of the mod
+ * @return the priority (the higher the more important). Returns -1 if the mod doesn't
+ * exist
+ */
+ virtual int priority(const QString& name) const = 0;
+
+ /**
+ * @brief change the priority of a mod
+ * @param name name of the mod
+ * @param newPriority new priority of the mod
+ * @return true on success, false if the priority change was not possible. This is
+ * usually because one of the parameters is invalid
+ * @note Very important: newPriority is the new priority after the move. Keep in mind
+ * that the mod disappears from it's old location and all mods with higher priority
+ * than the moved mod decrease in priority by one.
+ */
+ virtual bool setPriority(const QString& name, int newPriority) = 0;
+
+ /**
+ * @brief Add a new callback to be called when a new mod is installed.
+ *
+ * Parameters of the callback:
+ * - The installed mod.
+ *
+ * @param func Function to called when a mod has been installed.
+ */
+ virtual bool onModInstalled(const std::function<void(IModInterface*)>& func) = 0;
+
+ /**
+ * @brief Add a new callback to be called when a mod is removed.
+ *
+ * Parameters of the callback:
+ * - The name of the removed mod.
+ *
+ * @param func Function to called when a mod has been removed.
+ */
+ virtual bool onModRemoved(const std::function<void(QString const&)>& func) = 0;
+
+ /**
+ * @brief Installs a handler for the event that the state of mods changed
+ * (enabled/disabled, endorsed, ...).
+ *
+ * Parameters of the callback:
+ * - Map containing the mods whose states have changed. Keys are mod names and
+ * values are mod states.
+ *
+ * @param func The signal to be called when the state of any mod changes.
+ *
+ * @return true if the handler was successfully installed, false otherwise (there is
+ * as of now no known reason this should fail).
+ */
+ virtual bool onModStateChanged(
+ const std::function<void(const std::map<QString, ModStates>&)>& func) = 0;
+
+ /**
+ * installs a handler for the event that a mod changes priority
+ * @param func the signal to be called when the priority of a mod changes
+ * (first parameter for the handler is the name of the mod, second is the old
+ * priority, third the new one)
+ * @return true if the handler was successfully installed (there is as of now no known
+ * reason this should fail)
+ */
+ virtual bool
+ onModMoved(const std::function<void(const QString&, int, int)>& func) = 0;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(IModList::ModStates)
+
+} // namespace MOBase
+
+#endif // IMODLIST_H
diff --git a/libs/uibase/include/uibase/imodrepositorybridge.h b/libs/uibase/include/uibase/imodrepositorybridge.h
new file mode 100644
index 0000000..f2bf539
--- /dev/null
+++ b/libs/uibase/include/uibase/imodrepositorybridge.h
@@ -0,0 +1,219 @@
+#ifndef INEXUSBRIDGE_H
+#define INEXUSBRIDGE_H
+
+#include "modrepositoryfileinfo.h"
+#include <QNetworkReply>
+#include <QObject>
+#include <QVariant>
+
+namespace MOBase
+{
+
+class QDLLEXPORT IModRepositoryBridge : public QObject
+{
+ Q_OBJECT
+public:
+ IModRepositoryBridge(QObject* parent = nullptr) : QObject(parent) {}
+
+ /**
+ * @brief request description for a mod
+ *
+ * @param modID id of the mod caller is interested in
+ * @param userData user data to be returned with the result
+ **/
+ virtual void requestDescription(QString gameName, int modID, QVariant userData) = 0;
+
+ /**
+ * @brief request a list of the files belonging to a mod
+ *
+ * @param modID id of the mod caller is interested in
+ * @param userData user data to be returned with the result
+ **/
+ virtual void requestFiles(QString gameName, int modID, QVariant userData) = 0;
+
+ /**
+ * @brief request info about a single file of a mod
+ *
+ * @param modID id of the mod caller is interested in
+ * @param fileID id of the file the caller is interested in
+ * @param userData user data to be returned with the result
+ **/
+ virtual void requestFileInfo(QString game, int modID, int fileID,
+ QVariant userData) = 0;
+
+ /**
+ * @brief request the download url of a file
+ *
+ * @param modID id of the mod caller is interested in
+ * @param fileID id of the file the caller is interested in
+ * @param userData user data to be returned with the result
+ **/
+ virtual void requestDownloadURL(QString gameName, int modID, int fileID,
+ QVariant userData) = 0;
+
+ /**
+ * @brief requestToggleEndorsement
+ * @param modID id of the mod caller is interested in
+ * @param userData user data to be returned with the result
+ */
+ virtual void requestToggleEndorsement(QString gameName, int modID, QString modVersion,
+ bool endorse, QVariant userData) = 0;
+
+private:
+ Q_DISABLE_COPY(IModRepositoryBridge)
+
+Q_SIGNALS:
+
+ /**
+ * @brief sent when the description for a mod is reported by the repository
+ * @param modID id of the mod for which the request was made
+ * @param userData the data that was included in the request
+ * @param resultData result data. this is a variant map. keys are strings. Value is
+ * usually also a string, wrapped in a variant.
+ * @note valid keys might change as the repository page is updated. For nexus, the
+ * following keys are valid as of this writing: 'allow_view', 'ip',
+ * 'one_week_ratings', 'date', 'pm_notify', 'OLD_mid', 'OLD_u_downloads', 'game_id',
+ * 'OLD_perm_use', 'mod_page_uri', 'allow_topics', 'has_hot_image', 'id',
+ * 'two_weeks_ratings', 'description', 'lastupdate', 'perm_convert', 'author',
+ * 'OLD_image', 'translation_of', 'OLD_mname', 'version', 'allow_rating',
+ * 'perm_useinstructions', 'featured_count', 'donate', 'type', 'perm_credits',
+ * 'hidden_reason', 'OLD_views', 'perm_upload', 'has_back_image', 'adult',
+ * 'allow_images', 'OLD_endorsements', 'OLD_size', 'name', 'commenting', 'moderate',
+ * 'language', 'perm_others', 'lastcomment', 'OLD_readme', 'summary', 'perm_modify',
+ * 'OLD_downloads', 'lock_comments', 'suggested_category', 'allow_tagging',
+ * 'published', 'perm_notes', 'category_id', 'thread_id', 'perm_use', 'wizard_steps'
+ * @note in the python interface use the onDescriptionAvailable call to register a
+ * callback for this signal. The signature of your callback must match the signature
+ * of this signal
+ * @note this interface is going to be changed at some point to replace resultData
+ * with a less "dynamic" data structure
+ */
+ void descriptionAvailable(QString gameName, int modID, QVariant userData,
+ QVariant resultData);
+
+ /**
+ * @brief sent when the list of files for a mod is reported by the repository
+ * @param modID id of the mod for which the request was made
+ * @param userData the data that was included in the request
+ * @param resultData a list of (already decoded) file information objects
+ * @note in the python interface use the onFilesAvailable call to register a callback
+ * for this signal. The signature of your callback must match the signature of this
+ * signal
+ */
+ void filesAvailable(QString gameName, int modID, QVariant userData,
+ const QList<ModRepositoryFileInfo*>& resultData);
+
+ /**
+ * @brief sent when information about a file is reported by the repository
+ * @param modID id of the mod for which the request was made
+ * @param fileID id of the the file for which information was requested
+ * @param userData the data that was included in the request
+ * @param resultData a variant map of information about the file.
+ * @note valid keys might change as the repository page is updated. For nexus, the
+ * following keys are valid as of this writing: 'count', 'requirements_alert',
+ * 'u_count', 'description', 'uri', 'size', 'owner_id', 'primary', 'manager',
+ * 'version', 'date', 'game_id', 'mod_id', 'category_id', 'id', 'name'
+ * @note in the python interface use the onFileInfoAvailable call to register a
+ * callback for this signal. The signature of your callback must match the signature
+ * of this signal
+ * @note if you intend to download this file you don't have to request this
+ * information manually. call IDownloadManager::startDownloadNexusFile and let the
+ * download manager figure things out
+ * @note this interface is going to be changed at some point to replace resultData
+ * with a less "dynamic" data structure
+ */
+ void fileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData,
+ QVariant resultData);
+
+ /**
+ * @brief sent when the list of download urls for a file is returned by the repository
+ * @param modID id of the mod for which the request was made
+ * @param fileID id of the file for which the downloads
+ * @param userData the data that was included in the request
+ * @param resultData a variant map of information about the url
+ * @note this function is not exposed to python. please use
+ * IDownloadManager::startDownloadNexusFile to download a file, this lets the download
+ * manager figure out the best server according to user preference and stuff
+ * @note this interface is going to be changed at some point to replace resultData
+ * with a less "dynamic" data structure
+ */
+ void downloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData,
+ QVariant resultData);
+
+ /**
+ * @brief sent when the endorsement data is returned from the API
+ * @param userData the data that was included in the request
+ * @param resultData new endorsement state as a boolean (wrapped in a qvariant)
+ * @note in the python interface use the onEndorsementsAvailable call to register a
+ * callback for this signal. The signature of your callback must match the signature
+ * of this signal
+ */
+ void endorsementsAvailable(QVariant userData, QVariant resultData);
+
+ /**
+ * @brief sent when the endorsement state of a mod was changed (only sent as a result
+ * of our request)
+ * @param modID id of the mod for which the request was made
+ * @param userData the data that was included in the request
+ * @param resultData new endorsement state as a boolean (wrapped in a qvariant)
+ * @note in the python interface use the onEndorsementToggled call to register a
+ * callback for this signal. The signature of your callback must match the signature
+ * of this signal
+ * @note this interface is going to be changed at some point to replace resultData
+ * with a boolean
+ */
+ void endorsementToggled(QString gameName, int modID, QVariant userData,
+ QVariant resultData);
+
+ /**
+ * @brief sent when the tracked mod data is returned from the API
+ * @param userData the data that was included in the request
+ * @param resultData new tracked state as a list of maps (keys: domain_name, mod_id)
+ * @note in the python interface use the ontrackedModsAvailable call to register a
+ * callback for this signal. The signature of your callback must match the signature
+ * of this signal
+ */
+ void trackedModsAvailable(QVariant userData, QVariant resultData);
+
+ /**
+ * @brief sent when the tracking state of a mod was changed (only sent as a result of
+ * our request)
+ * @param modID id of the mod for which the request was made
+ * @param userData the data that was included in the request
+ * @param tracked new tracking state
+ * @note in the python interface use the onTrackingToggled call to register a callback
+ * for this signal. The signature of your callback must match the signature of this
+ * signal
+ */
+ void trackingToggled(QString gameName, int modID, QVariant userData, bool tracked);
+
+ /**
+ * @brief sent when the tracking state of a mod was changed (only sent as a result of
+ * our request)
+ * @param modID id of the mod for which the request was made
+ * @param userData the data that was included in the request
+ * @param tracked new tracking state
+ * @note in the python interface use the onTrackingToggled call to register a callback
+ * for this signal. The signature of your callback must match the signature of this
+ * signal
+ */
+ void gameInfoAvailable(QString gameName, QVariant userData, QVariant resultData);
+
+ /**
+ * @brief sent when a request to nexus failed
+ * @param modID id of the mod for which the request was made
+ * @param fileID id of the file for which the request was made (ignore if the request
+ * was for the mod in general)
+ * @param userData the data that was included in the request
+ * @param errorMessage textual description of the problem
+ * @note in the python interface use the onDescriptionAvailable call to register a
+ * callback for this signal. The signature of your callback must match the signature
+ * of this signal
+ */
+ void requestFailed(QString gameName, int modID, int fileID, QVariant userData,
+ int errorCode, const QString& errorMessage);
+};
+
+} // namespace MOBase
+
+#endif // INEXUSBRIDGE_H
diff --git a/libs/uibase/include/uibase/imoinfo.h b/libs/uibase/include/uibase/imoinfo.h
new file mode 100644
index 0000000..a9ec3d8
--- /dev/null
+++ b/libs/uibase/include/uibase/imoinfo.h
@@ -0,0 +1,616 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IMOINFO_H
+#define IMOINFO_H
+
+#include <QDir>
+#include <QList>
+#include <QMainWindow>
+#include <QString>
+#include <QVariant>
+#include <cstdint>
+#include <any>
+#include <functional>
+#include <memory>
+
+// Include utility.h for HANDLE/DWORD typedefs on Linux
+#include "utility.h"
+
+#include "game_features/game_feature.h"
+#include "guessedvalue.h"
+#include "imodlist.h"
+#include "iprofile.h"
+#include "versioninfo.h"
+#include "versioning.h"
+
+namespace MOBase
+{
+
+class IExecutablesList;
+class IFileTree;
+class IModInterface;
+class IModRepositoryBridge;
+class IDownloadManager;
+class IInstanceManager;
+class IPluginList;
+class IPlugin;
+class IPluginGame;
+class IGameFeatures;
+
+/**
+ * @brief Interface to class that provides information about the running session
+ * of Mod Organizer to be used by plugins
+ *
+ * When MO requires plugins but does not have a valid instance loaded (such as
+ * on first start in the instance creation dialog), init() will not be called at
+ * all, except for proxy plugins.
+ *
+ * In the case of proxy plugins, init() is called with a null IOrganizer.
+ */
+class QDLLEXPORT IOrganizer : public QObject
+{
+ Q_OBJECT
+
+public:
+ /**
+ * @brief information about a virtualised file
+ */
+ struct FileInfo
+ {
+ QString filePath; /// full path to the file
+ QString archive; /// name of the archive if this file is in a BSA, otherwise this
+ /// is empty.
+ QStringList origins; /// list of origins containing this file. the first one is the
+ /// highest priority one
+ };
+
+public:
+ virtual ~IOrganizer() {}
+
+ // the directory for plugin data, typically plugins/data
+ //
+ static QString getPluginDataPath();
+
+ /**
+ * @return create a new nexus interface class
+ */
+ virtual IModRepositoryBridge* createNexusBridge() const = 0;
+
+ /**
+ * @return the name of the current instance (the directory name or "Portable" for
+ * portable instances)
+ */
+ virtual QString instanceName() const = 0;
+
+ /**
+ * @return name of the active profile or an empty string if no profile is loaded (yet)
+ */
+ virtual QString profileName() const = 0;
+
+ /**
+ * @return the (absolute) path to the active profile or an empty string if no profile
+ * is loaded (yet)
+ */
+ virtual QString profilePath() const = 0;
+
+ /**
+ * @return the (absolute) path to the download directory
+ */
+ virtual QString downloadsPath() const = 0;
+
+ /**
+ * @return the (absolute) path to the overwrite directory
+ */
+ virtual QString overwritePath() const = 0;
+
+ /**
+ * @return the (absolute) path to the base directory
+ */
+ virtual QString basePath() const = 0;
+
+ /**
+ * @return the (absolute) path to the mods directory
+ */
+ virtual QString modsPath() const = 0;
+
+ /**
+ * @return the running version of Mod Organizer
+ */
+ [[deprecated]] virtual VersionInfo appVersion() const = 0;
+
+ /**
+ * @return the running version of Mod Organizer
+ */
+ virtual Version version() const = 0;
+
+ /**
+ * @brief create a new mod with the specified name
+ * @param name name of the new mod
+ * @return an interface that can be used to modify the mod. nullptr if the user
+ * canceled
+ * @note a popup asking the user to merge, rename or replace the mod is displayed if
+ * the mod already exists. That has to happen on the main thread and MO2 will deadlock
+ * if it happens on any other. If this needs to be called from another thread, use
+ * IModList::getMod() to verify the mod-name is unused first
+ */
+ virtual IModInterface* createMod(GuessedValue<QString>& name) = 0;
+
+ /**
+ * @brief get the game plugin matching the specified game
+ * @param gameName name of the game short name
+ * @return a game plugin, or nullptr if there is no match
+ */
+ virtual IPluginGame* getGame(const QString& gameName) const = 0;
+
+ /**
+ * @brief let the organizer know that a mod has changed
+ * @param the mod that has changed
+ */
+ virtual void modDataChanged(IModInterface* mod) = 0;
+
+ /**
+ * @brief Check if a plugin is enabled.
+ *
+ * @param pluginName Plugin to check.
+ *
+ * @return true if the plugin is enabled, false otherwise.
+ */
+ virtual bool isPluginEnabled(IPlugin* plugin) const = 0;
+
+ /**
+ * @brief Check if a plugin is enabled.
+ *
+ * @param pluginName Name of the plugin to check.
+ *
+ * @return true if the plugin is enabled, false otherwise.
+ */
+ virtual bool isPluginEnabled(QString const& pluginName) const = 0;
+
+ /**
+ * @brief Retrieve the specified setting for a plugin.
+ *
+ * @param pluginName Name of the plugin for which to retrieve a setting. This should
+ * always be IPlugin::name() unless you have a really good reason to access settings
+ * of another mod. You can not access settings for a plugin that isn't installed.
+ * @param key identifier of the setting
+ * @return the setting
+ * @note an invalid qvariant is returned if the setting has not been declared
+ */
+ virtual QVariant pluginSetting(const QString& pluginName,
+ const QString& key) const = 0;
+
+ /**
+ * @brief Set the specified setting for a plugin.
+ *
+ * This automatically emit pluginSettingChanged(), so you do not have to do it
+ * yourself.
+ *
+ * @param pluginName Name of the plugin for which to change a value. This should
+ * always be IPlugin::name() unless you have a really good reason to access data of
+ * another mod AND if you can verify that plugin is actually installed.
+ * @param key Identifier of the setting.
+ * @param value Value to set.
+ *
+ * @throw an exception is thrown if pluginName doesn't refer to an installed plugin.
+ */
+ virtual void setPluginSetting(const QString& pluginName, const QString& key,
+ const QVariant& value) = 0;
+
+ /**
+ * @brief retrieve the specified persistent value for a plugin
+ * @param pluginName name of the plugin for which to retrieve a value. This should
+ * always be IPlugin::name() unless you have a really good reason to access data of
+ * another mod.
+ * @param key identifier of the value
+ * @param def default value to return if the key is not (yet) set
+ * @return the value
+ * @note A persistent is an arbitrary value that the plugin can set and retrieve that
+ * is persistently stored by the main application. There is no UI for the user to
+ * change this value but (s)he can directly access the storage
+ */
+ virtual QVariant persistent(const QString& pluginName, const QString& key,
+ const QVariant& def = QVariant()) const = 0;
+
+ /**
+ * @brief set the specified persistent value for a plugin
+ * @param pluginName name of the plugin for which to change a value. This should
+ * always be IPlugin::name() unless you have a really good reason to access data of
+ * another mod AND if you can verify that plugin is actually installed
+ * @param key identifier of the value
+ * @param value value to set
+ * @param sync if true the storage is immediately written to disc. This costs
+ * performance but is safer against data loss
+ * @throw an exception is thrown if pluginName doesn't refer to an installed plugin
+ */
+ virtual void setPersistent(const QString& pluginName, const QString& key,
+ const QVariant& value, bool sync = true) = 0;
+
+ /**
+ * @return path to a directory where plugin data should be stored.
+ */
+ virtual QString pluginDataPath() const = 0;
+
+ /**
+ * @brief install a mod archive at the specified location
+ * @param fileName absolute file name of the mod to install
+ * @param nameSuggestion suggested name for this mod. This can still be changed by the
+ * user
+ * @return interface to the newly installed mod or nullptr if no installation took
+ * place (failure or use canceled
+ */
+ virtual IModInterface* installMod(const QString& fileName,
+ const QString& nameSuggestion = QString()) = 0;
+
+ /**
+ * @brief resolves a path relative to the virtual data directory to its absolute real
+ * path
+ * @param fileName path to resolve
+ * @return the absolute real path or an empty string
+ */
+ virtual QString resolvePath(const QString& fileName) const = 0;
+
+ /**
+ * @brief retrieves a list of (virtual) subdirectories for a path (relative to the
+ * data directory)
+ * @param directoryName relative path to the directory to list
+ * @return a list of directory names
+ */
+ virtual QStringList listDirectories(const QString& directoryName) const = 0;
+
+ /**
+ * @brief find files in the virtual directory matching the filename filter
+ * @param path the path to search in
+ * @param filter filter function to match against
+ * @return a list of matching files
+ */
+ virtual QStringList
+ findFiles(const QString& path,
+ const std::function<bool(const QString&)>& filter) const = 0;
+
+ /**
+ * @brief find files in the virtual directory matching the filename filter
+ * @param path the path to search in
+ * @param filters list of glob filters to match against
+ * @return a list of matching files
+ */
+ virtual QStringList findFiles(const QString& path,
+ const QStringList& filters) const = 0;
+
+ /**
+ * @brief retrieve the file origins for the speicified file. The origins are listed
+ * with their internal name
+ * @return list of origins that contain the specified file, sorted by their priority
+ * @note the internal name of a mod can differ from the display name for
+ * disambiguation
+ */
+ virtual QStringList getFileOrigins(const QString& fileName) const = 0;
+
+ /**
+ * @brief find files in the virtual directory matching the specified complex filter
+ * @param path the path to search in
+ * @param filter filter function to match against
+ * @return a list of matching files
+ * @note this function is more expensive than the one filtering by name so use the
+ * other one if it suffices
+ */
+ virtual QList<FileInfo>
+ findFileInfos(const QString& path,
+ const std::function<bool(const FileInfo&)>& filter) const = 0;
+
+ /**
+ * @return a IFileTree representing the virtual file tree.
+ */
+ virtual std::shared_ptr<const MOBase::IFileTree> virtualFileTree() const = 0;
+
+ /**
+ * @return interface to the instance manager
+ */
+ virtual IInstanceManager* instanceManager() const = 0;
+
+ /**
+ * @return interface to the download manager
+ */
+ virtual IDownloadManager* downloadManager() const = 0;
+
+ /**
+ * @return interface to the list of plugins (esps, esms, and esls)
+ */
+ virtual IPluginList* pluginList() const = 0;
+
+ /**
+ * @return interface to the list of mods
+ */
+ virtual IModList* modList() const = 0;
+
+ /**
+ * @return interface to the list of executables
+ */
+ virtual IExecutablesList* executablesList() const = 0;
+
+ /**
+ * @return interface to the active profile
+ */
+ virtual std::shared_ptr<IProfile> profile() const = 0;
+
+ /**
+ * @return list of names of all profiles
+ */
+ virtual QStringList profileNames() const = 0;
+
+ /**
+ * @brief retrieve a profile by name
+ *
+ * @param name name of the profile
+ *
+ * @return the profile with the specified name or nullptr if not found
+ */
+ virtual std::shared_ptr<const IProfile> getProfile(const QString& name) const = 0;
+
+ /**
+ * @return interface to game features.
+ */
+ virtual IGameFeatures* gameFeatures() const = 0;
+
+ /**
+ * @brief runs a program using the virtual filesystem
+ *
+ * @param executable either the name of an executable configured in MO, or
+ * a path to an executable; if relative, it is resolved
+ * against the game directory
+ *
+ * @param args arguments to pass to the executable; if this is empty
+ * and `executable` refers to a configured executable,
+ * its arguments are used
+ *
+ * @param cwd working directory for the executable; if this is empty,
+ * it is set to either the cwd set by the user in the
+ * configured executable (if any) or the directory of the
+ * executable
+ *
+ * @param profile name of the profile to use; defaults to the active
+ * profile
+ *
+ * @param forcedCustomOverwrite the name of the mod to set as the custom
+ * overwrite directory, regardless of what the
+ * profile has configured
+ *
+ * @param ignoreCustomOverwrite if `executable` is the name of a configured
+ * executable, ignores the executable's custom
+ * overwrite
+ *
+ * @return a handle to the process that was started or INVALID_HANDLE_VALUE
+ * if the application failed to start.
+ */
+ virtual HANDLE startApplication(const QString& executable,
+ const QStringList& args = QStringList(),
+ const QString& cwd = "", const QString& profile = "",
+ const QString& forcedCustomOverwrite = "",
+ bool ignoreCustomOverwrite = false) = 0;
+
+ /**
+ * @brief blocks until the given process has completed
+ *
+ * @param handle the process to wait for
+ * @param refresh whether MO should refresh after the process completed
+ * @param exitCode the exit code of the process after it ended
+ *
+ * @return true if the process completed successfully
+ *
+ * @note this will always show the lock overlay, regardless of whether the
+ * user has disabled locking in the setting, so use this with care;
+ * note that the lock overlay will always allow the user to unlock, in
+ * which case this will return false
+ */
+ virtual bool waitForApplication(HANDLE handle, bool refresh = true,
+ LPDWORD exitCode = nullptr) const = 0;
+
+ /**
+ * @brief Refresh the internal mods file structure from disk. This includes the mod
+ * list, the plugin list, data tab and other smaller things like problems button (same
+ * as pressing F5).
+ *
+ * @note The main part of the refresh of the mods file strcuture, modlist and
+ * pluginlist is done asynchronously, so you should not expect them to be up-to-date
+ * when this function returns.
+ *
+ * @param saveChanges If true, the relevant profile information is saved first
+ * (enabled mods and their order).
+ */
+ virtual void refresh(bool saveChanges = true) = 0;
+
+ /**
+ * @brief get the currently managed game info
+ */
+ virtual MOBase::IPluginGame const* managedGame() const = 0;
+
+ /**
+ * @brief Add a new callback to be called when an application is about to be run.
+ *
+ * Parameters of the callback:
+ * - Path (absolute) to the application to be run.
+ * - [Optional] Working directory for the run.
+ * - [Optional] Argument for the binary.
+ *
+ * The callback can return false to prevent the application from being launched.
+ *
+ * @param func Function to be called when an application is run.
+ */
+ virtual bool onAboutToRun(const std::function<bool(const QString&)>& func) = 0;
+ virtual bool onAboutToRun(
+ const std::function<bool(const QString&, const QDir&, const QString&)>& func) = 0;
+
+ /**
+ * @brief Add a new callback to be called when an has finished running.
+ *
+ * Parameters of the callback:
+ * - Path (absolute) to the application that has finished running.
+ * - Exit code of the application.
+ *
+ *
+ * @param func Function to be called when an application is run.
+ */
+ virtual bool
+ onFinishedRun(const std::function<void(const QString&, unsigned int)>& func) = 0;
+
+ /**
+ * @brief Add a new callback to be called when the user interface has been
+ * initialized.
+ *
+ * Parameters of the callback:
+ * - The main window of the application.
+ *
+ * @param func Function to be called when the user interface has been initialized.
+ */
+ virtual bool
+ onUserInterfaceInitialized(std::function<void(QMainWindow*)> const& func) = 0;
+
+ /**
+ * @brief Add a new callback to be called when the next refresh finishes (or
+ * immediately if possible).
+ *
+ * @param func Callback.
+ * @param immediateIfPossible If true and no refresh is in progress, the callback will
+ * be called immediately.
+ */
+ virtual bool onNextRefresh(std::function<void()> const& func,
+ bool immediateIfPossible = true) = 0;
+
+ /**
+ * @brief Add a new callback to be called when a new profile is created.
+ *
+ * Parameters of the callback:
+ * - The created profile (can be a temporary object, so it should not be stored).
+ *
+ * @param func Function to be called when a profile is created.
+ *
+ */
+ virtual bool onProfileCreated(std::function<void(IProfile*)> const& func) = 0;
+
+ /**
+ * @brief Add a new callback to be called when a profile is renamed.
+ *
+ * Parameters of the callback:
+ * - The renamed profile.
+ * - The old name of the profile.
+ * - The new name of the profile.
+ *
+ * @param func Function to be called when a profile is renamed.
+ *
+ */
+ virtual bool onProfileRenamed(
+ std::function<void(IProfile*, QString const&, QString const&)> const& func) = 0;
+
+ /**
+ * @brief Add a new callback to be called when a profile is removed.
+ *
+ * Parameters of the callback:
+ * - The name of the removed profile.
+ *
+ * The function is called after the profile has been removed, so the profile is not
+ * accessible anymore.
+ *
+ * @param func Function to be called when a profile is removed.
+ *
+ */
+ virtual bool onProfileRemoved(std::function<void(QString const&)> const& func) = 0;
+
+ /**
+ * @brief Add a new callback to be called when the current profile is changed.
+ *
+ * Parameters of the callback:
+ * - The old profile. Can be a null pointer if no profile was set (e.g. at startup).
+ * - The new profile, cannot be null.
+ *
+ * The function is called when the profile is changed but some operations related to
+ * the profile might not be finished when this is called (e.g., the virtual file
+ * system might not be up-to-date).
+ *
+ * @param func Function to be called when the current profile change.
+ *
+ */
+ virtual bool
+ onProfileChanged(std::function<void(IProfile*, IProfile*)> const& func) = 0;
+
+ /**
+ * @brief Add a new callback to be called when a plugin setting is changed.
+ *
+ * Parameters of the callback:
+ * - Name of the plugin.
+ * - Name of the setting.
+ * - Old value of the setting. Can be a default-constructed (invalid) QVariant if
+ * the setting did not exist before.
+ * - New value of the setting. Can be a default-constructed (invalid) QVariant if
+ * the setting has been removed.
+ *
+ * @param func Function to be called when a plugin setting is changed.
+ */
+ virtual bool onPluginSettingChanged(
+ std::function<void(QString const&, const QString& key, const QVariant&,
+ const QVariant&)> const& func) = 0;
+
+ /**
+ * @brief Add a new callback to be called when a plugin is enabled.
+ *
+ * Parameters of the callback:
+ * - The enabled plugin.
+ *
+ * @param func Function to be called when a plugin is enabled.
+ */
+ virtual bool onPluginEnabled(std::function<void(const IPlugin*)> const& func) = 0;
+
+ /**
+ * @brief Add a new callback to be called when the specified plugin is enabled.
+ *
+ * @param name Name of the plugin to watch.
+ * @param func Function to be called when a plugin is enabled.
+ */
+ virtual bool onPluginEnabled(const QString& pluginName,
+ std::function<void()> const& func) = 0;
+
+ /**
+ * @brief Add a new callback to be called when a plugin is disabled.
+ *
+ * Parameters of the callback:
+ * - The disabled plugin.
+ *
+ * @param func Function to be called when a plugin is disabled.
+ */
+ virtual bool onPluginDisabled(std::function<void(const IPlugin*)> const& func) = 0;
+
+ /**
+ * @brief Add a new callback to be called when the specified plugin is disabled.
+ *
+ * @param name Name of the plugin to watch.
+ * @param func Function to be called when a plugin is disabled.
+ */
+ virtual bool onPluginDisabled(const QString& pluginName,
+ std::function<void()> const& func) = 0;
+};
+
+} // namespace MOBase
+
+namespace MOBase::details
+{
+// called from MO
+QDLLEXPORT void setPluginDataPath(const QString& s);
+} // namespace MOBase::details
+
+#endif // IMOINFO_H
diff --git a/libs/uibase/include/uibase/iplugin.h b/libs/uibase/include/uibase/iplugin.h
new file mode 100644
index 0000000..aae89dc
--- /dev/null
+++ b/libs/uibase/include/uibase/iplugin.h
@@ -0,0 +1,146 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IPLUGIN_H
+#define IPLUGIN_H
+
+#include "imoinfo.h"
+#include "pluginrequirements.h"
+#include "pluginsetting.h"
+#include "versioninfo.h"
+#include <QList>
+#include <QObject>
+#include <QString>
+#include <vector>
+
+namespace MOBase
+{
+
+class IPlugin
+{
+public:
+ // For easier access in child class:
+ using Requirements = PluginRequirementFactory;
+
+public:
+ virtual ~IPlugin() {}
+
+ /**
+ * @brief Initialize the plugin.
+ *
+ * Note that this function may never be called if no IOrganizer is available
+ * at that time, such as when creating the first instance in MO. For proxy
+ * plugins, init() is always called, but given a null IOrganizer.
+ *
+ * Plugins will probably want to store the organizer pointer. It is guaranteed
+ * to be valid as long as the plugin is loaded.
+ *
+ * These functions may be called before init():
+ * - name()
+ * - see IPluginGame for more
+ *
+ * @return false if the plugin could not be initialized, true otherwise.
+ */
+ virtual bool init(IOrganizer* organizer) = 0;
+
+ /**
+ * this function may be called before init()
+ *
+ * @return the internal name of this plugin (used for example in the settings menu).
+ *
+ * @note Please ensure you use a name that will not change. Do NOT include a version
+ * number in the name. Do NOT use a localizable string (tr()) here. Settings for
+ * example are tied to this name, if you rename your plugin you lose settings users
+ * made.
+ */
+ virtual QString name() const = 0;
+
+ /**
+ * @return the localized name for this plugin.
+ *
+ * @note Unlike name(), this method can (and should!) return a localized name for the
+ * plugin.
+ * @note This method returns name() by default.
+ */
+ virtual QString localizedName() const { return name(); }
+
+ /**
+ * @brief Retrieve the name of the master plugin of this plugin.
+ *
+ * It is often easier to implement a functionality as multiple plugins in MO2, but
+ * ship the plugins together, e.g. as a Python module or using `createFunctions()`. In
+ * this case, having a master plugin (one of the plugin, or a separate one) tells MO2
+ * that these plugins are linked and should also be displayed together in the UI. If
+ * MO2 ever implements automatic updates for plugins, the `master()` plugin will also
+ * be used for this purpose.
+ *
+ * @return the name of the master plugin of this plugin, or an empty string if this
+ * plugin does not have a master.
+ */
+ virtual QString master() const { return ""; }
+
+ /**
+ * @brief Retrieve the requirements for the plugins.
+ *
+ * This method is called right after init().
+ *
+ * @return the requirements for this plugin.
+ */
+ virtual std::vector<std::shared_ptr<const IPluginRequirement>> requirements() const
+ {
+ return {};
+ }
+
+ /**
+ * @return the author of this plugin.
+ */
+ virtual QString author() const = 0;
+
+ /**
+ * @return a short description of the plugin to be displayed to the user.
+ */
+ virtual QString description() const = 0;
+
+ /**
+ * @return the version of the plugin. This can be used to detect outdated versions of
+ * plugins.
+ */
+ virtual VersionInfo version() const = 0;
+
+ /**
+ * @return the list of configurable settings for this plugin (in the user interface).
+ * The list may be empty.
+ *
+ * @note Plugin can store "hidden" (from the user) settings using
+ * IOrganizer::persistent / IOrganizer::setPersistent.
+ */
+ virtual QList<PluginSetting> settings() const = 0;
+
+ /**
+ * @return whether the plugin should be enabled by default
+ */
+ virtual bool enabledByDefault() const { return true; }
+};
+
+} // namespace MOBase
+
+Q_DECLARE_INTERFACE(MOBase::IPlugin, "com.tannin.ModOrganizer.Plugin/2.0")
+
+#endif // IPLUGIN_H
diff --git a/libs/uibase/include/uibase/iplugindiagnose.h b/libs/uibase/include/uibase/iplugindiagnose.h
new file mode 100644
index 0000000..5a9340c
--- /dev/null
+++ b/libs/uibase/include/uibase/iplugindiagnose.h
@@ -0,0 +1,103 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2013 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IPLUGINDIAGNOSE_H
+#define IPLUGINDIAGNOSE_H
+
+#include <QList>
+#include <QObject>
+#include <QString>
+#include <functional>
+#include <vector>
+
+namespace MOBase
+{
+
+/**
+ * @brief A plugin that creates problem reports to be displayed in the UI.
+ * This can be used to report problems related to the same plugin (which implements
+ * further interfaces) or as a stand-alone diagnosis tool. This does not derive from
+ * IPlugin to prevent multiple inheritance issues. For stand-alone diagnosis plugins,
+ * derive from IPlugin and IPluginDiagnose
+ */
+class IPluginDiagnose
+{
+public:
+ /**
+ * @return a list of keys of active problems
+ * @note this is not expected to be called if isActive returns false
+ */
+ virtual std::vector<unsigned int> activeProblems() const = 0;
+
+ /**
+ * @brief retrieve a short description for the specified problem for the overview
+ * page. HTML syntax is supported.
+ * @param key the identifier of the problem as reported by activeProblems()
+ * @return a (short) description text
+ * @throw should throw an exception if the key is not valid
+ */
+ virtual QString shortDescription(unsigned int key) const = 0;
+
+ /**
+ * @brief retrieve the full description for the specified problem. HTML syntax is
+ * supported.
+ * @param key the identifier of the problem as reported by activeProblems()
+ * @return a (long) description text
+ * @throw should throw an exception if the key is not valid
+ */
+ virtual QString fullDescription(unsigned int key) const = 0;
+
+ /**
+ * @param key the identifier of the problem as reported by activeProblems()
+ * @return true if this plugin provides a guide to fix the issue
+ * @throw should throw an exception if the key is not valid
+ */
+ virtual bool hasGuidedFix(unsigned int key) const = 0;
+
+ /**
+ * @brief start the guided fix for the specified problem
+ * @param key the identifier of the problem as reported by activeProblems()
+ * @throw should throw an exception if the key is not valid or if there is no guided
+ * fix for the issue
+ */
+ virtual void startGuidedFix(unsigned int key) const = 0;
+
+ /**
+ * @brief Register the callback to be called when this plugin is invalidated.
+ *
+ * Only one callback can be activate at a time.
+ */
+ void onInvalidated(std::function<void()> callback) { m_OnInvalidated = callback; }
+
+ virtual ~IPluginDiagnose() {}
+
+protected:
+ void invalidate() { m_OnInvalidated(); }
+
+private:
+ std::function<void()> m_OnInvalidated;
+};
+
+} // namespace MOBase
+
+Q_DECLARE_INTERFACE(MOBase::IPluginDiagnose,
+ "com.tannin.ModOrganizer.PluginDiagnose/1.1")
+
+#endif // IPLUGINDIAGNOSE_H
diff --git a/libs/uibase/include/uibase/ipluginfilemapper.h b/libs/uibase/include/uibase/ipluginfilemapper.h
new file mode 100644
index 0000000..350aa71
--- /dev/null
+++ b/libs/uibase/include/uibase/ipluginfilemapper.h
@@ -0,0 +1,49 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2015 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IPLUGINFILEMAPPER_H
+#define IPLUGINFILEMAPPER_H
+
+#include "filemapping.h"
+#include "iplugin.h"
+
+namespace MOBase
+{
+
+/**
+ * brief A plugin that adds virtual file links
+ * This does not derive from IPlugin to prevent multiple inheritance issues.
+ * For stand-alone mapping plugins, derive from IPlugin and IPluginDiagnose
+ */
+class IPluginFileMapper
+{
+public:
+ /**
+ * @return a list of file maps
+ */
+ virtual MappingType mappings() const = 0;
+};
+
+} // namespace MOBase
+
+Q_DECLARE_INTERFACE(MOBase::IPluginFileMapper,
+ "com.tannin.ModOrganizer.PluginFileMapper/2.0")
+
+#endif // IPLUGINDIAGNOSE_H
diff --git a/libs/uibase/include/uibase/iplugingame.h b/libs/uibase/include/uibase/iplugingame.h
new file mode 100644
index 0000000..0fa2508
--- /dev/null
+++ b/libs/uibase/include/uibase/iplugingame.h
@@ -0,0 +1,380 @@
+#ifndef IPLUGINGAME_H
+#define IPLUGINGAME_H
+
+#include "executableinfo.h"
+#include "iplugin.h"
+#include "isavegame.h"
+
+class QIcon;
+class QUrl;
+class QString;
+
+#include <any>
+#include <cstdint>
+#include <memory>
+#include <typeindex>
+#include <unordered_map>
+#include <vector>
+
+namespace MOBase
+{
+
+// Game plugins can be loaded without an IOrganizer being available, in which
+// case detectGame() is called, but not init().
+//
+// These functions may be called before init() (after detectGame()):
+// - gameName()
+// - isInstalled()
+// - gameIcon()
+// - gameDirectory()
+// - dataDirectory()
+// - gameVariants()
+// - looksValid()
+// - see IPlugin::init() for more
+//
+//
+class IPluginGame : public QObject, public IPlugin
+{
+ Q_INTERFACES(IPlugin)
+
+public:
+ enum class LoadOrderMechanism
+ {
+ None,
+ FileTime,
+ PluginsTxt
+ };
+
+ enum class SortMechanism
+ {
+ NONE,
+ MLOX,
+ BOSS,
+ LOOT
+ };
+
+ enum ProfileSetting
+ {
+ MODS = 0x01,
+ CONFIGURATION = 0x02,
+ SAVEGAMES = 0x04,
+ PREFER_DEFAULTS = 0x08
+ };
+
+ Q_DECLARE_FLAGS(ProfileSettings, ProfileSetting)
+
+public:
+ // Game plugin should not have requirements:
+ std::vector<std::shared_ptr<const IPluginRequirement>>
+ requirements() const final override
+ {
+ return {};
+ }
+
+ // Game plugin can not be disabled
+ bool enabledByDefault() const final override { return true; }
+
+ /**
+ * this function may be called before init()
+ *
+ * @return name of the game
+ */
+ virtual QString gameName() const = 0;
+
+ /**
+ * used to override display-specific text in place of the gameName
+ * for example the text for the main window or the initial instance creation game
+ * plugin selection/list
+ *
+ * added for future translation purposes or to make plugins visually more accurate in
+ * the ui
+ *
+ * @return display-specific name of the game
+ */
+ virtual QString displayGameName() const { return gameName(); }
+
+ /**
+ * @brief Detect the game.
+ *
+ * This method is the first method called for game plugins (before init()). The
+ * following methods can be called after detectGame() but before init():
+ * - gameName()
+ * - isInstalled()
+ * - gameIcon()
+ * - gameDirectory()
+ * - dataDirectory()
+ * - gameVariants()
+ * - looksValid()
+ * - see IPlugin::init() for more
+ */
+ virtual void detectGame() = 0;
+
+ /**
+ * @brief initialize a profile for this game
+ * @param directory the directory where the profile is to be initialized
+ * @param settings parameters for how the profile should be initialized
+ * @note the MO app does not yet support virtualizing only specific aspects but
+ * plugins should be written with this future functionality in mind
+ * @note this function will be used to initially create a profile, potentially to
+ * repair it or upgrade/downgrade it so the implementations have to gracefully handle
+ * the case that the directory already contains files!
+ */
+ virtual void initializeProfile(const QDir& directory,
+ ProfileSettings settings) const = 0;
+
+ /**
+ * @brief List save games in the specified folder.
+ *
+ * @param folder The folder containing the saves.
+ *
+ * @return the list of saves in the specified folder.
+ */
+ /**
+ * @return file extension of save games for this game
+ */
+ virtual std::vector<std::shared_ptr<const ISaveGame>>
+ listSaves(QDir folder) const = 0;
+
+ /**
+ * this function may be called before init()
+ *
+ * @return true if this game has been discovered as installed, false otherwise
+ */
+ virtual bool isInstalled() const = 0;
+
+ /**
+ * this function may be called before init()
+ *
+ * @return an icon for this game
+ */
+ virtual QIcon gameIcon() const = 0;
+
+ /**
+ * this function may be called before init()
+ *
+ * @return directory to the game installation
+ */
+ virtual QDir gameDirectory() const = 0;
+
+ /**
+ * this function may be called before init()
+ *
+ * @return directory where the game expects to find its data files
+ */
+ virtual QDir dataDirectory() const = 0;
+
+ virtual QString modDataDirectory() const { return ""; }
+
+ /**
+ * this function may be called before init()
+ *
+ * @return directories where we may find data files outside the main location
+ */
+ virtual QMap<QString, QDir> secondaryDataDirectories() const { return {}; }
+
+ /**
+ * @brief set the path to the managed game
+ * @param path to the game
+ * @note this will be called by by MO to set the concrete path of the game. This is
+ * particularly relevant if the path wasn't auto-detected but had to be set manually
+ * by the user
+ */
+ virtual void setGamePath(const QString& path) = 0;
+
+ /**
+ * @return directory of the documents folder where configuration files and such for
+ * this game reside
+ */
+ virtual QDir documentsDirectory() const = 0;
+
+ /**
+ * @return path to where save games are stored.
+ */
+ virtual QDir savesDirectory() const = 0;
+
+ /**
+ * @return list of automatically discovered executables of the game itself and tools
+ * surrounding it
+ */
+ virtual QList<ExecutableInfo> executables() const { return {}; }
+
+ /**
+ * @brief Get the default list of libraries that can be force loaded with executables
+ */
+ virtual QList<ExecutableForcedLoadSetting> executableForcedLoads() const = 0;
+
+ /**
+ * @return steam app id for this game. Should be empty for games not available on
+ * steam
+ * @note if a game is available in multiple versions those might have different app
+ * ids. the plugin should try to return the right one
+ */
+ virtual QString steamAPPId() const { return ""; }
+
+ /**
+ * @return list of plugins that are part of the game and not considered optional
+ */
+ virtual QStringList primaryPlugins() const { return {}; }
+
+ /**
+ * @return list of plugins enabled by the game but not in a strict load order
+ */
+ virtual QStringList enabledPlugins() const { return {}; }
+
+ /**
+ * this function may be called before init()
+ *
+ * @return list of game variants
+ * @note if there are multiple variants of a game (and the variants make a difference
+ * to the plugin) like a regular one and a GOTY-edition the plugin can return a list
+ * of them and the user gets to chose which one he owns.
+ */
+ virtual QStringList gameVariants() const { return {}; }
+
+ /**
+ * @brief if there are multiple game variants (returned by gameVariants) this will get
+ * called on start with the user-selected game edition
+ * @param variant the game edition selected by the user
+ */
+ virtual void setGameVariant(const QString& variant) = 0;
+
+ /**
+ * @brief Get the name of the executable that gets run
+ */
+ virtual QString binaryName() const = 0;
+
+ /**
+ * @brief Get the 'short' name of the game
+ *
+ * the short name of the game is used for - save ames, registry entries and
+ * nexus mod pages as far as I can see.
+ */
+ virtual QString gameShortName() const = 0;
+
+ /**
+ * @brief game name that's passed to the LOOT cli --game argument
+ *
+ * only applicable for games using LOOT based sorting
+ * defaults to gameShortName()
+ */
+ virtual QString lootGameName() const { return gameShortName(); }
+
+ /**
+ * @brief Get any primary alternative 'short' name for the game
+ *
+ * this is used to determine if a Nexus (or other) download source should be
+ * considered a 'primary' source for the game so that it isn't flagged as an
+ * alternative source
+ */
+ virtual QStringList primarySources() const { return {}; }
+
+ /**
+ * @brief Get any valid 'short' name for the game
+ *
+ * this is used to determine if a Nexus download is valid for the current game
+ * not all game variants have their own nexus pages and others can handle downloads
+ * from other nexus game pages and should be allowed
+ *
+ * the short name should be considered the primary handler for a directly supported
+ * game for puroses of auto-launching an instance
+ */
+ virtual QStringList validShortNames() const { return {}; }
+
+ /**
+ * @brief Get the 'short' name of the game
+ *
+ * the short name of the game is used for - save ames, registry entries and
+ * nexus mod pages as far as I can see.
+ */
+ virtual QString gameNexusName() const { return ""; }
+
+ /**
+ * @brief Get the list of .ini files this game uses
+ *
+ * @note It is important that the 'main' .ini file comes first in this list
+ */
+ virtual QStringList iniFiles() const { return {}; }
+
+ /**
+ * @brief Get a list of esp/esm files that are part of known dlcs
+ */
+ virtual QStringList DLCPlugins() const { return {}; }
+
+ /**
+ * @brief Get the current list of active Creation Club plugins
+ */
+ virtual QStringList CCPlugins() const { return {}; }
+
+ /*
+ * @brief determine the load order mechanism used by this game.
+ *
+ * @note this may throw an exception if the mechanism can't be determined
+ */
+ virtual LoadOrderMechanism loadOrderMechanism() const
+ {
+ return LoadOrderMechanism::None;
+ }
+
+ /**
+ * @brief determine the sorting mech
+ */
+ virtual SortMechanism sortMechanism() const { return SortMechanism::NONE; }
+
+ /**
+ * @brief Get the Nexus ID of Mod Organizer
+ */
+ virtual int nexusModOrganizerID() const { return 0; }
+
+ /**
+ * @brief Get the Nexus Game ID
+ */
+ virtual int nexusGameID() const = 0;
+
+ /**
+ * this function may be called before init()
+ *
+ * @brief See if the supplied directory looks like a valid game
+ */
+ virtual bool looksValid(QDir const&) const = 0;
+
+ /**
+ * @brief Get version of program
+ */
+ virtual QString gameVersion() const = 0;
+
+ /**
+ * @brief Get the name of the game launcher
+ */
+ virtual QString getLauncherName() const = 0;
+
+ /**
+ * @brief Get a URL for the support page for the game
+ */
+ virtual QString getSupportURL() const { return ""; }
+
+ /**
+ * @brief Gets a virtualization mapping for mod directories
+ *
+ * @note Maps internal mod directories to a list of paths
+ * @default Root directory maps to game data path(s)
+ */
+ virtual QMap<QString, QStringList> getModMappings() const
+ {
+ QMap<QString, QStringList> map;
+ QStringList dataDirs = {dataDirectory().absolutePath()};
+ for (auto path : secondaryDataDirectories()) {
+ dataDirs.append(path.absolutePath());
+ }
+ map[""] = dataDirs;
+ return map;
+ }
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(IPluginGame::ProfileSettings)
+
+} // namespace MOBase
+
+Q_DECLARE_INTERFACE(MOBase::IPluginGame, "com.tannin.ModOrganizer.PluginGame/2.0")
+Q_DECLARE_METATYPE(MOBase::IPluginGame const*)
+
+#endif // IPLUGINGAME_H
diff --git a/libs/uibase/include/uibase/iplugingamefeatures.h b/libs/uibase/include/uibase/iplugingamefeatures.h
new file mode 100644
index 0000000..553d302
--- /dev/null
+++ b/libs/uibase/include/uibase/iplugingamefeatures.h
@@ -0,0 +1,61 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2024 MO2 Team. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IPLUGINGAMEFEATURES_H
+#define IPLUGINGAMEFEATURES_H
+
+namespace MOBase
+{
+
+class BSAInvalidation;
+class DataArchives;
+class GamePlugins;
+class LocalSavegames;
+class ModDataChecker;
+class ModDataContent;
+class SaveGameInfo;
+class ScriptExtender;
+class UnmanagedMods;
+
+/**
+ * @brief A plugin to implement game features.
+ *
+ */
+class IPluginGameFeautres
+{
+public:
+ /**
+ * Retrieve the priority of these game features.
+ *
+ * For mergeable features, e.g., ModDataChecker, this indicates the order in which the
+ * features are merged, otherwise, it indicates which plugin should provide the
+ * feature.
+ *
+ * The managed game always has lowest priority.
+ *
+ * @return the priority of this set of features.
+ */
+ virtual int priority() = 0;
+};
+
+} // namespace MOBase
+
+#endif // IPLUGINDIAGNOSE_H
+#pragma once
diff --git a/libs/uibase/include/uibase/iplugininstaller.h b/libs/uibase/include/uibase/iplugininstaller.h
new file mode 100644
index 0000000..d033fe9
--- /dev/null
+++ b/libs/uibase/include/uibase/iplugininstaller.h
@@ -0,0 +1,157 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IPLUGININSTALLER_H
+#define IPLUGININSTALLER_H
+
+#include <set>
+
+#include "ifiletree.h"
+#include "imodinterface.h"
+#include "iplugin.h"
+
+namespace MOBase
+{
+
+class IInstallationManager;
+
+class IPluginInstaller : public IPlugin
+{
+
+ Q_INTERFACES(IPlugin)
+
+public:
+ enum EInstallResult
+ {
+ RESULT_SUCCESS,
+ RESULT_FAILED,
+ RESULT_CANCELED,
+ RESULT_MANUALREQUESTED,
+ RESULT_NOTATTEMPTED,
+ RESULT_SUCCESSCANCEL,
+ RESULT_CATEGORYREQUESTED
+ };
+
+public:
+ IPluginInstaller() : m_ParentWidget(nullptr), m_InstallationManager(nullptr) {}
+
+ /**
+ * @brief Retrieve the priority of this installer. If multiple installers are able
+ * to handle an archive, the one with the highest priority wins.
+ *
+ * @return the priority of this installer.
+ */
+ virtual unsigned int priority() const = 0;
+
+ /**
+ * @return true if this plugin should be treated as a manual installer if the user
+ * explicitly requested one. A manual installer should offer the user maximum amount
+ * of customizability.
+ */
+ virtual bool isManualInstaller() const = 0;
+
+ /**
+ * @brief Method calls at the start of the installation process, before any other
+ * methods. This method is only called once per installation process, even for
+ * recursive installations (e.g. with the bundle installer).
+ *
+ * @param archive Path to the archive that is going to be installed.
+ * @param reinstallation True if this is a reinstallation, false otherwise.
+ * @param mod A currently installed mod corresponding to the archive being installed,
+ * or a null if there is no such mod.
+ *
+ * @note If `reinstallation` is true, then the given mod is the mod being reinstalled
+ * (the one selected by the user). If `reinstallation` is false and `currentMod` is
+ * not null, then it corresponds to a mod MO2 thinks corresponds to the archive (e.g.
+ * based on matching Nexus ID or name).
+ * @note The default implementation does nothing.
+ */
+ virtual void onInstallationStart([[maybe_unused]] QString const& archive,
+ [[maybe_unused]] bool reinstallation,
+ [[maybe_unused]] IModInterface* currentMod)
+ {}
+
+ /**
+ * @brief Method calls at the end of the installation process. This method is only
+ * called once per installation process, even for recursive installations (e.g. with
+ * the bundle installer).
+ *
+ * @param result The result of the installation.
+ * @param mod If the installation succeeded (result is RESULT_SUCCESS), contains the
+ * newly installed mod, otherwise it contains a null pointer.
+ *
+ * @note The default implementation does nothing.
+ */
+ virtual void onInstallationEnd([[maybe_unused]] EInstallResult result,
+ [[maybe_unused]] IModInterface* newMod)
+ {}
+
+ /**
+ * @brief Test if the archive represented by the tree parameter can be installed
+ * through this installer.
+ *
+ * @param tree a directory tree representing the archive.
+ *
+ * @return true if this installer can handle the archive.
+ */
+ virtual bool isArchiveSupported(std::shared_ptr<const IFileTree> tree) const = 0;
+
+ /**
+ * @brief Sets the widget that the tool should use as the parent whenever
+ * it creates a new modal dialog.
+ *
+ * @param widget The new parent widget.
+ */
+ virtual void setParentWidget(QWidget* widget) { m_ParentWidget = widget; }
+
+ /**
+ * @brief Sets the installation manager responsible for the installation process
+ * it can be used by plugins to access utility functions.
+ *
+ * @param manager The new installation manager.
+ */
+ void setInstallationManager(IInstallationManager* manager)
+ {
+ m_InstallationManager = manager;
+ }
+
+protected:
+ /**
+ * @return the parent widget that the tool should use to create new dialogs and
+ * widgets.
+ */
+ QWidget* parentWidget() const { return m_ParentWidget; }
+
+ /**
+ * @return the manager responsible for the installation process.
+ */
+ IInstallationManager* manager() const { return m_InstallationManager; }
+
+private:
+ QWidget* m_ParentWidget;
+ IInstallationManager* m_InstallationManager;
+};
+
+} // namespace MOBase
+
+Q_DECLARE_INTERFACE(MOBase::IPluginInstaller,
+ "com.tannin.ModOrganizer.PluginInstaller/1.0")
+
+#endif // IPLUGININSTALLER_H
diff --git a/libs/uibase/include/uibase/iplugininstallercustom.h b/libs/uibase/include/uibase/iplugininstallercustom.h
new file mode 100644
index 0000000..204639f
--- /dev/null
+++ b/libs/uibase/include/uibase/iplugininstallercustom.h
@@ -0,0 +1,79 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IPLUGININSTALLERCUSTOM_H
+#define IPLUGININSTALLERCUSTOM_H
+
+#include "guessedvalue.h"
+#include "iplugininstaller.h"
+
+namespace MOBase
+{
+
+/**
+ * Custom installer for mods. Custom installers receive the archive name and have to go
+ * from there. They have to be able to extract the archive themself.
+ * Plugins implementing this interface have to contain the following line in the class
+ * declaration: Q_Interfaces(IPlugin IPluginInstaller IPluginInstallerCustom)
+ */
+class IPluginInstallerCustom : public QObject, public IPluginInstaller
+{
+
+ Q_INTERFACES(IPluginInstaller)
+
+public:
+ /**
+ * @brief test if the archive represented by the file name can be installed through
+ * this installer
+ * @param filename of the archive
+ * @return true if this installer can handle the archive
+ * @note this is only called if the archive couldn't be opened by the caller,
+ * otherwise IPluginInstaller::isArchiveSupported(const DirectoryTree &tree) is called
+ */
+ virtual bool isArchiveSupported(const QString& archiveName) const = 0;
+
+ /**
+ * @return returns a list of file extensions that may be supported by this installer
+ */
+ virtual std::set<QString> supportedExtensions() const = 0;
+
+ /**
+ * install call
+ * @param modName name of the mod to install. As an input parameter this is the
+ * suggested name (i.e. from meta data) The installer may change this parameter to
+ * rename the mod)
+ * @param filename of the archive
+ * @param version version of the mod. May be empty if the version is not yet known.
+ * The plugin is responsible for setting the version on the created mod
+ * @param nexusID id of the mod or -1 if unknown. The plugin is responsible for
+ * setting the mod id for the created mod
+ * @return result of the installation process
+ */
+ virtual EInstallResult install(GuessedValue<QString>& modName, QString gameName,
+ const QString& archiveName, const QString& version,
+ int nexusID) = 0;
+};
+
+} // namespace MOBase
+
+Q_DECLARE_INTERFACE(MOBase::IPluginInstallerCustom,
+ "com.tannin.ModOrganizer.PluginInstallerCustom/1.0")
+
+#endif // IPLUGININSTALLERCUSTOM_H
diff --git a/libs/uibase/include/uibase/iplugininstallersimple.h b/libs/uibase/include/uibase/iplugininstallersimple.h
new file mode 100644
index 0000000..a8795fc
--- /dev/null
+++ b/libs/uibase/include/uibase/iplugininstallersimple.h
@@ -0,0 +1,69 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IPLUGININSTALLERSIMPLE_H
+#define IPLUGININSTALLERSIMPLE_H
+
+#include "guessedvalue.h"
+#include "iplugininstaller.h"
+
+namespace MOBase
+{
+
+/**
+ * Simple installer for mods. Simple installers only deal with an in-memory structure
+ * representing the archive and can modify what to install where by editing this
+ * structure. Actually extracting the archive is handled by the caller Plugins
+ * implementing this interface have to contain the following line in the class
+ * declaration: Q_Interfaces(IPlugin IPluginInstaller IPluginInstallerCustom)
+ */
+class IPluginInstallerSimple : public QObject, public IPluginInstaller
+{
+
+ Q_INTERFACES(IPluginInstaller)
+
+public:
+ /**
+ * install call for the simple mode. The installer only needs to restructure the tree
+ * parameter, the caller does the rest
+ * @param modName name of the mod to install. As an input parameter this is the
+ * suggested name (i.e. from meta data) The installer may change this parameter to
+ * rename the mod)
+ * @param tree in-memory representation of the archive content
+ * @param version version of the mod. May be empty if the version is not yet known.
+ * Can be updated if the plugin can determine the version
+ * @param nexusID id of the mod or -1 if unknown. May be updated if the plugin can
+ * determine the mod id
+ * @return the result of the installation process. If "ERROR_NOTATTEMPTED" is
+ * returned, further installers will work with the modified tree. This may be useful
+ * when implementing a sort of filter, but usually tree should remain unchanged in
+ * that case.
+ */
+ virtual EInstallResult install(GuessedValue<QString>& modName,
+ std::shared_ptr<IFileTree>& tree, QString& version,
+ int& nexusID) = 0;
+};
+
+} // namespace MOBase
+
+Q_DECLARE_INTERFACE(MOBase::IPluginInstallerSimple,
+ "com.tannin.ModOrganizer.PluginInstallerSimple/1.0")
+
+#endif // IPLUGININSTALLERSIMPLE_H
diff --git a/libs/uibase/include/uibase/ipluginlist.h b/libs/uibase/include/uibase/ipluginlist.h
new file mode 100644
index 0000000..61f8f7d
--- /dev/null
+++ b/libs/uibase/include/uibase/ipluginlist.h
@@ -0,0 +1,268 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2013 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IPLUGINLIST_H
+#define IPLUGINLIST_H
+
+#include <QFlags>
+
+class QString;
+
+#include <functional>
+#include <map>
+
+namespace MOBase
+{
+
+class IPluginList
+{
+
+public:
+ enum PluginState
+ {
+ STATE_MISSING,
+ STATE_INACTIVE,
+ STATE_ACTIVE
+ };
+
+ Q_DECLARE_FLAGS(PluginStates, PluginState)
+
+public:
+ virtual ~IPluginList() {}
+
+ /**
+ * @return list of plugin names
+ */
+ virtual QStringList pluginNames() const = 0;
+
+ /**
+ * @brief retrieve the state of a plugin
+ * @param name filename of the plugin (without path but with file extension)
+ * @return one of the possible plugin states: missing, inactive or active
+ */
+ virtual PluginStates state(const QString& name) const = 0;
+
+ /**
+ * @brief set the state of a plugin
+ * @param name filename of the plugin (without path but with file extensions)
+ * @param state new state of the plugin. should be active or inactive
+ */
+ virtual void setState(const QString& name, PluginStates state) = 0;
+
+ /**
+ * @brief retrieve the priority of a plugin
+ * @param name filename of the plugin (without path but with file extension)
+ * @return the priority (the higher the more important). Returns -1 if the plugin
+ * doesn't exist
+ */
+ virtual int priority(const QString& name) const = 0;
+
+ /**
+ * @brief Change the priority of a plugin.
+ *
+ * @param name Filename of the plugin (without path but with file extension).
+ * @param newPriority New priority of the plugin.
+ *
+ * @return true on success, false if the priority change was not possible. This is
+ * usually because one of the parameters is invalid. The function returns true even if
+ * the plugin was not moved at the specified priority (e.g. when trying to move a
+ * non-master plugin before a master one).
+ */
+ virtual bool setPriority(const QString& name, int newPriority) = 0;
+
+ /**
+ * @brief retrieve the load order of a plugin
+ * @param name filename of the plugin (without path but with file extension)
+ * @return the load order of a plugin (the order in which the game loads it). If all
+ * plugins are enabled this is the same as the priority but disabled plugins will have
+ * a load order of -1. This also returns -1 if the plugin doesn't exist
+ */
+ virtual int loadOrder(const QString& name) const = 0;
+
+ /**
+ * @brief sets the load order of the plugin list.
+ * @param the new load order, specified by the list of plugin names, sorted.
+ * @note plugins not included in the list will be placed at highest priority
+ * in the order they were before
+ */
+ virtual void setLoadOrder(const QStringList& pluginList) = 0;
+
+ /**
+ * @brief determine if a plugin is a master file (basically a library, referenced by
+ * other plugins)
+ * @param name filename of the plugin (without path but with file extension)
+ * @return true if the file is a master, false if it isn't OR if the file doesn't
+ * exist.
+ * @note deprecated
+ */
+ [[deprecated]] virtual bool isMaster(const QString& name) const = 0;
+
+ /**
+ * @brief retrieve the list of masters required for this plugin
+ * @param name filename of the plugin (without path but with file extension)
+ * @return list of masters (filenames with extension, no path)
+ */
+ virtual QStringList masters(const QString& name) const = 0;
+
+ /**
+ * @brief retrieve the name of the origin of a plugin. This is either the (internal!)
+ * name of a mod or "overwrite" or "data"
+ * @param name filename of the plugin (without path but with file extension)
+ * @return name of the origin or an empty string if the plugin doesn't exist
+ * @note the internal name of a mod can differ from the display name for
+ * disambiguation
+ */
+ virtual QString origin(const QString& name) const = 0;
+
+ /**
+ * @brief invoked whenever the application felt it necessary to refresh the list (i.e.
+ * because of external changes)
+ */
+ virtual bool onRefreshed(const std::function<void()>& callback) = 0;
+
+ /**
+ * @brief invoked whenever a plugin has changed priority
+ */
+ virtual bool
+ onPluginMoved(const std::function<void(const QString&, int, int)>& func) = 0;
+
+ /**
+ * @brief Installs a handler for the event that the state of plugin changed
+ * (active/inactive).
+ *
+ * Parameters of the callback:
+ * - Map containing the plugins whose states have changed. Keys are plugin names and
+ * values are mod states.
+ *
+ * @param func The signal to be called when the states of any plugins change.
+ *
+ * @return true if the handler was successfully installed, false otherwise (there is
+ * as of now no known reason this should fail).
+ */
+ virtual bool onPluginStateChanged(
+ const std::function<void(const std::map<QString, PluginStates>&)>& func) = 0;
+
+ /**
+ * @brief determine if a plugin has the .esm extension (basically a library,
+ * referenced by other plugins)
+ * @param name filename of the plugin (without path but with file extension)
+ * @return true if the file has the .esm extension, false if it isn't OR if the file
+ * doesn't exist.
+ * @note in gamebryo games, a master file will usually have a .esm file
+ * extension but technically an esp can be flagged as master and an esm might
+ * not be
+ */
+ virtual bool hasMasterExtension(const QString& name) const = 0;
+
+ /**
+ * @brief determine if a plugin has the .esl extension
+ * @param name filename of the plugin (without path but with file extension)
+ * @return true if the file has the .esl extension, false if it isn't OR if the file
+ * doesn't exist.
+ * @note in gamebryo games, a light file will usually have a .esl file
+ * extension but technically an esp can be flagged as light and an esm might
+ * not be
+ */
+ virtual bool hasLightExtension(const QString& name) const = 0;
+
+ /**
+ * @brief determine if a plugin is flagged as master (basically a library, referenced
+ * by other plugins)
+ * @param name filename of the plugin (without path but with file extension)
+ * @return true if the file is flagged as master, false if it isn't OR if the file
+ * doesn't exist.
+ * @note in gamebryo games, a master file will usually have a .esm file
+ * extension but technically an esp can be flagged as master and an esm might
+ * not be
+ */
+ virtual bool isMasterFlagged(const QString& name) const = 0;
+
+ /**
+ * @brief determine if a plugin is flagged as medium
+ * @param name filename of the plugin (without path but with file extension)
+ * @return true if the file is flagged as medium, false if it isn't OR if the file
+ * doesn't exist.
+ * @note this plugin flag was added in Starfield and signifies plugins in between
+ * master and light plugins.
+ */
+ virtual bool isMediumFlagged(const QString& name) const = 0;
+
+ /**
+ * @brief determine if a plugin is flagged as light
+ * @param name filename of the plugin (without path but with file extension)
+ * @return true if the file is flagged as light, false if it isn't OR if the file
+ * doesn't exist.
+ * @note in gamebryo games, a light file will usually have a .esl file
+ */
+ virtual bool isLightFlagged(const QString& name) const = 0;
+
+ /**
+ * @brief determine if a plugin is flagged as blueprint
+ * @param name filename of the plugin (without path but with file extension)
+ * @return true if the file is flagged as blueprint, false if it isn't OR if the file
+ * doesn't exist.
+ * @note this plugin flag was added in Starfield and signifies plugins that are
+ * hidden in the Creation Kit and removed from Plugins.txt when the game loads a save
+ */
+ virtual bool isBlueprintFlagged(const QString& name) const = 0;
+
+ /**
+ * @brief determine if a plugin has no records
+ * @param name filename of the plugin (without path but with file extension)
+ * @return true if the file has no records, false if it does OR if the file doesn't
+ * exist.
+ */
+ virtual bool hasNoRecords(const QString& name) const = 0;
+
+ /**
+ * @brief retrieve the form version of a plugin
+ * @param name filename of the plugin (without path but with file extension)
+ * @return the form version of the plugin, 0 if it doesn't have a form version or -1
+ * if the plugin doesn't exist
+ * @note Oblivion-style plugin headers don't have a form version
+ */
+ virtual int formVersion(const QString& name) const = 0;
+
+ /**
+ * @brief retrieve the header version of a plugin
+ * @param name filename of the plugin (without path but with file extension)
+ * @return the header version of the plugin or -1 if the plugin doesn't exist
+ */
+ virtual float headerVersion(const QString& name) const = 0;
+
+ /**
+ * @brief retrieve the author of a plugin
+ * @param name filename of the plugin (without path but with file extension)
+ * @return the author of the plugin or an empty string if the plugin doesn't exist
+ */
+ virtual QString author(const QString& name) const = 0;
+
+ /**
+ * @brief retrieve the description of a plugin
+ * @param name filename of the plugin (without path but with file extension)
+ * @return the description of the plugin or an empty string if the plugin doesn't
+ * exist
+ */
+ virtual QString description(const QString& name) const = 0;
+};
+
+} // namespace MOBase
+
+#endif // IPLUGINLIST_H
diff --git a/libs/uibase/include/uibase/ipluginmodpage.h b/libs/uibase/include/uibase/ipluginmodpage.h
new file mode 100644
index 0000000..78630d9
--- /dev/null
+++ b/libs/uibase/include/uibase/ipluginmodpage.h
@@ -0,0 +1,92 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2013 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IPLUGINMODPAGE_H
+#define IPLUGINMODPAGE_H
+
+#include "iplugin.h"
+#include "modrepositoryfileinfo.h"
+#include <QIcon>
+
+namespace MOBase
+{
+
+class IPluginModPage : public QObject, public IPlugin
+{
+ Q_INTERFACES(IPlugin)
+public:
+ /**
+ * @return name of the Page as displayed in the ui
+ */
+ virtual QString displayName() const = 0;
+
+ /**
+ * @return icon to be displayed with the page
+ */
+ virtual QIcon icon() const = 0;
+
+ /**
+ * @return url to open when the user wants to visit this mod page
+ */
+ virtual QUrl pageURL() const = 0;
+
+ /**
+ * @return true if the page should be opened in the integrated browser
+ * @note unless the page provides a special means of starting downloads (like the
+ * nxm:// url schema on nexus) it will not be possible to handle downloads unless the
+ * integrated browser is used!
+ */
+ virtual bool useIntegratedBrowser() const = 0;
+
+ /**
+ * @brief test if the plugin handles a download
+ * @param pageURL url of the page that contained the donwload link
+ * @param downloadURL the actual download link
+ * @param fileInfo if the plugin can derive information from the urls it can store
+ * them here and they will be passed along with the download and be made available on
+ * installation
+ * @return true if this plugin wants to handle the specified download
+ */
+ virtual bool handlesDownload(const QUrl& pageURL, const QUrl& downloadURL,
+ ModRepositoryFileInfo& fileInfo) const = 0;
+
+ /**
+ * @brief sets the widget that the tool should use as the parent whenever
+ * it creates a new modal dialog
+ * @param widget the new parent widget
+ */
+ virtual void setParentWidget(QWidget* widget) { m_ParentWidget = widget; }
+
+protected:
+ /**
+ * @brief getter for the parent widget
+ * @return parent widget
+ */
+ QWidget* parentWidget() const { return m_ParentWidget; }
+
+private:
+ QWidget* m_ParentWidget;
+};
+
+} // namespace MOBase
+
+Q_DECLARE_INTERFACE(MOBase::IPluginModPage, "com.tannin.ModOrganizer.PluginModPage/1.0")
+
+#endif // IPLUGINMODPAGE_H
diff --git a/libs/uibase/include/uibase/ipluginpreview.h b/libs/uibase/include/uibase/ipluginpreview.h
new file mode 100644
index 0000000..47ace79
--- /dev/null
+++ b/libs/uibase/include/uibase/ipluginpreview.h
@@ -0,0 +1,72 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2014 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IPLUGINPREVIEW_H
+#define IPLUGINPREVIEW_H
+
+#include "iplugin.h"
+
+namespace MOBase
+{
+
+class IPluginPreview : public QObject, public IPlugin
+{
+ Q_INTERFACES(IPlugin)
+public:
+ /**
+ * @return returns a set of file extensions that may be supported
+ */
+ virtual std::set<QString> supportedExtensions() const = 0;
+
+ /**
+ * @return return whether or not the preview supports raw data previews (likely
+ * sourced from an archive)
+ */
+ virtual bool supportsArchives() const { return false; }
+
+ /**
+ * @brief generate a preview widget for the specified file
+ * @param fileName name of the file to preview
+ * @param maxSize maximum size of the generated widget
+ * @return a widget showing the file
+ */
+ virtual QWidget* genFilePreview(const QString& fileName,
+ const QSize& maxSize) const = 0;
+
+ /**
+ * @brief generate a preview widget from an archive file loaded into memory
+ * @param fileData data of file to preview
+ * @param fileName path of file in virtual filesystem
+ * @param maxSize maxiumum size of the generated widget
+ */
+ virtual QWidget* genDataPreview(const QByteArray& fileData, const QString& fileName,
+ const QSize& maxSize) const
+ {
+ return nullptr;
+ }
+
+private:
+};
+
+} // namespace MOBase
+
+Q_DECLARE_INTERFACE(MOBase::IPluginPreview, "com.tannin.ModOrganizer.PluginPreview/1.0")
+
+#endif // IPLUGINPREVIEW_H
diff --git a/libs/uibase/include/uibase/ipluginproxy.h b/libs/uibase/include/uibase/ipluginproxy.h
new file mode 100644
index 0000000..b0b8d84
--- /dev/null
+++ b/libs/uibase/include/uibase/ipluginproxy.h
@@ -0,0 +1,83 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IPLUGINPROXY_H
+#define IPLUGINPROXY_H
+
+#include <QDir>
+#include <QList>
+#include <QString>
+
+#include "iplugin.h"
+
+namespace MOBase
+{
+
+class IPluginProxy : public IPlugin
+{
+public:
+ IPluginProxy() : m_ParentWidget(nullptr) {}
+
+ /**
+ * @brief List the plugins managed by this proxy in the given
+ * folder.
+ *
+ * @param pluginPath Path containing the plugins.
+ *
+ * @return list of plugin identifiers that supported by this proxy.
+ */
+ virtual QStringList pluginList(const QDir& pluginPath) const = 0;
+
+ /**
+ * @brief Load the plugins corresponding to the given identifier.
+ *
+ * @param identifier Identifier of the proxied plugin to load.
+ *
+ * @return a list of QObject, one for each plugins in the given identifier.
+ */
+ virtual QList<QObject*> load(const QString& identifier) = 0;
+
+ /**
+ * @brief Unload the plugins corresponding to the given identifier.
+ *
+ * @param identifier Identifier of the proxied plugin to unload.
+ */
+ virtual void unload(const QString& identifier) = 0;
+
+ /**
+ * @brief Sets the widget that the tool should use as the parent whenever
+ * it creates a new modal dialog.
+ *
+ * @param widget The new parent widget.
+ */
+ void setParentWidget(QWidget* widget) { m_ParentWidget = widget; }
+
+protected:
+ QWidget* parentWidget() const { return m_ParentWidget; }
+
+private:
+ QWidget* m_ParentWidget;
+};
+
+} // namespace MOBase
+
+Q_DECLARE_INTERFACE(MOBase::IPluginProxy, "com.tannin.ModOrganizer.PluginProxy/1.0")
+
+#endif // IPLUGINPROXY_H
diff --git a/libs/uibase/include/uibase/iplugintool.h b/libs/uibase/include/uibase/iplugintool.h
new file mode 100644
index 0000000..303ab36
--- /dev/null
+++ b/libs/uibase/include/uibase/iplugintool.h
@@ -0,0 +1,87 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef IPLUGINTOOL_H
+#define IPLUGINTOOL_H
+
+#include "iplugin.h"
+#include <QIcon>
+
+namespace MOBase
+{
+
+class IPluginTool : public QObject, public virtual IPlugin
+{
+ Q_INTERFACES(IPlugin)
+public:
+ IPluginTool() : m_ParentWidget(nullptr) {}
+
+ /**
+ * @return name of the tool as displayed in the ui
+ */
+ virtual QString displayName() const = 0;
+
+ /**
+ * @brief For IPluginTool, this returns displayName().
+ *
+ */
+ virtual QString localizedName() const { return displayName(); }
+
+ /**
+ * @return tooltip string
+ */
+
+ virtual QString tooltip() const = 0;
+ /**
+ * @return icon to be displayed with the tool
+ */
+ virtual QIcon icon() const = 0;
+
+ /**
+ * @brief sets the widget that the tool should use as the parent whenever
+ * it creates a new modal dialog
+ * @param widget the new parent widget
+ */
+ virtual void setParentWidget(QWidget* widget) { m_ParentWidget = widget; }
+
+public Q_SLOTS:
+
+ /**
+ * @brief called when the user clicks to start the tool.
+ * @note This must not throw an exception!
+ */
+ virtual void display() const = 0;
+
+protected:
+ /**
+ * @brief getter for the parent widget
+ * @return parent widget
+ */
+ QWidget* parentWidget() const { return m_ParentWidget; }
+
+private:
+ QWidget* m_ParentWidget;
+};
+
+} // namespace MOBase
+
+Q_DECLARE_INTERFACE(MOBase::IPluginTool, "com.tannin.ModOrganizer.PluginTool/1.0")
+
+#endif // IPLUGINTOOL_H
diff --git a/libs/uibase/include/uibase/iprofile.h b/libs/uibase/include/uibase/iprofile.h
new file mode 100644
index 0000000..1706e3e
--- /dev/null
+++ b/libs/uibase/include/uibase/iprofile.h
@@ -0,0 +1,41 @@
+#ifndef IPROFILE
+#define IPROFILE
+
+#include <QList>
+#include <QString>
+
+namespace MOBase
+{
+
+class IProfile
+{
+
+public:
+ virtual ~IProfile() {}
+
+ virtual QString name() const = 0;
+ virtual QString absolutePath() const = 0;
+ virtual bool localSavesEnabled() const = 0;
+ virtual bool localSettingsEnabled() const = 0;
+ virtual bool invalidationActive(bool* supported) const = 0;
+
+ /**
+ * @brief Retrieve the absolute file path to the corresponding INI file for this
+ * profile.
+ *
+ * @param iniFile INI file to retrieve a path for. This can either be the
+ * name of a file or a path to the absolute file outside of the profile.
+ *
+ * @return the absolute path for the given INI file for this profile.
+ *
+ * @note If iniFile does not correspond to a file in the list of INI files for the
+ * current game (as returned by IPluginGame::iniFiles), the path to the global
+ * file will be returned (if iniFile is absolute, iniFile is returned, otherwiise
+ the path is assumed relative to the game documents directory).
+ */
+ virtual QString absoluteIniFilePath(QString iniFile) const = 0;
+};
+
+} // namespace MOBase
+
+#endif // IPROFILE
diff --git a/libs/uibase/include/uibase/isavegame.h b/libs/uibase/include/uibase/isavegame.h
new file mode 100644
index 0000000..2f69446
--- /dev/null
+++ b/libs/uibase/include/uibase/isavegame.h
@@ -0,0 +1,58 @@
+#ifndef ISAVEGAMEINFO_H
+#define ISAVEGAMEINFO_H
+
+#include <QMetaType>
+
+class QString;
+class QDateTime;
+
+namespace MOBase
+{
+
+/** Base class for information about what is in a save game */
+class ISaveGame
+{
+public:
+ virtual ~ISaveGame() {}
+
+ /**
+ * @return the path of the (main) save file, either as an absolute file or
+ * relative to the save folder for which this save was created.
+ */
+ virtual QString getFilepath() const = 0;
+
+ /**
+ * @brief Retrieve the creation time of the save.
+ *
+ * Note that this might not be the same as the creation time of the file.
+ */
+ virtual QDateTime getCreationTime() const = 0;
+
+ /**
+ * @brief Retrieve the name of this save.
+ *
+ * @return the name of this save.
+ */
+ virtual QString getName() const = 0;
+
+ /**
+ * @brief Get a name which can be used to identify sets of saves.
+ *
+ * This is usually the PC name for RPG games. The name can contain '/' that
+ * are considered separate section for better visualization (not yet implemented).
+ */
+ virtual QString getSaveGroupIdentifier() const = 0;
+
+ /**
+ * @brief Gets all the files related to this save
+ *
+ * Note: This must return the actual list, not the potential list.
+ */
+ virtual QStringList allFiles() const = 0;
+};
+
+} // namespace MOBase
+
+Q_DECLARE_METATYPE(MOBase::ISaveGame const*)
+
+#endif // SAVEGAMEINFO_H
diff --git a/libs/uibase/include/uibase/isavegameinfowidget.h b/libs/uibase/include/uibase/isavegameinfowidget.h
new file mode 100644
index 0000000..d0620b0
--- /dev/null
+++ b/libs/uibase/include/uibase/isavegameinfowidget.h
@@ -0,0 +1,30 @@
+#ifndef ISAVEGAMEINFOWIDGET_H
+#define ISAVEGAMEINFOWIDGET_H
+
+#include <QWidget>
+
+#include "isavegame.h"
+
+class QFile;
+
+namespace MOBase
+{
+
+/** Base class for a save game info widget.
+ *
+ * This supports something or other
+ */
+class ISaveGameInfoWidget : public QWidget
+{
+public:
+ ISaveGameInfoWidget(QWidget* parent = 0) : QWidget(parent) {}
+
+ virtual ~ISaveGameInfoWidget() {}
+
+ /** Set the save file to display in the widget */
+ virtual void setSave(ISaveGame const&) = 0;
+};
+
+} // namespace MOBase
+
+#endif // ISAVEGAMEINFOWIDGET_H
diff --git a/libs/uibase/include/uibase/json.h b/libs/uibase/include/uibase/json.h
new file mode 100644
index 0000000..ad41bbc
--- /dev/null
+++ b/libs/uibase/include/uibase/json.h
@@ -0,0 +1,95 @@
+/**
+ * QtJson - A simple class for parsing JSON data into a QVariant hierarchies and
+ * vice-versa. Copyright (C) 2011 Eeli Reilin
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file json.h
+ */
+
+#ifndef JSON_H
+#define JSON_H
+
+#include <QList>
+#include <QString>
+#include <QVariant>
+
+/**
+ * \namespace QtJson
+ * \brief A JSON data parser
+ *
+ * Json parses a JSON data into a QVariant hierarchy.
+ */
+namespace QtJson
+{
+typedef QVariantMap JsonObject;
+typedef QVariantList JsonArray;
+
+/**
+ * Parse a JSON string
+ *
+ * \param json The JSON data
+ */
+QVariant parse(const QString& json);
+
+/**
+ * Parse a JSON string
+ *
+ * \param json The JSON data
+ * \param success The success of the parsing
+ */
+QVariant parse(const QString& json, bool& success);
+
+/**
+ * This method generates a textual JSON representation
+ *
+ * \param data The JSON data generated by the parser.
+ *
+ * \return QByteArray Textual JSON representation in UTF-8
+ */
+QByteArray serialize(const QVariant& data);
+
+/**
+ * This method generates a textual JSON representation
+ *
+ * \param data The JSON data generated by the parser.
+ * \param success The success of the serialization
+ *
+ * \return QByteArray Textual JSON representation in UTF-8
+ */
+QByteArray serialize(const QVariant& data, bool& success);
+
+/**
+ * This method generates a textual JSON representation
+ *
+ * \param data The JSON data generated by the parser.
+ *
+ * \return QString Textual JSON representation
+ */
+QString serializeStr(const QVariant& data);
+
+/**
+ * This method generates a textual JSON representation
+ *
+ * \param data The JSON data generated by the parser.
+ * \param success The success of the serialization
+ *
+ * \return QString Textual JSON representation
+ */
+QString serializeStr(const QVariant& data, bool& success);
+} // namespace QtJson
+
+#endif // JSON_H
diff --git a/libs/uibase/include/uibase/lineeditclear.h b/libs/uibase/include/uibase/lineeditclear.h
new file mode 100644
index 0000000..dfded64
--- /dev/null
+++ b/libs/uibase/include/uibase/lineeditclear.h
@@ -0,0 +1,40 @@
+/****************************************************************************
+**
+** Copyright (c) 2007 Trolltech ASA <info@trolltech.com>
+**
+** Use, modification and distribution is allowed without limitation,
+** warranty, liability or support of any kind.
+**
+****************************************************************************/
+
+#ifndef LINEEDITCLEAR_H
+#define LINEEDITCLEAR_H
+
+#include "dllimport.h"
+#include <QLineEdit>
+
+class QToolButton;
+
+namespace MOBase
+{
+
+class QDLLEXPORT LineEditClear : public QLineEdit
+{
+ Q_OBJECT
+
+public:
+ LineEditClear(QWidget* parent = 0);
+
+protected:
+ void resizeEvent(QResizeEvent*);
+
+private slots:
+ void updateCloseButton(const QString& text);
+
+private:
+ QToolButton* clearButton;
+};
+
+} // namespace MOBase
+
+#endif // LINEEDITCLEAR_H
diff --git a/libs/uibase/include/uibase/linklabel.h b/libs/uibase/include/uibase/linklabel.h
new file mode 100644
index 0000000..80fa958
--- /dev/null
+++ b/libs/uibase/include/uibase/linklabel.h
@@ -0,0 +1,38 @@
+#ifndef UIBASE_LINKLABEL_INCLUDED
+#define UIBASE_LINKLABEL_INCLUDED
+
+#include "dllimport.h"
+#include <QLabel>
+
+// this is a hack to allow .qss files to change the link color
+//
+// there's nothing in qt to change link colors from a qss file and the color
+// can't even be changed on individual widgets, they all use the _global_
+// palette on qApp
+//
+// so as soon as there's a LinkLabel present on screen, the global link color
+// will be set to whatever's in the qss file for:
+//
+// LinkLabel { qproperty-linkColor: cssColor; }
+//
+// this doesn't work for links that are visible, so changing the qss live won't
+// change the colors for those, MO has to be restarted
+//
+// apart from that, `LinkLabel` is just a `QLabel`
+//
+class QDLLEXPORT LinkLabel : public QLabel
+{
+ Q_OBJECT;
+ Q_PROPERTY(QColor linkColor READ linkColor WRITE setLinkColor);
+
+public:
+ LinkLabel(QWidget* parent = nullptr);
+
+ QColor linkColor() const;
+ void setLinkColor(const QColor& c);
+
+private:
+ static QColor m_linkColor;
+};
+
+#endif // UIBASE_LINKLABEL_INCLUDED
diff --git a/libs/uibase/include/uibase/log.h b/libs/uibase/include/uibase/log.h
new file mode 100644
index 0000000..1049506
--- /dev/null
+++ b/libs/uibase/include/uibase/log.h
@@ -0,0 +1,346 @@
+#pragma once
+
+#include <QColor>
+#include <QFlag>
+#include <QFlags>
+#include <QList>
+#include <QRect>
+#include <QSize>
+#include <QString>
+#include <QStringView>
+#include <filesystem>
+#include <string>
+#include <vector>
+
+#include <format>
+
+#include "dllimport.h"
+#include "formatters.h"
+#include <uibase/strings.h>
+
+namespace spdlog
+{
+class logger;
+}
+namespace spdlog::sinks
+{
+class sink;
+}
+
+namespace MOBase::log
+{
+
+enum Levels
+{
+ Debug = 0,
+ Info = 1,
+ Warning = 2,
+ Error = 3
+};
+
+struct BlacklistEntry
+{
+ std::string filter;
+ std::string replacement;
+};
+
+} // namespace MOBase::log
+
+namespace MOBase::log::details
+{
+
+// TODO: remove for C++23
+template <typename T>
+concept formattable = requires(T& v, std::format_context ctx) {
+ std::formatter<std::remove_cvref_t<T>>().format(v, ctx);
+};
+
+template <typename F, typename... Args>
+concept RuntimeFormatString = requires(F&& f, Args&&... args) {
+ (formattable<Args> && ...);
+ !std::is_convertible_v<std::decay_t<F>, std::string_view>;
+};
+
+void QDLLEXPORT doLogImpl(spdlog::logger& lg, Levels lv, const std::string& s) noexcept;
+
+template <class... Args>
+void doLog(spdlog::logger& logger, Levels lv,
+ const std::vector<MOBase::log::BlacklistEntry> bl,
+ std::format_string<Args...> format, Args&&... args) noexcept
+{
+ // format errors are logged without much information to avoid throwing again
+
+ std::string s;
+ try {
+ s = std::format(format, std::forward<Args>(args)...);
+
+ // check the blacklist
+ for (const BlacklistEntry& entry : bl) {
+ MOBase::ireplace_all(s, entry.filter, entry.replacement);
+ }
+ } catch (std::format_error&) {
+ s = "format error while logging";
+ lv = Levels::Error;
+ } catch (std::exception&) {
+ s = "exception while formatting for logging";
+ lv = Levels::Error;
+ } catch (...) {
+ s = "unknown exception while formatting for logging";
+ lv = Levels::Error;
+ }
+
+ doLogImpl(logger, lv, s);
+}
+
+template <class F, class... Args>
+void doLog(spdlog::logger& logger, Levels lv,
+ const std::vector<MOBase::log::BlacklistEntry> bl, F&& format,
+ Args&&... args) noexcept
+{
+ std::string s;
+
+ // format errors are logged without much information to avoid throwing again
+
+ try {
+ if constexpr (sizeof...(Args) == 0) {
+ s = std::format("{}", std::forward<F>(format));
+ } else if constexpr (std::is_same_v<std::decay_t<F>, QString>) {
+ s = std::vformat(format.toStdString(), std::make_format_args(args...));
+ } else {
+ s = std::vformat(std::forward<F>(format), std::make_format_args(args...));
+ }
+
+ // check the blacklist
+ for (const BlacklistEntry& entry : bl) {
+ MOBase::ireplace_all(s, entry.filter, entry.replacement);
+ }
+ } catch (std::format_error&) {
+ s = "format error while logging";
+ lv = Levels::Error;
+ } catch (std::exception&) {
+ s = "exception while formatting for logging";
+ lv = Levels::Error;
+ } catch (...) {
+ s = "unknown exception while formatting for logging";
+ lv = Levels::Error;
+ }
+
+ doLogImpl(logger, lv, s);
+}
+
+} // namespace MOBase::log::details
+
+namespace MOBase::log
+{
+
+struct QDLLEXPORT File
+{
+public:
+ enum Types
+ {
+ None = 0,
+ Daily,
+ Rotating,
+ Single
+ };
+
+ File();
+
+ static File daily(std::filesystem::path file, int hour, int minute);
+
+ static File rotating(std::filesystem::path file, std::size_t maxSize,
+ std::size_t maxFiles);
+
+ static File single(std::filesystem::path file);
+
+ Types type;
+ std::filesystem::path file;
+ std::size_t maxSize, maxFiles;
+ int dailyHour, dailyMinute;
+};
+
+struct Entry
+{
+ std::chrono::system_clock::time_point time;
+ Levels level;
+ std::string message;
+ std::string formattedMessage;
+};
+
+using Callback = void(Entry);
+
+struct LoggerConfiguration
+{
+ std::string name;
+ Levels maxLevel = Levels::Info;
+ std::string pattern;
+ bool utc = false;
+ std::vector<BlacklistEntry> blacklist;
+};
+
+class QDLLEXPORT Logger
+{
+public:
+ Logger(LoggerConfiguration conf);
+ ~Logger();
+
+ Levels level() const;
+ void setLevel(Levels lv);
+
+ void setPattern(const std::string& pattern);
+ void setFile(const File& f);
+ void setCallback(Callback* f);
+
+ void addToBlacklist(const std::string& filter, const std::string& replacement);
+ void removeFromBlacklist(const std::string& filter);
+ void resetBlacklist();
+
+ template <class F, class... Args>
+ requires(details::RuntimeFormatString<F, Args...>)
+ void debug(F&& format, Args&&... args) noexcept
+ {
+ log(Debug, std::forward<F>(format), std::forward<Args>(args)...);
+ }
+
+ template <class... Args>
+ void debug(std::format_string<Args...> format, Args&&... args) noexcept
+ {
+ log(Debug, format, std::forward<Args>(args)...);
+ }
+
+ template <class F, class... Args>
+ requires(details::RuntimeFormatString<F, Args...>)
+ void info(F&& format, Args&&... args) noexcept
+ {
+ log(Info, std::forward<F>(format), std::forward<Args>(args)...);
+ }
+
+ template <class... Args>
+ void info(std::format_string<Args...> format, Args&&... args) noexcept
+ {
+ log(Info, format, std::forward<Args>(args)...);
+ }
+
+ template <class F, class... Args>
+ requires(details::RuntimeFormatString<F, Args...>)
+ void warn(F&& format, Args&&... args) noexcept
+ {
+ log(Warning, std::forward<F>(format), std::forward<Args>(args)...);
+ }
+
+ template <class... Args>
+ void warn(std::format_string<Args...> format, Args&&... args) noexcept
+ {
+ log(Warning, format, std::forward<Args>(args)...);
+ }
+
+ template <class F, class... Args>
+ requires(details::RuntimeFormatString<F, Args...>)
+ void error(F&& format, Args&&... args) noexcept
+ {
+ log(Error, std::forward<F>(format), std::forward<Args>(args)...);
+ }
+
+ template <class... Args>
+ void error(std::format_string<Args...> format, Args&&... args) noexcept
+ {
+ log(Error, format, std::forward<Args>(args)...);
+ }
+
+ template <class F, class... Args>
+ requires(details::RuntimeFormatString<F, Args...>)
+ void log(Levels lv, F&& format, Args&&... args) noexcept
+ {
+ details::doLog(*m_logger, lv, m_conf.blacklist, std::forward<F>(format),
+ std::forward<Args>(args)...);
+ }
+
+ template <class... Args>
+ void log(Levels lv, std::format_string<Args...> format, Args&&... args) noexcept
+ {
+ details::doLog(*m_logger, lv, m_conf.blacklist, format,
+ std::forward<Args>(args)...);
+ }
+
+private:
+ LoggerConfiguration m_conf;
+ std::unique_ptr<spdlog::logger> m_logger;
+ std::shared_ptr<spdlog::sinks::sink> m_sinks;
+ std::shared_ptr<spdlog::sinks::sink> m_console, m_callback, m_file;
+
+ void createLogger(const std::string& name);
+ void addSink(std::shared_ptr<spdlog::sinks::sink> sink);
+};
+
+QDLLEXPORT void createDefault(LoggerConfiguration conf);
+QDLLEXPORT Logger& getDefault();
+
+template <class F, class... Args>
+ requires(details::RuntimeFormatString<F, Args...>)
+void debug(F&& format, Args&&... args) noexcept
+{
+ getDefault().debug(std::forward<F>(format), std::forward<Args>(args)...);
+}
+
+template <class... Args>
+void debug(std::format_string<Args...> format, Args&&... args) noexcept
+{
+ getDefault().debug(format, std::forward<Args>(args)...);
+}
+
+template <class F, class... Args>
+ requires(details::RuntimeFormatString<F, Args...>)
+void info(F&& format, Args&&... args) noexcept
+{
+ getDefault().info(std::forward<F>(format), std::forward<Args>(args)...);
+}
+
+template <class... Args>
+void info(std::format_string<Args...> format, Args&&... args) noexcept
+{
+ getDefault().info(format, std::forward<Args>(args)...);
+}
+
+template <class F, class... Args>
+ requires(details::RuntimeFormatString<F, Args...>)
+void warn(F&& format, Args&&... args) noexcept
+{
+ getDefault().warn(std::forward<F>(format), std::forward<Args>(args)...);
+}
+
+template <class... Args>
+void warn(std::format_string<Args...> format, Args&&... args) noexcept
+{
+ getDefault().warn(format, std::forward<Args>(args)...);
+}
+
+template <class F, class... Args>
+ requires(details::RuntimeFormatString<F, Args...>)
+void error(F&& format, Args&&... args) noexcept
+{
+ getDefault().error(std::forward<F>(format), std::forward<Args>(args)...);
+}
+
+template <class... Args>
+void error(std::format_string<Args...> format, Args&&... args) noexcept
+{
+ getDefault().error(format, std::forward<Args>(args)...);
+}
+
+template <class F, class... Args>
+ requires(details::RuntimeFormatString<F, Args...>)
+void log(Levels lv, F&& format, Args&&... args) noexcept
+{
+ getDefault().log(lv, std::forward<F>(format), std::forward<Args>(args)...);
+}
+
+template <class... Args>
+void log(Levels lv, std::format_string<Args...> format, Args&&... args) noexcept
+{
+ getDefault().log(lv, format, std::forward<Args>(args)...);
+}
+
+//
+QDLLEXPORT QString levelToString(Levels level);
+
+} // namespace MOBase::log
diff --git a/libs/uibase/include/uibase/memoizedlock.h b/libs/uibase/include/uibase/memoizedlock.h
new file mode 100644
index 0000000..1888484
--- /dev/null
+++ b/libs/uibase/include/uibase/memoizedlock.h
@@ -0,0 +1,61 @@
+#ifndef MO_UIBASE_MEMOIZEDLOCK_INCLUDED
+#define MO_UIBASE_MEMOIZEDLOCK_INCLUDED
+
+// Do not put this in utility.h otherwise the C# projects will
+// fail to compile since apparently <mutex> is not available in
+// C++/CLI projects.
+
+#include <functional>
+#include <mutex>
+
+namespace MOBase
+{
+
+/**
+ * Class that can be used to perform thread-safe memoization.
+ *
+ * Each instance hold a flag indicating if the current value is up-to-date
+ * or not. This flag can be reset using `invalidate()`. When the value is queried,
+ * the flag is checked, and if it is not up-to-date, the given callback is used
+ * to compute the value.
+ *
+ * The computation and update of the value is locked to avoid concurrent modifications.
+ *
+ * @tparam T Type of value ot memoized.
+ * @tparam Fn Type of the callback.
+ */
+template <class T, class Fn = std::function<T()>>
+class MemoizedLocked
+{
+public:
+ 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) {
+ std::scoped_lock lock(m_Mutex);
+ if (m_NeedUpdating) {
+ m_Value = std::invoke(m_Fn, std::forward<Args>(args)...);
+ m_NeedUpdating = false;
+ }
+ }
+ return m_Value;
+ }
+
+ void invalidate() { m_NeedUpdating = true; }
+
+private:
+ mutable std::mutex m_Mutex;
+ mutable std::atomic<bool> m_NeedUpdating{true};
+
+ Fn m_Fn;
+ mutable T m_Value;
+};
+
+} // namespace MOBase
+
+#endif
diff --git a/libs/uibase/include/uibase/moassert.h b/libs/uibase/include/uibase/moassert.h
new file mode 100644
index 0000000..492381c
--- /dev/null
+++ b/libs/uibase/include/uibase/moassert.h
@@ -0,0 +1,36 @@
+#ifndef UIBASE_MOASSERT_INCLUDED
+#define UIBASE_MOASSERT_INCLUDED
+
+#include "log.h"
+#include <csignal>
+
+namespace MOBase
+{
+
+template <class T>
+inline void MOAssert(T&& t, const char* exp, const char* file, int line,
+ const char* func)
+{
+ if (!t) {
+ log::error("assertion failed: {}:{} {}: '{}'", file, line, func, exp);
+
+#ifdef _WIN32
+ if (IsDebuggerPresent()) {
+ DebugBreak();
+ }
+#else
+ // On Linux, raise SIGTRAP if a debugger might be attached
+ raise(SIGTRAP);
+#endif
+ }
+}
+
+} // namespace MOBase
+
+#ifdef _MSC_VER
+#define MO_ASSERT(v) MOBase::MOAssert(v, #v, __FILE__, __LINE__, __FUNCSIG__)
+#else
+#define MO_ASSERT(v) MOBase::MOAssert(v, #v, __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#endif
+
+#endif // UIBASE_MOASSERT_INCLUDED
diff --git a/libs/uibase/include/uibase/modrepositoryfileinfo.h b/libs/uibase/include/uibase/modrepositoryfileinfo.h
new file mode 100644
index 0000000..bf41f72
--- /dev/null
+++ b/libs/uibase/include/uibase/modrepositoryfileinfo.h
@@ -0,0 +1,56 @@
+#ifndef MODREPOSITORYFILEINFO_H
+#define MODREPOSITORYFILEINFO_H
+
+#include "versioninfo.h"
+#include <QDateTime>
+#include <QString>
+#include <QVariantMap>
+
+namespace MOBase
+{
+enum EFileCategory
+{
+ TYPE_UNKNOWN = 0,
+ TYPE_MAIN,
+ TYPE_UPDATE,
+ TYPE_OPTION
+};
+
+class QDLLEXPORT ModRepositoryFileInfo : public QObject
+{
+ Q_OBJECT
+
+public:
+ ModRepositoryFileInfo(const ModRepositoryFileInfo& reference);
+ ModRepositoryFileInfo(QString gameName = "", int modID = 0, int fileID = 0);
+ QString toString() const;
+
+ static ModRepositoryFileInfo createFromJson(const QString& data);
+ QString name;
+ QString uri;
+ QString description;
+ VersionInfo version;
+ VersionInfo newestVersion;
+ int categoryID;
+ QString modName;
+ QString gameName;
+ QString nexusKey;
+ int modID;
+ int fileID;
+ int nexusExpires;
+ int nexusDownloadUser;
+ size_t fileSize;
+ QString fileName;
+ int fileCategory;
+ QDateTime fileTime;
+ QString repository;
+
+ QVariantMap userData;
+
+ QString author;
+ QString uploader;
+ QString uploaderUrl;
+};
+} // namespace MOBase
+
+#endif // MODREPOSITORYFILEINFO_H
diff --git a/libs/uibase/include/uibase/nxmurl.h b/libs/uibase/include/uibase/nxmurl.h
new file mode 100644
index 0000000..bd06d3d
--- /dev/null
+++ b/libs/uibase/include/uibase/nxmurl.h
@@ -0,0 +1,87 @@
+/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef NXMURL_H
+#define NXMURL_H
+
+#include "dllimport.h"
+#include <QList>
+#include <QObject>
+#include <QString>
+
+/**
+ * @brief represents a nxm:// url
+ * @todo the game name encoded into the url is not interpreted
+ **/
+class QDLLEXPORT NXMUrl : public QObject
+{
+ Q_OBJECT
+
+public:
+ /**
+ * @brief constructor
+ *
+ * @param url url following the nxm-protocol
+ **/
+ NXMUrl(const QString& url);
+
+ /**
+ * @return name of the game
+ */
+ QString game() const { return m_Game; }
+
+ /**
+ * @return the NMX request key
+ */
+ QString key() const { return m_Key; }
+
+ /**
+ * @brief retrieve the mod id encoded into the url
+ *
+ * @return mod id
+ **/
+ int modId() const { return m_ModId; }
+
+ /**
+ * @brief retrieve the file id encoded into the url
+ *
+ * @return file id
+ **/
+ int fileId() const { return m_FileId; }
+
+ /**
+ * @return the expires timestamp
+ */
+ int expires() const { return m_Expires; }
+
+ /**
+ * @return the parsed user ID
+ */
+ int userId() const { return m_UserId; }
+
+private:
+ QString m_Game;
+ QString m_Key;
+ int m_ModId;
+ int m_FileId;
+ int m_Expires;
+ int m_UserId;
+};
+
+#endif // NXMURL_H
diff --git a/libs/uibase/include/uibase/pluginrequirements.h b/libs/uibase/include/uibase/pluginrequirements.h
new file mode 100644
index 0000000..ff3668a
--- /dev/null
+++ b/libs/uibase/include/uibase/pluginrequirements.h
@@ -0,0 +1,193 @@
+#ifndef PLUGINREQUIREMENTS_H
+#define PLUGINREQUIREMENTS_H
+
+#include <functional>
+#include <memory>
+#include <optional>
+
+#include <QList>
+#include <QString>
+
+#include "dllimport.h"
+
+namespace MOBase
+{
+
+class IOrganizer;
+class IPluginDiagnose;
+
+/**
+ * @brief The interface for plugin requirements.
+ */
+class IPluginRequirement
+{
+public:
+ class Problem
+ {
+ public:
+ /**
+ * @return a short description for the problem.
+ */
+ QString shortDescription() const { return m_ShortDescription; }
+
+ /**
+ * @return a long description for the problem.
+ */
+ QString longDescription() const { return m_LongDescription; }
+
+ /**
+ *
+ */
+ Problem(QString shortDescription, QString longDescription = "")
+ : m_ShortDescription(shortDescription),
+ m_LongDescription(longDescription.isEmpty() ? shortDescription
+ : longDescription)
+ {}
+
+ private:
+ QString m_ShortDescription, m_LongDescription;
+ };
+
+public:
+ /**
+ * @brief Check if the requirements is met.
+ *
+ * @param organizer The organizer proxy.
+ *
+ * @return a problem if the requirement is not met, otherwise an empty optional.
+ */
+ virtual std::optional<Problem> check(IOrganizer* organizer) const = 0;
+
+ virtual ~IPluginRequirement() {}
+};
+
+/**
+ * @brief Plugin dependency - The requirement is met if one of the
+ * given plugin is active.
+ */
+class QDLLEXPORT PluginDependencyRequirement : public IPluginRequirement
+{
+
+ friend class PluginRequirementFactory;
+
+public:
+ PluginDependencyRequirement(QStringList const& pluginNames);
+
+ virtual std::optional<Problem> check(IOrganizer* organizer) const;
+
+ /**
+ * @return the list of plugin names in this dependency requirement.
+ */
+ QStringList pluginNames() const { return m_PluginNames; }
+
+protected:
+ QString message() const;
+ QStringList m_PluginNames;
+};
+
+/**
+ * @brief Game dependency - The requirement is met if the active game
+ * is one of the specified game.
+ */
+class QDLLEXPORT GameDependencyRequirement : public IPluginRequirement
+{
+
+ friend class PluginRequirementFactory;
+
+public:
+ GameDependencyRequirement(QStringList const& gameNames);
+
+ virtual std::optional<Problem> check(IOrganizer* organizer) const;
+
+ /**
+ * @return the list of game names in this dependency requirement.
+ */
+ QStringList gameNames() const { return m_GameNames; }
+
+protected:
+ QString message() const;
+ QStringList m_GameNames;
+};
+
+/**
+ * @brief Diagnose dependency - This wrap a IPluginDiagnose into a plugin
+ * requirements.
+ *
+ * If the wrapped diagnose plugin reports a problem, the requirement fails
+ * and the associated message is the one from the diagnose plugin (or the
+ * list of messages if multiple problems were reported).
+ */
+class DiagnoseRequirement : public IPluginRequirement
+{
+
+ friend class PluginRequirementFactory;
+
+public:
+ DiagnoseRequirement(const IPluginDiagnose* diagnose);
+
+ virtual std::optional<Problem> check(IOrganizer* organizer) const;
+
+private:
+ const IPluginDiagnose* m_Diagnose;
+};
+
+/**
+ * Factory for plugin requirements.
+ */
+class QDLLEXPORT PluginRequirementFactory
+{
+public:
+ /**
+ * @brief Create a new plugin dependency. The requirement is met if one of the
+ * given plugin is enabled.
+ *
+ * If you want all plugins to be active, simply create multiple requirements.
+ *
+ * @param pluginNames Name of the plugin required.
+ */
+ static std::shared_ptr<const IPluginRequirement>
+ pluginDependency(QStringList const& pluginNames);
+ static std::shared_ptr<const IPluginRequirement>
+ pluginDependency(QString const& pluginName)
+ {
+ return pluginDependency(QStringList{pluginName});
+ }
+
+ /**
+ * @brief Create a new plugin dependency. The requirement is met if the current
+ * game plugin matches one of the given name.
+ *
+ * @param gameName Name of the game required.
+ *
+ * @note This differ from makePluginDependency only for the message.
+ */
+ static std::shared_ptr<const IPluginRequirement>
+ gameDependency(QStringList const& gameNames);
+ static std::shared_ptr<const IPluginRequirement>
+ gameDependency(QString const& gameName)
+ {
+ return gameDependency(QStringList{gameName});
+ }
+
+ /**
+ * @brief Create a requirement from the given diagnose plugin.
+ *
+ * @param diagnose The diagnose plugin.
+ */
+ static std::shared_ptr<const IPluginRequirement>
+ diagnose(const IPluginDiagnose* diagnose);
+
+ /**
+ * @brief Create a generic requirement with the given checker and message.
+ *
+ * @param checker The function to use to check if the requirement is met (should
+ * return true if the requirement is met).
+ * @param description The description to show user if the requirement is not met.
+ */
+ static std::shared_ptr<const IPluginRequirement>
+ basic(std::function<bool(IOrganizer*)> const& checker, QString const description);
+};
+
+} // namespace MOBase
+
+#endif
diff --git a/libs/uibase/include/uibase/pluginsetting.h b/libs/uibase/include/uibase/pluginsetting.h
new file mode 100644
index 0000000..6844f4d
--- /dev/null
+++ b/libs/uibase/include/uibase/pluginsetting.h
@@ -0,0 +1,50 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef PLUGINSETTING_H
+#define PLUGINSETTING_H
+
+#include <QList>
+#include <QString>
+#include <QVariant>
+
+namespace MOBase
+{
+
+/**
+ * @brief struct to hold the user-configurable parameters a plugin accepts. The purpose
+ * of this struct is only to inform the application what settings to offer to the user,
+ * it does not hold the actual value
+ */
+struct PluginSetting
+{
+ PluginSetting(const QString& key, const QString& description,
+ const QVariant& defaultValue)
+ : key(key), description(description), defaultValue(defaultValue)
+ {}
+
+ QString key;
+ QString description;
+ QVariant defaultValue;
+};
+
+} // namespace MOBase
+
+#endif // PLUGINSETTING_H
diff --git a/libs/uibase/include/uibase/questionboxmemory.h b/libs/uibase/include/uibase/questionboxmemory.h
new file mode 100644
index 0000000..c5224ea
--- /dev/null
+++ b/libs/uibase/include/uibase/questionboxmemory.h
@@ -0,0 +1,112 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef QUESTIONBOXMEMORY_H
+#define QUESTIONBOXMEMORY_H
+
+#include "dllimport.h"
+
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QList>
+#include <QObject>
+#include <QString>
+
+class QAbstractButton;
+class QMutex;
+class QSettings;
+class QWidget;
+
+namespace Ui
+{
+class QuestionBoxMemory;
+}
+
+namespace MOBase
+{
+
+class QDLLEXPORT QuestionBoxMemory : public QDialog
+{
+ Q_OBJECT
+
+public:
+ using Button = QDialogButtonBox::StandardButton;
+ static const auto NoButton = QDialogButtonBox::NoButton;
+
+ using GetButton = std::function<Button(const QString&, const QString&)>;
+ using SetWindowButton = std::function<void(const QString&, Button)>;
+ using SetFileButton = std::function<void(const QString&, const QString&, Button)>;
+
+ ~QuestionBoxMemory();
+
+ // QuestionBoxMemory needs to access the settings, but they're only in
+ // the modorganizer project; the only way to avoid accessing the ini file
+ // directly is to use callbacks registered in Settings' constructor
+ //
+ static void setCallbacks(GetButton get, SetWindowButton setWindow,
+ SetFileButton setFile);
+
+ static Button
+ query(QWidget* parent, const QString& windowName, const QString& title,
+ const QString& text,
+ QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes |
+ QDialogButtonBox::No,
+ QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton);
+
+ static Button
+ query(QWidget* parent, const QString& windowName, const QString& fileName,
+ const QString& title, const QString& text,
+ QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes |
+ QDialogButtonBox::No,
+ QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton);
+
+ static void setWindowMemory(const QString& windowName, Button b);
+
+ static void setFileMemory(const QString& windowName, const QString& filename,
+ Button b);
+
+ static Button getMemory(const QString& windowName, const QString& filename);
+
+ static QString buttonToString(Button b);
+
+private slots:
+ void buttonClicked(QAbstractButton* button);
+
+private:
+ explicit QuestionBoxMemory(QWidget* parent, const QString& title, const QString& text,
+ const QString* filename,
+ const QDialogButtonBox::StandardButtons buttons,
+ QDialogButtonBox::StandardButton defaultButton);
+
+private:
+ static Button queryImpl(
+ QWidget* parent, const QString& windowName, const QString* fileName,
+ const QString& title, const QString& text,
+ QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes |
+ QDialogButtonBox::No,
+ QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton);
+
+ std::unique_ptr<Ui::QuestionBoxMemory> ui;
+ QDialogButtonBox::StandardButton m_Button;
+};
+
+} // namespace MOBase
+
+#endif // QUESTIONBOXMEMORY_H
diff --git a/libs/uibase/include/uibase/registry.h b/libs/uibase/include/uibase/registry.h
new file mode 100644
index 0000000..00013df
--- /dev/null
+++ b/libs/uibase/include/uibase/registry.h
@@ -0,0 +1,39 @@
+/*
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef REGISTRY_H
+#define REGISTRY_H
+
+#include "dllimport.h"
+#include <QString>
+
+namespace MOBase
+{
+
+// On Linux, writes INI values using QSettings instead of WritePrivateProfileString
+QDLLEXPORT bool WriteRegistryValue(const QString& appName, const QString& keyName,
+ const QString& value, const QString& fileName);
+
+#ifdef _WIN32
+// Windows-specific overload using wide strings
+QDLLEXPORT bool WriteRegistryValue(const wchar_t* appName, const wchar_t* keyName,
+ const wchar_t* value, const wchar_t* fileName);
+#endif
+
+} // namespace MOBase
+
+#endif // REGISTRY_H
diff --git a/libs/uibase/include/uibase/report.h b/libs/uibase/include/uibase/report.h
new file mode 100644
index 0000000..f164681
--- /dev/null
+++ b/libs/uibase/include/uibase/report.h
@@ -0,0 +1,117 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef REPORT_H
+#define REPORT_H
+
+#include "dllimport.h"
+#include <QCheckBox>
+#include <QComboBox>
+#include <QList>
+#include <QMessageBox>
+#include <QPlainTextEdit>
+#include <wchar.h>
+
+namespace Ui
+{
+class TaskDialog;
+}
+
+namespace MOBase
+{
+
+class ExpanderWidget;
+
+// Convenience function displaying an error message box. This function uses
+// WinAPI if no Qt Window is available yet or QMessageBox otherwise.
+//
+QDLLEXPORT void reportError(const QString& message);
+
+// shows a critical message box that's raised to the top of the zorder, useful
+// for messages without a main window, which sometimes makes them pop up behind
+// all other windows
+//
+// the dialog is not topmost, it's just raised once when shown
+//
+QDLLEXPORT void criticalOnTop(const QString& message);
+
+struct QDLLEXPORT TaskDialogButton
+{
+ QString text, description;
+ QMessageBox::StandardButton button;
+
+ TaskDialogButton(QString text, QString description,
+ QMessageBox::StandardButton button);
+ TaskDialogButton(QString text, QMessageBox::StandardButton button);
+};
+
+class QDLLEXPORT TaskDialog
+{
+public:
+ TaskDialog(QWidget* parent = nullptr, QString title = {});
+ ~TaskDialog();
+
+ TaskDialog& title(const QString& s);
+ TaskDialog& main(const QString& s);
+ TaskDialog& content(const QString& s);
+ TaskDialog& details(const QString& s);
+ TaskDialog& icon(QMessageBox::Icon i);
+ TaskDialog& button(TaskDialogButton b);
+ TaskDialog& remember(const QString& action, const QString& file = {});
+
+ void addContent(QWidget* w);
+ void setWidth(int w);
+
+ QMessageBox::StandardButton exec();
+
+private:
+ std::unique_ptr<QDialog> m_dialog;
+ std::unique_ptr<Ui::TaskDialog> ui;
+ QString m_title, m_main, m_content, m_details;
+ QMessageBox::Icon m_icon;
+ std::vector<TaskDialogButton> m_buttons;
+ QMessageBox::StandardButton m_result;
+ std::unique_ptr<ExpanderWidget> m_expander;
+ int m_width;
+
+ QString m_rememberAction, m_rememberFile;
+ QCheckBox* m_rememberCheck;
+ QComboBox* m_rememberCombo;
+
+ QMessageBox::StandardButton checkMemory() const;
+ void rememberChoice();
+
+ void setDialog();
+ void setWidgets();
+ void setChoices();
+ void setButtons();
+ void setStandardButtons();
+ void setCommandButtons();
+ void setDetails();
+
+ QColor detailsColor() const;
+ QPixmap standardIcon(QMessageBox::Icon icon) const;
+ void setVisibleLines(QPlainTextEdit* w, int lines);
+ void setFontPercent(QWidget* w, double p);
+};
+
+} // namespace MOBase
+
+#endif // REPORT_H
diff --git a/libs/uibase/include/uibase/safewritefile.h b/libs/uibase/include/uibase/safewritefile.h
new file mode 100644
index 0000000..3ce9576
--- /dev/null
+++ b/libs/uibase/include/uibase/safewritefile.h
@@ -0,0 +1,72 @@
+/*
+Copyright (C) 2014 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SAFEWRITEFILE_H
+#define SAFEWRITEFILE_H
+
+#include "dllimport.h"
+#include "utility.h"
+#include <QFile>
+#include <QSaveFile>
+#include <QString>
+
+namespace MOBase
+{
+
+#ifndef _WIN32
+// Thin QFile wrapper that adds a no-op commit() for QSaveFile API compat.
+class DirectWriteFile : public QFile
+{
+public:
+ using QFile::QFile;
+ bool commit()
+ {
+ flush();
+ return true;
+ }
+};
+#endif
+
+/**
+ * @brief a wrapper for QSaveFile that handles errors when opening the file to reduce
+ * code duplication. On Linux, uses plain QFile because QSaveFile's temp-file
+ * strategy fails on many common filesystem configurations.
+ */
+class QDLLEXPORT SafeWriteFile
+{
+public:
+ SafeWriteFile(const QString& fileName);
+
+#ifdef _WIN32
+ QSaveFile* operator->();
+#else
+ DirectWriteFile* operator->();
+#endif
+
+private:
+#ifdef _WIN32
+ QSaveFile m_SaveFile;
+#else
+ DirectWriteFile m_File;
+#endif
+};
+
+} // namespace MOBase
+
+#endif // SAFEWRITEFILE_H
diff --git a/libs/uibase/include/uibase/scopeguard.h b/libs/uibase/include/uibase/scopeguard.h
new file mode 100644
index 0000000..11320f3
--- /dev/null
+++ b/libs/uibase/include/uibase/scopeguard.h
@@ -0,0 +1,296 @@
+#ifndef SCOPEGUARD_H
+#define SCOPEGUARD_H
+
+/*
+ Scopeguard, by Andrei Alexandrescu and Petru Marginean, December 2000.
+ Modified by Joshua Lehrer, FactSet Research Systems, November 2005.
+*/
+
+template <class T>
+class RefHolder
+{
+ T& ref_;
+
+public:
+ RefHolder(T& ref) : ref_(ref) {}
+ operator T&() const { return ref_; }
+
+private:
+ // Disable assignment - not implemented
+ RefHolder& operator=(const RefHolder&);
+};
+
+template <class T>
+inline RefHolder<T> ByRef(T& t)
+{
+ return RefHolder<T>(t);
+}
+
+class ScopeGuardImplBase
+{
+ ScopeGuardImplBase& operator=(const ScopeGuardImplBase&);
+
+protected:
+ ~ScopeGuardImplBase() {}
+ ScopeGuardImplBase(const ScopeGuardImplBase& other) throw()
+ : dismissed_(other.dismissed_)
+ {
+ other.Dismiss();
+ }
+ template <typename J>
+ static void SafeExecute(J& j) throw()
+ {
+ if (!j.dismissed_)
+ try {
+ j.Execute();
+ } catch (...) {
+ }
+ }
+
+ mutable bool dismissed_;
+
+public:
+ ScopeGuardImplBase() throw() : dismissed_(false) {}
+ void Dismiss() const throw() { dismissed_ = true; }
+};
+
+typedef const ScopeGuardImplBase& ScopeGuard;
+
+template <typename F>
+class ScopeGuardImpl0 : public ScopeGuardImplBase
+{
+public:
+ static ScopeGuardImpl0<F> MakeGuard(F fun) { return ScopeGuardImpl0<F>(fun); }
+ ~ScopeGuardImpl0() throw() { SafeExecute(*this); }
+ void Execute() { fun_(); }
+
+protected:
+ ScopeGuardImpl0(F fun) : fun_(fun) {}
+ F fun_;
+};
+
+template <typename F>
+inline ScopeGuardImpl0<F> MakeGuard(F fun)
+{
+ return ScopeGuardImpl0<F>::MakeGuard(fun);
+}
+
+template <typename F, typename P1>
+class ScopeGuardImpl1 : public ScopeGuardImplBase
+{
+public:
+ static ScopeGuardImpl1<F, P1> MakeGuard(F fun, P1 p1)
+ {
+ return ScopeGuardImpl1<F, P1>(fun, p1);
+ }
+ ~ScopeGuardImpl1() throw() { SafeExecute(*this); }
+ void Execute() { fun_(p1_); }
+
+protected:
+ ScopeGuardImpl1(F fun, P1 p1) : fun_(fun), p1_(p1) {}
+ F fun_;
+ const P1 p1_;
+};
+
+template <typename F, typename P1>
+inline ScopeGuardImpl1<F, P1> MakeGuard(F fun, P1 p1)
+{
+ return ScopeGuardImpl1<F, P1>::MakeGuard(fun, p1);
+}
+
+template <typename F, typename P1, typename P2>
+class ScopeGuardImpl2 : public ScopeGuardImplBase
+{
+public:
+ static ScopeGuardImpl2<F, P1, P2> MakeGuard(F fun, P1 p1, P2 p2)
+ {
+ return ScopeGuardImpl2<F, P1, P2>(fun, p1, p2);
+ }
+ ~ScopeGuardImpl2() throw() { SafeExecute(*this); }
+ void Execute() { fun_(p1_, p2_); }
+
+protected:
+ ScopeGuardImpl2(F fun, P1 p1, P2 p2) : fun_(fun), p1_(p1), p2_(p2) {}
+ F fun_;
+ const P1 p1_;
+ const P2 p2_;
+};
+
+template <typename F, typename P1, typename P2>
+inline ScopeGuardImpl2<F, P1, P2> MakeGuard(F fun, P1 p1, P2 p2)
+{
+ return ScopeGuardImpl2<F, P1, P2>::MakeGuard(fun, p1, p2);
+}
+
+template <typename F, typename P1, typename P2, typename P3>
+class ScopeGuardImpl3 : public ScopeGuardImplBase
+{
+public:
+ static ScopeGuardImpl3<F, P1, P2, P3> MakeGuard(F fun, P1 p1, P2 p2, P3 p3)
+ {
+ return ScopeGuardImpl3<F, P1, P2, P3>(fun, p1, p2, p3);
+ }
+ ~ScopeGuardImpl3() throw() { SafeExecute(*this); }
+ void Execute() { fun_(p1_, p2_, p3_); }
+
+protected:
+ ScopeGuardImpl3(F fun, P1 p1, P2 p2, P3 p3) : fun_(fun), p1_(p1), p2_(p2), p3_(p3) {}
+ F fun_;
+ const P1 p1_;
+ const P2 p2_;
+ const P3 p3_;
+};
+
+template <typename F, typename P1, typename P2, typename P3>
+inline ScopeGuardImpl3<F, P1, P2, P3> MakeGuard(F fun, P1 p1, P2 p2, P3 p3)
+{
+ return ScopeGuardImpl3<F, P1, P2, P3>::MakeGuard(fun, p1, p2, p3);
+}
+
+//************************************************************
+
+template <class Obj, typename MemFun>
+class ObjScopeGuardImpl0 : public ScopeGuardImplBase
+{
+public:
+ static ObjScopeGuardImpl0<Obj, MemFun> MakeObjGuard(Obj& obj, MemFun memFun)
+ {
+ return ObjScopeGuardImpl0<Obj, MemFun>(obj, memFun);
+ }
+ ~ObjScopeGuardImpl0() throw() { SafeExecute(*this); }
+ void Execute() { (obj_.*memFun_)(); }
+
+protected:
+ ObjScopeGuardImpl0(Obj& obj, MemFun memFun) : obj_(obj), memFun_(memFun) {}
+ Obj& obj_;
+ MemFun memFun_;
+};
+
+template <class Obj, typename MemFun>
+inline ObjScopeGuardImpl0<Obj, MemFun> MakeObjGuard(Obj& obj, MemFun memFun)
+{
+ return ObjScopeGuardImpl0<Obj, MemFun>::MakeObjGuard(obj, memFun);
+}
+
+template <typename Ret, class Obj1, class Obj2>
+inline ObjScopeGuardImpl0<Obj1, Ret (Obj2::*)()> MakeGuard(Ret (Obj2::*memFun)(),
+ Obj1& obj)
+{
+ return ObjScopeGuardImpl0<Obj1, Ret (Obj2::*)()>::MakeObjGuard(obj, memFun);
+}
+
+template <typename Ret, class Obj1, class Obj2>
+inline ObjScopeGuardImpl0<Obj1, Ret (Obj2::*)()> MakeGuard(Ret (Obj2::*memFun)(),
+ Obj1* obj)
+{
+ return ObjScopeGuardImpl0<Obj1, Ret (Obj2::*)()>::MakeObjGuard(*obj, memFun);
+}
+
+template <class Obj, typename MemFun, typename P1>
+class ObjScopeGuardImpl1 : public ScopeGuardImplBase
+{
+public:
+ static ObjScopeGuardImpl1<Obj, MemFun, P1> MakeObjGuard(Obj& obj, MemFun memFun,
+ P1 p1)
+ {
+ return ObjScopeGuardImpl1<Obj, MemFun, P1>(obj, memFun, p1);
+ }
+ ~ObjScopeGuardImpl1() throw() { SafeExecute(*this); }
+ void Execute() { (obj_.*memFun_)(p1_); }
+
+protected:
+ ObjScopeGuardImpl1(Obj& obj, MemFun memFun, P1 p1)
+ : obj_(obj), memFun_(memFun), p1_(p1)
+ {}
+ Obj& obj_;
+ MemFun memFun_;
+ const P1 p1_;
+};
+
+template <class Obj, typename MemFun, typename P1>
+inline ObjScopeGuardImpl1<Obj, MemFun, P1> MakeObjGuard(Obj& obj, MemFun memFun, P1 p1)
+{
+ return ObjScopeGuardImpl1<Obj, MemFun, P1>::MakeObjGuard(obj, memFun, p1);
+}
+
+template <typename Ret, class Obj1, class Obj2, typename P1a, typename P1b>
+inline ObjScopeGuardImpl1<Obj1, Ret (Obj2::*)(P1a), P1b>
+MakeGuard(Ret (Obj2::*memFun)(P1a), Obj1& obj, P1b p1)
+{
+ return ObjScopeGuardImpl1<Obj1, Ret (Obj2::*)(P1a), P1b>::MakeObjGuard(obj, memFun,
+ p1);
+}
+
+template <typename Ret, class Obj1, class Obj2, typename P1a, typename P1b>
+inline ObjScopeGuardImpl1<Obj1, Ret (Obj2::*)(P1a), P1b>
+MakeGuard(Ret (Obj2::*memFun)(P1a), Obj1* obj, P1b p1)
+{
+ return ObjScopeGuardImpl1<Obj1, Ret (Obj2::*)(P1a), P1b>::MakeObjGuard(*obj, memFun,
+ p1);
+}
+
+template <class Obj, typename MemFun, typename P1, typename P2>
+class ObjScopeGuardImpl2 : public ScopeGuardImplBase
+{
+public:
+ static ObjScopeGuardImpl2<Obj, MemFun, P1, P2> MakeObjGuard(Obj& obj, MemFun memFun,
+ P1 p1, P2 p2)
+ {
+ return ObjScopeGuardImpl2<Obj, MemFun, P1, P2>(obj, memFun, p1, p2);
+ }
+ ~ObjScopeGuardImpl2() throw() { SafeExecute(*this); }
+ void Execute() { (obj_.*memFun_)(p1_, p2_); }
+
+protected:
+ ObjScopeGuardImpl2(Obj& obj, MemFun memFun, P1 p1, P2 p2)
+ : obj_(obj), memFun_(memFun), p1_(p1), p2_(p2)
+ {}
+ Obj& obj_;
+ MemFun memFun_;
+ const P1 p1_;
+ const P2 p2_;
+};
+
+template <class Obj, typename MemFun, typename P1, typename P2>
+inline ObjScopeGuardImpl2<Obj, MemFun, P1, P2> MakeObjGuard(Obj& obj, MemFun memFun,
+ P1 p1, P2 p2)
+{
+ return ObjScopeGuardImpl2<Obj, MemFun, P1, P2>::MakeObjGuard(obj, memFun, p1, p2);
+}
+
+template <typename Ret, class Obj1, class Obj2, typename P1a, typename P1b,
+ typename P2a, typename P2b>
+inline ObjScopeGuardImpl2<Obj1, Ret (Obj2::*)(P1a, P2a), P1b, P2b>
+MakeGuard(Ret (Obj2::*memFun)(P1a, P2a), Obj1& obj, P1b p1, P2b p2)
+{
+ return ObjScopeGuardImpl2<Obj1, Ret (Obj2::*)(P1a, P2a), P1b, P2b>::MakeObjGuard(
+ obj, memFun, p1, p2);
+}
+
+template <typename Ret, class Obj1, class Obj2, typename P1a, typename P1b,
+ typename P2a, typename P2b>
+inline ObjScopeGuardImpl2<Obj1, Ret (Obj2::*)(P1a, P2a), P1b, P2b>
+MakeGuard(Ret (Obj2::*memFun)(P1a, P2a), Obj1* obj, P1b p1, P2b p2)
+{
+ return ObjScopeGuardImpl2<Obj1, Ret (Obj2::*)(P1a, P2a), P1b, P2b>::MakeObjGuard(
+ *obj, memFun, p1, p2);
+}
+
+#define CONCATENATE_DIRECT(s1, s2) s1##s2
+#define CONCATENATE(s1, s2) CONCATENATE_DIRECT(s1, s2)
+#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)
+
+// #define ON_BLOCK_EXIT ScopeGuard ANONYMOUS_VARIABLE(scopeGuard) = MakeGuard
+#define ON_BLOCK_EXIT(arg) \
+ ScopeGuard ANONYMOUS_VARIABLE(scopeGuard) = MakeGuard(arg); \
+ UNUSED_VAR(ANONYMOUS_VARIABLE(scopeGuard))
+// #define ON_BLOCK_EXIT_OBJ ScopeGuard ANONYMOUS_VARIABLE(scopeGuard) = MakeObjGuard
+
+#define UNUSED_VAR(VAR) (void)VAR
+
+namespace MOBase
+{
+
+} // namespace MOBase
+
+#endif // SCOPEGUARD_H
diff --git a/libs/uibase/include/uibase/sortabletreewidget.h b/libs/uibase/include/uibase/sortabletreewidget.h
new file mode 100644
index 0000000..4dc7881
--- /dev/null
+++ b/libs/uibase/include/uibase/sortabletreewidget.h
@@ -0,0 +1,64 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2013 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef SORTABLETREEWIDGET_H
+#define SORTABLETREEWIDGET_H
+
+#include "dllimport.h"
+#include <QTreeWidget>
+
+namespace MOBase
+{
+
+class QDLLEXPORT SortableTreeWidget : public QTreeWidget
+{
+ Q_OBJECT
+
+public:
+ SortableTreeWidget(QWidget* parent = nullptr);
+
+ /**
+ * @brief sets whether sorting is allowed only within the branch of a tree
+ * @param localOnly if true, objects can only be moved within a branch. if false
+ * (default) they can be moved to a different branch
+ */
+ void setLocalMoveOnly(bool localOnly);
+signals:
+ /**
+ * @brief signaled when items got moved. minimal atm
+ */
+ void itemsMoved();
+
+protected:
+ virtual void dropEvent(QDropEvent* event);
+ virtual bool dropMimeData(QTreeWidgetItem* parent, int index, const QMimeData* data,
+ Qt::DropAction action);
+ virtual Qt::DropActions supportedDropActions() const;
+
+private:
+ bool moveSelection(QTreeWidgetItem* parent, int index);
+
+private:
+ bool m_LocalMoveOnly;
+};
+
+} // namespace MOBase
+
+#endif // SORTABLETREEWIDGET_H
diff --git a/libs/uibase/include/uibase/steamutility.h b/libs/uibase/include/uibase/steamutility.h
new file mode 100644
index 0000000..4789ad3
--- /dev/null
+++ b/libs/uibase/include/uibase/steamutility.h
@@ -0,0 +1,47 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2019 MO2 Contributors. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef STEAMUTILITY_H
+#define STEAMUTILITY_H
+
+#include "dllimport.h"
+#include <QList>
+#include <QString>
+
+namespace MOBase
+{
+
+/**
+ * @brief Gets the installation path to Steam according to the registy
+ **/
+QDLLEXPORT QString findSteam();
+
+/**
+ * @brief Gets the installation path to a Steam game
+ *
+ * @param appName The Steam application name, i.e., the expected steamapps folder name
+ * @param validFile If the given file exists in the found installation path, the game is
+ consider to be valid. May be blank.
+ **/
+QDLLEXPORT QString findSteamGame(const QString& appName, const QString& validFile);
+
+} // namespace MOBase
+
+#endif // STEAMUTILITY_H
diff --git a/libs/uibase/include/uibase/strings.h b/libs/uibase/include/uibase/strings.h
new file mode 100644
index 0000000..be388ab
--- /dev/null
+++ b/libs/uibase/include/uibase/strings.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <string>
+#include <string_view>
+
+#include "dllimport.h"
+
+namespace MOBase
+{
+#ifdef __cplusplus
+extern "C++" {
+#endif
+
+QDLLEXPORT void ireplace_all(std::string& input, std::string_view search,
+ std::string_view replace) noexcept;
+
+QDLLEXPORT bool iequals(std::string_view lhs, std::string_view rhs);
+
+#ifdef __cplusplus
+}
+#endif
+} // namespace MOBase
diff --git a/libs/uibase/include/uibase/taskprogressmanager.h b/libs/uibase/include/uibase/taskprogressmanager.h
new file mode 100644
index 0000000..8dba8d1
--- /dev/null
+++ b/libs/uibase/include/uibase/taskprogressmanager.h
@@ -0,0 +1,60 @@
+#ifndef TASKPROGRESSMANAGER_H
+#define TASKPROGRESSMANAGER_H
+
+#include "dllimport.h"
+#include <QMutexLocker>
+#include <QObject>
+#include <QTime>
+#include <QTimer>
+#include <cstdint>
+#include <map>
+
+#ifdef _WIN32
+#include <Windows.h>
+#include <shobjidl.h>
+#else
+using HWND = void*;
+#endif
+
+namespace MOBase
+{
+
+class QDLLEXPORT TaskProgressManager : QObject
+{
+
+ Q_OBJECT
+
+public:
+ static TaskProgressManager& instance();
+
+ void forgetMe(quint32 id);
+ void updateProgress(quint32 id, qint64 value, qint64 max);
+
+ quint32 getId();
+
+public slots:
+ bool tryCreateTaskbar();
+
+private:
+ TaskProgressManager();
+ void showProgress();
+
+private:
+ std::map<quint32, std::pair<QTime, qint64>> m_Percentages;
+ QMutex m_Mutex;
+ quint32 m_NextId;
+ QTimer m_CreateTimer;
+ int m_CreateTries;
+
+ HWND m_WinId;
+
+#ifdef _WIN32
+ ITaskbarList3* m_Taskbar;
+#else
+ void* m_Taskbar;
+#endif
+};
+
+} // namespace MOBase
+
+#endif // TASKPROGRESSMANAGER_H
diff --git a/libs/uibase/include/uibase/textviewer.h b/libs/uibase/include/uibase/textviewer.h
new file mode 100644
index 0000000..4f5dfa2
--- /dev/null
+++ b/libs/uibase/include/uibase/textviewer.h
@@ -0,0 +1,97 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef TEXTVIEWER_H
+#define TEXTVIEWER_H
+
+#include "dllimport.h"
+#include <QDialog>
+#include <QTabWidget>
+#include <QTextEdit>
+#include <set>
+
+namespace Ui
+{
+class TextViewer;
+}
+
+namespace MOBase
+{
+
+class FindDialog;
+
+/**
+ * @brief rudimentary tabbed text editor
+ **/
+class QDLLEXPORT TextViewer : public QDialog
+{
+ Q_OBJECT
+
+public:
+ /**
+ * @brief constructor
+ * @param parent parent widget
+ **/
+ explicit TextViewer(const QString& title, QWidget* parent = 0);
+
+ ~TextViewer();
+
+ /**
+ * @brief set the description text to inform the user what he's editing
+ *
+ * @param description the description to set
+ **/
+ void setDescription(const QString& description);
+
+ /**
+ * @brief add a new tab with the specified file open
+ *
+ * @param fileName name of the file to open
+ * @param writable if true, the file can be modified
+ **/
+ void addFile(const QString& fileName, bool writable);
+
+protected:
+ void closeEvent(QCloseEvent* event);
+ bool eventFilter(QObject* obj, QEvent* event);
+
+private slots:
+
+ void saveFile();
+ void modified();
+ void patternChanged(QString newPattern);
+ void findNext();
+ void showWhitespaceChanged(int state);
+
+private:
+ void saveFile(const QTextEdit* editor);
+ void find();
+
+private:
+ Ui::TextViewer* ui;
+ QTabWidget* m_EditorTabs;
+ std::set<QTextEdit*> m_Modified;
+ FindDialog* m_FindDialog;
+ QString m_FindPattern;
+};
+
+} // namespace MOBase
+
+#endif // TEXTVIEWER_H
diff --git a/libs/uibase/include/uibase/tutorabledialog.h b/libs/uibase/include/uibase/tutorabledialog.h
new file mode 100644
index 0000000..aaaa093
--- /dev/null
+++ b/libs/uibase/include/uibase/tutorabledialog.h
@@ -0,0 +1,65 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef TUTORABLEDIALOG_H
+#define TUTORABLEDIALOG_H
+
+#include "dllimport.h"
+#include "tutorialcontrol.h"
+#include <QDialog>
+#include <QResizeEvent>
+#include <QShowEvent>
+
+namespace MOBase
+{
+
+/**
+ * @brief A dialog for which a tutorial can be displayed. Dialogs should derive from
+ * this instead of QDialog and delegate all showEvent- and resizeEvent-calls to
+ * TutorableDialog for tutorials to work
+ */
+class QDLLEXPORT TutorableDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ /**
+ * @brief constructor
+ * @param name a unique name for this dialog type. This is used to refer to the dialog
+ * from tutorials
+ * @param parent the parent widget of the dialog
+ */
+ explicit TutorableDialog(const QString& name, QWidget* parent = 0);
+
+signals:
+
+public slots:
+
+protected:
+ void showEvent(QShowEvent* event);
+ void resizeEvent(QResizeEvent* event);
+
+private:
+ TutorialControl m_Tutorial;
+};
+
+} // namespace MOBase
+
+#endif // TUTORABLEDIALOG_H
diff --git a/libs/uibase/include/uibase/tutorialcontrol.h b/libs/uibase/include/uibase/tutorialcontrol.h
new file mode 100644
index 0000000..4dc85f6
--- /dev/null
+++ b/libs/uibase/include/uibase/tutorialcontrol.h
@@ -0,0 +1,83 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef TUTORIALCONTROL_H
+#define TUTORIALCONTROL_H
+
+#include "dllimport.h"
+#include <QQuickWidget>
+#include <QRect>
+#include <QWidget>
+#include <utility>
+
+namespace MOBase
+{
+
+class TutorialManager;
+
+class QDLLEXPORT TutorialControl : public QObject
+{
+ Q_OBJECT
+public:
+ TutorialControl(const TutorialControl& reference);
+ TutorialControl(QWidget* targetControl, const QString& name);
+
+ ~TutorialControl();
+
+ void registerControl();
+ void resize(const QSize& size);
+
+ void expose(const QString& widgetName, QObject* widget);
+
+ void startTutorial(const QString& tutorial);
+
+ Q_INVOKABLE void finish();
+ Q_INVOKABLE QRect getRect(const QString& widgetName);
+ Q_INVOKABLE QRect getActionRect(const QString& widgetName);
+ Q_INVOKABLE QRect getMenuRect(const QString& widgetName);
+ Q_INVOKABLE QWidget* getChild(const QString& name);
+ Q_INVOKABLE bool waitForButton(const QString& buttonName);
+ Q_INVOKABLE bool waitForAction(const QString& actionName);
+ Q_INVOKABLE bool waitForTabOpen(const QString& tabControlName, const QString& tab);
+ Q_INVOKABLE const QString getTabName(const QString& tabControlName);
+ Q_INVOKABLE void lockUI(bool locked);
+ Q_INVOKABLE void simulateClick(int x, int y);
+
+private slots:
+
+ void tabChangedProxy(int selected);
+ void nextTutorialStepProxy();
+
+private:
+ QWidget* m_TargetControl;
+ QString m_Name;
+ QQuickWidget* m_TutorialView;
+
+ TutorialManager& m_Manager;
+
+ std::vector<std::pair<QString, QObject*>> m_ExposedObjects;
+ QList<QMetaObject::Connection> m_Connections;
+ int m_ExpectedTab;
+ QWidget* m_CurrentClickControl;
+};
+
+} // namespace MOBase
+
+#endif // TUTORIALCONTROL_H
diff --git a/libs/uibase/include/uibase/tutorialmanager.h b/libs/uibase/include/uibase/tutorialmanager.h
new file mode 100644
index 0000000..8120164
--- /dev/null
+++ b/libs/uibase/include/uibase/tutorialmanager.h
@@ -0,0 +1,109 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef TUTORIALMANAGER_H
+#define TUTORIALMANAGER_H
+
+#include "dllimport.h"
+#include <QObject>
+#include <map>
+
+namespace MOBase
+{
+
+class TutorialControl;
+
+class QDLLEXPORT TutorialManager : public QObject
+{
+
+ Q_OBJECT
+
+public:
+ /**
+ * @brief set up the tutorial manager
+ * @param path to where the tutorials are stored
+ */
+ static void init(const QString& tutorialPath, QObject* organizerCore);
+
+ static TutorialManager& instance();
+
+ QObject* organizerCore() { return m_OrganizerCore; }
+
+ /**
+ * @brief registers a control that can be used to display tutorial messages in one
+ * window. This this called when the window becomes visible
+ * @param windowName name of the window. This is used to reference the window from
+ * qml/js files, as well as for the unregister-call
+ * @param control the control to register
+ */
+ void registerControl(const QString& windowName, TutorialControl* control);
+
+ /**
+ * @brief unregister a tutorial control. This is called when the window to which the
+ * control belongs gets closed
+ * @param windowName name of the control to hide
+ */
+ void unregisterControl(const QString& windowName);
+
+ /**
+ * @brief tests if the specified tutorial exists
+ * @param tutorialName name of the tutorial to test
+ * @return true if there is a tutorial with the specified name
+ */
+ bool hasTutorial(const QString& tutorialName);
+
+ /**
+ * @brief start a tutorial for the specified window
+ * @param windowName window for which to start the tutorial
+ * @param tutorialName name of the tutorial script to run
+ */
+ Q_INVOKABLE void activateTutorial(const QString& windowName,
+ const QString& tutorialName);
+
+ /**
+ * @brief mark a window tutorial as completed. It will not show up again
+ * @param windowName name of the window for which the tutorial completed
+ */
+ Q_INVOKABLE void finishWindowTutorial(const QString& windowName);
+
+ Q_INVOKABLE QWidget* findControl(const QString& controlName);
+signals:
+
+ void windowTutorialFinished(const QString& windowName);
+
+ void tabChanged(int index);
+
+private:
+ TutorialManager(const QString& tutorialPath, QObject* organizerCore);
+
+private:
+ static TutorialManager* s_Instance;
+
+ // QScriptEngine m_ScriptEngine;
+ QString m_TutorialPath;
+ QObject* m_OrganizerCore;
+
+ std::map<QString, TutorialControl*> m_Controls;
+ std::map<QString, QString> m_PendingTutorials;
+};
+
+} // namespace MOBase
+
+#endif // TUTORIALMANAGER_H
diff --git a/libs/uibase/include/uibase/utility.h b/libs/uibase/include/uibase/utility.h
new file mode 100644
index 0000000..c08133b
--- /dev/null
+++ b/libs/uibase/include/uibase/utility.h
@@ -0,0 +1,443 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef MO_UIBASE_UTILITY_INCLUDED
+#define MO_UIBASE_UTILITY_INCLUDED
+
+#include <QDir>
+#include <QIcon>
+#include <QList>
+#include <QString>
+#include <QTextStream>
+#include <QUrl>
+#include <QVariant>
+#include <algorithm>
+#include <set>
+#include <vector>
+#include <cstdint>
+#include <fstream>
+
+#ifdef _WIN32
+#include <ShlObj.h>
+#include <Windows.h>
+#else
+// POSIX compatibility types
+#include "windows_compat.h"
+#endif
+
+#include "dllimport.h"
+#include "exceptions.h"
+
+namespace MOBase
+{
+
+/**
+ * @brief remove the specified directory including all sub-directories
+ *
+ * @param dirName name of the directory to delete
+ * @return true on success. in case of an error, "removeDir" itself displays an error
+ *message
+ **/
+QDLLEXPORT bool removeDir(const QString& dirName);
+
+/**
+ * @brief copy a directory recursively
+ * @param sourceName name of the directory to copy
+ * @param destinationName name of the target directory
+ * @param merge if true, the destination directory is allowed to exist, files will then
+ * be added to that directory. If false, the call will fail in that case
+ * @return true if files were copied. This doesn't necessary mean ALL files were copied
+ * @note symbolic links are not followed to prevent endless recursion
+ */
+QDLLEXPORT bool copyDir(const QString& sourceName, const QString& destinationName,
+ bool merge);
+
+/**
+ * @brief move a file, creating subdirectories as needed
+ * @param source source file name
+ * @param destination destination file name
+ * @return true if the file was successfully copied
+ */
+QDLLEXPORT bool moveFileRecursive(const QString& source, const QString& baseDir,
+ const QString& destination);
+
+/**
+ * @brief copy a file, creating subdirectories as needed
+ * @param source source file name
+ * @param destination destination file name
+ * @return true if the file was successfully copied
+ */
+QDLLEXPORT bool copyFileRecursive(const QString& source, const QString& baseDir,
+ const QString& destination);
+
+/**
+ * @brief copy one or multiple files using a shell operation (this will ask the user for
+ *confirmation on overwrite or elevation requirement)
+ * @param sourceNames names of files to be copied. This can include wildcards
+ * @param destinationNames names of the files in the destination location or the
+ *destination directory to copy to. There has to be one destination name for each source
+ *name or a single directory
+ * @param dialog a dialog to be the parent of possible confirmation dialogs
+ * @return true on success, false on error
+ **/
+QDLLEXPORT bool shellCopy(const QStringList& sourceNames,
+ const QStringList& destinationNames,
+ QWidget* dialog = nullptr);
+
+QDLLEXPORT bool shellCopy(const QString& sourceNames, const QString& destinationNames,
+ bool yesToAll = false, QWidget* dialog = nullptr);
+
+QDLLEXPORT bool shellMove(const QStringList& sourceNames,
+ const QStringList& destinationNames,
+ QWidget* dialog = nullptr);
+
+QDLLEXPORT bool shellMove(const QString& sourceNames, const QString& destinationNames,
+ bool yesToAll = false, QWidget* dialog = nullptr);
+
+QDLLEXPORT bool shellRename(const QString& oldName, const QString& newName,
+ bool yesToAll = false, QWidget* dialog = nullptr);
+
+QDLLEXPORT bool shellDelete(const QStringList& fileNames, bool recycle = false,
+ QWidget* dialog = nullptr);
+
+QDLLEXPORT bool shellDeleteQuiet(const QString& fileName, QWidget* dialog = nullptr);
+
+namespace shell
+{
+ namespace details
+ {
+ // used by HandlePtr on Windows, stub on Linux
+ struct HandleCloser
+ {
+ using pointer = HANDLE;
+
+ void operator()(HANDLE h)
+ {
+#ifdef _WIN32
+ if (h != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(h);
+ }
+#else
+ (void)h;
+#endif
+ }
+ };
+
+ using HandlePtr = std::unique_ptr<HANDLE, HandleCloser>;
+ } // namespace details
+
+ // returned by the various shell functions
+ class QDLLEXPORT Result
+ {
+ public:
+ Result(bool success, DWORD error, QString message, HANDLE process);
+
+ // non-copyable
+ Result(const Result&) = delete;
+ Result& operator=(const Result&) = delete;
+ Result(Result&&) = default;
+ Result& operator=(Result&&) = default;
+
+ static Result makeFailure(DWORD error, QString message = {});
+ static Result makeSuccess(HANDLE process = INVALID_HANDLE_VALUE);
+
+ bool success() const;
+ explicit operator bool() const;
+
+ DWORD error();
+
+ const QString& message() const;
+
+ HANDLE processHandle() const;
+
+ HANDLE stealProcessHandle();
+
+ QString toString() const;
+
+ private:
+ bool m_success;
+ DWORD m_error;
+ QString m_message;
+ details::HandlePtr m_process;
+ };
+
+ QDLLEXPORT QString formatError(int i);
+
+ QDLLEXPORT Result Explore(const QFileInfo& info);
+ QDLLEXPORT Result Explore(const QString& path);
+ QDLLEXPORT Result Explore(const QDir& dir);
+
+ QDLLEXPORT Result Open(const QString& path);
+ QDLLEXPORT Result Open(const QUrl& url);
+
+ QDLLEXPORT Result Execute(const QString& program, const QString& params = {});
+
+ QDLLEXPORT Result Delete(const QFileInfo& path);
+
+ QDLLEXPORT Result Rename(const QFileInfo& src, const QFileInfo& dest);
+ QDLLEXPORT Result Rename(const QFileInfo& src, const QFileInfo& dest,
+ bool copyAllowed);
+
+ QDLLEXPORT Result CreateDirectories(const QDir& dir);
+ QDLLEXPORT Result DeleteDirectoryRecursive(const QDir& dir);
+
+ QDLLEXPORT void SetUrlHandler(const QString& cmd);
+} // namespace shell
+
+template <typename T>
+QString VectorJoin(const std::vector<T>& value, const QString& separator,
+ size_t maximum = UINT_MAX)
+{
+ QString result;
+ if (value.size() != 0) {
+ QTextStream stream(&result);
+ stream << value[0];
+ for (unsigned int i = 1; i < (std::min)(value.size(), maximum); ++i) {
+ stream << separator << value[i];
+ }
+ if (maximum < value.size()) {
+ stream << separator << "...";
+ }
+ }
+ return result;
+}
+
+template <typename T>
+QString SetJoin(const std::set<T>& value, const QString& separator,
+ size_t maximum = UINT_MAX)
+{
+ QString result;
+ typename std::set<T>::const_iterator iter = value.begin();
+ if (iter != value.end()) {
+ QTextStream stream(&result);
+ stream << *iter;
+ ++iter;
+ unsigned int pos = 1;
+ for (; iter != value.end() && pos < maximum; ++iter) {
+ stream << separator << *iter;
+ }
+ if (maximum < value.size()) {
+ stream << separator << "...";
+ }
+ }
+ return result;
+}
+
+template <typename T>
+QList<T> ConvertList(const QVariantList& variants)
+{
+ QList<T> result;
+ for (const QVariant& var : variants) {
+ if (!var.canConvert<T>()) {
+ throw Exception("invalid variant type");
+ }
+ result.append(var.value<T>());
+ }
+}
+
+QDLLEXPORT std::wstring ToWString(const QString& source);
+QDLLEXPORT std::string ToString(const QString& source, bool utf8 = true);
+QDLLEXPORT QString ToQString(const std::string& source);
+QDLLEXPORT QString ToQString(const std::wstring& source);
+
+#ifdef _WIN32
+QDLLEXPORT QString ToString(const SYSTEMTIME& time);
+#endif
+
+QDLLEXPORT int naturalCompare(const QString& a, const QString& b,
+ Qt::CaseSensitivity cs = Qt::CaseInsensitive);
+
+class QDLLEXPORT NaturalSort
+{
+public:
+ NaturalSort(Qt::CaseSensitivity cs = Qt::CaseInsensitive) : m_cs(cs) {}
+
+ bool operator()(const QString& a, const QString& b)
+ {
+ return (naturalCompare(a, b, m_cs) < 0);
+ }
+
+private:
+ Qt::CaseSensitivity m_cs;
+};
+
+#ifdef _WIN32
+QDLLEXPORT QDir getKnownFolder(KNOWNFOLDERID id, const QString& what = {});
+QDLLEXPORT QString getOptionalKnownFolder(KNOWNFOLDERID id);
+#endif
+
+QDLLEXPORT QString getDesktopDirectory();
+QDLLEXPORT QString getStartMenuDirectory();
+
+QDLLEXPORT QString readFileText(const QString& fileName, QString* encoding = nullptr,
+ bool* hadBOM = nullptr);
+
+QDLLEXPORT QString decodeTextData(const QByteArray& fileData,
+ QString* encoding = nullptr, bool* hadBOM = nullptr);
+
+QDLLEXPORT void removeOldFiles(const QString& path, const QString& pattern,
+ int numToKeep, QDir::SortFlags sorting = QDir::Time);
+
+QDLLEXPORT QIcon iconForExecutable(const QString& filePath);
+
+QDLLEXPORT QString getFileVersion(QString const& filepath);
+QDLLEXPORT QString getProductVersion(QString const& program);
+
+QDLLEXPORT bool isWindowsDrivePath(const QString& path);
+QDLLEXPORT bool isWineZDrivePath(const QString& path);
+QDLLEXPORT QString toWinePath(const QString& path);
+QDLLEXPORT QString fromWinePath(const QString& path);
+QDLLEXPORT QString normalizePathForHost(const QString& path);
+QDLLEXPORT QString normalizePathForWine(const QString& path);
+
+QDLLEXPORT void deleteChildWidgets(QWidget* w);
+
+template <typename T>
+bool isOneOf(const T& val, const std::initializer_list<T>& list)
+{
+ return std::find(list.begin(), list.end(), val) != list.end();
+}
+
+QDLLEXPORT std::wstring formatSystemMessage(DWORD id);
+QDLLEXPORT std::wstring formatNtMessage(NTSTATUS s);
+
+inline std::wstring formatSystemMessage(HRESULT hr)
+{
+ return formatSystemMessage(static_cast<DWORD>(hr));
+}
+
+QDLLEXPORT QString windowsErrorString(DWORD errorCode);
+
+QDLLEXPORT QString localizedByteSize(unsigned long long bytes);
+QDLLEXPORT QString localizedByteSpeed(unsigned long long bytesPerSecond);
+
+QDLLEXPORT QString localizedTimeRemaining(unsigned int msecs);
+
+template <class F>
+class Guard
+{
+public:
+ Guard() : m_call(false) {}
+
+ Guard(F f) : m_f(f), m_call(true) {}
+
+ Guard(Guard&& g) : m_f(std::move(g.m_f)) { g.m_call = false; }
+
+ ~Guard()
+ {
+ if (m_call)
+ m_f();
+ }
+
+ Guard& operator=(Guard&& g)
+ {
+ m_f = std::move(g.m_f);
+ g.m_call = false;
+ return *this;
+ }
+
+ void kill() { m_call = false; }
+
+ Guard(const Guard&) = delete;
+ Guard& operator=(const Guard&) = delete;
+
+private:
+ F m_f;
+ bool m_call;
+};
+
+class QDLLEXPORT TimeThis
+{
+public:
+ TimeThis(const QString& what = {});
+ ~TimeThis();
+
+ TimeThis(const TimeThis&) = delete;
+ TimeThis& operator=(const TimeThis&) = delete;
+
+ void start(const QString& what = {});
+ void stop();
+
+private:
+ using Clock = std::chrono::high_resolution_clock;
+
+ QString m_what;
+ Clock::time_point m_start;
+ bool m_running;
+};
+
+template <class F>
+bool forEachLineInFile(const QString& filePath, F&& f)
+{
+ QFile file(filePath);
+ if (!file.open(QIODevice::ReadOnly)) {
+ return false;
+ }
+
+ QByteArray data = file.readAll();
+ file.close();
+
+ const char* lineStart = data.constData();
+ const char* p = lineStart;
+ const char* end = data.constData() + data.size();
+
+ while (p < end) {
+ // skip all newline characters
+ while ((p < end) && (*p == '\n' || *p == '\r')) {
+ ++p;
+ }
+
+ // line starts here
+ lineStart = p;
+
+ // find end of line
+ while ((p < end) && *p != '\n' && *p != '\r') {
+ ++p;
+ }
+
+ if (p != lineStart) {
+ // skip whitespace at beginning of line, don't go past end of line
+ while (std::isspace(*lineStart) && lineStart < p) {
+ ++lineStart;
+ }
+
+ // skip comments
+ if (*lineStart != '#') {
+ // skip line if it only had whitespace
+ if (lineStart < p) {
+ // skip white at end of line
+ const char* lineEnd = p - 1;
+ while (std::isspace(*lineEnd) && lineEnd > lineStart) {
+ --lineEnd;
+ }
+ ++lineEnd;
+
+ f(QString::fromUtf8(lineStart, lineEnd - lineStart));
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace MOBase
+
+#endif // MO_UIBASE_UTILITY_INCLUDED
diff --git a/libs/uibase/include/uibase/versioninfo.h b/libs/uibase/include/uibase/versioninfo.h
new file mode 100644
index 0000000..fd5d481
--- /dev/null
+++ b/libs/uibase/include/uibase/versioninfo.h
@@ -0,0 +1,185 @@
+/*
+Mod Organizer shared UI functionality
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef MODVERSION_H
+#define MODVERSION_H
+
+#include "dllimport.h"
+#include <QList>
+#include <QString>
+
+class QVersionNumber;
+
+namespace MOBase
+{
+
+/**
+ * @brief represents the version of a mod or plugin
+ *
+ * this will try to parse machine-readable information from a string.
+ **/
+class QDLLEXPORT VersionInfo
+{
+
+ friend QDLLEXPORT bool operator<(const VersionInfo& LHS, const VersionInfo& RHS);
+ friend QDLLEXPORT bool operator>(const VersionInfo& LHS, const VersionInfo& RHS);
+ friend QDLLEXPORT bool operator<=(const VersionInfo& LHS, const VersionInfo& RHS);
+ friend QDLLEXPORT bool operator>=(const VersionInfo& LHS, const VersionInfo& RHS);
+ friend QDLLEXPORT bool operator!=(const VersionInfo& LHS, const VersionInfo& RHS);
+ friend QDLLEXPORT bool operator==(const VersionInfo& LHS, const VersionInfo& RHS);
+
+public:
+ enum ReleaseType
+ {
+ RELEASE_PREALPHA,
+ RELEASE_ALPHA,
+ RELEASE_BETA,
+ RELEASE_CANDIDATE,
+ RELEASE_FINAL
+ };
+
+ enum VersionScheme
+ {
+ SCHEME_DISCOVER, // use regular scheme unless the string contains a hint that it's
+ // one of the others
+ SCHEME_REGULAR,
+ SCHEME_DECIMALMARK, // for schemes that treat the version as a decimal number with
+ // the dot as the decimal mark
+ SCHEME_NUMBERSANDLETTERS, // for schemes that mix numbers and letters
+ // (1.0.1a, 1.0.1c, ...). otherwise this is the regular
+ // scheme
+ SCHEME_DATE, // contains a release date instead of a version number
+ SCHEME_LITERAL // use the version string as is, unmodified
+ };
+
+public:
+ /**
+ * @brief default constructor
+ * constructs an invalid version
+ **/
+ VersionInfo();
+
+ /**
+ * @brief constructor
+ * @param major major version
+ * @param minor minor version
+ * @param subminor subminor version
+ * @param releaseType release type
+ */
+ VersionInfo(int major, int minor, int subminor,
+ ReleaseType releaseType = RELEASE_FINAL);
+
+ /**
+ * @brief constructor
+ * @param major major version
+ * @param minor minor version
+ * @param subminor subminor version
+ * @param subsubminor subsubminor version
+ * @param releaseType release type
+ */
+ VersionInfo(int major, int minor, int subminor, int subsubminor,
+ ReleaseType releaseType = RELEASE_FINAL);
+
+ /**
+ * @brief constructor
+ * @param versionString the string to construct from
+ **/
+ VersionInfo(const QString& versionString, VersionScheme scheme = SCHEME_DISCOVER);
+
+ /**
+ * @brief constructor
+ * @param versionString the string to construct from
+ * @param manualInput if true the versionString is treated as input from a user
+ **/
+ VersionInfo(const QString& versionString, VersionScheme scheme, bool manualInput);
+
+ /**
+ * @brief resets this structure to an invalid version
+ */
+ void clear();
+
+ /**
+ * @brief parse the version from the specified string
+ *
+ * @param versionString the string to parse
+ **/
+ void parse(const QString& versionString, VersionScheme scheme = SCHEME_DISCOVER,
+ bool manualInput = false);
+
+ /**
+ * @return a canonicalized version string
+ * @note due to support for different versioning schemes this somewhat lost it's
+ *original intention. This is now supposed to return a version string that can be
+ *parsed to re-create this VersionInfo without information loss.
+ **/
+ QString canonicalString() const;
+
+ /**
+ * @return a version string for display to the user. This may loose information as it
+ * doesn't contain information about the versioning scheme
+ *
+ * @param forcedVersionSegments the number of version segments to display even if the
+ * version is 0. 1 is major, 2 is major and minor, etc. The only implemented ranges
+ * are (-inf,2] for major/minor, [3] for major/minor/subminor, and [4,inf) for
+ * major/minor/subminor/subsubminor. This only versions with a regular scheme.
+ */
+ QString displayString(int forcedVersionSegments = 2) const;
+
+ /**
+ * @return true if this version is valid, false if it wasn't initialised or
+ * the version string was not parsable
+ */
+ bool isValid() const { return m_Valid; }
+
+ /**
+ * @return the versioning scheme in effect
+ */
+ VersionScheme scheme() const { return m_Scheme; }
+
+ // returns this version number as a QVersionNumber
+ //
+ QVersionNumber asQVersionNumber() const;
+
+private:
+ /**
+ * @brief determine the release type
+ * @param versionString the version string to parse
+ * @return the version string with the release type removed
+ **/
+ QString parseReleaseType(QString versionString);
+
+private:
+ VersionScheme m_Scheme;
+
+ bool m_Valid;
+ ReleaseType m_ReleaseType;
+ int m_Major;
+ int m_Minor;
+ int m_SubMinor;
+ int m_SubSubMinor;
+
+ int m_DecimalPositions;
+
+ QString m_Rest;
+};
+
+} // namespace MOBase
+
+#endif // MODVERSION_H
diff --git a/libs/uibase/include/uibase/versioning.h b/libs/uibase/include/uibase/versioning.h
new file mode 100644
index 0000000..c4f9541
--- /dev/null
+++ b/libs/uibase/include/uibase/versioning.h
@@ -0,0 +1,181 @@
+#pragma once
+
+#include <compare>
+#include <string>
+#include <variant>
+#include <vector>
+
+#include <QFlags>
+#include <QString>
+
+#include "dllimport.h"
+#include "exceptions.h"
+
+namespace MOBase
+{
+
+class InvalidVersionException : public Exception
+{
+public:
+ using Exception::Exception;
+};
+
+// class representing a Version object
+//
+// valid versions are an "extension" of SemVer (see https://semver.org/) with the
+// following tweaks:
+// - version can have a sub-patch, i.e., x.y.z.p, which are normally not allowed by
+// SemVer
+// - non-integer pre-release identifiers are limited to dev, alpha (a), beta (b) and rc,
+// and dev is lower than alpha (according to SemVer, the pre-release should be
+// ordered alphabetically)
+// - the '-' between version and pre-release can be made optional, and also the '.'
+// between pre-releases segment
+//
+// the extension from SemVer are only meant to be used by MO2 and USVFS versioning,
+// plugins and extensions should follow SemVer standard (and not use dev), this is
+// mainly
+// - for back-compatibility purposes, because USVFS versioning contains sub-patches and
+// there are old MO2 releases with sub-patch
+// - because MO2 is not going to become MO3, so having an extra level make sense
+//
+// unlike VersionInfo, this class is immutable and only hold valid versions
+//
+class QDLLEXPORT Version
+{
+public:
+ enum class ParseMode
+ {
+ // official semver parsing with pre-release limited to dev, alpha/a, beta/b and rc
+ //
+ SemVer,
+
+ // MO2 parsing, e.g., 2.5.1rc1 - this either parse a string with no pre-release
+ // information (e.g. 2.5.1) or with a single pre-release + a version (e.g., 2.5.1a1
+ // or 2.5.2rc1)
+ //
+ // this mode can parse sub-patch (SemVer mode cannot)
+ //
+ MO2
+ };
+
+ enum class FormatMode
+ {
+ // show subpatch even if subpatch is 0
+ //
+ ForceSubPatch = 0b0001,
+
+ // do not add separators between version and pre-release (-) or between pre-release
+ // segments (.)
+ //
+ NoSeparator = 0b0010,
+
+ // uses short form for alpha and beta (a/b instead of alpha/beta)
+ //
+ ShortAlphaBeta = 0b0100,
+
+ // do not add metadata even if present
+ //
+ NoMetadata = 0b1000
+ };
+ Q_DECLARE_FLAGS(FormatModes, FormatMode);
+
+ // condensed format, no separator, short alpha/beta and no metadata
+ //
+ static constexpr auto FormatCondensed = FormatModes{
+ FormatMode::NoSeparator, FormatMode::ShortAlphaBeta, FormatMode::NoMetadata};
+
+ enum class ReleaseType
+ {
+ Development, // -dev
+ Alpha, // -alpha, -a
+ Beta, // -beta, -b
+ ReleaseCandidate, // -rc
+ };
+ using enum ReleaseType;
+
+public: // parsing
+ // parse version from the given string, throw InvalidVersionException if the string
+ // cannot be parsed
+ //
+ static Version parse(QString const& value, ParseMode mode = ParseMode::SemVer);
+
+public: // constructors
+ Version(int major, int minor, int patch, QString metadata = {});
+ Version(int major, int minor, int patch, int subpatch, QString metadata = {});
+
+ Version(int major, int minor, int patch, ReleaseType type, QString metadata = {});
+ Version(int major, int minor, int patch, int subpatch, ReleaseType type,
+ QString metadata = {});
+
+ Version(int major, int minor, int patch, ReleaseType type, int prerelease,
+ QString metadata = {});
+ Version(int major, int minor, int patch, int subpatch, ReleaseType type,
+ int prerelease, QString metadata = {});
+
+ Version(int major, int minor, int patch, int subpatch,
+ std::vector<std::variant<int, ReleaseType>> prereleases,
+ QString metadata = {});
+
+public: // special member functions
+ Version(const Version&) = default;
+ Version(Version&&) = default;
+
+ Version& operator=(const Version&) = default;
+ Version& operator=(Version&&) = default;
+
+public:
+ // check if this version corresponds to a pre-release version (dev, alpha, beta, etc.)
+ //
+ bool isPreRelease() const { return !m_PreReleases.empty(); }
+
+ // retrieve major, minor, patch and sub-patch of this version
+ //
+ int major() const { return m_Major; }
+ int minor() const { return m_Minor; }
+ int patch() const { return m_Patch; }
+ int subpatch() const { return m_SubPatch; }
+
+ // retrieve pre-releases information for this version
+ //
+ const auto& preReleases() const { return m_PreReleases; }
+
+ // retrieve build metadata, if any, otherwise return an empty string
+ //
+ const auto& buildMetadata() const { return m_BuildMetadata; }
+
+ // convert this version to a string
+ //
+ QString string(const FormatModes& modes = {}) const;
+
+private:
+ // major.minor.patch
+ int m_Major, m_Minor, m_Patch, m_SubPatch;
+
+ // pre-release information
+ std::vector<std::variant<int, ReleaseType>> m_PreReleases;
+
+ // metadata
+ QString m_BuildMetadata;
+};
+
+QDLLEXPORT std::strong_ordering operator<=>(const Version& lhs, const Version& rhs);
+
+inline bool operator==(const Version& lhs, const Version& rhs)
+{
+ return (lhs <=> rhs) == 0;
+}
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(Version::FormatModes);
+
+} // namespace MOBase
+
+template <class CharT>
+struct std::formatter<MOBase::Version, CharT> : std::formatter<QString, CharT>
+{
+ template <class FmtContext>
+ FmtContext::iterator format(const MOBase::Version& v, FmtContext& ctx) const
+ {
+ return std::formatter<QString, CharT>::format(v.string(), ctx);
+ }
+};
diff --git a/libs/uibase/include/uibase/widgetutility.h b/libs/uibase/include/uibase/widgetutility.h
new file mode 100644
index 0000000..10dc6f9
--- /dev/null
+++ b/libs/uibase/include/uibase/widgetutility.h
@@ -0,0 +1,22 @@
+#ifndef UIBASE_WIDGETUTILITY_INCLUDED
+#define UIBASE_WIDGETUTILITY_INCLUDED
+
+#include "dllimport.h"
+#include <QAbstractItemView>
+#include <QTreeView>
+
+namespace MOBase
+{
+
+/**
+ * Custom user-role that can be used in conjunction with `setCustomizableColumns`. If
+ * a column has a value for this role, and the value is false, the checkbox
+ * corresponding to the column will be disabled, otherwise the checkbox is enabled.
+ */
+constexpr auto EnabledColumnRole = Qt::UserRole + 1;
+
+QDLLEXPORT void setCustomizableColumns(QTreeView* view);
+
+} // namespace MOBase
+
+#endif // UIBASE_WIDGETUTILITY_INCLUDED
diff --git a/libs/uibase/include/uibase/windows_compat.h b/libs/uibase/include/uibase/windows_compat.h
new file mode 120000
index 0000000..bce219f
--- /dev/null
+++ b/libs/uibase/include/uibase/windows_compat.h
@@ -0,0 +1 @@
+../../../../src/src/shared/windows_compat.h \ No newline at end of file