diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
| commit | 7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch) | |
| tree | 27fb39be241fdb5ac2734c574de678977d1856d0 /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')
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 |
