From bb4f3fd74b94dc339f326baeefa474141fca8af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 8 Jul 2024 21:26:49 +0200 Subject: [PATCH 01/26] Move to VCPKG. * Remove SConscript related files. * Update for libbsarch. * Force-load translations from uibase and gamebryo/creation. * Set option to use deprecated uibase include paths. * Bring githubpp here and add a standalone preset. * Full standalone build. --- .github/workflows/build.yml | 46 +- .hgignore | 46 - CMakeLists.txt | 21 +- CMakePresets.json | 59 + SConstruct | 713 ---------- appveyor.yml | 77 -- mappings.imp | 30 - qt5_4.imp | 2478 ----------------------------------- scons_configure_template.py | 33 - src/CMakeLists.txt | 111 +- src/ModOrganizer.pro | 60 - src/SConscript | 179 --- src/archivefiletree.h | 4 +- src/github.cpp | 204 +++ src/github.h | 108 ++ src/installationmanager.h | 12 +- src/mainwindow.cpp | 18 +- src/mainwindow.h | 15 +- src/organizercore.cpp | 16 +- src/pluginlist.cpp | 37 +- src/selfupdater.h | 5 +- src/settings.h | 6 +- src/shared/directoryentry.h | 3 +- src/shared/util.cpp | 6 +- src/spawn.cpp | 11 +- src/usvfsconnector.cpp | 2 +- src/usvfsconnector.h | 8 +- vcpkg-configuration.json | 21 + vcpkg.json | 32 + win.imp | 91 -- 30 files changed, 642 insertions(+), 3810 deletions(-) delete mode 100644 .hgignore create mode 100644 CMakePresets.json delete mode 100644 SConstruct delete mode 100644 appveyor.yml delete mode 100644 mappings.imp delete mode 100644 qt5_4.imp delete mode 100644 scons_configure_template.py delete mode 100644 src/ModOrganizer.pro delete mode 100644 src/SConscript create mode 100644 src/github.cpp create mode 100644 src/github.h create mode 100644 vcpkg-configuration.json create mode 100644 vcpkg.json delete mode 100644 win.imp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c58575748..4c538a949 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,15 +6,47 @@ on: pull_request: types: [opened, synchronize, reopened] +env: + VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" + jobs: build: runs-on: windows-2022 steps: - - name: Build ModOrganizer 2 - uses: ModOrganizer2/build-with-mob-action@master + # https://learn.microsoft.com/en-us/vcpkg/consume/binary-caching-github-actions-cache + - name: Export GitHub Actions cache environment variables + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + version: 6.7.0 + modules: + cache: true + + - uses: actions/checkout@v4 + + - name: "Set environmental variables" + shell: bash + run: | + echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> $GITHUB_ENV + + - name: Configure ModOrganizer + shell: pwsh + run: | + cmake --preset vs2022-windows-standalone ` + "-DCMAKE_PREFIX_PATH=${env:QT_ROOT_DIR}\msvc2019_64" ` + "-DCMAKE_INSTALL_PREFIX=install" + + - name: Build ModOrganizer + run: cmake --build vsbuild --config RelWithDebInfo --target INSTALL + + - name: Package ModOrganizer + uses: actions/upload-artifact@master with: - qt-modules: qtpositioning qtwebchannel qtwebengine qtwebsockets - mo2-third-parties: - 7z zlib gtest libbsarch libloot openssl bzip2 python lz4 spdlog - boost boost-di sip pyqt pybind11 ss licenses explorerpp DirectXTex - mo2-dependencies: usvfs cmake_common uibase githubpp bsatk esptk archive lootcli game_gamebryo + name: modorganizer + path: ./install diff --git a/.hgignore b/.hgignore deleted file mode 100644 index f67264ff5..000000000 --- a/.hgignore +++ /dev/null @@ -1,46 +0,0 @@ -syntax: glob -scons_configure.py -scons-ModOrganizer-* -ModOrganizer-build-desktop* -outputd/* -output/* -build-ModOrganizer-* -source/NCC/*/bin -source/NCC/*/obj -source/NCC/bin -*.orig -source/plugins/proxyPython/build -staging/* -source - Copy/* -ModOrganizer-build-* -pdbs/* -source/NCC/BossDummy.x/* -*.ts -staging_prepare/* -staging_trans/* -tools/python_zip/* -Makefile -html -*.vcxproj -*.pdb -*.dll -*.exp -*.tlog -*.user -*.obj -*.suo -*.sln -*.log -*.filters -*.lib -source/organizer/resources/contents/icons -source/plugins/build-* -*/GeneratedFiles/* -translations/* -source/LocalPaths.pri -source/*/Win32/Debug/* -source/plugins/*/Win32/Debug/* -*~ -syntax: regexp -Makefile\.(Debug|Release) -source/.*/debug/.* diff --git a/CMakeLists.txt b/CMakeLists.txt index c1c1cf9b9..85ba23f05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,21 @@ cmake_minimum_required(VERSION 3.16) -# TODO: move these to cmake_common? -set(OPENSSL_USE_STATIC_LIBS FALSE CACHE STRING "" FORCE) -set(MySQL_INCLUDE_DIRS CACHE STRING "" FORCE) +# TODO: clean include directives +set(MO2_CMAKE_DEPRECATED_UIBASE_INCLUDE ON) -if(DEFINED DEPENDENCIES_DIR) - include(${DEPENDENCIES_DIR}/modorganizer_super/cmake_common/mo2.cmake) +project(organizer) + +# if MO2_INSTALL_IS_BIN is set, this means that we should install directly into the +# installation prefix, without the bin/ subfolder, typically for a standalone build +# to update an existing install +if (MO2_INSTALL_IS_BIN) + set(_bin ".") else() - include(${CMAKE_CURRENT_LIST_DIR}/../cmake_common/mo2.cmake) + set(_bin bin) endif() -project(organizer) add_subdirectory(src) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/dump_running_process.bat DESTINATION bin) +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT organizer) + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/dump_running_process.bat DESTINATION ${_bin}) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 000000000..eeeaaacd0 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,59 @@ +{ + "configurePresets": [ + { + "errors": { + "deprecated": true + }, + "hidden": true, + "name": "cmake-dev", + "warnings": { + "deprecated": true, + "dev": true + } + }, + { + "cacheVariables": { + "VCPKG_MANIFEST_NO_DEFAULT_FEATURES": { + "type": "BOOL", + "value": "ON" + } + }, + "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "hidden": true, + "name": "vcpkg" + }, + { + "binaryDir": "${sourceDir}/vsbuild", + "architecture": { + "strategy": "set", + "value": "x64" + }, + "cacheVariables": { + "CMAKE_CXX_FLAGS": "/EHsc /MP /W4", + "VCPKG_TARGET_TRIPLET": { + "type": "STRING", + "value": "x64-windows-static-md" + } + }, + "generator": "Visual Studio 17 2022", + "inherits": ["cmake-dev", "vcpkg"], + "name": "vs2022-windows", + "toolset": "v143" + }, + { + "cacheVariables": { + "VCPKG_MANIFEST_FEATURES": { + "type": "STRING", + "value": "standalone" + }, + "MO2_INSTALL_IS_BIN": { + "type": "BOOL", + "value": "ON" + } + }, + "inherits": "vs2022-windows", + "name": "vs2022-windows-standalone" + } + ], + "version": 4 +} diff --git a/SConstruct b/SConstruct deleted file mode 100644 index d0528b954..000000000 --- a/SConstruct +++ /dev/null @@ -1,713 +0,0 @@ -import distutils.sysconfig -import os -import re -import sys - -def setup_config_variables(): - """ Set up defaults values and load the configuration settings """ - # Take a sensible default for pythonpath - must_be_specified = 'Must be specified ***' - - boostpath = must_be_specified - if 'BOOSTPATH' in os.environ: - boostpath = os.environ['BOOSTPATH'] - - lootpath = must_be_specified - if 'LOOTPATH' in os.environ: - lootpath = os.environ['LOOTPATH'] - - pythonpath = must_be_specified - if 'PYTHONPATH' in os.environ: - pythonpath = os.environ['PYTHONPATH'] - else: - pythonpath = distutils.sysconfig.EXEC_PREFIX - - # Take qtdir from the os environment if set - qtdir = must_be_specified - if 'QTDIR' in os.environ: - qtdir = os.environ['QTDIR'] - elif 'QT4DIR' in os.environ: - qtdir = os.environ['QT4DIR'] - - sevenzippath = must_be_specified - if 'SEVENZIPPATH' in os.environ: - sevenzippath = os.environ['SEVENZIPPATH'] - - zlibpath = must_be_specified - if 'ZLIBPATH' in os.environ: - zlibpath = os.environ['ZLIBPATH'] - - git = 'git' - if 'GIT' in os.environ: - git = os.environ['GIT'] - - mercurial = 'hg' - if 'MERCURIAL' in os.environ: - hg = os.environ['HG'] - - vars = Variables('scons_configure.py') - vars.AddVariables( - PathVariable('BOOSTPATH', 'Set to point to your boost directory', - boostpath, PathVariable.PathIsDir), - PathVariable('LOOTPATH', 'Set to point to your LOOT API directory', - lootpath, PathVariable.PathIsDir), - ('MSVC_VERSION', 'Version of msvc, defaults to latest installed'), - PathVariable('PYTHONPATH', 'Path to python install', pythonpath, - PathVariable.PathIsDir), - PathVariable('QTDIR', 'Path to the version of QT to use', qtdir, - PathVariable.PathIsDir), - PathVariable('SEVENZIPPATH', 'Path to 7zip sources', sevenzippath, - PathVariable.PathIsDir), - PathVariable('ZLIBPATH', 'Path to zlib install', zlibpath, - PathVariable.PathIsDir), - PathVariable('GIT', 'Path to git executable', git, - PathVariable.PathIsFile), - PathVariable('MERCURIAL', 'Path to hg executable', mercurial, - PathVariable.PathIsFile), - PathVariable('IWYU', 'Path to include-what-you-use executable', None, - PathVariable.PathIsFile) - ) - - return vars - -def add_moc_files(self, files): - """ - The QT4 tool only sets up moc for .cpp files. If there's a header file - with no corresponding source we have to do it ourselves. This makes it - easier. - Note: I could just supress the scanning and moc all the HEADER files... - """ - targets = [] - for file in self.Flatten([files]): - contents = file.get_contents() - target = str(file)[:-1] + 'cpp' - if not os.path.exists(target): - if 'Q_OBJECT' in file.get_contents(): - header = file.name - target = self['QT5_XMOCHPREFIX'] + header[:-2] + \ - self['QT5_XMOCHSUFFIX'] - targets.append(self.ExplicitMoc5(target, header)) - return targets - -def link_emitter_wrapper(target, source, env): - # This could be better written! - if '/DEBUG' in env['LINKFLAGS']: - name = str(target[0])[:-3] + 'pdb' - target.append(name) - return (target, source) - -def shlib_emitter_wrapper(target, source, env): - if 'WINDOWS_EMBED_MANIFEST' in env: - env.AppendUnique(LINKFLAGS = '/MANIFEST') - return link_emitter_wrapper(target, source, env) - -def fixup_qt4(): - import SCons.Tool - oldpath = sys.path - sys.path = SCons.Tool.DefaultToolpath + sys.path - import qt4 - def my_qrc_path(head, prefix, tail, suffix): - return "%s%s%s" % (prefix, tail, suffix) - qt4.__qrc_path = my_qrc_path - sys.path = oldpath - -metadata = re.compile(r'Q_PLUGIN_METADATA\(IID *".*" *FILE *"(.*)"\)') - -def json_emitter(target, source, env): - depends = [] - for s in source: - match = metadata.search(s.get_contents()) - if match: - depends += [ match.group(1) ] - env.Depends(target, depends) - return target, source - -def fixup_qt5(): - import SCons.Tool - oldpath = sys.path - sys.path = SCons.Tool.DefaultToolpath + sys.path - import qt5 - def my_qrc_path(head, prefix, tail, suffix): - return "%s%s%s" % (prefix, tail, suffix) - qt5.__qrc_path = my_qrc_path - sys.path = oldpath - - qt_env['BUILDERS']['Moc5'].builder.emitter = json_emitter - -moduleDefines = { - '3Support' : [ 'QT_QT3SUPPORT_LIB', 'QT3_SUPPORT' ], - 'Core' : [ 'QT_CORE_LIB' ], - 'Declarative' : [ 'QT_DECLARATIVE_LIB' ], - 'Gui' : [ 'QT_GUI_LIB' ], - 'Network' : [ 'QT_NETWORK_LIB' ], - 'OpenGL' : [ 'QT_OPENGL_LIB' ], - 'Script' : [ 'QT_SCRIPT_LIB' ], - 'Sql' : [ 'QT_SQL_LIB' ], - 'Svg' : [ 'QT_SVG_LIB' ], - 'WebKit' : [ 'QT_WEBKIT_LIB' ], - 'Xml' : [ 'QT_XML_LIB' ], - 'XmlPatterns' : [ 'QT_XMLPATTERNS_LIB' ], -# qt5 - 'Qml' : [ 'QT_QML_LIB' ], - 'QuickWidgets' : [ 'QT_QUICKWIDGETS_LIB', 'QT_WIDGETS_LIB', 'QT_QUICK_LIB' ], - 'Widgets' : [ 'QT_WIDGETS_LIB' ], - 'WebKitWidgets' : [ 'QT_WEBKITWIDGETS_LIB' ], - 'WinExtras' : [ 'QT_WINEXTRAS_LIB' ] -} - -staticModules = [ - 'UiTools', -] - -def get_qt_lib_info(env): - " Deal with the various QT naming conventions. sigh " - - # For QT4, the libraries are QTlibname{,d}4 - # For QT5, they are QT5libname{,d} - - prefix = 'QT' - suffix = 'd' if env['CONFIG'] == 'debug' else '' - - _suffix = suffix # Because they can't be consistent within a version, - # let alone between - version = env['QT_MAJOR_VERSION'] - if version <= 4: - suffix += str(version) - else: - prefix += str(version) - return prefix, suffix, _suffix - -def EnableQtModules(self, *modules): - """ Enable the specified QT modules, mainly by adding defines - and libraries - """ - self.AppendUnique(QT_USED_MODULES = modules) - for module in modules: - try: - self.AppendUnique(CPPDEFINES = moduleDefines[module]) - if self['CONFIG'] == 'debug': - if module == 'Declarative': - self.AppendUnique(CPPDEFINES = 'QT_DECLARATIVE_DEBUG') - elif module == 'Qml': - self.AppendUnique(CPPDEFINES = 'QT_QML_DEBUG') - except: - print 'module', module, 'has no -D' - pass - - if "Assistant" in modules: - self.AppendUnique(CPPPATH = [ - os.path.join('$QTDIR', 'include', 'QtAssistant') - ]) - modules.remove('Assistant') - modules.append('AssistantClient') - - prefix, suffix, _suffix = get_qt_lib_info(self) - self.AppendUnique(LIBS = [ - prefix + lib + suffix for lib in modules if lib not in staticModules - ]) - - self.PrependUnique(LIBS = [ - lib + _suffix for lib in modules if lib in staticModules - ]) - - if 'OpenGL' in modules: - self.AppendUnique(LIBS = [ 'opengl32' ]) - - self.AppendUnique(CPPPATH = [ - os.path.join('$QTDIR', 'include', 'QT' + module) for module in modules - ]) - -def DisableQtModules(self, *modules): - """ Disable the specified QT modules similar to enabling them """ - for module in modules: - try: - self['QT_USED_MODULES'].remove(module) - self['CPPDEFINES'].remove(moduleDefines[module]) - if self['CONFIG'] == 'debug' and module == 'Declarative': - self['CPPDEFINES'].remove('QT_DECLARATIVE_DEBUG') - except: - pass - - - if "Assistant" in modules: - self['CPPPATH'].remove(os.path.join('$QTDIR', 'include', 'QtAssistant')) - modules.remove('Assistant') - modules.append('AssistantClient') - - prefix, suffix, _suffix = get_qt_lib_info(self) - - for lib in modules: - self['LIBS'].remove(prefix + lib + suffix) - - if 'OpenGL' in modules: - self['LIBS'].remove('opengl32') - - for module in modules: - self['CPPPATH'].remove(os.path.join('$QTDIR', 'include', 'QT' + module)) - -def setup_IWYU(env): - import SCons.Defaults - import SCons.Builder - original_shared = SCons.Defaults.SharedObjectEmitter - original_static = SCons.Defaults.StaticObjectEmitter - - def DoIWYU(env, source, target): - for i in range(len(source)): - s = source[i] - dir, name = os.path.split(str(s)) # I'm sure theres a way of getting this from scons - # Don't bother looking at moc files and 7zip source - if not name.startswith('moc_') and \ - not dir.startswith(env['SEVENZIPPATH']): - # Put the .iwyu in the same place as the .obj - targ = os.path.splitext(str(target[i]))[0] - env.Depends(env.IWYU(targ + '.iwyu', s), target[i]) - - def shared_emitter(target, source, env): - DoIWYU(env, source, target) - return original_shared(target, source, env) - - def static_emitter(target, source, env): - DoIWYU(env, source, target) - return original_static(target, source, env) - - SCons.Defaults.SharedObjectEmitter = shared_emitter - SCons.Defaults.StaticObjectEmitter = static_emitter - - def emitter(target, source, env): - env.Depends(target, env['IWYU_MAPPING_FILE']) - env.Depends(target, env['IWYU_MASSAGE']) - return target, source - - def _concat_list(prefixes, list, suffixes, env, f=lambda x: x, target=None, source=None): - """ Creates a new list from 'list' by first interpolating each element - in the list using the 'env' dictionary and then calling f on the - list, and concatenate the 'prefix' and 'suffix' LISTS onto each element of the list. - A trailing space on the last element of 'prefix' or leading space on the - first element of 'suffix' will cause them to be put into separate list - elements rather than being concatenated. - """ - - if not list: - return list - - l = f(SCons.PathList.PathList(list).subst_path(env, target, source)) - if l is not None: - list = l - - # This bit replaces current concat_ixes - - result = [] - - def process_stringlist(s): - return [ str(env.subst(p, SCons.Subst.SUBST_RAW)) - for p in Flatten([s]) if p != '' ] - - # ensure that prefix and suffix are strings - prefixes = process_stringlist(prefixes) - prefix = '' - if len(prefixes) != 0: - if prefixes[-1][-1] != ' ': - prefix = prefixes.pop() - - suffixes = process_stringlist(suffixes) - suffix = '' - if len(suffixes) != 0: - if suffixes[-1][0] != ' ': - suffix = suffixes.pop(0) - - for x in list: - if isinstance(x, SCons.Node.FS.File): - result.append(x) - continue - x = str(x) - if x: - result.append(prefixes) - if prefix: - if x[:len(prefix)] != prefix: - x = prefix + x - result.append(x) - if suffix: - if x[-len(suffix):] != suffix: - result[-1] = result[-1] + suffix - result.append(suffixes) - return result - - env['_concat_list'] = _concat_list - # Note to self: command 2>&1 | other command appears to work as I would hope - # except it eats errors - iwyu = SCons.Builder.Builder( - action=[ - '$IWYU_MASSAGE $TARGET $IWYU $IWYU_FLAGS $IWYU_MAPPINGS $IWYU_COMCOM $SOURCE' - ], - emitter=emitter, - suffix='.iwyu', - src_suffix='.cpp') - - env.Append(BUILDERS={'IWYU': iwyu}) - - # Sigh - IWYU is a right bum as it doesn't recognise /I so I have to - # duplicate most of the usual stuff - - env['IWYU_FLAGS'] = [ - # This might turn down the output a bit. I hope - '-Xiwyu', '--transitive_includes_only', - # Seem to be needed for a windows build - '-D_MT', '-D_DLL', '-m32', - # This is something to do with clang, windows and boost headers - '-DBOOST_USE_WINDOWS_H', - # There's a lot of this, disabled for now - '-Wno-inconsistent-missing-override', - # Mark boost and Qt headers as system headers to disable a lot of noise. - # I'm sure there has to be a better way than saying 'prefix=Q' - '--system-header-prefix=Q', - '--system-header-prefix=boost/', - # Should be able to get this info from our setup really - '-fmsc-version=1800', '-D_MSC_VER=1800', - # clang and qt don't agree about these because clang says its gcc 4.2 - # and QT doesn't realise it's clang - '-DQ_COMPILER_INITIALIZER_LISTS', - '-DQ_COMPILER_DECLTYPE', - '-DQ_COMPILER_VARIADIC_TEMPLATES', - ] - if env['CONFIG'] == 'debug': - env['IWYU_FLAGS'] += [ '-D_DEBUG' ] - - env['IWYU_DEFPREFIX'] = '-D' - env['IWYU_DEFSUFFIX'] = '' - env['IWYU_CPPDEFFLAGS'] = '${_defines(IWYU_DEFPREFIX, CPPDEFINES, IWYU_DEFSUFFIX, __env__)}' - - env['IWYU_INCPREFIX'] = '-I' - env['IWYU_INCSUFFIX'] = '' - env['IWYU_CPPINCFLAGS'] = '$( ${_concat(IWYU_INCPREFIX, CPPPATH, IWYU_INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)' - - env['IWYU_PCH_PREFIX'] = '-include' # Amazingly this works without a space - env['IWYU_PCH_SUFFIX'] = '' - env['IWYU_PCHFILES'] = '$( ${_concat(IWYU_PCH_PREFIX, PCHSTOP, IWYU_PCH_SUFFIX, __env__, target=TARGET, source=SOURCE)} $)' - - env['IWYU_COMCOM'] = '$IWYU_CPPDEFFLAGS $IWYU_CPPINCFLAGS $IWYU_PCHFILES $CCPDBFLAGS' - env['IWYU_MAPPING_PREFIX'] = ['-Xiwyu', '--mapping_file='] - env['IWYU_MAPPING_SUFFIX'] = '' - env['IWYU_MAPPINGS'] = '$( ${_concat_list(IWYU_MAPPING_PREFIX, IWYU_MAPPING_FILE, IWYU_MAPPING_SUFFIX, __env__, f=lambda l: [ str(x) for x in l], target=TARGET, source=SOURCE)} $)' - - env['IWYU_MAPPING_FILE'] = [ - env.File('#/modorganizer/qt5_4.imp'), - env.File('#/modorganizer/win.imp'), - env.File('#/modorganizer/mappings.imp') - ] - - env['IWYU_MASSAGE'] = env.File('#/modorganizer/massage_messages.py') - -# Create base environment -vars = setup_config_variables() -env = Environment(variables = vars, TARGET_ARCH = 'x86') - -# I'd really like to validate for unexpected settings in 'variables', but scons -# appears to throw them away -#ok = True -#for key, value in vars.UnknownVariables(): -# print "unknown variable in scons_configure.py: %s=%s" % (key, value) -# ok = False -#if not ok: -# sys.exit(1) - -# Patch scons to realise it's generating PDB files if /DEBUG is set -env.AppendUnique(PROGEMITTER = [ link_emitter_wrapper ]) -# Ditto + windows_embed_manifest doesn't generate a manifest file -env.AppendUnique(SHLIBEMITTER = [ shlib_emitter_wrapper ]) - -# Work out where to find boost libraries -libdir = os.path.join(env['BOOSTPATH'], 'lib32-msvc-' + env['MSVC_VERSION']) -if not os.path.exists(libdir): - libdir = os.path.join(env['BOOSTPATH'], 'stage', 'lib') -env.AppendUnique(LIBPATH = libdir) - -# Process command line to find out what/where we're building -config = ARGUMENTS.get('CONFIG', 'debug') -env['CONFIG'] = config - -# I think this is a bug in scons. It returns 12.0 for studio 13 -# This needs to match the QT specified version or bad things will happen! -msvs_version = int(env['MSVS_VERSION'].split('.')[0]) + 2000 -if msvs_version > 2010: - msvs_version += 1 - -# Read the QT version info -with open(os.path.join(env['QTDIR'], 'mkspecs', 'qconfig.pri')) as qtinfo: - for line in qtinfo: - info = re.split(r'\s*=\s*', line.rstrip()) - if info[0] == 'QT_VERSION': - env[info[0]] = info[1] - elif '_VERSION' in info[0]: - env[info[0]] = int(info[1]) - -build_dir = 'scons-ModOrganizer-QT_%s_%sfor_MSVS%d_32bit-%s' % ( - env['QT_VERSION'].replace('.', '_'), - 'OpenGL_' if 'opengl' in env['QTDIR'] else '', - msvs_version, - config.title()) - -# Put the sconsign file somewhere sane -env.SConsignFile(os.path.join(build_dir, '.sconsign.dblite')) -env.CacheDir(os.path.join(build_dir, '.cache')) - -#this doesn't seem to work -#env.VariantDir('build/$CONFIG', 'source') -#env.VariantDir('build/$CONFIG', 'source', duplicate = 0) - -# Ripped off from qmake.conf - -# Compiler defines. Note that scons uses the same variables for C and C++ -# defines and include paths so be careful if you mix languages. - -# A note: QT puts _MSC_VER into the compile line, but I can see no earthly -# reason for this. - -env.AppendUnique(CPPDEFINES = [ - 'UNICODE', - 'WIN32', - 'NOMINMAX' # Nukes boost all over the place -]) - -# Default warning level. -env['WARNING_LEVEL'] = 3 - -# C compiler flags -env.AppendUnique(CPPFLAGS = [ - '/Zm200', - #'/Zc:wchar_t-', # 4 v 5 - '/Zc:wchar_t', - '/W$WARNING_LEVEL' -]) - -# C++ compiler flags -env.AppendUnique(CXXFLAGS = [ - '/w34100', - '/w34189', - '/EHsc', # STL on - '/GR' # RTTI on -]) - -""" -# qmake.conf -#QMAKE_CFLAGS_RELEASE = -O2 -MD -#QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -O2 -MD -Zi -#QMAKE_CFLAGS_LTCG = -GL -#QMAKE_CFLAGS_MP = -MP - -QMAKE_LFLAGS_RELEASE = /INCREMENTAL:NO -QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO = /DEBUG /OPT:REF -QMAKE_LFLAGS_LTCG = /LTCG - -# project -""" - -env['WINDOWS_EMBED_MANIFEST'] = True - - -# Seriously, the linker doesn't apply these by default? -env.AppendUnique(LINKFLAGS = [ - '/DYNAMICBASE', - '/NXCOMPAT' -]) - -# Display full path in messages. Sadly even this isn't good enough for Creator -# env.AppendUnique(CPPFLAGS = [ '/FC' ]) - -if env['CONFIG'] == 'debug': - env.AppendUnique(CPPFLAGS = [ '/MDd', '/Z7' ]) - env.AppendUnique(LINKFLAGS = [ '/DEBUG' ]) #, '/OPT:REF']) -else: - env.AppendUnique(CPPFLAGS = [ '/O2', '/MD' ]) - env.AppendUnique(LINKFLAGS = [ '/OPT:REF', '/OPT:ICF' ]) - -# Set up include what you use. Add this as an extra compile step. Note it -# doesn't currently generate an output file (use the output instead!). -if 'IWYU' in env: - setup_IWYU(env) - -# /OPT:REF removes unreferenced code -# for release, use /OPT:ICF (comdat folding: coalesce identical blocks of code) - -# We have to make the install path absolute. *sigh*. But it appears to have to -# be in build_dir... -env['INSTALL_PATH'] = os.path.join(os.getcwd(), build_dir, '_ModOrganizer') - -# Create the environment for QT -qt_env = env.Clone() - -# If you don't do this for the release build, QT gets very upset... -if qt_env['CONFIG'] != 'debug': - qt_env.AppendUnique(CPPDEFINES = [ 'QT_NO_DEBUG' ]) - -qt_env.Tool('qt%d' % qt_env['QT_MAJOR_VERSION']) - -# FIXME See if I can work out how to get official scons qt to work. Appears -# to only work with QT5 -fixup_qt5() - -qt_env.AddMethod(add_moc_files, 'AddExtraMoc') - -# A very strange rune which QT sets -qt_env['EXE_MANIFEST_DEPENDENCY'] =\ - '/MANIFESTDEPENDENCY:"' + ' '.join(("type='win32'", - "name='Microsoft.Windows.Common-Controls'", - "version='6.0.0.0'", - "publicKeyToken='6595b64144ccf1df'", - "language='*'", - "processorArchitecture='*'")) + '"' - -# Not sure how necessary this is. Moreover it says msvc but seems to expect -# the msvs number -qt_env.AppendUnique(CPPPATH = [ - os.path.join(env['QTDIR'], 'mkspecs', 'win32-msvc%d' % msvs_version) -]) - -qt_env.AddMethod(EnableQtModules) -qt_env.AddMethod(DisableQtModules) - -# This is a hack. we should redirect uic to uic4/uic5 and fix the scripts -def Uicc5(self, *args, **kwargs): - return self.Uic5(*args, **kwargs) - -qt_env.AddMethod(Uicc5, 'Uic') - -# Enable the base libraries. -qt_env.EnableQt5Modules([], debug = qt_env['CONFIG'] == 'debug') - -# Causes too many problems if you don't do this -qt_env['QT%d_MOCCPPPATH' % qt_env['QT_MAJOR_VERSION']] = '$CPPPATH' -# Yechhh. Note: the the _VER depends on ms compiler version (and could -# be dragged from the qmake conf file or preferrably from ms compiler) -qt_env['QT%d_MOCDEFINES' % qt_env['QT_MAJOR_VERSION']] =\ - '${_defines(QT5_MOCDEFPREFIX, MOCDEFINES+CPPDEFINES, QT5_MOCDEFSUFFIX, __env__)}' -qt_env['MOCDEFINES'] = [ - '_MSC_VER=1800', -] -# QTCreator appears to add these automatically. Some of these look moderately -# dangerous if you're attempting to cross-compile -# only for qt4? -if qt_env['QT_MAJOR_VERSION'] <= 4: - qt_env.AppendUnique(CPPDEFINES = [ - 'QT_DLL', - 'QT_HAVE_MMX', - 'QT_HAVE_3DNOW', - 'QT_HAVE_SSE', - 'QT_HAVE_MMXEXT', - 'QT_HAVE_SSE2', - 'QT_THREAD_SUPPORT' - ]) - -# It also adds this to the end of the include path. -# -I"c:\Apps\Qt\4.8.6\include\ActiveQt" - -# Export environment. Rename it first to encourage instant Clone() calls -Export('env qt_env') - -# And away we go -libs_to_install = env.SConscript('source/SConscript', - variant_dir = build_dir, - duplicate = 0) -libs_to_install = sorted(set(filter(lambda x: x is not None, - env.Flatten(libs_to_install)))) -# There are some odd implicit dependencies -if qt_env['QT_MAJOR_VERSION'] > 4: - libs_to_install += [ - 'Multimedia', - 'MultimediaWidgets', - 'OpenGL', - 'Positioning', - 'PrintSupport', - 'Quick', - 'Sensors', - 'WebChannel', - ] - -# Finally, set up rules to install the DLLs. -# use windeployqt.exe to install all required libraries -#SET(windeploy_parameters --no-translations --no-plugins --libdir dlls --release-with-debug-info --no-compiler-runtime) -#INSTALL( -# CODE -# "EXECUTE_PROCESS( -# COMMAND -# ${qt5bin}/windeployqt.exe ModOrganizer.exe ${windeploy_parameters} -# COMMAND -# ${qt5bin}/windeployqt.exe uibase.dll ${windeploy_parameters} -# WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin -# )" -#) -# this should probably be a rule though it seems to produce an awful lot of -# Stuff(TM). or a postaction - -dll_path = os.path.join('$INSTALL_PATH', 'DLLs') - -prefix, suffix, suffix_ = get_qt_lib_info(qt_env) - -dlls_to_install = [] -dlls_to_install = [ - os.path.join(env['QTDIR'], 'bin', prefix + lib + suffix + '.dll') - for lib in libs_to_install -] - -if env['CONFIG'] == 'debug': - dlls_to_install += [ - os.path.join(env['QTDIR'], 'bin', prefix + lib + suffix + '.pdb') - for lib in libs_to_install - ] - -# There is something wrong with webkit4 and/or this build as it seems -# to need to live in the same directory as mod organiser. -if 'WebKit' in libs_to_install and qt_env['QT_MAJOR_VERSION'] == 4: - libname = prefix + 'WebKit' + suffix - env.Install(env['INSTALL_PATH'], - os.path.join(env['QTDIR'], 'bin', libname + '.dll')) - if env['CONFIG'] == 'debug': - env.Install(env['INSTALL_PATH'], - os.path.join(env['QTDIR'], 'bin', libname + '.pdb')) - -if qt_env['QT_MAJOR_VERSION'] > 4: - # Guesswork a bit. - dlls_to_install += [ - os.path.join(env['QTDIR'], - 'bin', - 'icu%s%d%d.dll' % (lib, - qt_env['QT_MAJOR_VERSION'], - qt_env['QT_MINOR_VERSION'] - 1)) - for lib in ('dt','in', 'uc') - ] - - platform_dlls = [] - if env['CONFIG'] == 'debug': - platform_dlls += [ 'qwindowsd.dll', 'qwindowsd.pdb' ] - else: - platform_dlls += [ 'qwindows.dll' ] - - # Note: Appears to work fine in DLLs or at the top level, but I'm all for - # keeping the top directory a bit clean - env.Install(os.path.join(dll_path, 'platforms'), - [ os.path.join(env['QTDIR'], 'plugins', 'platforms', dll) - for dll in platform_dlls ]) - - image_dlls = [] - for image in ('dds', 'gif', 'jpeg', 'tga'): - if env['CONFIG'] == 'debug': - image_dlls += [ 'q' + image + 'd.dll', 'q' + image + 'd.pdb' ] - else: - image_dlls += [ 'q' + image + '.dll' ] - env.Install(os.path.join(dll_path, 'imageformats'), - [ os.path.join(env['QTDIR'], 'plugins', 'imageformats', dll) - for dll in image_dlls ]) - -# Build your own? -dlls_to_install += [ - os.path.join('tools', 'static_data', 'dlls', '7z.dll') -] - -env.Install(dll_path, dlls_to_install) - -# And loot which goes somewhere else (maybe this should be done in loot_cli?) -env.Install(os.path.join('${INSTALL_PATH}', 'loot'), - os.path.join('${LOOTPATH}', 'loot32.dll')) - -# also pythondll and zip, boost python dll (all for python proxy!) -# the dll we can drag from the python install -# the zip i'm not sure about. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 854e3aaa4..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,77 +0,0 @@ -version: dev-appveyor{build} -skip_branch_with_pr: true -image: Visual Studio 2019 -init: -- ps: >- - # define build version depending on nightly or normal build - - if($env:APPVEYOR_SCHEDULED_BUILD -eq 'True'){ - $timestamp= Get-Date -Format "ddMMyyyy-HHmm" - Update-AppveyorBuild -Version "$($env:MO_VERSION)$($env:VER_STUB_NIGHTLY)$timestamp" - } else { - Update-AppveyorBuild -Version "$($env:MO_VERSION)$($env:VER_STUB_NORMAL)$($env:APPVEYOR_BUILD_NUMBER)" - } - - Write-Host Build version set to: $env:APPVEYOR_BUILD_VERSION -environment: - WEBHOOK_URL: - secure: gOKbXaZM9ImtMD5XrYITvdyZUW/az082G9OIN1EC1VaKiI9iefpxhBavJ6Al6CzIZvQ+3pxnLqjmNgA7cDc22wcj2kB4hSG5qhbTI8wGa8jLQ5L65nuRZ3vrIqghBz9G3GLglgZkg6eqH9r3Kqc6UzcpCGzxxPOqm550nRcIiUU= - MO_VERSION: 2.4.0 - VER_STUB_NORMAL: dev-appveyor- - VER_STUB_NIGHTLY: dev-nightly- -build_script: -- pwsh: >- - # Maintenance comments: - - # APPVEYOR_BUILD_FOLDER= "c:\projects\modorganizer-slug" - - # -Need to update py3 version used to invoke unimake.py once in a while. - - # -Need update MO_VERSION env variable after each release. - - # -Always clones umbrella master - - # -Will checkout all the branches matching the one that triggered the build on the main repo. - - # End comments. - - $ErrorActionPreference = 'Stop' - - git clone --depth=1 --no-single-branch https://github.com/ModOrganizer2/modorganizer-umbrella.git c:\projects\modorganizer-umbrella - - New-Item -ItemType Directory -Path ${env:APPVEYOR_BUILD_FOLDER}\modorganizer-build - - cd c:\projects\modorganizer-umbrella - - ($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH -eq $null) ? ($branch = $env:APPVEYOR_REPO_BRANCH) : ($branch = $env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH) - - git checkout $(git show-ref --verify --quiet refs/remotes/origin/${branch} || echo '-b') ${branch} - - C:\Python38-x64\python.exe unimake.py -d ${env:APPVEYOR_BUILD_FOLDER}\modorganizer-build -s Appveyor_Build=True -s Feature_Branch=${env:APPVEYOR_REPO_BRANCH} -s override_build_version=${env:APPVEYOR_BUILD_VERSION} - - if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) } -test: off -artifacts: -- path: '\modorganizer-build\install\bin\ModOrganizer.exe' - name: Mod.Organizer-$(APPVEYOR_BUILD_VERSION) -- path: '\modorganizer-build\install\pdb\ModOrganizer.pdb' - name: PDB-Mod.Organizer-$(APPVEYOR_BUILD_VERSION) -deploy: off -on_success: -- ps: >- - Set-Location -Path $env:APPVEYOR_BUILD_FOLDER - - Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 - - if($env:APPVEYOR_SCHEDULED_BUILD -ne 'True'){ - ./send.ps1 success $env:WEBHOOK_URL - } -on_failure: -- ps: >- - Set-Location -Path $env:APPVEYOR_BUILD_FOLDER - - Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 - - if($env:APPVEYOR_SCHEDULED_BUILD -ne 'True'){ - ./send.ps1 failure $env:WEBHOOK_URL - } diff --git a/mappings.imp b/mappings.imp deleted file mode 100644 index 6af65e3c6..000000000 --- a/mappings.imp +++ /dev/null @@ -1,30 +0,0 @@ -[ - -# for boost??? -# These are probably correct but might need a revisit as if you look at the boost documentation pages, it -# can give you huge lists of alternate includes... - { symbol: [ "BOOST_FOREACH", "private", "", "public" ] }, - - { include: [ "@\"boost/bind/.*\"", "private", "", "public" ] }, - { include: [ "@\"boost/algorithm/string/.*\"", "private", "", "public" ] }, - { include: [ "@\"boost/assign/.*\"", "private", "", "public" ] }, - { include: [ "@\"boost/filesystem/.*\"", "private", "", "public" ] }, - { include: [ "@\"boost/format/.*\"", "private", "", "public" ] }, - { include: [ "@\"boost/function/.*\"", "private", "", "public" ] }, - { include: [ "@\"boost/local/.*\"", "private", "", "public" ] }, - { include: [ "@\"boost/python/.*\"", "private", "", "public" ] }, - { include: [ "@\"boost/signals2/.*\"", "private", "", "public" ] }, - { include: [ "\"boost/smart_ptr/scoped_array.hpp\"", "private", "", "public" ] }, - { include: [ "\"boost/smart_ptr/shared_ptr.hpp\"", "private", "", "public" ] }, - # this appears to be excessive - #{ include: [ "@\"boost/thread/.*\"", "private", "", "public" ] }, - -# And this is specific to us - { include: [ "\"appconfig.inc\"", "private", "\"appconfig.h\"", "public" ] }, - -] - -# Ones I don't yet know how to deal with -#include "boost/fusion/container/vector/vector10_fwd.hpp" // for fusion -#include "boost/iterator/iterator_facade.hpp" // for operator!= -#include "boost/iterator/iterator_facade.hpp" diff --git a/qt5_4.imp b/qt5_4.imp deleted file mode 100644 index 5f919085d..000000000 --- a/qt5_4.imp +++ /dev/null @@ -1,2478 +0,0 @@ -[ - -# Per le documentation, each class lives in it's own header file. These are the -# official header files (as far as I can determine, from a scan of the QT includes -# directory) -# It'd be nice if IWYU could be told to recognise X::y as coming from the same header as X - - { symbol: [ "ActiveQt", "private", "", "public" ] }, - { symbol: [ "ActiveQtDepends", "private", "", "public" ] }, - { symbol: [ "ActiveQtVersion", "private", "", "public" ] }, - { symbol: [ "Enginio", "private", "", "public" ] }, - { symbol: [ "EnginioDepends", "private", "", "public" ] }, - { symbol: [ "EnginioVersion", "private", "", "public" ] }, - { symbol: [ "QAbstractAnimation", "private", "", "public" ] }, - { symbol: [ "QAbstractAudioDeviceInfo", "private", "", "public" ] }, - { symbol: [ "QAbstractAudioInput", "private", "", "public" ] }, - { symbol: [ "QAbstractAudioOutput", "private", "", "public" ] }, - { symbol: [ "QAbstractButton", "private", "", "public" ] }, - { symbol: [ "QAbstractEventDispatcher", "private", "", "public" ] }, - { symbol: [ "QAbstractExtensionFactory", "private", "", "public" ] }, - { symbol: [ "QAbstractExtensionManager", "private", "", "public" ] }, - { symbol: [ "QAbstractFormBuilder", "private", "", "public" ] }, - { symbol: [ "QAbstractGraphicsShapeItem", "private", "", "public" ] }, - { symbol: [ "QAbstractItemDelegate", "private", "", "public" ] }, - { symbol: [ "QAbstractItemModel", "private", "", "public" ] }, - { symbol: [ "QAbstractItemView", "private", "", "public" ] }, - { symbol: [ "QAbstractListModel", "private", "", "public" ] }, - { symbol: [ "QAbstractMessageHandler", "private", "", "public" ] }, - { symbol: [ "QAbstractNativeEventFilter", "private", "", "public" ] }, - { symbol: [ "QAbstractNetworkCache", "private", "", "public" ] }, - { symbol: [ "QAbstractPlanarVideoBuffer", "private", "", "public" ] }, - { symbol: [ "QAbstractPrintDialog", "private", "", "public" ] }, - { symbol: [ "QAbstractProxyModel", "private", "", "public" ] }, - { symbol: [ "QAbstractScrollArea", "private", "", "public" ] }, - { symbol: [ "QAbstractSlider", "private", "", "public" ] }, - { symbol: [ "QAbstractSocket", "private", "", "public" ] }, - { symbol: [ "QAbstractSpinBox", "private", "", "public" ] }, - { symbol: [ "QAbstractState", "private", "", "public" ] }, - { symbol: [ "QAbstractTableModel", "private", "", "public" ] }, - { symbol: [ "QAbstractTextDocumentLayout", "private", "", "public" ] }, - { symbol: [ "QAbstractTransition", "private", "", "public" ] }, - { symbol: [ "QAbstractUndoItem", "private", "", "public" ] }, - { symbol: [ "QAbstractUriResolver", "private", "", "public" ] }, - { symbol: [ "QAbstractVideoBuffer", "private", "", "public" ] }, - { symbol: [ "QAbstractVideoSurface", "private", "", "public" ] }, - { symbol: [ "QAbstractXmlNodeModel", "private", "", "public" ] }, - { symbol: [ "QAbstractXmlReceiver", "private", "", "public" ] }, - { symbol: [ "QAccelerometer", "private", "", "public" ] }, - { symbol: [ "QAccelerometerFilter", "private", "", "public" ] }, - { symbol: [ "QAccelerometerReading", "private", "", "public" ] }, - { symbol: [ "QAccessible", "private", "", "public" ] }, - { symbol: [ "QAccessibleAbstractScrollArea", "private", "", "public" ] }, - { symbol: [ "QAccessibleAbstractSlider", "private", "", "public" ] }, - { symbol: [ "QAccessibleAbstractSpinBox", "private", "", "public" ] }, - { symbol: [ "QAccessibleActionInterface", "private", "", "public" ] }, - { symbol: [ "QAccessibleApplication", "private", "", "public" ] }, - { symbol: [ "QAccessibleBridge", "private", "", "public" ] }, - { symbol: [ "QAccessibleBridgePlugin", "private", "", "public" ] }, - { symbol: [ "QAccessibleButton", "private", "", "public" ] }, - { symbol: [ "QAccessibleCalendarWidget", "private", "", "public" ] }, - { symbol: [ "QAccessibleComboBox", "private", "", "public" ] }, - { symbol: [ "QAccessibleDial", "private", "", "public" ] }, - { symbol: [ "QAccessibleDialogButtonBox", "private", "", "public" ] }, - { symbol: [ "QAccessibleDisplay", "private", "", "public" ] }, - { symbol: [ "QAccessibleDockWidget", "private", "", "public" ] }, - { symbol: [ "QAccessibleDoubleSpinBox", "private", "", "public" ] }, - { symbol: [ "QAccessibleEditableTextInterface", "private", "", "public" ] }, - { symbol: [ "QAccessibleEvent", "private", "", "public" ] }, - { symbol: [ "QAccessibleGroupBox", "private", "", "public" ] }, - { symbol: [ "QAccessibleImageInterface", "private", "", "public" ] }, - { symbol: [ "QAccessibleInterface", "private", "", "public" ] }, - { symbol: [ "QAccessibleLineEdit", "private", "", "public" ] }, - { symbol: [ "QAccessibleMainWindow", "private", "", "public" ] }, - { symbol: [ "QAccessibleMdiArea", "private", "", "public" ] }, - { symbol: [ "QAccessibleMdiSubWindow", "private", "", "public" ] }, - { symbol: [ "QAccessibleMenu", "private", "", "public" ] }, - { symbol: [ "QAccessibleMenuBar", "private", "", "public" ] }, - { symbol: [ "QAccessibleMenuItem", "private", "", "public" ] }, - { symbol: [ "QAccessibleObject", "private", "", "public" ] }, - { symbol: [ "QAccessiblePlainTextEdit", "private", "", "public" ] }, - { symbol: [ "QAccessiblePlugin", "private", "", "public" ] }, - { symbol: [ "QAccessibleProgressBar", "private", "", "public" ] }, - { symbol: [ "QAccessibleScrollArea", "private", "", "public" ] }, - { symbol: [ "QAccessibleScrollBar", "private", "", "public" ] }, - { symbol: [ "QAccessibleSlider", "private", "", "public" ] }, - { symbol: [ "QAccessibleSpinBox", "private", "", "public" ] }, - { symbol: [ "QAccessibleStackedWidget", "private", "", "public" ] }, - { symbol: [ "QAccessibleStateChangeEvent", "private", "", "public" ] }, - { symbol: [ "QAccessibleTabBar", "private", "", "public" ] }, - { symbol: [ "QAccessibleTable", "private", "", "public" ] }, - { symbol: [ "QAccessibleTableCell", "private", "", "public" ] }, - { symbol: [ "QAccessibleTableCellInterface", "private", "", "public" ] }, - { symbol: [ "QAccessibleTableCornerButton", "private", "", "public" ] }, - { symbol: [ "QAccessibleTableHeaderCell", "private", "", "public" ] }, - { symbol: [ "QAccessibleTableInterface", "private", "", "public" ] }, - { symbol: [ "QAccessibleTableModelChangeEvent", "private", "", "public" ] }, - { symbol: [ "QAccessibleTextBrowser", "private", "", "public" ] }, - { symbol: [ "QAccessibleTextCursorEvent", "private", "", "public" ] }, - { symbol: [ "QAccessibleTextEdit", "private", "", "public" ] }, - { symbol: [ "QAccessibleTextInsertEvent", "private", "", "public" ] }, - { symbol: [ "QAccessibleTextInterface", "private", "", "public" ] }, - { symbol: [ "QAccessibleTextRemoveEvent", "private", "", "public" ] }, - { symbol: [ "QAccessibleTextSelectionEvent", "private", "", "public" ] }, - { symbol: [ "QAccessibleTextUpdateEvent", "private", "", "public" ] }, - { symbol: [ "QAccessibleTextWidget", "private", "", "public" ] }, - { symbol: [ "QAccessibleToolBox", "private", "", "public" ] }, - { symbol: [ "QAccessibleToolButton", "private", "", "public" ] }, - { symbol: [ "QAccessibleTree", "private", "", "public" ] }, - { symbol: [ "QAccessibleValueChangeEvent", "private", "", "public" ] }, - { symbol: [ "QAccessibleValueInterface", "private", "", "public" ] }, - { symbol: [ "QAccessibleWidget", "private", "", "public" ] }, - { symbol: [ "QAccessibleWindowContainer", "private", "", "public" ] }, - { symbol: [ "QAction", "private", "", "public" ] }, - { symbol: [ "QActionEvent", "private", "", "public" ] }, - { symbol: [ "QActionGroup", "private", "", "public" ] }, - { symbol: [ "QAltimeter", "private", "", "public" ] }, - { symbol: [ "QAltimeterFilter", "private", "", "public" ] }, - { symbol: [ "QAltimeterReading", "private", "", "public" ] }, - { symbol: [ "QAmbientLightFilter", "private", "", "public" ] }, - { symbol: [ "QAmbientLightReading", "private", "", "public" ] }, - { symbol: [ "QAmbientLightSensor", "private", "", "public" ] }, - { symbol: [ "QAmbientTemperatureFilter", "private", "", "public" ] }, - { symbol: [ "QAmbientTemperatureReading", "private", "", "public" ] }, - { symbol: [ "QAmbientTemperatureSensor", "private", "", "public" ] }, - { symbol: [ "QAnimationDriver", "private", "", "public" ] }, - { symbol: [ "QAnimationGroup", "private", "", "public" ] }, - { symbol: [ "QApplication", "private", "", "public" ] }, - { symbol: [ "QApplicationStateChangeEvent", "private", "", "public" ] }, - { symbol: [ "QArgument", "private", "", "public" ] }, - { symbol: [ "QArrayData", "private", "", "public" ] }, - { symbol: [ "QArrayDataPointer", "private", "", "public" ] }, - { symbol: [ "QArrayDataPointerRef", "private", "", "public" ] }, - { symbol: [ "QAssociativeIterable", "private", "", "public" ] }, - { symbol: [ "QAtomicInt", "private", "", "public" ] }, - { symbol: [ "QAtomicInteger", "private", "", "public" ] }, - { symbol: [ "QAtomicPointer", "private", "", "public" ] }, - { symbol: [ "QAudio", "private", "", "public" ] }, - { symbol: [ "QAudioBuffer", "private", "", "public" ] }, - { symbol: [ "QAudioDecoder", "private", "", "public" ] }, - { symbol: [ "QAudioDecoderControl", "private", "", "public" ] }, - { symbol: [ "QAudioDeviceInfo", "private", "", "public" ] }, - { symbol: [ "QAudioEncoderSettings", "private", "", "public" ] }, - { symbol: [ "QAudioEncoderSettingsControl", "private", "", "public" ] }, - { symbol: [ "QAudioFormat", "private", "", "public" ] }, - { symbol: [ "QAudioInput", "private", "", "public" ] }, - { symbol: [ "QAudioInputSelectorControl", "private", "", "public" ] }, - { symbol: [ "QAudioOutput", "private", "", "public" ] }, - { symbol: [ "QAudioOutputSelectorControl", "private", "", "public" ] }, - { symbol: [ "QAudioProbe", "private", "", "public" ] }, - { symbol: [ "QAudioRecorder", "private", "", "public" ] }, - { symbol: [ "QAudioSystemFactoryInterface", "private", "", "public" ] }, - { symbol: [ "QAudioSystemPlugin", "private", "", "public" ] }, - { symbol: [ "QAuthenticator", "private", "", "public" ] }, - { symbol: [ "QAxAggregated", "private", "", "public" ] }, - { symbol: [ "QAxBase", "private", "", "public" ] }, - { symbol: [ "QAxBindable", "private", "", "public" ] }, - { symbol: [ "QAxFactory", "private", "", "public" ] }, - { symbol: [ "QAxObject", "private", "", "public" ] }, - { symbol: [ "QAxScript", "private", "", "public" ] }, - { symbol: [ "QAxScriptEngine", "private", "", "public" ] }, - { symbol: [ "QAxScriptManager", "private", "", "public" ] }, - { symbol: [ "QAxSelect", "private", "", "public" ] }, - { symbol: [ "QAxWidget", "private", "", "public" ] }, - { symbol: [ "QBBSystemLocaleData", "private", "", "public" ] }, - { symbol: [ "QBackingStore", "private", "", "public" ] }, - { symbol: [ "QBasicMutex", "private", "", "public" ] }, - { symbol: [ "QBasicTimer", "private", "", "public" ] }, - { symbol: [ "QBitArray", "private", "", "public" ] }, - { symbol: [ "QBitRef", "private", "", "public" ] }, - { symbol: [ "QBitmap", "private", "", "public" ] }, - { symbol: [ "QBluetoothAddress", "private", "", "public" ] }, - { symbol: [ "QBluetoothDeviceDiscoveryAgent", "private", "", "public" ] }, - { symbol: [ "QBluetoothDeviceInfo", "private", "", "public" ] }, - { symbol: [ "QBluetoothHostInfo", "private", "", "public" ] }, - { symbol: [ "QBluetoothLocalDevice", "private", "", "public" ] }, - { symbol: [ "QBluetoothServer", "private", "", "public" ] }, - { symbol: [ "QBluetoothServiceDiscoveryAgent", "private", "", "public" ] }, - { symbol: [ "QBluetoothServiceInfo", "private", "", "public" ] }, - { symbol: [ "QBluetoothSocket", "private", "", "public" ] }, - { symbol: [ "QBluetoothTransferManager", "private", "", "public" ] }, - { symbol: [ "QBluetoothTransferReply", "private", "", "public" ] }, - { symbol: [ "QBluetoothTransferRequest", "private", "", "public" ] }, - { symbol: [ "QBluetoothUuid", "private", "", "public" ] }, - { symbol: [ "QBoxLayout", "private", "", "public" ] }, - { symbol: [ "QBrush", "private", "", "public" ] }, - { symbol: [ "QBrushData", "private", "", "public" ] }, - { symbol: [ "QBuffer", "private", "", "public" ] }, - { symbol: [ "QButtonGroup", "private", "", "public" ] }, - { symbol: [ "QByteArray", "private", "", "public" ] }, - { symbol: [ "QByteArrayData", "private", "", "public" ] }, - { symbol: [ "QByteArrayDataPtr", "private", "", "public" ] }, - { symbol: [ "QByteArrayList", "private", "", "public" ] }, - { symbol: [ "QByteArrayListIterator", "private", "", "public" ] }, - { symbol: [ "QByteArrayMatcher", "private", "", "public" ] }, - { symbol: [ "QByteRef", "private", "", "public" ] }, - { symbol: [ "QCache", "private", "", "public" ] }, - { symbol: [ "QCalendarWidget", "private", "", "public" ] }, - { symbol: [ "QCamera", "private", "", "public" ] }, - { symbol: [ "QCameraCaptureBufferFormatControl", "private", "", "public" ] }, - { symbol: [ "QCameraCaptureDestinationControl", "private", "", "public" ] }, - { symbol: [ "QCameraControl", "private", "", "public" ] }, - { symbol: [ "QCameraExposure", "private", "", "public" ] }, - { symbol: [ "QCameraExposureControl", "private", "", "public" ] }, - { symbol: [ "QCameraFeedbackControl", "private", "", "public" ] }, - { symbol: [ "QCameraFlashControl", "private", "", "public" ] }, - { symbol: [ "QCameraFocus", "private", "", "public" ] }, - { symbol: [ "QCameraFocusControl", "private", "", "public" ] }, - { symbol: [ "QCameraFocusZone", "private", "", "public" ] }, - { symbol: [ "QCameraFocusZoneList", "private", "", "public" ] }, - { symbol: [ "QCameraImageCapture", "private", "", "public" ] }, - { symbol: [ "QCameraImageCaptureControl", "private", "", "public" ] }, - { symbol: [ "QCameraImageProcessing", "private", "", "public" ] }, - { symbol: [ "QCameraImageProcessingControl", "private", "", "public" ] }, - { symbol: [ "QCameraInfo", "private", "", "public" ] }, - { symbol: [ "QCameraInfoControl", "private", "", "public" ] }, - { symbol: [ "QCameraLocksControl", "private", "", "public" ] }, - { symbol: [ "QCameraViewfinder", "private", "", "public" ] }, - { symbol: [ "QCameraViewfinderSettingsControl", "private", "", "public" ] }, - { symbol: [ "QCameraZoomControl", "private", "", "public" ] }, - { symbol: [ "QChar", "private", "", "public" ] }, - { symbol: [ "QCharRef", "private", "", "public" ] }, - { symbol: [ "QCheckBox", "private", "", "public" ] }, - { symbol: [ "QChildEvent", "private", "", "public" ] }, - { symbol: [ "QClipboard", "private", "", "public" ] }, - { symbol: [ "QCloseEvent", "private", "", "public" ] }, - { symbol: [ "QCocoaNativeContext", "private", "", "public" ] }, - { symbol: [ "QCollator", "private", "", "public" ] }, - { symbol: [ "QCollatorSortKey", "private", "", "public" ] }, - { symbol: [ "QColor", "private", "", "public" ] }, - { symbol: [ "QColorDialog", "private", "", "public" ] }, - { symbol: [ "QColormap", "private", "", "public" ] }, - { symbol: [ "QColumnView", "private", "", "public" ] }, - { symbol: [ "QComboBox", "private", "", "public" ] }, - { symbol: [ "QCommandLineOption", "private", "", "public" ] }, - { symbol: [ "QCommandLineParser", "private", "", "public" ] }, - { symbol: [ "QCommandLinkButton", "private", "", "public" ] }, - { symbol: [ "QCommonStyle", "private", "", "public" ] }, - { symbol: [ "QCompass", "private", "", "public" ] }, - { symbol: [ "QCompassFilter", "private", "", "public" ] }, - { symbol: [ "QCompassReading", "private", "", "public" ] }, - { symbol: [ "QCompleter", "private", "", "public" ] }, - { symbol: [ "QConicalGradient", "private", "", "public" ] }, - { symbol: [ "QContextMenuEvent", "private", "", "public" ] }, - { symbol: [ "QContiguousCache", "private", "", "public" ] }, - { symbol: [ "QContiguousCacheData", "private", "", "public" ] }, - { symbol: [ "QContiguousCacheTypedData", "private", "", "public" ] }, - { symbol: [ "QCoreApplication", "private", "", "public" ] }, - { symbol: [ "QCryptographicHash", "private", "", "public" ] }, - { symbol: [ "QCursor", "private", "", "public" ] }, - { symbol: [ "QDBusAbstractAdaptor", "private", "", "public" ] }, - { symbol: [ "QDBusAbstractInterface", "private", "", "public" ] }, - { symbol: [ "QDBusAbstractInterfaceBase", "private", "", "public" ] }, - { symbol: [ "QDBusArgument", "private", "", "public" ] }, - { symbol: [ "QDBusConnection", "private", "", "public" ] }, - { symbol: [ "QDBusConnectionInterface", "private", "", "public" ] }, - { symbol: [ "QDBusContext", "private", "", "public" ] }, - { symbol: [ "QDBusError", "private", "", "public" ] }, - { symbol: [ "QDBusInterface", "private", "", "public" ] }, - { symbol: [ "QDBusMessage", "private", "", "public" ] }, - { symbol: [ "QDBusMetaType", "private", "", "public" ] }, - { symbol: [ "QDBusObjectPath", "private", "", "public" ] }, - { symbol: [ "QDBusPendingCall", "private", "", "public" ] }, - { symbol: [ "QDBusPendingCallWatcher", "private", "", "public" ] }, - { symbol: [ "QDBusPendingReply", "private", "", "public" ] }, - { symbol: [ "QDBusPendingReplyData", "private", "", "public" ] }, - { symbol: [ "QDBusReply", "private", "", "public" ] }, - { symbol: [ "QDBusServer", "private", "", "public" ] }, - { symbol: [ "QDBusServiceWatcher", "private", "", "public" ] }, - { symbol: [ "QDBusSignature", "private", "", "public" ] }, - { symbol: [ "QDBusUnixFileDescriptor", "private", "", "public" ] }, - { symbol: [ "QDBusVariant", "private", "", "public" ] }, - { symbol: [ "QDBusVirtualObject", "private", "", "public" ] }, - { symbol: [ "QDataStream", "private", "", "public" ] }, - { symbol: [ "QDataWidgetMapper", "private", "", "public" ] }, - { symbol: [ "QDate", "private", "", "public" ] }, - { symbol: [ "QDateEdit", "private", "", "public" ] }, - { symbol: [ "QDateTime", "private", "", "public" ] }, - { symbol: [ "QDateTimeEdit", "private", "", "public" ] }, - { symbol: [ "QDebug", "private", "", "public" ] }, - { symbol: [ "QDebugStateSaver", "private", "", "public" ] }, - { symbol: [ "QDeclarativeAttachedPropertiesFunc", "private", "", "public" ] }, - { symbol: [ "QDeclarativeComponent", "private", "", "public" ] }, - { symbol: [ "QDeclarativeContext", "private", "", "public" ] }, - { symbol: [ "QDeclarativeDebuggingEnabler", "private", "", "public" ] }, - { symbol: [ "QDeclarativeEngine", "private", "", "public" ] }, - { symbol: [ "QDeclarativeError", "private", "", "public" ] }, - { symbol: [ "QDeclarativeExpression", "private", "", "public" ] }, - { symbol: [ "QDeclarativeExtensionInterface", "private", "", "public" ] }, - { symbol: [ "QDeclarativeExtensionPlugin", "private", "", "public" ] }, - { symbol: [ "QDeclarativeImageProvider", "private", "", "public" ] }, - { symbol: [ "QDeclarativeInfo", "private", "", "public" ] }, - { symbol: [ "QDeclarativeItem", "private", "", "public" ] }, - { symbol: [ "QDeclarativeListProperty", "private", "", "public" ] }, - { symbol: [ "QDeclarativeListReference", "private", "", "public" ] }, - { symbol: [ "QDeclarativeNetworkAccessManagerFactory", "private", "", "public" ] }, - { symbol: [ "QDeclarativeParserStatus", "private", "", "public" ] }, - { symbol: [ "QDeclarativeProperties", "private", "", "public" ] }, - { symbol: [ "QDeclarativeProperty", "private", "", "public" ] }, - { symbol: [ "QDeclarativePropertyMap", "private", "", "public" ] }, - { symbol: [ "QDeclarativePropertyValueInterceptor", "private", "", "public" ] }, - { symbol: [ "QDeclarativePropertyValueSource", "private", "", "public" ] }, - { symbol: [ "QDeclarativeScriptString", "private", "", "public" ] }, - { symbol: [ "QDeclarativeTypeInfo", "private", "", "public" ] }, - { symbol: [ "QDeclarativeView", "private", "", "public" ] }, - { symbol: [ "QDeferredDeleteEvent", "private", "", "public" ] }, - { symbol: [ "QDesignerActionEditorInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerComponents", "private", "", "public" ] }, - { symbol: [ "QDesignerContainerExtension", "private", "", "public" ] }, - { symbol: [ "QDesignerCustomWidgetCollectionInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerCustomWidgetInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerDnDItemInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerDynamicPropertySheetExtension", "private", "", "public" ] }, - { symbol: [ "QDesignerExportWidget", "private", "", "public" ] }, - { symbol: [ "QDesignerExtraInfoExtension", "private", "", "public" ] }, - { symbol: [ "QDesignerFormEditorInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerFormEditorPluginInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerFormWindowCursorInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerFormWindowInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerFormWindowManagerInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerFormWindowToolInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerIntegration", "private", "", "public" ] }, - { symbol: [ "QDesignerIntegrationInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerLanguageExtension", "private", "", "public" ] }, - { symbol: [ "QDesignerLayoutDecorationExtension", "private", "", "public" ] }, - { symbol: [ "QDesignerMemberSheetExtension", "private", "", "public" ] }, - { symbol: [ "QDesignerMetaDataBaseInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerMetaDataBaseItemInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerNewFormWidgetInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerObjectInspectorInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerOptionsPageInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerPromotionInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerPropertyEditorInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerPropertySheetExtension", "private", "", "public" ] }, - { symbol: [ "QDesignerResourceBrowserInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerSettingsInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerTaskMenuExtension", "private", "", "public" ] }, - { symbol: [ "QDesignerWidgetBoxInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerWidgetDataBaseInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerWidgetDataBaseItemInterface", "private", "", "public" ] }, - { symbol: [ "QDesignerWidgetFactoryInterface", "private", "", "public" ] }, - { symbol: [ "QDesktopServices", "private", "", "public" ] }, - { symbol: [ "QDesktopWidget", "private", "", "public" ] }, - { symbol: [ "QDial", "private", "", "public" ] }, - { symbol: [ "QDialog", "private", "", "public" ] }, - { symbol: [ "QDialogButtonBox", "private", "", "public" ] }, - { symbol: [ "QDir", "private", "", "public" ] }, - { symbol: [ "QDirIterator", "private", "", "public" ] }, - { symbol: [ "QDirModel", "private", "", "public" ] }, - { symbol: [ "QDistanceFilter", "private", "", "public" ] }, - { symbol: [ "QDistanceReading", "private", "", "public" ] }, - { symbol: [ "QDistanceSensor", "private", "", "public" ] }, - { symbol: [ "QDnsDomainNameRecord", "private", "", "public" ] }, - { symbol: [ "QDnsHostAddressRecord", "private", "", "public" ] }, - { symbol: [ "QDnsLookup", "private", "", "public" ] }, - { symbol: [ "QDnsMailExchangeRecord", "private", "", "public" ] }, - { symbol: [ "QDnsServiceRecord", "private", "", "public" ] }, - { symbol: [ "QDnsTextRecord", "private", "", "public" ] }, - { symbol: [ "QDockWidget", "private", "", "public" ] }, - { symbol: [ "QDomAttr", "private", "", "public" ] }, - { symbol: [ "QDomCDATASection", "private", "", "public" ] }, - { symbol: [ "QDomCharacterData", "private", "", "public" ] }, - { symbol: [ "QDomComment", "private", "", "public" ] }, - { symbol: [ "QDomDocument", "private", "", "public" ] }, - { symbol: [ "QDomDocumentFragment", "private", "", "public" ] }, - { symbol: [ "QDomDocumentType", "private", "", "public" ] }, - { symbol: [ "QDomElement", "private", "", "public" ] }, - { symbol: [ "QDomEntity", "private", "", "public" ] }, - { symbol: [ "QDomEntityReference", "private", "", "public" ] }, - { symbol: [ "QDomImplementation", "private", "", "public" ] }, - { symbol: [ "QDomNamedNodeMap", "private", "", "public" ] }, - { symbol: [ "QDomNode", "private", "", "public" ] }, - { symbol: [ "QDomNodeList", "private", "", "public" ] }, - { symbol: [ "QDomNotation", "private", "", "public" ] }, - { symbol: [ "QDomProcessingInstruction", "private", "", "public" ] }, - { symbol: [ "QDomText", "private", "", "public" ] }, - { symbol: [ "QDoubleSpinBox", "private", "", "public" ] }, - { symbol: [ "QDoubleValidator", "private", "", "public" ] }, - { symbol: [ "QDrag", "private", "", "public" ] }, - { symbol: [ "QDragEnterEvent", "private", "", "public" ] }, - { symbol: [ "QDragLeaveEvent", "private", "", "public" ] }, - { symbol: [ "QDragMoveEvent", "private", "", "public" ] }, - { symbol: [ "QDropEvent", "private", "", "public" ] }, - { symbol: [ "QDynamicPropertyChangeEvent", "private", "", "public" ] }, - { symbol: [ "QEGLNativeContext", "private", "", "public" ] }, - { symbol: [ "QEasingCurve", "private", "", "public" ] }, - { symbol: [ "QEglFSFunctions", "private", "", "public" ] }, - { symbol: [ "QElapsedTimer", "private", "", "public" ] }, - { symbol: [ "QEnableSharedFromThis", "private", "", "public" ] }, - { symbol: [ "QEnterEvent", "private", "", "public" ] }, - { symbol: [ "QErrorMessage", "private", "", "public" ] }, - { symbol: [ "QEvent", "private", "", "public" ] }, - { symbol: [ "QEventLoop", "private", "", "public" ] }, - { symbol: [ "QEventLoopLocker", "private", "", "public" ] }, - { symbol: [ "QEventSizeOfChecker", "private", "", "public" ] }, - { symbol: [ "QEventTransition", "private", "", "public" ] }, - { symbol: [ "QException", "private", "", "public" ] }, - { symbol: [ "QExplicitlySharedDataPointer", "private", "", "public" ] }, - { symbol: [ "QExposeEvent", "private", "", "public" ] }, - { symbol: [ "QExtensionFactory", "private", "", "public" ] }, - { symbol: [ "QExtensionManager", "private", "", "public" ] }, - { symbol: [ "QFactoryInterface", "private", "", "public" ] }, - { symbol: [ "QFile", "private", "", "public" ] }, - { symbol: [ "QFileDevice", "private", "", "public" ] }, - { symbol: [ "QFileDialog", "private", "", "public" ] }, - { symbol: [ "QFileIconProvider", "private", "", "public" ] }, - { symbol: [ "QFileInfo", "private", "", "public" ] }, - { symbol: [ "QFileInfoList", "private", "", "public" ] }, - { symbol: [ "QFileOpenEvent", "private", "", "public" ] }, - { symbol: [ "QFileSelector", "private", "", "public" ] }, - { symbol: [ "QFileSystemModel", "private", "", "public" ] }, - { symbol: [ "QFileSystemWatcher", "private", "", "public" ] }, - { symbol: [ "QFinalState", "private", "", "public" ] }, - { symbol: [ "QFlag", "private", "", "public" ] }, - { symbol: [ "QFlags", "private", "", "public" ] }, - { symbol: [ "QFocusEvent", "private", "", "public" ] }, - { symbol: [ "QFocusFrame", "private", "", "public" ] }, - { symbol: [ "QFont", "private", "", "public" ] }, - { symbol: [ "QFontComboBox", "private", "", "public" ] }, - { symbol: [ "QFontDatabase", "private", "", "public" ] }, - { symbol: [ "QFontDialog", "private", "", "public" ] }, - { symbol: [ "QFontInfo", "private", "", "public" ] }, - { symbol: [ "QFontMetrics", "private", "", "public" ] }, - { symbol: [ "QFontMetricsF", "private", "", "public" ] }, - { symbol: [ "QForeachContainer", "private", "", "public" ] }, - { symbol: [ "QFormBuilder", "private", "", "public" ] }, - { symbol: [ "QFormLayout", "private", "", "public" ] }, - { symbol: [ "QFrame", "private", "", "public" ] }, - { symbol: [ "QFunctionPointer", "private", "", "public" ] }, - { symbol: [ "QFuture", "private", "", "public" ] }, - { symbol: [ "QFutureInterface", "private", "", "public" ] }, - { symbol: [ "QFutureInterfaceBase", "private", "", "public" ] }, - { symbol: [ "QFutureIterator", "private", "", "public" ] }, - { symbol: [ "QFutureSynchronizer", "private", "", "public" ] }, - { symbol: [ "QFutureWatcher", "private", "", "public" ] }, - { symbol: [ "QFutureWatcherBase", "private", "", "public" ] }, - { symbol: [ "QGL", "private", "", "public" ] }, - { symbol: [ "QGLBuffer", "private", "", "public" ] }, - { symbol: [ "QGLColormap", "private", "", "public" ] }, - { symbol: [ "QGLContext", "private", "", "public" ] }, - { symbol: [ "QGLFormat", "private", "", "public" ] }, - { symbol: [ "QGLFramebufferObject", "private", "", "public" ] }, - { symbol: [ "QGLFramebufferObjectFormat", "private", "", "public" ] }, - { symbol: [ "QGLFunctions", "private", "", "public" ] }, - { symbol: [ "QGLFunctionsPrivate", "private", "", "public" ] }, - { symbol: [ "QGLPixelBuffer", "private", "", "public" ] }, - { symbol: [ "QGLShader", "private", "", "public" ] }, - { symbol: [ "QGLShaderProgram", "private", "", "public" ] }, - { symbol: [ "QGLWidget", "private", "", "public" ] }, - { symbol: [ "QGLXNativeContext", "private", "", "public" ] }, - { symbol: [ "QGenericArgument", "private", "", "public" ] }, - { symbol: [ "QGenericMatrix", "private", "", "public" ] }, - { symbol: [ "QGenericPlugin", "private", "", "public" ] }, - { symbol: [ "QGenericPluginFactory", "private", "", "public" ] }, - { symbol: [ "QGenericReturnArgument", "private", "", "public" ] }, - { symbol: [ "QGeoAddress", "private", "", "public" ] }, - { symbol: [ "QGeoAreaMonitorInfo", "private", "", "public" ] }, - { symbol: [ "QGeoAreaMonitorSource", "private", "", "public" ] }, - { symbol: [ "QGeoCircle", "private", "", "public" ] }, - { symbol: [ "QGeoCodeReply", "private", "", "public" ] }, - { symbol: [ "QGeoCodingManager", "private", "", "public" ] }, - { symbol: [ "QGeoCodingManagerEngine", "private", "", "public" ] }, - { symbol: [ "QGeoCoordinate", "private", "", "public" ] }, - { symbol: [ "QGeoLocation", "private", "", "public" ] }, - { symbol: [ "QGeoManeuver", "private", "", "public" ] }, - { symbol: [ "QGeoPositionInfo", "private", "", "public" ] }, - { symbol: [ "QGeoPositionInfoSource", "private", "", "public" ] }, - { symbol: [ "QGeoPositionInfoSourceFactory", "private", "", "public" ] }, - { symbol: [ "QGeoRectangle", "private", "", "public" ] }, - { symbol: [ "QGeoRoute", "private", "", "public" ] }, - { symbol: [ "QGeoRouteReply", "private", "", "public" ] }, - { symbol: [ "QGeoRouteRequest", "private", "", "public" ] }, - { symbol: [ "QGeoRouteSegment", "private", "", "public" ] }, - { symbol: [ "QGeoRoutingManager", "private", "", "public" ] }, - { symbol: [ "QGeoRoutingManagerEngine", "private", "", "public" ] }, - { symbol: [ "QGeoSatelliteInfo", "private", "", "public" ] }, - { symbol: [ "QGeoSatelliteInfoSource", "private", "", "public" ] }, - { symbol: [ "QGeoServiceProvider", "private", "", "public" ] }, - { symbol: [ "QGeoServiceProviderFactory", "private", "", "public" ] }, - { symbol: [ "QGeoShape", "private", "", "public" ] }, - { symbol: [ "QGesture", "private", "", "public" ] }, - { symbol: [ "QGestureEvent", "private", "", "public" ] }, - { symbol: [ "QGestureRecognizer", "private", "", "public" ] }, - { symbol: [ "QGlobalStatic", "private", "", "public" ] }, - { symbol: [ "QGlyphRun", "private", "", "public" ] }, - { symbol: [ "QGradient", "private", "", "public" ] }, - { symbol: [ "QGradientStop", "private", "", "public" ] }, - { symbol: [ "QGradientStops", "private", "", "public" ] }, - { symbol: [ "QGraphicsAnchor", "private", "", "public" ] }, - { symbol: [ "QGraphicsAnchorLayout", "private", "", "public" ] }, - { symbol: [ "QGraphicsBlurEffect", "private", "", "public" ] }, - { symbol: [ "QGraphicsColorizeEffect", "private", "", "public" ] }, - { symbol: [ "QGraphicsDropShadowEffect", "private", "", "public" ] }, - { symbol: [ "QGraphicsEffect", "private", "", "public" ] }, - { symbol: [ "QGraphicsEllipseItem", "private", "", "public" ] }, - { symbol: [ "QGraphicsGridLayout", "private", "", "public" ] }, - { symbol: [ "QGraphicsItem", "private", "", "public" ] }, - { symbol: [ "QGraphicsItemAnimation", "private", "", "public" ] }, - { symbol: [ "QGraphicsItemGroup", "private", "", "public" ] }, - { symbol: [ "QGraphicsLayout", "private", "", "public" ] }, - { symbol: [ "QGraphicsLayoutItem", "private", "", "public" ] }, - { symbol: [ "QGraphicsLineItem", "private", "", "public" ] }, - { symbol: [ "QGraphicsLinearLayout", "private", "", "public" ] }, - { symbol: [ "QGraphicsObject", "private", "", "public" ] }, - { symbol: [ "QGraphicsOpacityEffect", "private", "", "public" ] }, - { symbol: [ "QGraphicsPathItem", "private", "", "public" ] }, - { symbol: [ "QGraphicsPixmapItem", "private", "", "public" ] }, - { symbol: [ "QGraphicsPolygonItem", "private", "", "public" ] }, - { symbol: [ "QGraphicsProxyWidget", "private", "", "public" ] }, - { symbol: [ "QGraphicsRectItem", "private", "", "public" ] }, - { symbol: [ "QGraphicsRotation", "private", "", "public" ] }, - { symbol: [ "QGraphicsScale", "private", "", "public" ] }, - { symbol: [ "QGraphicsScene", "private", "", "public" ] }, - { symbol: [ "QGraphicsSceneContextMenuEvent", "private", "", "public" ] }, - { symbol: [ "QGraphicsSceneDragDropEvent", "private", "", "public" ] }, - { symbol: [ "QGraphicsSceneEvent", "private", "", "public" ] }, - { symbol: [ "QGraphicsSceneHelpEvent", "private", "", "public" ] }, - { symbol: [ "QGraphicsSceneHoverEvent", "private", "", "public" ] }, - { symbol: [ "QGraphicsSceneMouseEvent", "private", "", "public" ] }, - { symbol: [ "QGraphicsSceneMoveEvent", "private", "", "public" ] }, - { symbol: [ "QGraphicsSceneResizeEvent", "private", "", "public" ] }, - { symbol: [ "QGraphicsSceneWheelEvent", "private", "", "public" ] }, - { symbol: [ "QGraphicsSimpleTextItem", "private", "", "public" ] }, - { symbol: [ "QGraphicsSvgItem", "private", "", "public" ] }, - { symbol: [ "QGraphicsTextItem", "private", "", "public" ] }, - { symbol: [ "QGraphicsTransform", "private", "", "public" ] }, - { symbol: [ "QGraphicsVideoItem", "private", "", "public" ] }, - { symbol: [ "QGraphicsView", "private", "", "public" ] }, - { symbol: [ "QGraphicsWebView", "private", "", "public" ] }, - { symbol: [ "QGraphicsWidget", "private", "", "public" ] }, - { symbol: [ "QGridLayout", "private", "", "public" ] }, - { symbol: [ "QGroupBox", "private", "", "public" ] }, - { symbol: [ "QGuiApplication", "private", "", "public" ] }, - { symbol: [ "QGyroscope", "private", "", "public" ] }, - { symbol: [ "QGyroscopeFilter", "private", "", "public" ] }, - { symbol: [ "QGyroscopeReading", "private", "", "public" ] }, - { symbol: [ "QHBoxLayout", "private", "", "public" ] }, - { symbol: [ "QHash", "private", "", "public" ] }, - { symbol: [ "QHashData", "private", "", "public" ] }, - { symbol: [ "QHashDummyValue", "private", "", "public" ] }, - { symbol: [ "QHashIterator", "private", "", "public" ] }, - { symbol: [ "QHashNode", "private", "", "public" ] }, - { symbol: [ "QHeaderView", "private", "", "public" ] }, - { symbol: [ "QHelpContentItem", "private", "", "public" ] }, - { symbol: [ "QHelpContentModel", "private", "", "public" ] }, - { symbol: [ "QHelpContentWidget", "private", "", "public" ] }, - { symbol: [ "QHelpEngine", "private", "", "public" ] }, - { symbol: [ "QHelpEngineCore", "private", "", "public" ] }, - { symbol: [ "QHelpEvent", "private", "", "public" ] }, - { symbol: [ "QHelpGlobal", "private", "", "public" ] }, - { symbol: [ "QHelpIndexModel", "private", "", "public" ] }, - { symbol: [ "QHelpIndexWidget", "private", "", "public" ] }, - { symbol: [ "QHelpSearchEngine", "private", "", "public" ] }, - { symbol: [ "QHelpSearchQuery", "private", "", "public" ] }, - { symbol: [ "QHelpSearchQueryWidget", "private", "", "public" ] }, - { symbol: [ "QHelpSearchResultWidget", "private", "", "public" ] }, - { symbol: [ "QHideEvent", "private", "", "public" ] }, - { symbol: [ "QHistoryState", "private", "", "public" ] }, - { symbol: [ "QHolsterFilter", "private", "", "public" ] }, - { symbol: [ "QHolsterReading", "private", "", "public" ] }, - { symbol: [ "QHolsterSensor", "private", "", "public" ] }, - { symbol: [ "QHostAddress", "private", "", "public" ] }, - { symbol: [ "QHostInfo", "private", "", "public" ] }, - { symbol: [ "QHoverEvent", "private", "", "public" ] }, - { symbol: [ "QHttpMultiPart", "private", "", "public" ] }, - { symbol: [ "QHttpPart", "private", "", "public" ] }, - { symbol: [ "QIODevice", "private", "", "public" ] }, - { symbol: [ "QIPv6Address", "private", "", "public" ] }, - { symbol: [ "QIRProximityFilter", "private", "", "public" ] }, - { symbol: [ "QIRProximityReading", "private", "", "public" ] }, - { symbol: [ "QIRProximitySensor", "private", "", "public" ] }, - { symbol: [ "QIcon", "private", "", "public" ] }, - { symbol: [ "QIconDragEvent", "private", "", "public" ] }, - { symbol: [ "QIconEngine", "private", "", "public" ] }, - { symbol: [ "QIconEnginePlugin", "private", "", "public" ] }, - { symbol: [ "QIconEngineV2", "private", "", "public" ] }, - { symbol: [ "QIdentityProxyModel", "private", "", "public" ] }, - { symbol: [ "QImage", "private", "", "public" ] }, - { symbol: [ "QImageCleanupFunction", "private", "", "public" ] }, - { symbol: [ "QImageEncoderControl", "private", "", "public" ] }, - { symbol: [ "QImageEncoderSettings", "private", "", "public" ] }, - { symbol: [ "QImageIOHandler", "private", "", "public" ] }, - { symbol: [ "QImageIOPlugin", "private", "", "public" ] }, - { symbol: [ "QImageReader", "private", "", "public" ] }, - { symbol: [ "QImageTextKeyLang", "private", "", "public" ] }, - { symbol: [ "QImageWriter", "private", "", "public" ] }, - { symbol: [ "QIncompatibleFlag", "private", "", "public" ] }, - { symbol: [ "QInputDialog", "private", "", "public" ] }, - { symbol: [ "QInputEvent", "private", "", "public" ] }, - { symbol: [ "QInputMethod", "private", "", "public" ] }, - { symbol: [ "QInputMethodEvent", "private", "", "public" ] }, - { symbol: [ "QInputMethodQueryEvent", "private", "", "public" ] }, - { symbol: [ "QIntValidator", "private", "", "public" ] }, - { symbol: [ "QIntegerForSize", "private", "", "public" ] }, - { symbol: [ "QInternal", "private", "", "public" ] }, - { symbol: [ "QItemDelegate", "private", "", "public" ] }, - { symbol: [ "QItemEditorCreator", "private", "", "public" ] }, - { symbol: [ "QItemEditorCreatorBase", "private", "", "public" ] }, - { symbol: [ "QItemEditorFactory", "private", "", "public" ] }, - { symbol: [ "QItemSelection", "private", "", "public" ] }, - { symbol: [ "QItemSelectionModel", "private", "", "public" ] }, - { symbol: [ "QItemSelectionRange", "private", "", "public" ] }, - { symbol: [ "QJSEngine", "private", "", "public" ] }, - { symbol: [ "QJSValue", "private", "", "public" ] }, - { symbol: [ "QJSValueIterator", "private", "", "public" ] }, - { symbol: [ "QJSValueList", "private", "", "public" ] }, - { symbol: [ "QJsonArray", "private", "", "public" ] }, - { symbol: [ "QJsonDocument", "private", "", "public" ] }, - { symbol: [ "QJsonObject", "private", "", "public" ] }, - { symbol: [ "QJsonParseError", "private", "", "public" ] }, - { symbol: [ "QJsonValue", "private", "", "public" ] }, - { symbol: [ "QJsonValuePtr", "private", "", "public" ] }, - { symbol: [ "QJsonValueRef", "private", "", "public" ] }, - { symbol: [ "QJsonValueRefPtr", "private", "", "public" ] }, - { symbol: [ "QKeyEvent", "private", "", "public" ] }, - { symbol: [ "QKeyEventTransition", "private", "", "public" ] }, - { symbol: [ "QKeySequence", "private", "", "public" ] }, - { symbol: [ "QKeySequenceEdit", "private", "", "public" ] }, - { symbol: [ "QLCDNumber", "private", "", "public" ] }, - { symbol: [ "QLabel", "private", "", "public" ] }, - { symbol: [ "QLatin1Char", "private", "", "public" ] }, - { symbol: [ "QLatin1Literal", "private", "", "public" ] }, - { symbol: [ "QLatin1String", "private", "", "public" ] }, - { symbol: [ "QLayout", "private", "", "public" ] }, - { symbol: [ "QLayoutItem", "private", "", "public" ] }, - { symbol: [ "QLibrary", "private", "", "public" ] }, - { symbol: [ "QLibraryInfo", "private", "", "public" ] }, - { symbol: [ "QLightFilter", "private", "", "public" ] }, - { symbol: [ "QLightReading", "private", "", "public" ] }, - { symbol: [ "QLightSensor", "private", "", "public" ] }, - { symbol: [ "QLine", "private", "", "public" ] }, - { symbol: [ "QLineEdit", "private", "", "public" ] }, - { symbol: [ "QLineF", "private", "", "public" ] }, - { symbol: [ "QLinearGradient", "private", "", "public" ] }, - { symbol: [ "QLinkedList", "private", "", "public" ] }, - { symbol: [ "QLinkedListData", "private", "", "public" ] }, - { symbol: [ "QLinkedListIterator", "private", "", "public" ] }, - { symbol: [ "QLinkedListNode", "private", "", "public" ] }, - { symbol: [ "QList", "private", "", "public" ] }, - { symbol: [ "QListData", "private", "", "public" ] }, - { symbol: [ "QListIterator", "private", "", "public" ] }, - { symbol: [ "QListSpecialMethods", "private", "", "public" ] }, - { symbol: [ "QListView", "private", "", "public" ] }, - { symbol: [ "QListWidget", "private", "", "public" ] }, - { symbol: [ "QListWidgetItem", "private", "", "public" ] }, - { symbol: [ "QLocalServer", "private", "", "public" ] }, - { symbol: [ "QLocalSocket", "private", "", "public" ] }, - { symbol: [ "QLocale", "private", "", "public" ] }, - { symbol: [ "QLocation", "private", "", "public" ] }, - { symbol: [ "QLockFile", "private", "", "public" ] }, - { symbol: [ "QLockFile", "private", "", "public" ] }, - { symbol: [ "QLoggingCategory", "private", "", "public" ] }, - { symbol: [ "QLowEnergyCharacteristic", "private", "", "public" ] }, - { symbol: [ "QLowEnergyController", "private", "", "public" ] }, - { symbol: [ "QLowEnergyDescriptor", "private", "", "public" ] }, - { symbol: [ "QLowEnergyHandle", "private", "", "public" ] }, - { symbol: [ "QLowEnergyService", "private", "", "public" ] }, - { symbol: [ "QMacCocoaViewContainer", "private", "", "public" ] }, - { symbol: [ "QMacNativeWidget", "private", "", "public" ] }, - { symbol: [ "QMagnetometer", "private", "", "public" ] }, - { symbol: [ "QMagnetometerFilter", "private", "", "public" ] }, - { symbol: [ "QMagnetometerReading", "private", "", "public" ] }, - { symbol: [ "QMainWindow", "private", "", "public" ] }, - { symbol: [ "QMap", "private", "", "public" ] }, - { symbol: [ "QMapData", "private", "", "public" ] }, - { symbol: [ "QMapDataBase", "private", "", "public" ] }, - { symbol: [ "QMapIterator", "private", "", "public" ] }, - { symbol: [ "QMapNode", "private", "", "public" ] }, - { symbol: [ "QMapNodeBase", "private", "", "public" ] }, - { symbol: [ "QMargins", "private", "", "public" ] }, - { symbol: [ "QMarginsF", "private", "", "public" ] }, - { symbol: [ "QMaskGenerator", "private", "", "public" ] }, - { symbol: [ "QMatrix", "private", "", "public" ] }, - { symbol: [ "QMatrix2x2", "private", "", "public" ] }, - { symbol: [ "QMatrix2x3", "private", "", "public" ] }, - { symbol: [ "QMatrix2x4", "private", "", "public" ] }, - { symbol: [ "QMatrix3x2", "private", "", "public" ] }, - { symbol: [ "QMatrix3x3", "private", "", "public" ] }, - { symbol: [ "QMatrix3x4", "private", "", "public" ] }, - { symbol: [ "QMatrix4x2", "private", "", "public" ] }, - { symbol: [ "QMatrix4x3", "private", "", "public" ] }, - { symbol: [ "QMatrix4x4", "private", "", "public" ] }, - { symbol: [ "QMdiArea", "private", "", "public" ] }, - { symbol: [ "QMdiSubWindow", "private", "", "public" ] }, - { symbol: [ "QMediaAudioProbeControl", "private", "", "public" ] }, - { symbol: [ "QMediaAvailabilityControl", "private", "", "public" ] }, - { symbol: [ "QMediaBindableInterface", "private", "", "public" ] }, - { symbol: [ "QMediaContainerControl", "private", "", "public" ] }, - { symbol: [ "QMediaContent", "private", "", "public" ] }, - { symbol: [ "QMediaControl", "private", "", "public" ] }, - { symbol: [ "QMediaGaplessPlaybackControl", "private", "", "public" ] }, - { symbol: [ "QMediaMetaData", "private", "", "public" ] }, - { symbol: [ "QMediaNetworkAccessControl", "private", "", "public" ] }, - { symbol: [ "QMediaObject", "private", "", "public" ] }, - { symbol: [ "QMediaPlayer", "private", "", "public" ] }, - { symbol: [ "QMediaPlayerControl", "private", "", "public" ] }, - { symbol: [ "QMediaPlaylist", "private", "", "public" ] }, - { symbol: [ "QMediaRecorder", "private", "", "public" ] }, - { symbol: [ "QMediaRecorderControl", "private", "", "public" ] }, - { symbol: [ "QMediaResource", "private", "", "public" ] }, - { symbol: [ "QMediaResourceList", "private", "", "public" ] }, - { symbol: [ "QMediaService", "private", "", "public" ] }, - { symbol: [ "QMediaServiceCameraInfoInterface", "private", "", "public" ] }, - { symbol: [ "QMediaServiceDefaultDeviceInterface", "private", "", "public" ] }, - { symbol: [ "QMediaServiceFeaturesInterface", "private", "", "public" ] }, - { symbol: [ "QMediaServiceProviderFactoryInterface", "private", "", "public" ] }, - { symbol: [ "QMediaServiceProviderHint", "private", "", "public" ] }, - { symbol: [ "QMediaServiceProviderPlugin", "private", "", "public" ] }, - { symbol: [ "QMediaServiceSupportedDevicesInterface", "private", "", "public" ] }, - { symbol: [ "QMediaServiceSupportedFormatsInterface", "private", "", "public" ] }, - { symbol: [ "QMediaStreamsControl", "private", "", "public" ] }, - { symbol: [ "QMediaTimeInterval", "private", "", "public" ] }, - { symbol: [ "QMediaTimeRange", "private", "", "public" ] }, - { symbol: [ "QMediaVideoProbeControl", "private", "", "public" ] }, - { symbol: [ "QMenu", "private", "", "public" ] }, - { symbol: [ "QMenuBar", "private", "", "public" ] }, - { symbol: [ "QMessageAuthenticationCode", "private", "", "public" ] }, - { symbol: [ "QMessageBox", "private", "", "public" ] }, - { symbol: [ "QMessageLogContext", "private", "", "public" ] }, - { symbol: [ "QMessageLogger", "private", "", "public" ] }, - { symbol: [ "QMetaClassInfo", "private", "", "public" ] }, - { symbol: [ "QMetaDataReaderControl", "private", "", "public" ] }, - { symbol: [ "QMetaDataWriterControl", "private", "", "public" ] }, - { symbol: [ "QMetaEnum", "private", "", "public" ] }, - { symbol: [ "QMetaMethod", "private", "", "public" ] }, - { symbol: [ "QMetaObject", "private", "", "public" ] }, - { symbol: [ "QMetaProperty", "private", "", "public" ] }, - { symbol: [ "QMetaType", "private", "", "public" ] }, - { symbol: [ "QMetaTypeId", "private", "", "public" ] }, - { symbol: [ "QMetaTypeId2", "private", "", "public" ] }, - { symbol: [ "QMetaTypeIdQObject", "private", "", "public" ] }, - { symbol: [ "QMimeData", "private", "", "public" ] }, - { symbol: [ "QMimeDatabase", "private", "", "public" ] }, - { symbol: [ "QMimeType", "private", "", "public" ] }, - { symbol: [ "QModelIndex", "private", "", "public" ] }, - { symbol: [ "QModelIndexList", "private", "", "public" ] }, - { symbol: [ "QMouseEvent", "private", "", "public" ] }, - { symbol: [ "QMouseEventTransition", "private", "", "public" ] }, - { symbol: [ "QMoveEvent", "private", "", "public" ] }, - { symbol: [ "QMovie", "private", "", "public" ] }, - { symbol: [ "QMultiHash", "private", "", "public" ] }, - { symbol: [ "QMultiMap", "private", "", "public" ] }, - { symbol: [ "QMultimedia", "private", "", "public" ] }, - { symbol: [ "QMutableByteArrayListIterator", "private", "", "public" ] }, - { symbol: [ "QMutableFutureIterator", "private", "", "public" ] }, - { symbol: [ "QMutableHashIterator", "private", "", "public" ] }, - { symbol: [ "QMutableLinkedListIterator", "private", "", "public" ] }, - { symbol: [ "QMutableListIterator", "private", "", "public" ] }, - { symbol: [ "QMutableMapIterator", "private", "", "public" ] }, - { symbol: [ "QMutableSetIterator", "private", "", "public" ] }, - { symbol: [ "QMutableStringListIterator", "private", "", "public" ] }, - { symbol: [ "QMutableVectorIterator", "private", "", "public" ] }, - { symbol: [ "QMutex", "private", "", "public" ] }, - { symbol: [ "QMutexLocker", "private", "", "public" ] }, - { symbol: [ "QNativeGestureEvent", "private", "", "public" ] }, - { symbol: [ "QNdefFilter", "private", "", "public" ] }, - { symbol: [ "QNdefMessage", "private", "", "public" ] }, - { symbol: [ "QNdefNfcIconRecord", "private", "", "public" ] }, - { symbol: [ "QNdefNfcSmartPosterRecord", "private", "", "public" ] }, - { symbol: [ "QNdefNfcTextRecord", "private", "", "public" ] }, - { symbol: [ "QNdefNfcUriRecord", "private", "", "public" ] }, - { symbol: [ "QNdefRecord", "private", "", "public" ] }, - { symbol: [ "QNearFieldManager", "private", "", "public" ] }, - { symbol: [ "QNearFieldShareManager", "private", "", "public" ] }, - { symbol: [ "QNearFieldShareTarget", "private", "", "public" ] }, - { symbol: [ "QNearFieldTarget", "private", "", "public" ] }, - { symbol: [ "QNetworkAccessManager", "private", "", "public" ] }, - { symbol: [ "QNetworkAddressEntry", "private", "", "public" ] }, - { symbol: [ "QNetworkCacheMetaData", "private", "", "public" ] }, - { symbol: [ "QNetworkConfiguration", "private", "", "public" ] }, - { symbol: [ "QNetworkConfigurationManager", "private", "", "public" ] }, - { symbol: [ "QNetworkCookie", "private", "", "public" ] }, - { symbol: [ "QNetworkCookieJar", "private", "", "public" ] }, - { symbol: [ "QNetworkDiskCache", "private", "", "public" ] }, - { symbol: [ "QNetworkInterface", "private", "", "public" ] }, - { symbol: [ "QNetworkProxy", "private", "", "public" ] }, - { symbol: [ "QNetworkProxyFactory", "private", "", "public" ] }, - { symbol: [ "QNetworkProxyQuery", "private", "", "public" ] }, - { symbol: [ "QNetworkReply", "private", "", "public" ] }, - { symbol: [ "QNetworkRequest", "private", "", "public" ] }, - { symbol: [ "QNetworkSession", "private", "", "public" ] }, - { symbol: [ "QNmeaPositionInfoSource", "private", "", "public" ] }, - { symbol: [ "QNoDebug", "private", "", "public" ] }, - { symbol: [ "QObject", "private", "", "public" ] }, - { symbol: [ "QObjectCleanupHandler", "private", "", "public" ] }, - { symbol: [ "QObjectData", "private", "", "public" ] }, - { symbol: [ "QObjectList", "private", "", "public" ] }, - { symbol: [ "QObjectUserData", "private", "", "public" ] }, - { symbol: [ "QOffscreenSurface", "private", "", "public" ] }, - { symbol: [ "QOpenGLBuffer", "private", "", "public" ] }, - { symbol: [ "QOpenGLContext", "private", "", "public" ] }, - { symbol: [ "QOpenGLContextGroup", "private", "", "public" ] }, - { symbol: [ "QOpenGLDebugLogger", "private", "", "public" ] }, - { symbol: [ "QOpenGLDebugMessage", "private", "", "public" ] }, - { symbol: [ "QOpenGLExtensions", "private", "", "public" ] }, - { symbol: [ "QOpenGLFramebufferObject", "private", "", "public" ] }, - { symbol: [ "QOpenGLFramebufferObjectFormat", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctionsPrivate", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_1_0", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_1_1", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_1_2", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_1_3", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_1_4", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_1_5", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_2_0", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_2_1", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_3_0", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_3_1", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_3_2_Compatibility", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_3_2_Core", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_3_3_Compatibility", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_3_3_Core", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_4_0_Compatibility", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_4_0_Core", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_4_1_Compatibility", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_4_1_Core", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_4_2_Compatibility", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_4_2_Core", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_4_3_Compatibility", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_4_3_Core", "private", "", "public" ] }, - { symbol: [ "QOpenGLFunctions_ES2", "private", "", "public" ] }, - { symbol: [ "QOpenGLPaintDevice", "private", "", "public" ] }, - { symbol: [ "QOpenGLPixelTransferOptions", "private", "", "public" ] }, - { symbol: [ "QOpenGLShader", "private", "", "public" ] }, - { symbol: [ "QOpenGLShaderProgram", "private", "", "public" ] }, - { symbol: [ "QOpenGLTexture", "private", "", "public" ] }, - { symbol: [ "QOpenGLTimeMonitor", "private", "", "public" ] }, - { symbol: [ "QOpenGLTimerQuery", "private", "", "public" ] }, - { symbol: [ "QOpenGLVersionFunctions", "private", "", "public" ] }, - { symbol: [ "QOpenGLVersionProfile", "private", "", "public" ] }, - { symbol: [ "QOpenGLVertexArrayObject", "private", "", "public" ] }, - { symbol: [ "QOpenGLWidget", "private", "", "public" ] }, - { symbol: [ "QOpenGLWindow", "private", "", "public" ] }, - { symbol: [ "QOrientationFilter", "private", "", "public" ] }, - { symbol: [ "QOrientationReading", "private", "", "public" ] }, - { symbol: [ "QOrientationSensor", "private", "", "public" ] }, - { symbol: [ "QPageLayout", "private", "", "public" ] }, - { symbol: [ "QPageSetupDialog", "private", "", "public" ] }, - { symbol: [ "QPageSize", "private", "", "public" ] }, - { symbol: [ "QPagedPaintDevice", "private", "", "public" ] }, - { symbol: [ "QPaintDevice", "private", "", "public" ] }, - { symbol: [ "QPaintDeviceWindow", "private", "", "public" ] }, - { symbol: [ "QPaintEngine", "private", "", "public" ] }, - { symbol: [ "QPaintEngineState", "private", "", "public" ] }, - { symbol: [ "QPaintEvent", "private", "", "public" ] }, - { symbol: [ "QPainter", "private", "", "public" ] }, - { symbol: [ "QPainterPath", "private", "", "public" ] }, - { symbol: [ "QPainterPathStroker", "private", "", "public" ] }, - { symbol: [ "QPair", "private", "", "public" ] }, - { symbol: [ "QPalette", "private", "", "public" ] }, - { symbol: [ "QPanGesture", "private", "", "public" ] }, - { symbol: [ "QParallelAnimationGroup", "private", "", "public" ] }, - { symbol: [ "QPauseAnimation", "private", "", "public" ] }, - { symbol: [ "QPdfWriter", "private", "", "public" ] }, - { symbol: [ "QPen", "private", "", "public" ] }, - { symbol: [ "QPersistentModelIndex", "private", "", "public" ] }, - { symbol: [ "QPicture", "private", "", "public" ] }, - { symbol: [ "QPictureFormatPlugin", "private", "", "public" ] }, - { symbol: [ "QPictureIO", "private", "", "public" ] }, - { symbol: [ "QPinchGesture", "private", "", "public" ] }, - { symbol: [ "QPixelFormat", "private", "", "public" ] }, - { symbol: [ "QPixmap", "private", "", "public" ] }, - { symbol: [ "QPixmapCache", "private", "", "public" ] }, - { symbol: [ "QPlace", "private", "", "public" ] }, - { symbol: [ "QPlaceAttribute", "private", "", "public" ] }, - { symbol: [ "QPlaceCategory", "private", "", "public" ] }, - { symbol: [ "QPlaceContactDetail", "private", "", "public" ] }, - { symbol: [ "QPlaceContent", "private", "", "public" ] }, - { symbol: [ "QPlaceContentReply", "private", "", "public" ] }, - { symbol: [ "QPlaceContentRequest", "private", "", "public" ] }, - { symbol: [ "QPlaceDetailsReply", "private", "", "public" ] }, - { symbol: [ "QPlaceEditorial", "private", "", "public" ] }, - { symbol: [ "QPlaceIcon", "private", "", "public" ] }, - { symbol: [ "QPlaceIdReply", "private", "", "public" ] }, - { symbol: [ "QPlaceImage", "private", "", "public" ] }, - { symbol: [ "QPlaceManager", "private", "", "public" ] }, - { symbol: [ "QPlaceManagerEngine", "private", "", "public" ] }, - { symbol: [ "QPlaceMatchReply", "private", "", "public" ] }, - { symbol: [ "QPlaceMatchRequest", "private", "", "public" ] }, - { symbol: [ "QPlaceProposedSearchResult", "private", "", "public" ] }, - { symbol: [ "QPlaceRatings", "private", "", "public" ] }, - { symbol: [ "QPlaceReply", "private", "", "public" ] }, - { symbol: [ "QPlaceResult", "private", "", "public" ] }, - { symbol: [ "QPlaceReview", "private", "", "public" ] }, - { symbol: [ "QPlaceSearchReply", "private", "", "public" ] }, - { symbol: [ "QPlaceSearchRequest", "private", "", "public" ] }, - { symbol: [ "QPlaceSearchResult", "private", "", "public" ] }, - { symbol: [ "QPlaceSearchSuggestionReply", "private", "", "public" ] }, - { symbol: [ "QPlaceSupplier", "private", "", "public" ] }, - { symbol: [ "QPlaceUser", "private", "", "public" ] }, - { symbol: [ "QPlainTextDocumentLayout", "private", "", "public" ] }, - { symbol: [ "QPlainTextEdit", "private", "", "public" ] }, - { symbol: [ "QPluginLoader", "private", "", "public" ] }, - { symbol: [ "QPoint", "private", "", "public" ] }, - { symbol: [ "QPointF", "private", "", "public" ] }, - { symbol: [ "QPointer", "private", "", "public" ] }, - { symbol: [ "QPolygon", "private", "", "public" ] }, - { symbol: [ "QPolygonF", "private", "", "public" ] }, - { symbol: [ "QPressureFilter", "private", "", "public" ] }, - { symbol: [ "QPressureReading", "private", "", "public" ] }, - { symbol: [ "QPressureSensor", "private", "", "public" ] }, - { symbol: [ "QPrintDialog", "private", "", "public" ] }, - { symbol: [ "QPrintEngine", "private", "", "public" ] }, - { symbol: [ "QPrintPreviewDialog", "private", "", "public" ] }, - { symbol: [ "QPrintPreviewWidget", "private", "", "public" ] }, - { symbol: [ "QPrinter", "private", "", "public" ] }, - { symbol: [ "QPrinterInfo", "private", "", "public" ] }, - { symbol: [ "QProcess", "private", "", "public" ] }, - { symbol: [ "QProcessEnvironment", "private", "", "public" ] }, - { symbol: [ "QProgressBar", "private", "", "public" ] }, - { symbol: [ "QProgressDialog", "private", "", "public" ] }, - { symbol: [ "QPropertyAnimation", "private", "", "public" ] }, - { symbol: [ "QProximityFilter", "private", "", "public" ] }, - { symbol: [ "QProximityReading", "private", "", "public" ] }, - { symbol: [ "QProximitySensor", "private", "", "public" ] }, - { symbol: [ "QProxyStyle", "private", "", "public" ] }, - { symbol: [ "QPushButton", "private", "", "public" ] }, - { symbol: [ "QQmlAbstractUrlInterceptor", "private", "", "public" ] }, - { symbol: [ "QQmlApplicationEngine", "private", "", "public" ] }, - { symbol: [ "QQmlAttachedPropertiesFunc", "private", "", "public" ] }, - { symbol: [ "QQmlComponent", "private", "", "public" ] }, - { symbol: [ "QQmlContext", "private", "", "public" ] }, - { symbol: [ "QQmlDebuggingEnabler", "private", "", "public" ] }, - { symbol: [ "QQmlEngine", "private", "", "public" ] }, - { symbol: [ "QQmlError", "private", "", "public" ] }, - { symbol: [ "QQmlExpression", "private", "", "public" ] }, - { symbol: [ "QQmlExtensionInterface", "private", "", "public" ] }, - { symbol: [ "QQmlExtensionPlugin", "private", "", "public" ] }, - { symbol: [ "QQmlFile", "private", "", "public" ] }, - { symbol: [ "QQmlFileSelector", "private", "", "public" ] }, - { symbol: [ "QQmlImageProviderBase", "private", "", "public" ] }, - { symbol: [ "QQmlIncubationController", "private", "", "public" ] }, - { symbol: [ "QQmlIncubator", "private", "", "public" ] }, - { symbol: [ "QQmlInfo", "private", "", "public" ] }, - { symbol: [ "QQmlListProperty", "private", "", "public" ] }, - { symbol: [ "QQmlListReference", "private", "", "public" ] }, - { symbol: [ "QQmlNdefRecord", "private", "", "public" ] }, - { symbol: [ "QQmlNetworkAccessManagerFactory", "private", "", "public" ] }, - { symbol: [ "QQmlParserStatus", "private", "", "public" ] }, - { symbol: [ "QQmlProperties", "private", "", "public" ] }, - { symbol: [ "QQmlProperty", "private", "", "public" ] }, - { symbol: [ "QQmlPropertyMap", "private", "", "public" ] }, - { symbol: [ "QQmlPropertyValueSource", "private", "", "public" ] }, - { symbol: [ "QQmlScriptString", "private", "", "public" ] }, - { symbol: [ "QQmlTypeInfo", "private", "", "public" ] }, - { symbol: [ "QQmlTypesExtensionInterface", "private", "", "public" ] }, - { symbol: [ "QQmlWebChannel", "private", "", "public" ] }, - { symbol: [ "QQuaternion", "private", "", "public" ] }, - { symbol: [ "QQueue", "private", "", "public" ] }, - { symbol: [ "QQuickFramebufferObject", "private", "", "public" ] }, - { symbol: [ "QQuickImageProvider", "private", "", "public" ] }, - { symbol: [ "QQuickItem", "private", "", "public" ] }, - { symbol: [ "QQuickItemGrabResult", "private", "", "public" ] }, - { symbol: [ "QQuickPaintedItem", "private", "", "public" ] }, - { symbol: [ "QQuickRenderControl", "private", "", "public" ] }, - { symbol: [ "QQuickTextDocument", "private", "", "public" ] }, - { symbol: [ "QQuickTextureFactory", "private", "", "public" ] }, - { symbol: [ "QQuickTransform", "private", "", "public" ] }, - { symbol: [ "QQuickView", "private", "", "public" ] }, - { symbol: [ "QQuickWidget", "private", "", "public" ] }, - { symbol: [ "QQuickWindow", "private", "", "public" ] }, - { symbol: [ "QRadialGradient", "private", "", "public" ] }, - { symbol: [ "QRadioButton", "private", "", "public" ] }, - { symbol: [ "QRadioData", "private", "", "public" ] }, - { symbol: [ "QRadioDataControl", "private", "", "public" ] }, - { symbol: [ "QRadioTuner", "private", "", "public" ] }, - { symbol: [ "QRadioTunerControl", "private", "", "public" ] }, - { symbol: [ "QRasterWindow", "private", "", "public" ] }, - { symbol: [ "QRawFont", "private", "", "public" ] }, - { symbol: [ "QReadLocker", "private", "", "public" ] }, - { symbol: [ "QReadWriteLock", "private", "", "public" ] }, - { symbol: [ "QRect", "private", "", "public" ] }, - { symbol: [ "QRectF", "private", "", "public" ] }, - { symbol: [ "QRegExp", "private", "", "public" ] }, - { symbol: [ "QRegExpValidator", "private", "", "public" ] }, - { symbol: [ "QRegion", "private", "", "public" ] }, - { symbol: [ "QRegularExpression", "private", "", "public" ] }, - { symbol: [ "QRegularExpressionMatch", "private", "", "public" ] }, - { symbol: [ "QRegularExpressionMatchIterator", "private", "", "public" ] }, - { symbol: [ "QRegularExpressionValidator", "private", "", "public" ] }, - { symbol: [ "QResizeEvent", "private", "", "public" ] }, - { symbol: [ "QResource", "private", "", "public" ] }, - { symbol: [ "QReturnArgument", "private", "", "public" ] }, - { symbol: [ "QRgb", "private", "", "public" ] }, - { symbol: [ "QRotationFilter", "private", "", "public" ] }, - { symbol: [ "QRotationReading", "private", "", "public" ] }, - { symbol: [ "QRotationSensor", "private", "", "public" ] }, - { symbol: [ "QRubberBand", "private", "", "public" ] }, - { symbol: [ "QRunnable", "private", "", "public" ] }, - { symbol: [ "QSGAbstractRenderer", "private", "", "public" ] }, - { symbol: [ "QSGBasicGeometryNode", "private", "", "public" ] }, - { symbol: [ "QSGClipNode", "private", "", "public" ] }, - { symbol: [ "QSGDynamicTexture", "private", "", "public" ] }, - { symbol: [ "QSGEngine", "private", "", "public" ] }, - { symbol: [ "QSGFlatColorMaterial", "private", "", "public" ] }, - { symbol: [ "QSGGeometry", "private", "", "public" ] }, - { symbol: [ "QSGGeometryNode", "private", "", "public" ] }, - { symbol: [ "QSGMaterial", "private", "", "public" ] }, - { symbol: [ "QSGMaterialShader", "private", "", "public" ] }, - { symbol: [ "QSGMaterialType", "private", "", "public" ] }, - { symbol: [ "QSGNode", "private", "", "public" ] }, - { symbol: [ "QSGNodeVisitor", "private", "", "public" ] }, - { symbol: [ "QSGOpacityNode", "private", "", "public" ] }, - { symbol: [ "QSGOpaqueTextureMaterial", "private", "", "public" ] }, - { symbol: [ "QSGRootNode", "private", "", "public" ] }, - { symbol: [ "QSGSimpleMaterial", "private", "", "public" ] }, - { symbol: [ "QSGSimpleMaterialComparableMaterial", "private", "", "public" ] }, - { symbol: [ "QSGSimpleMaterialShader", "private", "", "public" ] }, - { symbol: [ "QSGSimpleRectNode", "private", "", "public" ] }, - { symbol: [ "QSGSimpleTextureNode", "private", "", "public" ] }, - { symbol: [ "QSGTexture", "private", "", "public" ] }, - { symbol: [ "QSGTextureMaterial", "private", "", "public" ] }, - { symbol: [ "QSGTextureProvider", "private", "", "public" ] }, - { symbol: [ "QSGTransformNode", "private", "", "public" ] }, - { symbol: [ "QSGVertexColorMaterial", "private", "", "public" ] }, - { symbol: [ "QSGVideoNodeFactory_I420", "private", "", "public" ] }, - { symbol: [ "QSGVideoNodeFactory_RGB", "private", "", "public" ] }, - { symbol: [ "QSGVideoNodeFactory_Texture", "private", "", "public" ] }, - { symbol: [ "QSGVideoNode_I420", "private", "", "public" ] }, - { symbol: [ "QSGVideoNode_RGB", "private", "", "public" ] }, - { symbol: [ "QSGVideoNode_Texture", "private", "", "public" ] }, - { symbol: [ "QSaveFile", "private", "", "public" ] }, - { symbol: [ "QScopedArrayPointer", "private", "", "public" ] }, - { symbol: [ "QScopedPointer", "private", "", "public" ] }, - { symbol: [ "QScopedPointerArrayDeleter", "private", "", "public" ] }, - { symbol: [ "QScopedPointerDeleteLater", "private", "", "public" ] }, - { symbol: [ "QScopedPointerDeleter", "private", "", "public" ] }, - { symbol: [ "QScopedPointerObjectDeleteLater", "private", "", "public" ] }, - { symbol: [ "QScopedPointerPodDeleter", "private", "", "public" ] }, - { symbol: [ "QScopedValueRollback", "private", "", "public" ] }, - { symbol: [ "QScreen", "private", "", "public" ] }, - { symbol: [ "QScreenOrientationChangeEvent", "private", "", "public" ] }, - { symbol: [ "QScriptClass", "private", "", "public" ] }, - { symbol: [ "QScriptClassPropertyIterator", "private", "", "public" ] }, - { symbol: [ "QScriptContext", "private", "", "public" ] }, - { symbol: [ "QScriptContextInfo", "private", "", "public" ] }, - { symbol: [ "QScriptContextInfoList", "private", "", "public" ] }, - { symbol: [ "QScriptEngine", "private", "", "public" ] }, - { symbol: [ "QScriptEngineAgent", "private", "", "public" ] }, - { symbol: [ "QScriptEngineDebugger", "private", "", "public" ] }, - { symbol: [ "QScriptExtensionInterface", "private", "", "public" ] }, - { symbol: [ "QScriptExtensionPlugin", "private", "", "public" ] }, - { symbol: [ "QScriptProgram", "private", "", "public" ] }, - { symbol: [ "QScriptString", "private", "", "public" ] }, - { symbol: [ "QScriptSyntaxCheckResult", "private", "", "public" ] }, - { symbol: [ "QScriptValue", "private", "", "public" ] }, - { symbol: [ "QScriptValueIterator", "private", "", "public" ] }, - { symbol: [ "QScriptValueList", "private", "", "public" ] }, - { symbol: [ "QScriptable", "private", "", "public" ] }, - { symbol: [ "QScrollArea", "private", "", "public" ] }, - { symbol: [ "QScrollBar", "private", "", "public" ] }, - { symbol: [ "QScrollEvent", "private", "", "public" ] }, - { symbol: [ "QScrollPrepareEvent", "private", "", "public" ] }, - { symbol: [ "QScroller", "private", "", "public" ] }, - { symbol: [ "QScrollerProperties", "private", "", "public" ] }, - { symbol: [ "QSemaphore", "private", "", "public" ] }, - { symbol: [ "QSensor", "private", "", "public" ] }, - { symbol: [ "QSensorBackend", "private", "", "public" ] }, - { symbol: [ "QSensorBackendFactory", "private", "", "public" ] }, - { symbol: [ "QSensorChangesInterface", "private", "", "public" ] }, - { symbol: [ "QSensorFilter", "private", "", "public" ] }, - { symbol: [ "QSensorGesture", "private", "", "public" ] }, - { symbol: [ "QSensorGestureManager", "private", "", "public" ] }, - { symbol: [ "QSensorGesturePluginInterface", "private", "", "public" ] }, - { symbol: [ "QSensorGestureRecognizer", "private", "", "public" ] }, - { symbol: [ "QSensorManager", "private", "", "public" ] }, - { symbol: [ "QSensorPluginInterface", "private", "", "public" ] }, - { symbol: [ "QSensorReading", "private", "", "public" ] }, - { symbol: [ "QSequentialAnimationGroup", "private", "", "public" ] }, - { symbol: [ "QSequentialIterable", "private", "", "public" ] }, - { symbol: [ "QSerialPort", "private", "", "public" ] }, - { symbol: [ "QSerialPortInfo", "private", "", "public" ] }, - { symbol: [ "QSessionManager", "private", "", "public" ] }, - { symbol: [ "QSet", "private", "", "public" ] }, - { symbol: [ "QSetIterator", "private", "", "public" ] }, - { symbol: [ "QSettings", "private", "", "public" ] }, - { symbol: [ "QSharedData", "private", "", "public" ] }, - { symbol: [ "QSharedDataPointer", "private", "", "public" ] }, - { symbol: [ "QSharedMemory", "private", "", "public" ] }, - { symbol: [ "QSharedPointer", "private", "", "public" ] }, - { symbol: [ "QShortcut", "private", "", "public" ] }, - { symbol: [ "QShortcutEvent", "private", "", "public" ] }, - { symbol: [ "QShowEvent", "private", "", "public" ] }, - { symbol: [ "QSignalBlocker", "private", "", "public" ] }, - { symbol: [ "QSignalMapper", "private", "", "public" ] }, - { symbol: [ "QSignalSpy", "private", "", "public" ] }, - { symbol: [ "QSignalTransition", "private", "", "public" ] }, - { symbol: [ "QSimpleXmlNodeModel", "private", "", "public" ] }, - { symbol: [ "QSize", "private", "", "public" ] }, - { symbol: [ "QSizeF", "private", "", "public" ] }, - { symbol: [ "QSizeGrip", "private", "", "public" ] }, - { symbol: [ "QSizePolicy", "private", "", "public" ] }, - { symbol: [ "QSlider", "private", "", "public" ] }, - { symbol: [ "QSocketNotifier", "private", "", "public" ] }, - { symbol: [ "QSortFilterProxyModel", "private", "", "public" ] }, - { symbol: [ "QSound", "private", "", "public" ] }, - { symbol: [ "QSoundEffect", "private", "", "public" ] }, - { symbol: [ "QSourceLocation", "private", "", "public" ] }, - { symbol: [ "QSpacerItem", "private", "", "public" ] }, - { symbol: [ "QSpinBox", "private", "", "public" ] }, - { symbol: [ "QSplashScreen", "private", "", "public" ] }, - { symbol: [ "QSplitter", "private", "", "public" ] }, - { symbol: [ "QSplitterHandle", "private", "", "public" ] }, - { symbol: [ "QSpontaneKeyEvent", "private", "", "public" ] }, - { symbol: [ "QSql", "private", "", "public" ] }, - { symbol: [ "QSqlDatabase", "private", "", "public" ] }, - { symbol: [ "QSqlDriver", "private", "", "public" ] }, - { symbol: [ "QSqlDriverCreator", "private", "", "public" ] }, - { symbol: [ "QSqlDriverCreatorBase", "private", "", "public" ] }, - { symbol: [ "QSqlDriverPlugin", "private", "", "public" ] }, - { symbol: [ "QSqlError", "private", "", "public" ] }, - { symbol: [ "QSqlField", "private", "", "public" ] }, - { symbol: [ "QSqlIndex", "private", "", "public" ] }, - { symbol: [ "QSqlQuery", "private", "", "public" ] }, - { symbol: [ "QSqlQueryModel", "private", "", "public" ] }, - { symbol: [ "QSqlRecord", "private", "", "public" ] }, - { symbol: [ "QSqlRelation", "private", "", "public" ] }, - { symbol: [ "QSqlRelationalDelegate", "private", "", "public" ] }, - { symbol: [ "QSqlRelationalTableModel", "private", "", "public" ] }, - { symbol: [ "QSqlResult", "private", "", "public" ] }, - { symbol: [ "QSqlTableModel", "private", "", "public" ] }, - { symbol: [ "QSsl", "private", "", "public" ] }, - { symbol: [ "QSslCertificate", "private", "", "public" ] }, - { symbol: [ "QSslCertificateExtension", "private", "", "public" ] }, - { symbol: [ "QSslCipher", "private", "", "public" ] }, - { symbol: [ "QSslConfiguration", "private", "", "public" ] }, - { symbol: [ "QSslError", "private", "", "public" ] }, - { symbol: [ "QSslKey", "private", "", "public" ] }, - { symbol: [ "QSslSocket", "private", "", "public" ] }, - { symbol: [ "QStack", "private", "", "public" ] }, - { symbol: [ "QStackedLayout", "private", "", "public" ] }, - { symbol: [ "QStackedWidget", "private", "", "public" ] }, - { symbol: [ "QStandardItem", "private", "", "public" ] }, - { symbol: [ "QStandardItemEditorCreator", "private", "", "public" ] }, - { symbol: [ "QStandardItemModel", "private", "", "public" ] }, - { symbol: [ "QStandardPaths", "private", "", "public" ] }, - { symbol: [ "QState", "private", "", "public" ] }, - { symbol: [ "QStateMachine", "private", "", "public" ] }, - { symbol: [ "QStaticArrayData", "private", "", "public" ] }, - { symbol: [ "QStaticAssertFailure", "private", "", "public" ] }, - { symbol: [ "QStaticByteArrayData", "private", "", "public" ] }, - { symbol: [ "QStaticPlugin", "private", "", "public" ] }, - { symbol: [ "QStaticStringData", "private", "", "public" ] }, - { symbol: [ "QStaticText", "private", "", "public" ] }, - { symbol: [ "QStatusBar", "private", "", "public" ] }, - { symbol: [ "QStatusTipEvent", "private", "", "public" ] }, - { symbol: [ "QStorageInfo", "private", "", "public" ] }, - { symbol: [ "QString", "private", "", "public" ] }, - { symbol: [ "QStringBuilder", "private", "", "public" ] }, - { symbol: [ "QStringData", "private", "", "public" ] }, - { symbol: [ "QStringDataPtr", "private", "", "public" ] }, - { symbol: [ "QStringList", "private", "", "public" ] }, - { symbol: [ "QStringListIterator", "private", "", "public" ] }, - { symbol: [ "QStringListModel", "private", "", "public" ] }, - { symbol: [ "QStringMatcher", "private", "", "public" ] }, - { symbol: [ "QStringRef", "private", "", "public" ] }, - { symbol: [ "QStyle", "private", "", "public" ] }, - { symbol: [ "QStyleFactory", "private", "", "public" ] }, - { symbol: [ "QStyleHintReturn", "private", "", "public" ] }, - { symbol: [ "QStyleHintReturnMask", "private", "", "public" ] }, - { symbol: [ "QStyleHintReturnVariant", "private", "", "public" ] }, - { symbol: [ "QStyleHints", "private", "", "public" ] }, - { symbol: [ "QStyleOption", "private", "", "public" ] }, - { symbol: [ "QStyleOptionButton", "private", "", "public" ] }, - { symbol: [ "QStyleOptionComboBox", "private", "", "public" ] }, - { symbol: [ "QStyleOptionComplex", "private", "", "public" ] }, - { symbol: [ "QStyleOptionDockWidget", "private", "", "public" ] }, - { symbol: [ "QStyleOptionDockWidgetV2", "private", "", "public" ] }, - { symbol: [ "QStyleOptionFocusRect", "private", "", "public" ] }, - { symbol: [ "QStyleOptionFrame", "private", "", "public" ] }, - { symbol: [ "QStyleOptionFrameV2", "private", "", "public" ] }, - { symbol: [ "QStyleOptionFrameV3", "private", "", "public" ] }, - { symbol: [ "QStyleOptionGraphicsItem", "private", "", "public" ] }, - { symbol: [ "QStyleOptionGroupBox", "private", "", "public" ] }, - { symbol: [ "QStyleOptionHeader", "private", "", "public" ] }, - { symbol: [ "QStyleOptionMenuItem", "private", "", "public" ] }, - { symbol: [ "QStyleOptionProgressBar", "private", "", "public" ] }, - { symbol: [ "QStyleOptionProgressBarV2", "private", "", "public" ] }, - { symbol: [ "QStyleOptionRubberBand", "private", "", "public" ] }, - { symbol: [ "QStyleOptionSizeGrip", "private", "", "public" ] }, - { symbol: [ "QStyleOptionSlider", "private", "", "public" ] }, - { symbol: [ "QStyleOptionSpinBox", "private", "", "public" ] }, - { symbol: [ "QStyleOptionTab", "private", "", "public" ] }, - { symbol: [ "QStyleOptionTabBarBase", "private", "", "public" ] }, - { symbol: [ "QStyleOptionTabBarBaseV2", "private", "", "public" ] }, - { symbol: [ "QStyleOptionTabV2", "private", "", "public" ] }, - { symbol: [ "QStyleOptionTabV3", "private", "", "public" ] }, - { symbol: [ "QStyleOptionTabWidgetFrame", "private", "", "public" ] }, - { symbol: [ "QStyleOptionTabWidgetFrameV2", "private", "", "public" ] }, - { symbol: [ "QStyleOptionTitleBar", "private", "", "public" ] }, - { symbol: [ "QStyleOptionToolBar", "private", "", "public" ] }, - { symbol: [ "QStyleOptionToolBox", "private", "", "public" ] }, - { symbol: [ "QStyleOptionToolBoxV2", "private", "", "public" ] }, - { symbol: [ "QStyleOptionToolButton", "private", "", "public" ] }, - { symbol: [ "QStyleOptionViewItem", "private", "", "public" ] }, - { symbol: [ "QStyleOptionViewItemV2", "private", "", "public" ] }, - { symbol: [ "QStyleOptionViewItemV3", "private", "", "public" ] }, - { symbol: [ "QStyleOptionViewItemV4", "private", "", "public" ] }, - { symbol: [ "QStylePainter", "private", "", "public" ] }, - { symbol: [ "QStylePlugin", "private", "", "public" ] }, - { symbol: [ "QStyledItemDelegate", "private", "", "public" ] }, - { symbol: [ "QSurface", "private", "", "public" ] }, - { symbol: [ "QSurfaceFormat", "private", "", "public" ] }, - { symbol: [ "QSvgGenerator", "private", "", "public" ] }, - { symbol: [ "QSvgRenderer", "private", "", "public" ] }, - { symbol: [ "QSvgWidget", "private", "", "public" ] }, - { symbol: [ "QSwipeGesture", "private", "", "public" ] }, - { symbol: [ "QSyntaxHighlighter", "private", "", "public" ] }, - { symbol: [ "QSysInfo", "private", "", "public" ] }, - { symbol: [ "QSystemSemaphore", "private", "", "public" ] }, - { symbol: [ "QSystemTrayIcon", "private", "", "public" ] }, - { symbol: [ "QTabBar", "private", "", "public" ] }, - { symbol: [ "QTabWidget", "private", "", "public" ] }, - { symbol: [ "QTableView", "private", "", "public" ] }, - { symbol: [ "QTableWidget", "private", "", "public" ] }, - { symbol: [ "QTableWidgetItem", "private", "", "public" ] }, - { symbol: [ "QTableWidgetSelectionRange", "private", "", "public" ] }, - { symbol: [ "QTabletEvent", "private", "", "public" ] }, - { symbol: [ "QTapAndHoldGesture", "private", "", "public" ] }, - { symbol: [ "QTapFilter", "private", "", "public" ] }, - { symbol: [ "QTapGesture", "private", "", "public" ] }, - { symbol: [ "QTapReading", "private", "", "public" ] }, - { symbol: [ "QTapSensor", "private", "", "public" ] }, - { symbol: [ "QTcpServer", "private", "", "public" ] }, - { symbol: [ "QTcpSocket", "private", "", "public" ] }, - { symbol: [ "QTemporaryDir", "private", "", "public" ] }, - { symbol: [ "QTemporaryFile", "private", "", "public" ] }, - { symbol: [ "QTest", "private", "", "public" ] }, - { symbol: [ "QTestAccessibility", "private", "", "public" ] }, - { symbol: [ "QTestData", "private", "", "public" ] }, - { symbol: [ "QTestDelayEvent", "private", "", "public" ] }, - { symbol: [ "QTestEvent", "private", "", "public" ] }, - { symbol: [ "QTestEventList", "private", "", "public" ] }, - { symbol: [ "QTestEventLoop", "private", "", "public" ] }, - { symbol: [ "QTestKeyClicksEvent", "private", "", "public" ] }, - { symbol: [ "QTestKeyEvent", "private", "", "public" ] }, - { symbol: [ "QTestMouseEvent", "private", "", "public" ] }, - { symbol: [ "QTextBlock", "private", "", "public" ] }, - { symbol: [ "QTextBlockFormat", "private", "", "public" ] }, - { symbol: [ "QTextBlockGroup", "private", "", "public" ] }, - { symbol: [ "QTextBlockUserData", "private", "", "public" ] }, - { symbol: [ "QTextBoundaryFinder", "private", "", "public" ] }, - { symbol: [ "QTextBrowser", "private", "", "public" ] }, - { symbol: [ "QTextCharFormat", "private", "", "public" ] }, - { symbol: [ "QTextCodec", "private", "", "public" ] }, - { symbol: [ "QTextCursor", "private", "", "public" ] }, - { symbol: [ "QTextDecoder", "private", "", "public" ] }, - { symbol: [ "QTextDocument", "private", "", "public" ] }, - { symbol: [ "QTextDocumentFragment", "private", "", "public" ] }, - { symbol: [ "QTextDocumentWriter", "private", "", "public" ] }, - { symbol: [ "QTextEdit", "private", "", "public" ] }, - { symbol: [ "QTextEncoder", "private", "", "public" ] }, - { symbol: [ "QTextFormat", "private", "", "public" ] }, - { symbol: [ "QTextFragment", "private", "", "public" ] }, - { symbol: [ "QTextFrame", "private", "", "public" ] }, - { symbol: [ "QTextFrameFormat", "private", "", "public" ] }, - { symbol: [ "QTextFrameLayoutData", "private", "", "public" ] }, - { symbol: [ "QTextImageFormat", "private", "", "public" ] }, - { symbol: [ "QTextInlineObject", "private", "", "public" ] }, - { symbol: [ "QTextItem", "private", "", "public" ] }, - { symbol: [ "QTextLayout", "private", "", "public" ] }, - { symbol: [ "QTextLength", "private", "", "public" ] }, - { symbol: [ "QTextLine", "private", "", "public" ] }, - { symbol: [ "QTextList", "private", "", "public" ] }, - { symbol: [ "QTextListFormat", "private", "", "public" ] }, - { symbol: [ "QTextObject", "private", "", "public" ] }, - { symbol: [ "QTextObjectInterface", "private", "", "public" ] }, - { symbol: [ "QTextOption", "private", "", "public" ] }, - { symbol: [ "QTextStream", "private", "", "public" ] }, - { symbol: [ "QTextStreamFunction", "private", "", "public" ] }, - { symbol: [ "QTextStreamManipulator", "private", "", "public" ] }, - { symbol: [ "QTextTable", "private", "", "public" ] }, - { symbol: [ "QTextTableCell", "private", "", "public" ] }, - { symbol: [ "QTextTableCellFormat", "private", "", "public" ] }, - { symbol: [ "QTextTableFormat", "private", "", "public" ] }, - { symbol: [ "QThread", "private", "", "public" ] }, - { symbol: [ "QThreadPool", "private", "", "public" ] }, - { symbol: [ "QThreadStorage", "private", "", "public" ] }, - { symbol: [ "QThreadStorageData", "private", "", "public" ] }, - { symbol: [ "QTileRules", "private", "", "public" ] }, - { symbol: [ "QTiltFilter", "private", "", "public" ] }, - { symbol: [ "QTiltReading", "private", "", "public" ] }, - { symbol: [ "QTiltSensor", "private", "", "public" ] }, - { symbol: [ "QTime", "private", "", "public" ] }, - { symbol: [ "QTimeEdit", "private", "", "public" ] }, - { symbol: [ "QTimeLine", "private", "", "public" ] }, - { symbol: [ "QTimeZone", "private", "", "public" ] }, - { symbol: [ "QTimer", "private", "", "public" ] }, - { symbol: [ "QTimerEvent", "private", "", "public" ] }, - { symbol: [ "QToolBar", "private", "", "public" ] }, - { symbol: [ "QToolBarChangeEvent", "private", "", "public" ] }, - { symbol: [ "QToolBox", "private", "", "public" ] }, - { symbol: [ "QToolButton", "private", "", "public" ] }, - { symbol: [ "QToolTip", "private", "", "public" ] }, - { symbol: [ "QTouchDevice", "private", "", "public" ] }, - { symbol: [ "QTouchEvent", "private", "", "public" ] }, - { symbol: [ "QTransform", "private", "", "public" ] }, - { symbol: [ "QTranslator", "private", "", "public" ] }, - { symbol: [ "QTreeView", "private", "", "public" ] }, - { symbol: [ "QTreeWidget", "private", "", "public" ] }, - { symbol: [ "QTreeWidgetItem", "private", "", "public" ] }, - { symbol: [ "QTreeWidgetItemIterator", "private", "", "public" ] }, - { symbol: [ "QTypeInfo", "private", "", "public" ] }, - { symbol: [ "QTypeInfoMerger", "private", "", "public" ] }, - { symbol: [ "QUdpSocket", "private", "", "public" ] }, - { symbol: [ "QUiLoader", "private", "", "public" ] }, - { symbol: [ "QUndoCommand", "private", "", "public" ] }, - { symbol: [ "QUndoGroup", "private", "", "public" ] }, - { symbol: [ "QUndoStack", "private", "", "public" ] }, - { symbol: [ "QUndoView", "private", "", "public" ] }, - { symbol: [ "QUnhandledException", "private", "", "public" ] }, - { symbol: [ "QUrl", "private", "", "public" ] }, - { symbol: [ "QUrlQuery", "private", "", "public" ] }, - { symbol: [ "QUrlTwoFlags", "private", "", "public" ] }, - { symbol: [ "QUuid", "private", "", "public" ] }, - { symbol: [ "QVBoxLayout", "private", "", "public" ] }, - { symbol: [ "QValidator", "private", "", "public" ] }, - { symbol: [ "QVarLengthArray", "private", "", "public" ] }, - { symbol: [ "QVariant", "private", "", "public" ] }, - { symbol: [ "QVariantAnimation", "private", "", "public" ] }, - { symbol: [ "QVariantComparisonHelper", "private", "", "public" ] }, - { symbol: [ "QVariantHash", "private", "", "public" ] }, - { symbol: [ "QVariantList", "private", "", "public" ] }, - { symbol: [ "QVariantMap", "private", "", "public" ] }, - { symbol: [ "QVector", "private", "", "public" ] }, - { symbol: [ "QVector2D", "private", "", "public" ] }, - { symbol: [ "QVector3D", "private", "", "public" ] }, - { symbol: [ "QVector4D", "private", "", "public" ] }, - { symbol: [ "QVectorIterator", "private", "", "public" ] }, - { symbol: [ "QVideoDeviceSelectorControl", "private", "", "public" ] }, - { symbol: [ "QVideoEncoderSettings", "private", "", "public" ] }, - { symbol: [ "QVideoEncoderSettingsControl", "private", "", "public" ] }, - { symbol: [ "QVideoFrame", "private", "", "public" ] }, - { symbol: [ "QVideoProbe", "private", "", "public" ] }, - { symbol: [ "QVideoRendererControl", "private", "", "public" ] }, - { symbol: [ "QVideoSurfaceFormat", "private", "", "public" ] }, - { symbol: [ "QVideoWidget", "private", "", "public" ] }, - { symbol: [ "QVideoWidgetControl", "private", "", "public" ] }, - { symbol: [ "QVideoWindowControl", "private", "", "public" ] }, - { symbol: [ "QWGLNativeContext", "private", "", "public" ] }, - { symbol: [ "QWaitCondition", "private", "", "public" ] }, - { symbol: [ "QWeakPointer", "private", "", "public" ] }, - { symbol: [ "QWebChannel", "private", "", "public" ] }, - { symbol: [ "QWebChannelAbstractTransport", "private", "", "public" ] }, - { symbol: [ "QWebDatabase", "private", "", "public" ] }, - { symbol: [ "QWebElement", "private", "", "public" ] }, - { symbol: [ "QWebElementCollection", "private", "", "public" ] }, - { symbol: [ "QWebFrame", "private", "", "public" ] }, - { symbol: [ "QWebFullScreenVideoHandler", "private", "", "public" ] }, - { symbol: [ "QWebHapticFeedbackPlayer", "private", "", "public" ] }, - { symbol: [ "QWebHistory", "private", "", "public" ] }, - { symbol: [ "QWebHistoryInterface", "private", "", "public" ] }, - { symbol: [ "QWebHistoryItem", "private", "", "public" ] }, - { symbol: [ "QWebHitTestResult", "private", "", "public" ] }, - { symbol: [ "QWebInspector", "private", "", "public" ] }, - { symbol: [ "QWebKitPlatformPlugin", "private", "", "public" ] }, - { symbol: [ "QWebNotificationData", "private", "", "public" ] }, - { symbol: [ "QWebNotificationPresenter", "private", "", "public" ] }, - { symbol: [ "QWebPage", "private", "", "public" ] }, - { symbol: [ "QWebPluginFactory", "private", "", "public" ] }, - { symbol: [ "QWebSecurityOrigin", "private", "", "public" ] }, - { symbol: [ "QWebSelectData", "private", "", "public" ] }, - { symbol: [ "QWebSelectMethod", "private", "", "public" ] }, - { symbol: [ "QWebSettings", "private", "", "public" ] }, - { symbol: [ "QWebSocket", "private", "", "public" ] }, - { symbol: [ "QWebSocketCorsAuthenticator", "private", "", "public" ] }, - { symbol: [ "QWebSocketServer", "private", "", "public" ] }, - { symbol: [ "QWebSpellChecker", "private", "", "public" ] }, - { symbol: [ "QWebTouchModifier", "private", "", "public" ] }, - { symbol: [ "QWebView", "private", "", "public" ] }, - { symbol: [ "QWhatsThis", "private", "", "public" ] }, - { symbol: [ "QWhatsThisClickedEvent", "private", "", "public" ] }, - { symbol: [ "QWheelEvent", "private", "", "public" ] }, - { symbol: [ "QWidget", "private", "", "public" ] }, - { symbol: [ "QWidgetAction", "private", "", "public" ] }, - { symbol: [ "QWidgetData", "private", "", "public" ] }, - { symbol: [ "QWidgetItem", "private", "", "public" ] }, - { symbol: [ "QWidgetItemV2", "private", "", "public" ] }, - { symbol: [ "QWidgetList", "private", "", "public" ] }, - { symbol: [ "QWidgetMapper", "private", "", "public" ] }, - { symbol: [ "QWidgetSet", "private", "", "public" ] }, - { symbol: [ "QWinColorizationChangeEvent", "private", "", "public" ] }, - { symbol: [ "QWinCompositionChangeEvent", "private", "", "public" ] }, - { symbol: [ "QWinEvent", "private", "", "public" ] }, - { symbol: [ "QWinEventNotifier", "private", "", "public" ] }, - { symbol: [ "QWinEventNotifier", "private", "", "public" ] }, - { symbol: [ "QWinJumpList", "private", "", "public" ] }, - { symbol: [ "QWinJumpListCategory", "private", "", "public" ] }, - { symbol: [ "QWinJumpListItem", "private", "", "public" ] }, - { symbol: [ "QWinMime", "private", "", "public" ] }, - { symbol: [ "QWinTaskbarButton", "private", "", "public" ] }, - { symbol: [ "QWinTaskbarProgress", "private", "", "public" ] }, - { symbol: [ "QWinThumbnailToolBar", "private", "", "public" ] }, - { symbol: [ "QWinThumbnailToolButton", "private", "", "public" ] }, - { symbol: [ "QWindow", "private", "", "public" ] }, - { symbol: [ "QWindowList", "private", "", "public" ] }, - { symbol: [ "QWindowStateChangeEvent", "private", "", "public" ] }, - { symbol: [ "QWizard", "private", "", "public" ] }, - { symbol: [ "QWizardPage", "private", "", "public" ] }, - { symbol: [ "QWriteLocker", "private", "", "public" ] }, - { symbol: [ "QXcbWindowFunctions", "private", "", "public" ] }, - { symbol: [ "QXmlAttributes", "private", "", "public" ] }, - { symbol: [ "QXmlContentHandler", "private", "", "public" ] }, - { symbol: [ "QXmlDTDHandler", "private", "", "public" ] }, - { symbol: [ "QXmlDeclHandler", "private", "", "public" ] }, - { symbol: [ "QXmlDefaultHandler", "private", "", "public" ] }, - { symbol: [ "QXmlEntityResolver", "private", "", "public" ] }, - { symbol: [ "QXmlErrorHandler", "private", "", "public" ] }, - { symbol: [ "QXmlFormatter", "private", "", "public" ] }, - { symbol: [ "QXmlInputSource", "private", "", "public" ] }, - { symbol: [ "QXmlItem", "private", "", "public" ] }, - { symbol: [ "QXmlLexicalHandler", "private", "", "public" ] }, - { symbol: [ "QXmlLocator", "private", "", "public" ] }, - { symbol: [ "QXmlName", "private", "", "public" ] }, - { symbol: [ "QXmlNamePool", "private", "", "public" ] }, - { symbol: [ "QXmlNamespaceSupport", "private", "", "public" ] }, - { symbol: [ "QXmlNodeModelIndex", "private", "", "public" ] }, - { symbol: [ "QXmlParseException", "private", "", "public" ] }, - { symbol: [ "QXmlQuery", "private", "", "public" ] }, - { symbol: [ "QXmlReader", "private", "", "public" ] }, - { symbol: [ "QXmlResultItems", "private", "", "public" ] }, - { symbol: [ "QXmlSchema", "private", "", "public" ] }, - { symbol: [ "QXmlSchemaValidator", "private", "", "public" ] }, - { symbol: [ "QXmlSerializer", "private", "", "public" ] }, - { symbol: [ "QXmlSimpleReader", "private", "", "public" ] }, - { symbol: [ "QXmlStreamAttribute", "private", "", "public" ] }, - { symbol: [ "QXmlStreamAttributes", "private", "", "public" ] }, - { symbol: [ "QXmlStreamEntityDeclaration", "private", "", "public" ] }, - { symbol: [ "QXmlStreamEntityDeclarations", "private", "", "public" ] }, - { symbol: [ "QXmlStreamEntityResolver", "private", "", "public" ] }, - { symbol: [ "QXmlStreamNamespaceDeclaration", "private", "", "public" ] }, - { symbol: [ "QXmlStreamNamespaceDeclarations", "private", "", "public" ] }, - { symbol: [ "QXmlStreamNotationDeclaration", "private", "", "public" ] }, - { symbol: [ "QXmlStreamNotationDeclarations", "private", "", "public" ] }, - { symbol: [ "QXmlStreamReader", "private", "", "public" ] }, - { symbol: [ "QXmlStreamStringRef", "private", "", "public" ] }, - { symbol: [ "QXmlStreamWriter", "private", "", "public" ] }, - { symbol: [ "Q_IPV6ADDR", "private", "", "public" ] }, - { symbol: [ "Q_PID", "private", "", "public" ] }, - { symbol: [ "Qt", "private", "", "public" ] }, - { symbol: [ "QtAlgorithms", "private", "", "public" ] }, - { symbol: [ "QtBluetooth", "private", "", "public" ] }, - { symbol: [ "QtBluetoothDepends", "private", "", "public" ] }, - { symbol: [ "QtBluetoothVersion", "private", "", "public" ] }, - { symbol: [ "QtCLucene", "private", "", "public" ] }, - { symbol: [ "QtCLuceneDepends", "private", "", "public" ] }, - { symbol: [ "QtCLuceneVersion", "private", "", "public" ] }, - { symbol: [ "QtCleanUpFunction", "private", "", "public" ] }, - { symbol: [ "QtConcurrent", "private", "", "public" ] }, - { symbol: [ "QtConcurrentDepends", "private", "", "public" ] }, - { symbol: [ "QtConcurrentFilter", "private", "", "public" ] }, - { symbol: [ "QtConcurrentMap", "private", "", "public" ] }, - { symbol: [ "QtConcurrentRun", "private", "", "public" ] }, - { symbol: [ "QtConcurrentVersion", "private", "", "public" ] }, - { symbol: [ "QtConfig", "private", "", "public" ] }, - { symbol: [ "QtContainerFwd", "private", "", "public" ] }, - { symbol: [ "QtCore", "private", "", "public" ] }, - { symbol: [ "QtCoreDepends", "private", "", "public" ] }, - { symbol: [ "QtCoreVersion", "private", "", "public" ] }, - { symbol: [ "QtDBus", "private", "", "public" ] }, - { symbol: [ "QtDBusDepends", "private", "", "public" ] }, - { symbol: [ "QtDBusVersion", "private", "", "public" ] }, - { symbol: [ "QtDebug", "private", "", "public" ] }, - { symbol: [ "QtDeclarative", "private", "", "public" ] }, - { symbol: [ "QtDeclarativeDepends", "private", "", "public" ] }, - { symbol: [ "QtDeclarativeVersion", "private", "", "public" ] }, - { symbol: [ "QtDesigner", "private", "", "public" ] }, - { symbol: [ "QtDesignerComponents", "private", "", "public" ] }, - { symbol: [ "QtDesignerComponentsDepends", "private", "", "public" ] }, - { symbol: [ "QtDesignerComponentsVersion", "private", "", "public" ] }, - { symbol: [ "QtDesignerDepends", "private", "", "public" ] }, - { symbol: [ "QtDesignerVersion", "private", "", "public" ] }, - { symbol: [ "QtEndian", "private", "", "public" ] }, - { symbol: [ "QtEvents", "private", "", "public" ] }, - { symbol: [ "QtGlobal", "private", "", "public" ] }, - { symbol: [ "QtGui", "private", "", "public" ] }, - { symbol: [ "QtGuiDepends", "private", "", "public" ] }, - { symbol: [ "QtGuiVersion", "private", "", "public" ] }, - { symbol: [ "QtHelp", "private", "", "public" ] }, - { symbol: [ "QtHelpDepends", "private", "", "public" ] }, - { symbol: [ "QtHelpVersion", "private", "", "public" ] }, - { symbol: [ "QtLocation", "private", "", "public" ] }, - { symbol: [ "QtLocationDepends", "private", "", "public" ] }, - { symbol: [ "QtLocationVersion", "private", "", "public" ] }, - { symbol: [ "QtMath", "private", "", "public" ] }, - { symbol: [ "QtMessageHandler", "private", "", "public" ] }, - { symbol: [ "QtMsgHandler", "private", "", "public" ] }, - { symbol: [ "QtMultimedia", "private", "", "public" ] }, - { symbol: [ "QtMultimediaDepends", "private", "", "public" ] }, - { symbol: [ "QtMultimediaQuick_p", "private", "", "public" ] }, - { symbol: [ "QtMultimediaQuick_pDepends", "private", "", "public" ] }, - { symbol: [ "QtMultimediaQuick_pVersion", "private", "", "public" ] }, - { symbol: [ "QtMultimediaVersion", "private", "", "public" ] }, - { symbol: [ "QtMultimediaWidgets", "private", "", "public" ] }, - { symbol: [ "QtMultimediaWidgetsDepends", "private", "", "public" ] }, - { symbol: [ "QtMultimediaWidgetsVersion", "private", "", "public" ] }, - { symbol: [ "QtNetwork", "private", "", "public" ] }, - { symbol: [ "QtNetworkDepends", "private", "", "public" ] }, - { symbol: [ "QtNetworkVersion", "private", "", "public" ] }, - { symbol: [ "QtNfc", "private", "", "public" ] }, - { symbol: [ "QtNfcDepends", "private", "", "public" ] }, - { symbol: [ "QtNfcVersion", "private", "", "public" ] }, - { symbol: [ "QtNumeric", "private", "", "public" ] }, - { symbol: [ "QtOpenGL", "private", "", "public" ] }, - { symbol: [ "QtOpenGLDepends", "private", "", "public" ] }, - { symbol: [ "QtOpenGLExtensions", "private", "", "public" ] }, - { symbol: [ "QtOpenGLExtensionsDepends", "private", "", "public" ] }, - { symbol: [ "QtOpenGLExtensionsVersion", "private", "", "public" ] }, - { symbol: [ "QtOpenGLVersion", "private", "", "public" ] }, - { symbol: [ "QtPlatformHeaders", "private", "", "public" ] }, - { symbol: [ "QtPlatformHeadersDepends", "private", "", "public" ] }, - { symbol: [ "QtPlatformHeadersVersion", "private", "", "public" ] }, - { symbol: [ "QtPlatformSupport", "private", "", "public" ] }, - { symbol: [ "QtPlatformSupportDepends", "private", "", "public" ] }, - { symbol: [ "QtPlatformSupportVersion", "private", "", "public" ] }, - { symbol: [ "QtPlugin", "private", "", "public" ] }, - { symbol: [ "QtPluginInstanceFunction", "private", "", "public" ] }, - { symbol: [ "QtPluginMetaDataFunction", "private", "", "public" ] }, - { symbol: [ "QtPositioning", "private", "", "public" ] }, - { symbol: [ "QtPositioningDepends", "private", "", "public" ] }, - { symbol: [ "QtPositioningVersion", "private", "", "public" ] }, - { symbol: [ "QtPrintSupport", "private", "", "public" ] }, - { symbol: [ "QtPrintSupportDepends", "private", "", "public" ] }, - { symbol: [ "QtPrintSupportVersion", "private", "", "public" ] }, - { symbol: [ "QtQml", "private", "", "public" ] }, - { symbol: [ "QtQmlDepends", "private", "", "public" ] }, - { symbol: [ "QtQmlVersion", "private", "", "public" ] }, - { symbol: [ "QtQuick", "private", "", "public" ] }, - { symbol: [ "QtQuickDepends", "private", "", "public" ] }, - { symbol: [ "QtQuickParticles", "private", "", "public" ] }, - { symbol: [ "QtQuickParticlesDepends", "private", "", "public" ] }, - { symbol: [ "QtQuickParticlesVersion", "private", "", "public" ] }, - { symbol: [ "QtQuickTest", "private", "", "public" ] }, - { symbol: [ "QtQuickTestDepends", "private", "", "public" ] }, - { symbol: [ "QtQuickTestVersion", "private", "", "public" ] }, - { symbol: [ "QtQuickVersion", "private", "", "public" ] }, - { symbol: [ "QtQuickWidgets", "private", "", "public" ] }, - { symbol: [ "QtQuickWidgetsDepends", "private", "", "public" ] }, - { symbol: [ "QtQuickWidgetsVersion", "private", "", "public" ] }, - { symbol: [ "QtScript", "private", "", "public" ] }, - { symbol: [ "QtScriptDepends", "private", "", "public" ] }, - { symbol: [ "QtScriptTools", "private", "", "public" ] }, - { symbol: [ "QtScriptToolsDepends", "private", "", "public" ] }, - { symbol: [ "QtScriptToolsVersion", "private", "", "public" ] }, - { symbol: [ "QtScriptVersion", "private", "", "public" ] }, - { symbol: [ "QtSensors", "private", "", "public" ] }, - { symbol: [ "QtSensorsDepends", "private", "", "public" ] }, - { symbol: [ "QtSensorsVersion", "private", "", "public" ] }, - { symbol: [ "QtSerialPort", "private", "", "public" ] }, - { symbol: [ "QtSerialPortDepends", "private", "", "public" ] }, - { symbol: [ "QtSerialPortVersion", "private", "", "public" ] }, - { symbol: [ "QtSql", "private", "", "public" ] }, - { symbol: [ "QtSqlDepends", "private", "", "public" ] }, - { symbol: [ "QtSqlVersion", "private", "", "public" ] }, - { symbol: [ "QtSvg", "private", "", "public" ] }, - { symbol: [ "QtSvgDepends", "private", "", "public" ] }, - { symbol: [ "QtSvgVersion", "private", "", "public" ] }, - { symbol: [ "QtTest", "private", "", "public" ] }, - { symbol: [ "QtTestDepends", "private", "", "public" ] }, - { symbol: [ "QtTestGui", "private", "", "public" ] }, - { symbol: [ "QtTestVersion", "private", "", "public" ] }, - { symbol: [ "QtTestWidgets", "private", "", "public" ] }, - { symbol: [ "QtUiTools", "private", "", "public" ] }, - { symbol: [ "QtUiToolsDepends", "private", "", "public" ] }, - { symbol: [ "QtUiToolsVersion", "private", "", "public" ] }, - { symbol: [ "QtWebChannel", "private", "", "public" ] }, - { symbol: [ "QtWebChannelDepends", "private", "", "public" ] }, - { symbol: [ "QtWebChannelVersion", "private", "", "public" ] }, - { symbol: [ "QtWebKit", "private", "", "public" ] }, - { symbol: [ "QtWebKitDepends", "private", "", "public" ] }, - { symbol: [ "QtWebKitVersion", "private", "", "public" ] }, - { symbol: [ "QtWebKitWidgets", "private", "", "public" ] }, - { symbol: [ "QtWebKitWidgetsDepends", "private", "", "public" ] }, - { symbol: [ "QtWebKitWidgetsVersion", "private", "", "public" ] }, - { symbol: [ "QtWebSockets", "private", "", "public" ] }, - { symbol: [ "QtWebSocketsDepends", "private", "", "public" ] }, - { symbol: [ "QtWebSocketsVersion", "private", "", "public" ] }, - { symbol: [ "QtWidgets", "private", "", "public" ] }, - { symbol: [ "QtWidgetsDepends", "private", "", "public" ] }, - { symbol: [ "QtWidgetsVersion", "private", "", "public" ] }, - { symbol: [ "QtWin", "private", "", "public" ] }, - { symbol: [ "QtWinExtras", "private", "", "public" ] }, - { symbol: [ "QtWinExtrasDepends", "private", "", "public" ] }, - { symbol: [ "QtWinExtrasVersion", "private", "", "public" ] }, - { symbol: [ "QtXml", "private", "", "public" ] }, - { symbol: [ "QtXmlDepends", "private", "", "public" ] }, - { symbol: [ "QtXmlPatterns", "private", "", "public" ] }, - { symbol: [ "QtXmlPatternsDepends", "private", "", "public" ] }, - { symbol: [ "QtXmlPatternsVersion", "private", "", "public" ] }, - { symbol: [ "QtXmlVersion", "private", "", "public" ] }, - -## other things not picked up by the above - #{ symbol: [ "qobject_cast", "private", "", "public" ] }, - #{ symbol: [ "qApp", "private", "", "public" ] }, - #{ symbol: [ "qHash", "private", "", "public" ] }, - -# This is necessary because QList::toSet ends up in QSet which is wrong. See note -# at top as to why this shouldn't be necessary - - { symbol: [ "QList::toSet", "private", "", "public" ] }, - -# Even if IWYU recognised A::B as coming from a.h, we'd still need a lot of these for -# free operators - -# Generated with -# perl -le "use File::Find;use File::Basename; sub wanted { $x = lc $_. '.h'; print ' { include: [ -@\-('.basename($File::Find::dir).'/)?'.$x.'\--, -private-, -<'.$_.'>-, -public- ] },' if -e $x } find(\&wanted, '.')" -# on windows - - { include: [ "@\"(ActiveQt/)?activeqtversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(ActiveQt/)?qaxaggregated\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(ActiveQt/)?qaxbase\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(ActiveQt/)?qaxbindable\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(ActiveQt/)?qaxfactory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(ActiveQt/)?qaxobject\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(ActiveQt/)?qaxscript\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(ActiveQt/)?qaxselect\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(ActiveQt/)?qaxwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(Enginio/)?enginio\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(Enginio/)?enginioversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothaddress\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothdevicediscoveryagent\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothdeviceinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothhostinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothlocaldevice\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothserver\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothservicediscoveryagent\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothserviceinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothsocket\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothtransfermanager\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothtransferreply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothtransferrequest\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qbluetoothuuid\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qlowenergycharacteristic\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qlowenergycontroller\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qlowenergydescriptor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qlowenergyservice\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtBluetooth/)?qtbluetoothversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCLucene/)?qtcluceneversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtConcurrent/)?qtconcurrentfilter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtConcurrent/)?qtconcurrentmap\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtConcurrent/)?qtconcurrentrun\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtConcurrent/)?qtconcurrentversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qabstractanimation\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qabstracteventdispatcher\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qabstractitemmodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qabstractnativeeventfilter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qabstractproxymodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qabstractstate\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qabstracttransition\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qanimationgroup\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qarraydata\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qarraydatapointer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qbasictimer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qbitarray\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qbuffer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qbytearray\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qbytearraylist\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qbytearraymatcher\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qcache\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qchar\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qcollator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qcommandlineoption\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qcommandlineparser\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qcontiguouscache\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qcoreapplication\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qcryptographichash\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qdatastream\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qdatetime\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qdebug\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qdir\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qdiriterator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qeasingcurve\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qelapsedtimer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qeventloop\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qeventtransition\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qexception\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qfactoryinterface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qfile\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qfiledevice\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qfileinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qfileselector\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qfilesystemwatcher\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qfinalstate\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qflags\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qfuture\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qfutureinterface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qfuturesynchronizer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qfuturewatcher\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qglobalstatic\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qhash\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qhistorystate\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qidentityproxymodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qiodevice\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qitemselectionmodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qjsonarray\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qjsondocument\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qjsonobject\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qjsonvalue\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qlibrary\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qlibraryinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qline\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qlinkedlist\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qlist\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qlocale\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qlockfile\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qloggingcategory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qmap\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qmargins\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qmessageauthenticationcode\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qmetaobject\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qmetatype\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qmimedata\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qmimedatabase\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qmimetype\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qmutex\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qobject\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qobjectcleanuphandler\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qpair\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qparallelanimationgroup\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qpauseanimation\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qpluginloader\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qpoint\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qpointer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qprocess\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qpropertyanimation\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qqueue\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qreadwritelock\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qrect\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qregexp\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qregularexpression\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qresource\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qrunnable\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsavefile\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qscopedpointer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qscopedvaluerollback\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsemaphore\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsequentialanimationgroup\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qset\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsettings\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qshareddata\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsharedmemory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsharedpointer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsignalmapper\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsignaltransition\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsize\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsocketnotifier\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsortfilterproxymodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qstack\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qstandardpaths\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qstate\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qstatemachine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qstorageinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qstring\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qstringbuilder\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qstringlist\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qstringlistmodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qstringmatcher\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsysinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qsystemsemaphore\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qtcoreversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qtemporarydir\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qtemporaryfile\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qtextboundaryfinder\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qtextcodec\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qtextstream\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qthread\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qthreadpool\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qthreadstorage\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qtimeline\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qtimer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qtimezone\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qtranslator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qtypeinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qurl\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qurlquery\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?quuid\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qvariant\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qvariantanimation\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qvarlengtharray\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qvector\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qwaitcondition\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qwineventnotifier\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusabstractadaptor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusabstractinterface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusargument\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusconnection\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusconnectioninterface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbuscontext\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbuserror\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusinterface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusmessage\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusmetatype\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbuspendingcall\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbuspendingreply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusreply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusserver\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusservicewatcher\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusunixfiledescriptor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qdbusvirtualobject\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDBus/)?qtdbusversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativecomponent\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativecontext\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativeengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativeerror\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativeexpression\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativeextensioninterface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativeextensionplugin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativeimageprovider\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativeinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativeitem\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativenetworkaccessmanagerfactory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativeparserstatus\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativeproperty\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativepropertymap\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativepropertyvalueinterceptor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativepropertyvaluesource\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativescriptstring\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qdeclarativeview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDeclarative/)?qtdeclarativeversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDesigner/)?qdesignerexportwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDesigner/)?qextensionmanager\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDesigner/)?qtdesignerversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtDesignerComponents/)?qtdesignercomponentsversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qabstracttextdocumentlayout\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qaccessible\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qaccessiblebridge\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qaccessibleobject\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qaccessibleplugin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qbackingstore\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qbitmap\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qbrush\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qclipboard\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qcolor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qcursor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qdesktopservices\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qdrag\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qfont\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qfontdatabase\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qfontinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qfontmetrics\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qgenericmatrix\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qgenericplugin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qgenericpluginfactory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qglyphrun\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qguiapplication\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qicon\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qiconengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qiconengineplugin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qimage\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qimageiohandler\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qimagereader\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qimagewriter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qinputmethod\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qkeysequence\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qmatrix4x4\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qmatrix\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qmovie\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qoffscreensurface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglbuffer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglcontext\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglframebufferobject\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_1_0\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_1_1\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_1_2\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_1_3\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_1_4\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_1_5\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_2_0\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_2_1\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_3_0\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_3_1\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_3_2_compatibility\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_3_2_core\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_3_3_compatibility\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_3_3_core\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_4_0_compatibility\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_4_0_core\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_4_1_compatibility\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_4_1_core\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_4_2_compatibility\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_4_2_core\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_4_3_compatibility\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_4_3_core\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglfunctions_es2\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglpaintdevice\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglpixeltransferoptions\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglshaderprogram\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopengltexture\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopengltimerquery\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglversionfunctions\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglvertexarrayobject\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qopenglwindow\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpagedpaintdevice\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpagelayout\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpagesize\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpaintdevice\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpaintdevicewindow\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpaintengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpainter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpainterpath\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpalette\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpdfwriter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpen\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpicture\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpictureformatplugin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpixelformat\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpixmap\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpixmapcache\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qpolygon\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qquaternion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qrasterwindow\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qrawfont\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qregion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qrgb\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qscreen\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qsessionmanager\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qstandarditemmodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qstatictext\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qstylehints\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qsurface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qsurfaceformat\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qsyntaxhighlighter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtextcursor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtextdocument\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtextdocumentfragment\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtextdocumentwriter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtextformat\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtextlayout\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtextlist\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtextobject\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtextoption\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtexttable\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtguiversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtouchdevice\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qtransform\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qvalidator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qvector2d\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qvector3d\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qvector4d\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtGui/)?qwindow\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtHelp/)?qhelpcontentwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtHelp/)?qhelpengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtHelp/)?qhelpenginecore\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtHelp/)?qhelpindexwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtHelp/)?qhelpsearchengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtHelp/)?qhelpsearchquerywidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtHelp/)?qhelpsearchresultwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtHelp/)?qthelpversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qgeocodereply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qgeocodingmanager\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qgeocodingmanagerengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qgeomaneuver\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qgeoroute\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qgeoroutereply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qgeorouterequest\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qgeoroutesegment\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qgeoroutingmanager\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qgeoroutingmanagerengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qgeoserviceprovider\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qgeoserviceproviderfactory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qlocation\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplace\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplaceattribute\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacecategory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacecontactdetail\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacecontent\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacecontentreply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacecontentrequest\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacedetailsreply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplaceeditorial\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplaceicon\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplaceidreply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplaceimage\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacemanager\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacemanagerengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacematchreply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacematchrequest\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplaceproposedsearchresult\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplaceratings\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacereply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplaceresult\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacereview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacesearchreply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacesearchrequest\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacesearchresult\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacesearchsuggestionreply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplacesupplier\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qplaceuser\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtLocation/)?qtlocationversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qabstractvideobuffer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qabstractvideosurface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudio\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudiobuffer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudiodecoder\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudiodecodercontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudiodeviceinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudioencodersettingscontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudioformat\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudioinput\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudioinputselectorcontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudiooutput\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudiooutputselectorcontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudioprobe\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudiorecorder\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qaudiosystemplugin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcamera\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcameracapturebufferformatcontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcameracapturedestinationcontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcameracontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcameraexposure\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcameraexposurecontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcamerafeedbackcontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcameraflashcontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcamerafocus\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcamerafocuscontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcameraimagecapture\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcameraimagecapturecontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcameraimageprocessing\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcameraimageprocessingcontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcamerainfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcamerainfocontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcameralockscontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcameraviewfindersettingscontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qcamerazoomcontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qimageencodercontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediaaudioprobecontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediaavailabilitycontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediabindableinterface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediacontainercontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediacontent\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediacontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediagaplessplaybackcontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediametadata\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmedianetworkaccesscontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediaobject\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediaplayer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediaplayercontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediaplaylist\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediarecorder\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediarecordercontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediaresource\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediaservice\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediaserviceproviderplugin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediastreamscontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediatimerange\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmediavideoprobecontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmetadatareadercontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmetadatawritercontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qmultimedia\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qradiodata\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qradiodatacontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qradiotuner\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qradiotunercontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qsound\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qsoundeffect\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qtmultimediaversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qvideodeviceselectorcontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qvideoencodersettingscontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qvideoframe\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qvideoprobe\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qvideorenderercontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qvideosurfaceformat\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimedia/)?qvideowindowcontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimediaQuick_p/)?qsgvideonode_i420\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimediaQuick_p/)?qsgvideonode_rgb\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimediaQuick_p/)?qsgvideonode_texture\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimediaQuick_p/)?qtmultimediaquick_pversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimediaWidgets/)?qcameraviewfinder\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimediaWidgets/)?qgraphicsvideoitem\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimediaWidgets/)?qtmultimediawidgetsversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimediaWidgets/)?qvideowidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtMultimediaWidgets/)?qvideowidgetcontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qabstractnetworkcache\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qabstractsocket\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qauthenticator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qdnslookup\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qhostaddress\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qhostinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qhttpmultipart\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qlocalserver\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qlocalsocket\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qnetworkaccessmanager\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qnetworkconfiguration\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qnetworkcookie\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qnetworkcookiejar\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qnetworkdiskcache\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qnetworkinterface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qnetworkproxy\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qnetworkreply\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qnetworkrequest\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qnetworksession\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qssl\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qsslcertificate\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qsslcertificateextension\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qsslcipher\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qsslconfiguration\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qsslerror\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qsslkey\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qsslsocket\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qtcpserver\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qtcpsocket\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qtnetworkversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNetwork/)?qudpsocket\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNfc/)?qndeffilter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNfc/)?qndefmessage\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNfc/)?qndefnfcsmartposterrecord\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNfc/)?qndefnfctextrecord\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNfc/)?qndefnfcurirecord\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNfc/)?qndefrecord\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNfc/)?qnearfieldmanager\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNfc/)?qnearfieldsharemanager\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNfc/)?qnearfieldsharetarget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNfc/)?qnearfieldtarget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNfc/)?qqmlndefrecord\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtNfc/)?qtnfcversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtOpenGL/)?qgl\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtOpenGL/)?qglbuffer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtOpenGL/)?qglcolormap\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtOpenGL/)?qglframebufferobject\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtOpenGL/)?qglfunctions\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtOpenGL/)?qglpixelbuffer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtOpenGL/)?qglshaderprogram\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtOpenGL/)?qtopenglversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtOpenGLExtensions/)?qopenglextensions\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtOpenGLExtensions/)?qtopenglextensionsversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPlatformHeaders/)?qcocoanativecontext\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPlatformHeaders/)?qeglfsfunctions\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPlatformHeaders/)?qeglnativecontext\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPlatformHeaders/)?qglxnativecontext\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPlatformHeaders/)?qtplatformheadersversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPlatformHeaders/)?qwglnativecontext\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPlatformHeaders/)?qxcbwindowfunctions\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPlatformSupport/)?qtplatformsupportversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeoaddress\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeoareamonitorinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeoareamonitorsource\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeocircle\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeocoordinate\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeolocation\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeopositioninfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeopositioninfosource\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeopositioninfosourcefactory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeorectangle\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeosatelliteinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeosatelliteinfosource\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qgeoshape\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qnmeapositioninfosource\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPositioning/)?qtpositioningversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPrintSupport/)?qabstractprintdialog\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPrintSupport/)?qpagesetupdialog\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPrintSupport/)?qprintdialog\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPrintSupport/)?qprintengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPrintSupport/)?qprinter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPrintSupport/)?qprinterinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPrintSupport/)?qprintpreviewdialog\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPrintSupport/)?qprintpreviewwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtPrintSupport/)?qtprintsupportversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qjsengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qjsvalue\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qjsvalueiterator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlabstracturlinterceptor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlapplicationengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlcomponent\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlcontext\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlerror\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlexpression\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlextensioninterface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlextensionplugin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlfile\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlfileselector\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlincubator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlnetworkaccessmanagerfactory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlparserstatus\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlproperty\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlpropertymap\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlpropertyvaluesource\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qqmlscriptstring\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQml/)?qtqmlversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qquickframebufferobject\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qquickimageprovider\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qquickitem\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qquickitemgrabresult\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qquickpainteditem\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qquickrendercontrol\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qquicktextdocument\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qquickview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qquickwindow\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsgabstractrenderer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsgengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsgflatcolormaterial\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsggeometry\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsgmaterial\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsgnode\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsgsimplematerial\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsgsimplerectnode\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsgsimpletexturenode\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsgtexture\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsgtexturematerial\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsgtextureprovider\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qsgvertexcolormaterial\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuick/)?qtquickversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuickParticles/)?qtquickparticlesversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuickTest/)?qtquicktestversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuickWidgets/)?qquickwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtQuickWidgets/)?qtquickwidgetsversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptable\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptclass\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptclasspropertyiterator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptcontext\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptcontextinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptengine\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptengineagent\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptextensioninterface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptextensionplugin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptprogram\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptstring\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptvalue\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qscriptvalueiterator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScript/)?qtscriptversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScriptTools/)?qscriptenginedebugger\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtScriptTools/)?qtscripttoolsversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qaccelerometer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qaltimeter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qambientlightsensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qambienttemperaturesensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qcompass\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qdistancesensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qgyroscope\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qholstersensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qirproximitysensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qlightsensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qmagnetometer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qorientationsensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qpressuresensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qproximitysensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qrotationsensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qsensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qsensorbackend\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qsensorgesture\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qsensorgesturemanager\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qsensorgestureplugininterface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qsensorgesturerecognizer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qsensormanager\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qtapsensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qtiltsensor\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSensors/)?qtsensorsversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSerialPort/)?qlockfile\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSerialPort/)?qserialport\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSerialPort/)?qserialportinfo\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSerialPort/)?qtserialportversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSerialPort/)?qwineventnotifier\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsql\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqldatabase\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqldriver\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqldriverplugin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqlerror\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqlfield\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqlindex\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqlquery\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqlquerymodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqlrecord\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqlrelationaldelegate\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqlrelationaltablemodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqlresult\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qsqltablemodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSql/)?qtsqlversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSvg/)?qgraphicssvgitem\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSvg/)?qsvggenerator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSvg/)?qsvgrenderer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSvg/)?qsvgwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtSvg/)?qtsvgversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtTest/)?qsignalspy\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtTest/)?qtest\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtTest/)?qtestdata\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtTest/)?qtestevent\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtTest/)?qtesteventloop\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtTest/)?qttestversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtUiTools/)?qtuitoolsversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtUiTools/)?quiloader\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebChannel/)?qqmlwebchannel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebChannel/)?qtwebchannelversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebChannel/)?qwebchannel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebChannel/)?qwebchannelabstracttransport\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKit/)?qtwebkitversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKit/)?qwebdatabase\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKit/)?qwebelement\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKit/)?qwebhistory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKit/)?qwebhistoryinterface\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKit/)?qwebkitplatformplugin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKit/)?qwebpluginfactory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKit/)?qwebsecurityorigin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKit/)?qwebsettings\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKitWidgets/)?qgraphicswebview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKitWidgets/)?qtwebkitwidgetsversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKitWidgets/)?qwebframe\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKitWidgets/)?qwebinspector\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKitWidgets/)?qwebpage\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebKitWidgets/)?qwebview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebSockets/)?qmaskgenerator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebSockets/)?qtwebsocketsversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebSockets/)?qwebsocket\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebSockets/)?qwebsocketcorsauthenticator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWebSockets/)?qwebsocketserver\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qabstractbutton\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qabstractitemdelegate\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qabstractitemview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qabstractscrollarea\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qabstractslider\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qabstractspinbox\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qaccessiblemenu\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qaccessiblewidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qaction\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qactiongroup\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qapplication\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qboxlayout\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qbuttongroup\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qcalendarwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qcheckbox\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qcolordialog\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qcolormap\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qcolumnview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qcombobox\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qcommandlinkbutton\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qcommonstyle\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qcompleter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qdatawidgetmapper\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qdatetimeedit\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qdesktopwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qdial\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qdialog\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qdialogbuttonbox\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qdirmodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qdockwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qerrormessage\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qfiledialog\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qfileiconprovider\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qfilesystemmodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qfocusframe\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qfontcombobox\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qfontdialog\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qformlayout\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qframe\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgesture\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgesturerecognizer\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicsanchorlayout\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicseffect\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicsgridlayout\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicsitem\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicsitemanimation\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicslayout\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicslayoutitem\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicslinearlayout\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicsproxywidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicsscene\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicssceneevent\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicstransform\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicsview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgraphicswidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgridlayout\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qgroupbox\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qheaderview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qinputdialog\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qitemdelegate\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qitemeditorfactory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qkeyeventtransition\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qkeysequenceedit\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qlabel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qlayout\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qlayoutitem\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qlcdnumber\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qlineedit\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qlistview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qlistwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qmainwindow\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qmdiarea\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qmdisubwindow\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qmenu\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qmenubar\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qmessagebox\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qmouseeventtransition\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qopenglwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qplaintextedit\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qprogressbar\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qprogressdialog\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qproxystyle\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qpushbutton\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qradiobutton\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qrubberband\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qscrollarea\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qscrollbar\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qscroller\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qscrollerproperties\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qshortcut\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qsizegrip\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qsizepolicy\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qslider\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qspinbox\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qsplashscreen\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qsplitter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qstackedlayout\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qstackedwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qstatusbar\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qstyle\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qstyleditemdelegate\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qstylefactory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qstyleoption\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qstylepainter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qstyleplugin\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qsystemtrayicon\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtabbar\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtableview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtablewidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtabwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtextbrowser\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtextedit\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtoolbar\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtoolbox\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtoolbutton\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtooltip\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtreeview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtreewidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtreewidgetitemiterator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qtwidgetsversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qundogroup\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qundostack\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qundoview\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qwhatsthis\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qwidget\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qwidgetaction\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWidgets/)?qwizard\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWinExtras/)?qtwinextrasversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWinExtras/)?qwinevent\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWinExtras/)?qwinjumplist\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWinExtras/)?qwinjumplistcategory\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWinExtras/)?qwinjumplistitem\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWinExtras/)?qwinmime\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWinExtras/)?qwintaskbarbutton\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWinExtras/)?qwintaskbarprogress\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWinExtras/)?qwinthumbnailtoolbar\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtWinExtras/)?qwinthumbnailtoolbutton\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXml/)?qtxmlversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qabstractmessagehandler\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qabstracturiresolver\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qabstractxmlnodemodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qabstractxmlreceiver\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qsimplexmlnodemodel\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qsourcelocation\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qtxmlpatternsversion\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qxmlformatter\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qxmlname\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qxmlnamepool\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qxmlquery\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qxmlresultitems\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qxmlschema\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qxmlschemavalidator\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtXmlPatterns/)?qxmlserializer\\.h\"", "private", "", "public" ] }, - -# And lastly, things stored in difficult places - { include: [ "@\"(QtCore/)?qobjectdefs\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qglobal\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qnamespace\\.h\"", "private", "", "public" ] }, - { include: [ "@\"(QtCore/)?qlogging\\.h\"", "private", "", "public" ] }, #qDebug, qWarning, etc - { include: [ "@\"(QtCore/)?qalgorithms\\.h\"", "private", "", "public" ] }, #qSort, etc - { include: [ "@\"(QtWinExtras/)?qwinfunctions\\.h\"", "private", "", "public" ] }, # for fromHICON - -# These ones are just madness. For instance, why with the above do we get -# #include "QtCore/qcoreevent.h" // for QEvent (ptr only), etc - { include: [ "@\"(QtCore/)?qcoreevent\\.h\"", "private", "", "public" ] }, - -# These ones seem spurious -#include "QtCore/qtypetraits.h" // for remove_reference<>::type -#include "QtCore/qsharedpointer_impl.h" // for swap -#include "QtCore/qatomic_msvc.h" - -] diff --git a/scons_configure_template.py b/scons_configure_template.py deleted file mode 100644 index 67a8fad6b..000000000 --- a/scons_configure_template.py +++ /dev/null @@ -1,33 +0,0 @@ -# This python script contains the configuration for scons -# Copy this to scons_configure.py and adjust to taste. - -# Path to your boost install - it should have a boost/ subdirectory and a stage/ -# subdirectory. The scons script will use stage/lib if there, or the appropriate -# version for your compiler, if you installed the multiple-build version -BOOSTPATH = r"C:\Apps\boost_1_55_0" - -# Version of Visual Studio to use, if you wish to use a specific version. If you -# don't specify a version, the latest will be picked.. See the scons manual for -# supported values. -#MSVC_VERSION = '10.0Exp' - -# Path to your python install -# You don't really need to set this up but you might if (say) you have a 32- and -# 64-bit python install and scons has been installed for the 64 bit version -#PYTHONPATH=r"C:\Apps\Python" - -# Path to your QT install. This might constrain the version of MSVC you can use. -# This seems to be set by QTCreator -#QTDIR = r"C:\Apps\Qt\4.8.6" - -# Path to 7-zip sources -SEVENZIPPATH = r"C:\Apps\7-Zip\7z920" - -# Path to zlib. Please read the README file for more information about how this -# needs to be set up -ZLIBPATH = r"C:\Apps\zlib-1.2.8" - -# Source control programs. Sadly I can't get this information from qt, even -# though you have to set it up in the configuration -GIT = r"C:\Program Files\git\bin\git.exe" -MERCURIAL = r"C:\Program Files\TortoiseHg\hg.exe" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7188c03b8..10e71e6b4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,36 +1,106 @@ cmake_minimum_required(VERSION 3.16) +find_package(mo2-cmake CONFIG REQUIRED) + +find_package(usvfs CONFIG REQUIRED) + +find_package(mo2-uibase CONFIG REQUIRED) +find_package(mo2-archive CONFIG REQUIRED) +find_package(mo2-lootcli-header CONFIG REQUIRED) +find_package(mo2-bsatk CONFIG REQUIRED) +find_package(mo2-esptk CONFIG REQUIRED) +find_package(mo2-dds-header CONFIG REQUIRED) +find_package(mo2-libbsarch CONFIG REQUIRED) + +find_package(Qt6 REQUIRED COMPONENTS WebEngineWidgets WebSockets) +find_package(Boost CONFIG REQUIRED COMPONENTS program_options thread interprocess signals2 uuid accumulators) +find_package(7zip CONFIG REQUIRED) +find_package(lz4 CONFIG REQUIRED) +find_package(ZLIB REQUIRED) + add_executable(organizer) -set_target_properties(organizer PROPERTIES OUTPUT_NAME "ModOrganizer") -mo2_configure_executable(organizer - WARNINGS OFF - EXTRA_TRANSLATIONS ${MO2_SUPER_PATH}/game_gamebryo/src ${MO2_UIBASE_PATH}/src - PRIVATE_DEPENDS - uibase githubpp bsatk esptk archive usvfs lootcli boost::program_options - DirectXTex libbsarch Qt::WebEngineWidgets Qt::WebSockets) -target_link_libraries(organizer PUBLIC Shlwapi) -target_include_directories(organizer PUBLIC ${DDS_ROOT}) -mo2_install_target(organizer) +set_target_properties(organizer PROPERTIES + OUTPUT_NAME "ModOrganizer" + WIN32_EXECUTABLE TRUE) + +# disable translations because we want to be able to install somewhere else if +# required +mo2_configure_target(organizer WARNINGS 4 TRANSLATIONS OFF) + +# we add translations "manually" to handle MO2_INSTALL_IS_BIN +mo2_add_translations(organizer + INSTALL_RELEASE + INSTALL_DIRECTORY "${_bin}/translations" + SOURCES ${CMAKE_CURRENT_SOURCE_DIR}) + +mo2_set_project_to_run_from_install( + organizer EXECUTABLE ${CMAKE_INSTALL_PREFIX}/${_bin}/ModOrganizer.exe) + +target_link_libraries(organizer PRIVATE + Shlwapi Bcrypt + usvfs::usvfs mo2::uibase mo2::archive mo2::libbsarch + mo2::bsatk mo2::esptk mo2::lootcli-header + Boost::program_options Boost::signals2 Boost::uuid Boost::accumulators + Qt6::WebEngineWidgets Qt6::WebSockets Version Dbghelp) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.qt6" - DESTINATION bin/dlls + DESTINATION ${_bin}/dlls CONFIGURATIONS Release RelWithDebInfo RENAME dlls.manifest) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.debug.qt6" - DESTINATION bin/dlls + DESTINATION ${_bin}/dlls CONFIGURATIONS Debug RENAME dlls.manifest) -install(DIRECTORY - "${CMAKE_CURRENT_SOURCE_DIR}/stylesheets" - "${CMAKE_CURRENT_SOURCE_DIR}/tutorials" - DESTINATION bin) +if (NOT MO2_SKIP_STYLESHEETS_INSTALL) + install( + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/stylesheets" + DESTINATION ${_bin}) +endif() + +if (NOT MO2_SKIP_TUTORIALS_INSTALL) + install( + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tutorials" + DESTINATION ${_bin}) +endif() install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/resources/markdown.html" - DESTINATION bin/resources) + DESTINATION ${_bin}/resources) + +# install ModOrganizer.exe itself +install(FILES $ DESTINATION ${_bin}) + +# install dependencies DLLs +install(FILES $ DESTINATION ${_bin}/dlls) +install(FILES $ DESTINATION ${_bin}/dlls) +install(FILES $ DESTINATION ${_bin}/dlls) + +# this may copy over the ones from uibase/usvfs +# - when building with mob, this should not matter as the files should be identical +# - when building standalone, this should help having matching USVFS DLL between the +# build and the installation +# - this may cause issue with uibase in standalone mode if the installed version does +# not match the one used for the build, but there would be other issue anyway (e.g. +# different uibase.dll between modorganizer and plugins) +# +install(FILES + $ + $ + $ + $ + $ +DESTINATION ${_bin}) + +# do not install PDB if CMAKE_INSTALL_PREFIX is "bin" +if (NOT MO2_INSTALL_IS_BIN) + install(FILES $ DESTINATION pdb) +endif() -mo2_deploy_qt(BINARIES ModOrganizer.exe uibase.dll plugins/bsa_packer.dll) +mo2_deploy_qt( + DIRECTORY ${_bin} + BINARIES ModOrganizer.exe $) +# set source groups for VS mo2_add_filter(NAME src/application GROUPS iuserinterface commandline @@ -49,14 +119,15 @@ mo2_add_filter(NAME src/browser GROUPS ) mo2_add_filter(NAME src/categories GROUPS - categories + categories categoriestable - categoriesdialog + categoriesdialog categoryimportdialog ) mo2_add_filter(NAME src/core GROUPS archivefiletree + githubpp installationmanager nexusinterface nxmaccessmanager diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro deleted file mode 100644 index c4fd534eb..000000000 --- a/src/ModOrganizer.pro +++ /dev/null @@ -1,60 +0,0 @@ -TEMPLATE = subdirs - -SUBDIRS = bsatk \ - shared \ - uibase \ - esptk \ - organizer \ - hookdll \ - archive \ - helper \ - plugins \ - nxmhandler \ - BossDummy \ - pythonRunner \ - loot_cli - -pythonRunner.depends = uibase -plugins.depends = pythonRunner uibase -hookdll.depends = shared -organizer.depends = shared uibase plugins - -CONFIG(debug, debug|release) { - DESTDIR = $$PWD/../outputd -} else { - DESTDIR = $$PWD/../output -} - -STATICDATAPATH = $${DESTDIR}\\..\\tools\\static_data\\dlls -DLLSPATH = $${DESTDIR}\\dlls - -otherlibs.path = $$DLLSPATH -otherlibs.files += $${STATICDATAPATH}\\7z.dll \ - $${BOOSTPATH}\\stage\\lib\\boost_python-vc*-mt-1*.dll - -qtlibs.path = $$DLLSPATH - -greaterThan(QT_MAJOR_VERSION, 4) { - QTLIBNAMES += Core Gui Network OpenGL Script Sql Svg Qml Quick Webkit Widgets Xml XmlPatterns -} else { - QTLIBNAMES += Core Declarative Gui Network OpenGL Script Sql Svg Webkit Xml XmlPatterns -} - -greaterThan(QT_MAJOR_VERSION, 5) { - QTLIBNAMES += OpenGLWidgets -} - -QTLIBSUFFIX = $${QT_MAJOR_VERSION}.dll -CONFIG(debug, debug|release): QTLIBSUFFIX = "d$${QTLIBSUFFIX}" # Can't use Debug: .. here, it ignores the line - no idea why, as it works in BossDummy.pro - -for(QTNAME, QTLIBNAMES) { - QTFILE = Qt$${QTNAME} - qtlibs.files += $$[QT_INSTALL_BINS]\\$${QTFILE}$${QTLIBSUFFIX} -} - -INSTALLS += qtlibs otherlibs - -OTHER_FILES +=\ - ../SConstruct\ - ../scons_configure.py\ - SConscript diff --git a/src/SConscript b/src/SConscript deleted file mode 100644 index 6de7cb62a..000000000 --- a/src/SConscript +++ /dev/null @@ -1,179 +0,0 @@ -import ctypes -import os -import subprocess - -def resolve_name(source): - # Get the actual name of the file, after reparse points and symlinks are - # taken into account. - GENERIC_READ = 0x80000000 - FILE_SHARE_READ = 0x1 - OPEN_EXISTING = 0x3 - FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 - handle = ctypes.windll.kernel32.CreateFileA(source, - GENERIC_READ, - FILE_SHARE_READ, - None, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - None) - # get the target - FILE_NAME_NORMALIZED = 0x0 - FILE_NAME_OPENED = 0x8 - buff = ctypes.create_string_buffer(1024) - res = ctypes.windll.kernel32.GetFinalPathNameByHandleA(handle, - buff, - ctypes.sizeof(buff), - FILE_NAME_NORMALIZED) - target = buff.value - ctypes.windll.kernel32.CloseHandle(handle) - return target - -def search_up(path, target): - while True: - if os.path.exists(os.path.join(path, target)): - return True - npath = os.path.dirname(path) - if npath == path: - break - path = npath - return False - -Import('qt_env') - -env = qt_env.Clone() - -modules = [ - 'Core', - 'Gui', - 'Network', - 'Script', - 'Sql', - 'WebKit', - 'Xml', - 'XmlPatterns', - 'Declarative' -] - -if env['QT_MAJOR_VERSION'] > 4: - modules += [ - 'Widgets', - 'Qml', - 'WebKitWidgets' - ] - -env.EnableQtModules(*modules) - -env.Uic(env.Glob('*.ui')) - -env.RequireLibraries('uibase', 'shared', 'bsatk', 'esptk') - -env.AppendUnique(LIBS = [ - 'shell32', - 'user32', - 'ole32', - 'advapi32', - 'gdi32', - 'shlwapi', - 'Psapi', - 'Version' -]) - -# We have to 'persuade' moc to generate certain other targets and inject them -# into the list of cpps -other_sources = env.AddExtraMoc(env.Glob('*.h')) - -for file in env.Glob('*.rc'): - other_sources.append(env.RES(file)) - -# Note the order of this is important, or you can pick up the wrong report.h... -# Doing appendunique seems to throw the moc code into a tizzy -env['CPPPATH'] += [ - '../archive', - '../plugins/gamefeatures', - '.', # Why is this necessary? - '${LOOTPATH}', - '${BOOSTPATH}', -] - -#########################FUDGE############################### -env['CPPPATH'] += [ - '../plugins/gameGamebryo', - ] -############################################################# - -env.AppendUnique(CPPDEFINES = [ - '_UNICODE', - '_CRT_SECURE_NO_WARNINGS', - '_SCL_SECURE_NO_WARNINGS', - 'BOOST_DISABLE_ASSERTS', - 'NDEBUG', - 'QT_MESSAGELOGCONTEXT' -]) - -# Boost produces very long names with msvc truncates. Doesn't seem to cause -# problems. -# Also note to remove the -wd4100 I hacked the boost headers (tagged_argument.hpp) -# appropriately. -env.AppendUnique(CPPFLAGS = [ '-wd4503' ]) - -env.AppendUnique(LINKFLAGS = [ - '/SUBSYSTEM:WINDOWS', - '${EXE_MANIFEST_DEPENDENCY}' -]) - -# modeltest is optional and it doesn't compile anyway... -cpp_files = [ - x for x in env.Glob('*.cpp', source = True) - if x.name != 'modeltest.cpp' and x.name != 'aboutdialog.cpp' and \ - not x.name.startswith('moc_') # I think this is a strange bug -] - -about_env = env.Clone() -# This is somewhat of a hack until I can work out a way of setting up a build -# with all the repos without using millions of junction points -try: - target = resolve_name(Dir('.').srcnode().abspath) - if search_up(target, '.hg'): - hgid = subprocess.check_output([env['MERCURIAL'], 'id', '-i']).rstrip() - elif search_up(target, '.git'): - hgid = subprocess.check_output([env['GIT'], '-C', target, 'describe', - '--tag']).rstrip() - else: - hgid = "Unknown" -except: - hgid = "Problem determining version" - -# FIXME: It'd be much easier to stringify this in the source code -about_env.AppendUnique(CPPDEFINES = [ 'HGID=\\"%s\\"' % hgid ]) -other_sources.append(about_env.StaticObject('aboutdialog.cpp')) - -env.AppendUnique(LIBPATH = "${ZLIBPATH}/build") -env.AppendUnique(LIBS = 'zlibstatic') - -prog = env.Program('ModOrganizer', - cpp_files + env.Glob('*.qrc') + other_sources) - -env.InstallModule(prog) - -for subdir in ('tutorials', 'stylesheets'): - env.Install(os.path.join(env['INSTALL_PATH'], subdir), - env.Glob(os.path.join(subdir, '*'))) - -# FIXME Sort the translations. Except they don't exist on the 1.2 branch - -res = env['QT_USED_MODULES'] -Return('res') - -""" -CONFIG(debug, debug|release) { -} else { - QMAKE_CXXFLAGS += /Zi /GL - QMAKE_LFLAGS += /DEBUG /LTCG /OPT:REF /OPT:ICF -} - -TRANSLATIONS = organizer_en.ts - - -QMAKE_POST_LINK += xcopy /y /s /I $$quote($$BASEDIR\\*.qm) $$quote($$DSTDIR)\\translations $$escape_expand(\\n) - -""" diff --git a/src/archivefiletree.h b/src/archivefiletree.h index 3b95769d0..89ef5be0b 100644 --- a/src/archivefiletree.h +++ b/src/archivefiletree.h @@ -20,8 +20,8 @@ along with Mod Organizer. If not, see . #ifndef ARCHIVEFILENETRY_H #define ARCHIVEFILENTRY_H -#include "archive.h" -#include "ifiletree.h" +#include +#include /** * diff --git a/src/github.cpp b/src/github.cpp new file mode 100644 index 000000000..1c91cface --- /dev/null +++ b/src/github.cpp @@ -0,0 +1,204 @@ +#include "github.h" +#include +#include +#include + +#include +#include + +static const QString GITHUB_URL("https://api.github.com"); +static const QString USER_AGENT("GitHubPP"); + +GitHub::GitHub(const char* clientId) : m_AccessManager(new QNetworkAccessManager(this)) +{} + +GitHub::~GitHub() +{ + // delete all the replies since they depend on the access manager, which is + // about to be deleted + for (auto* reply : m_replies) { + reply->disconnect(); + delete reply; + } +} + +QJsonArray GitHub::releases(const Repository& repo) +{ + QJsonDocument result = request( + Method::GET, QString("repos/%1/%2/releases").arg(repo.owner, repo.project), + QByteArray(), true); + return result.array(); +} + +void GitHub::releases(const Repository& repo, + const std::function& callback) +{ + request( + Method::GET, QString("repos/%1/%2/releases").arg(repo.owner, repo.project), + QByteArray(), + [callback](const QJsonDocument& result) { + callback(result.array()); + }, + true); +} + +QJsonDocument GitHub::handleReply(QNetworkReply* reply) +{ + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (statusCode != 200) { + return QJsonDocument(QJsonObject( + {{"http_status", statusCode}, + {"redirection", + reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString()}, + {"reason", + reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()}})); + } + + QByteArray data = reply->readAll(); + if (data.isNull() || data.isEmpty() || (strcmp(data.constData(), "null") == 0)) { + return QJsonDocument(); + } + + QJsonParseError parseError; + QJsonDocument result = QJsonDocument::fromJson(data, &parseError); + + if (parseError.error != QJsonParseError::NoError) { + return QJsonDocument(QJsonObject({{"parse_error", parseError.errorString()}})); + } + + return result; +} + +QNetworkReply* GitHub::genReply(Method method, const QString& path, + const QByteArray& data, bool relative) +{ + QNetworkRequest request(relative ? GITHUB_URL + "/" + path : path); + + request.setHeader(QNetworkRequest::UserAgentHeader, USER_AGENT); + request.setRawHeader("Accept", "application/vnd.github.v3+json"); + + switch (method) { + case Method::GET: + return m_AccessManager->get(request); + case Method::POST: + return m_AccessManager->post(request, data); + default: + // this shouldn't be possible as all enum options are handled + throw std::runtime_error("invalid method"); + } +} + +QJsonDocument GitHub::request(Method method, const QString& path, + const QByteArray& data, bool relative) +{ + QEventLoop wait; + QNetworkReply* reply = genReply(method, path, data, relative); + + connect(reply, SIGNAL(finished), &wait, SLOT(quit())); + wait.exec(); + QJsonDocument result = handleReply(reply); + reply->deleteLater(); + + QJsonObject object = result.object(); + if (object.value("http_status").toDouble() == 301.0) { + return request(method, object.value("redirection").toString(), data, false); + } else { + return result; + } +} + +void GitHub::request(Method method, const QString& path, const QByteArray& data, + const std::function& callback, + bool relative) +{ + // make sure the timer is owned by this so it's deleted correctly and + // doesn't fire after the GitHub object is destroyed; this happens when + // restarting MO by switching instances, for example + QTimer* timer = new QTimer(this); + timer->setSingleShot(true); + timer->setInterval(10000); + + QNetworkReply* reply = genReply(method, path, data, relative); + + // remember this reply so it can be deleted in the destructor if necessary + m_replies.push_back(reply); + + Request req = {method, data, callback, timer, reply}; + + // finished + connect(reply, &QNetworkReply::finished, [this, req] { + onFinished(req); + }); + + // error + connect(reply, qOverload(&QNetworkReply::errorOccurred), + [this, req](auto&& error) { + onError(req, error); + }); + + // timeout + connect(timer, &QTimer::timeout, [this, req] { + onTimeout(req); + }); + + timer->start(); +} + +void GitHub::onFinished(const Request& req) +{ + QJsonDocument result = handleReply(req.reply); + QJsonObject object = result.object(); + + req.timer->stop(); + + if (object.value("http_status").toInt() == 301) { + request(req.method, object.value("redirection").toString(), req.data, req.callback, + false); + } else { + req.callback(result); + } + + deleteReply(req.reply); +} + +void GitHub::onError(const Request& req, QNetworkReply::NetworkError error) +{ + // the only way the request can be aborted is when there's a timeout, which + // already logs a message + if (error != QNetworkReply::OperationCanceledError) { + qCritical().noquote().nospace() + << "Github: request for " << req.reply->url().toString() << " failed, " + << req.reply->errorString() << " (" << error << ")"; + } + + req.timer->stop(); + req.reply->disconnect(); + + QJsonObject root({{"network_error", req.reply->errorString()}}); + QJsonDocument doc(root); + + req.callback(doc); + + deleteReply(req.reply); +} + +void GitHub::onTimeout(const Request& req) +{ + qCritical().noquote().nospace() + << "Github: request for " << req.reply->url().toString() << " timed out"; + + // don't delete the reply, abort will fire the error() handler above + req.reply->abort(); +} + +void GitHub::deleteReply(QNetworkReply* reply) +{ + // remove from the list + auto itor = std::find(m_replies.begin(), m_replies.end(), reply); + if (itor != m_replies.end()) { + m_replies.erase(itor); + } + + // delete + reply->deleteLater(); +} diff --git a/src/github.h b/src/github.h new file mode 100644 index 000000000..0085fd5ac --- /dev/null +++ b/src/github.h @@ -0,0 +1,108 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +class GitHubException : public std::exception +{ +public: + GitHubException(const QJsonObject& errorObj) : std::exception() + { + initMessage(errorObj); + } + + virtual ~GitHubException() throw() override {} + + virtual const char* what() const throw() { return m_Message.constData(); } + +private: + void initMessage(const QJsonObject& obj) + { + if (obj.contains("http_status")) { + m_Message = QString("HTTP Status %1: %2") + .arg(obj.value("http_status").toInt()) + .arg(obj.value("reason").toString()) + .toUtf8(); + } else if (obj.contains("parse_error")) { + m_Message = QString("Parsing failed: %1") + .arg(obj.value("parse_error").toString()) + .toUtf8(); + } else if (obj.contains("network_error")) { + m_Message = QString("Network failed: %1") + .arg(obj.value("network_error").toString()) + .toUtf8(); + } else { + m_Message = "Unknown error"; + } + } + + QByteArray m_Message; +}; + +class GitHub : public QObject +{ + + Q_OBJECT + +public: + enum class Method + { + GET, + POST + }; + + struct Repository + { + Repository(const QString& owner, const QString& project) + : owner(owner), project(project) + {} + QString owner; + QString project; + }; + +public: + GitHub(const char* clientId = nullptr); + ~GitHub(); + + QJsonArray releases(const Repository& repo); + void releases(const Repository& repo, + const std::function& callback); + +private: + QJsonDocument request(Method method, const QString& path, const QByteArray& data, + bool relative); + void request(Method method, const QString& path, const QByteArray& data, + const std::function& callback, + bool relative); + + QJsonDocument handleReply(QNetworkReply* reply); + QNetworkReply* genReply(Method method, const QString& path, const QByteArray& data, + bool relative); + +private: + struct Request + { + Method method = Method::GET; + QByteArray data; + std::function callback; + QTimer* timer = nullptr; + QNetworkReply* reply = nullptr; + }; + + QNetworkAccessManager* m_AccessManager; + + // remember the replies that are in flight and delete them in the destructor + std::vector m_replies; + + void onFinished(const Request& req); + void onError(const Request& req, QNetworkReply::NetworkError error); + void onTimeout(const Request& req); + + void deleteReply(QNetworkReply* reply); +}; diff --git a/src/installationmanager.h b/src/installationmanager.h index e8e975152..e125bbe94 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -20,17 +20,17 @@ along with Mod Organizer. If not, see . #ifndef INSTALLATIONMANAGER_H #define INSTALLATIONMANAGER_H -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #define WIN32_LEAN_AND_MEAN #include #include -#include -#include #include #include diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f346a3e44..cb6031ed1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -60,8 +60,6 @@ along with Mod Organizer. If not, see . #include "problemsdialog.h" #include "profile.h" #include "profilesdialog.h" -#include "report.h" -#include "savegameinfo.h" #include "savestab.h" #include "selectiondialog.h" #include "serverinfo.h" @@ -69,15 +67,17 @@ along with Mod Organizer. If not, see . #include "shared/appconfig.h" #include "spawn.h" #include "statusbar.h" -#include "tutorialmanager.h" -#include "versioninfo.h" #include #include #include #include #include -#include -#include +#include +#include +#include +#include +#include +#include #include "directoryrefresher.h" #include "shared/directoryentry.h" @@ -2884,6 +2884,12 @@ void MainWindow::languageChange(const QString& newLanguage) installTranslator("qt"); installTranslator("qtbase"); installTranslator(ToQString(AppConfig::translationPrefix())); + installTranslator("uibase"); + + // TODO: this will probably be changed once extension come out + installTranslator("game_gamebryo"); + installTranslator("game_creation"); + for (const QString& fileName : m_PluginContainer.pluginFileNames()) { installTranslator(QFileInfo(fileName).baseName()); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 42d3d5e7d..b3f4233aa 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -20,19 +20,20 @@ along with Mod Organizer. If not, see . #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include "bsafolder.h" +#include +#include +#include +#include +#include +#include + #include "delayedfilewriter.h" -#include "errorcodes.h" -#include "imoinfo.h" -#include "iplugingame.h" //namespace MOBase { class IPluginGame; } #include "iuserinterface.h" #include "modinfo.h" #include "modlistbypriorityproxy.h" #include "modlistsortproxy.h" -#include "plugincontainer.h" //class PluginContainer; +#include "plugincontainer.h" #include "shared/fileregisterfwd.h" -#include "tutorialcontrol.h" -#include class Executable; class CategoryFactory; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 0f6a14551..a8926dc65 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -29,15 +29,15 @@ #include "spawn.h" #include "syncoverwritedialog.h" #include "virtualfiletree.h" -#include #include -#include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -71,7 +71,7 @@ #include #include -#include "bs_archive.h" +#include #include "organizerproxy.h" diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 04d4462a3..90da82593 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -16,24 +16,11 @@ 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 . */ - #include "pluginlist.h" -#include "modinfo.h" -#include "modlist.h" -#include "scopeguard.h" -#include "settings.h" -#include "shared/directoryentry.h" -#include "shared/fileentry.h" -#include "shared/filesorigin.h" -#include "viewmarkingscrollbar.h" -#include "shared/windows_error.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include #include #include @@ -50,11 +37,23 @@ along with Mod Organizer. If not, see . #include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include "gameplugins.h" +#include "modinfo.h" +#include "modlist.h" #include "organizercore.h" +#include "settings.h" +#include "shared/directoryentry.h" +#include "shared/fileentry.h" +#include "shared/filesorigin.h" +#include "shared/windows_error.h" +#include "viewmarkingscrollbar.h" using namespace MOBase; using namespace MOShared; diff --git a/src/selfupdater.h b/src/selfupdater.h index cc68ad275..612e70238 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -22,8 +22,7 @@ along with Mod Organizer. If not, see . #include -#include -#include +#include class Archive; class NexusInterface; @@ -43,6 +42,8 @@ class QNetworkReply; class QProgressDialog; class Settings; +#include "github.h" + /** * @brief manages updates for Mod Organizer itself * This class is used to update the Mod Organizer diff --git a/src/settings.h b/src/settings.h index c4be0d7bc..bd58e10d4 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,11 +21,11 @@ along with Mod Organizer. If not, see . #define SETTINGS_H #include "envdump.h" -#include -#include #include #include -#include +#include +#include +#include #ifdef interface #undef interface diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 42aef8c7e..cb8d5c781 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -20,8 +20,9 @@ along with Mod Organizer. If not, see . #ifndef MO_REGISTER_DIRECTORYENTRY_INCLUDED #define MO_REGISTER_DIRECTORYENTRY_INCLUDED +#include + #include "fileregister.h" -#include namespace env { diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 55a76c235..d3c6e2011 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -21,9 +21,9 @@ along with Mod Organizer. If not, see . #include "../env.h" #include "../mainwindow.h" #include "windows_error.h" -#include -#include -#include +#include +#include +#include using namespace MOBase; diff --git a/src/spawn.cpp b/src/spawn.cpp index 83ac720e5..aad1e1cc9 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -23,20 +23,19 @@ along with Mod Organizer. If not, see . #include "envmodule.h" #include "envsecurity.h" #include "envwindows.h" -#include "report.h" #include "settings.h" #include "settingsdialogworkarounds.h" #include "shared/appconfig.h" #include "shared/windows_error.h" -#include "utility.h" #include #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include using namespace MOBase; using namespace MOShared; diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 7b75b870d..8d1d29601 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -30,7 +30,7 @@ along with Mod Organizer. If not, see . #include #include #include -#include +#include static const char SHMID[] = "mod_organizer_instance"; using namespace MOBase; diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index d578ddd49..ecf68a2ce 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -21,16 +21,16 @@ along with Mod Organizer. If not, see . #define USVFSCONNECTOR_H #include "envdump.h" -#include "executableinfo.h" #include #include #include #include #include #include -#include -#include -#include +#include +#include +#include +#include class LogWorker : public QThread { diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 000000000..0f3cef6ea --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,21 @@ +{ + "default-registry": { + "kind": "git", + "repository": "https://github.com/Microsoft/vcpkg", + "baseline": "f61a294e765b257926ae9e9d85f96468a0af74e7" + }, + "registries": [ + { + "kind": "git", + "repository": "https://github.com/Microsoft/vcpkg", + "baseline": "f61a294e765b257926ae9e9d85f96468a0af74e7", + "packages": ["boost*", "boost-*"] + }, + { + "kind": "git", + "repository": "https://github.com/ModOrganizer2/vcpkg-registry", + "baseline": "210f6e8f6eaefd6abfdf685f7deeb6dabdc78512", + "packages": ["mo2-*", "7zip", "usvfs"] + } + ] +} diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 000000000..4a8c5c2c4 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,32 @@ +{ + "dependencies": [ + "7zip", + "boost-accumulators", + "boost-assign", + "boost-date-time", + "boost-graph", + "boost-headers", + "boost-interprocess", + "boost-program-options", + "boost-signals2", + "boost-thread", + "boost-uuid", + "lz4", + "mo2-cmake", + "mo2-libbsarch", + "zlib" + ], + "features": { + "standalone": { + "description": "Build Standalone.", + "dependencies": [ + "mo2-archive", + "mo2-bsatk", + "mo2-esptk", + "mo2-lootcli-header", + "mo2-uibase", + "usvfs" + ] + } + } +} diff --git a/win.imp b/win.imp deleted file mode 100644 index 246357755..000000000 --- a/win.imp +++ /dev/null @@ -1,91 +0,0 @@ -[ - # Microsft visual C? - - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, - - { include: [ "", "private", "", "public" ] }, - -# Windows -# Looks like the documentation says the 1st char is u/c the rest are l/c - -# You have to be kidding me. ULONG is defined in winsmcrd.h? - { symbol: [ "ULONG", "private", "", "private" ] }, - - { include: [ "", "private", "", "private" ] }, # Stringapiset.h - { include: [ "", "private", "", "public" ] }, - -# These are all in windef.h apparently. Which m/s then says 'use Windows.h' - { include: [ "", "private", "", "private" ] }, # or in winnt apparently - { include: [ "", "private", "", "private" ] }, - { include: [ "", "private", "", "private" ] }, - { include: [ "", "private", "", "private" ] }, - -# Similary, but for winbase.h - { include: [ "", "private", "", "private" ] }, - { include: [ "", "private", "", "private" ] }, - { include: [ "", "private", "", "private" ] }, - { include: [ "", "private", "", "private" ] }, - { include: [ "", "private", "", "private" ] }, - { include: [ "", "private", "", "private" ] }, - -# These ones say xxxx.h (include Windows.h) on the ms web site - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, # VerRsrc.h - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, - -# These ones are in Windows.h but the documentation post windows 8 says they are individual headers, -# which looks like M/S are trying to get their act together. Maybe. - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, - -# These ones are *not* defined to be in Windows.h, but it seems to work. These should probably be cleaned up - { include: [ "", "private", "", "public" ] }, - # These 3 should go to Shellapi.h - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, # official name according to website - { include: [ "", "private", "", "public" ] }, - # - { include: [ "", "private", "", "public" ] }, - { include: [ "", "private", "", "public" ] }, - -# These ones are in shtypes.h but the only documentation I can find says -# to include Knownfolders.h for these - { symbol: [ "REFKNOWNFOLDERID", "private", "", "public" ] }, - { symbol: [ "KNOWNFOLDERID", "private", "", "public" ] }, - -# IWYU doesn't understand upper/lower case which is *really* annoying on -# windows, though to be fair I'm not sure M/S understand it either. - { include: [ "", "private", "", "public" ] }, - -# Files that are included by other files which seem to then come for free in Windows.h but -# shouldn't. Again, should be cleaned up. - - { include: [ "", "private", "", "private" ] }, - { include: [ "", "private", "", "private" ] }, - { include: [ "", "private", "", "public" ] }, - - { include: [ "", "private", "", "private" ] }, - { include: [ "", "private", "", "public" ] }, - - { include: [ "", "private", "", "private" ] }, - { include: [ "", "private", "", "public" ] }, - -# Huh? This one is sane? - { include: [ "", "private", "", "public" ] }, - - -] - -#include // for operator delete[], etc - -#include // for _Simple_types<>::value_type -#include // for _Tree_const_iterator From d0d0a983bb48e276e4f49b764262f502be3bb3db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 5 Aug 2024 15:39:24 +0200 Subject: [PATCH 02/26] Switch VersionInfo -> Version for ModOrganizer2. (#2063) --- .github/workflows/build.yml | 4 +- src/env.cpp | 2 +- src/mainwindow.cpp | 21 +- src/moapplication.cpp | 4 +- src/modinfo.h | 2 +- src/nexusinterface.cpp | 3 +- src/nexusinterface.h | 1 - src/organizer_en.ts | 983 +++++++--------------------------- src/organizercore.cpp | 2 +- src/organizercore.h | 46 +- src/organizerproxy.cpp | 46 +- src/organizerproxy.h | 112 ++-- src/selfupdater.cpp | 17 +- src/selfupdater.h | 10 +- src/settingsdialoggeneral.cpp | 2 +- src/shared/util.cpp | 34 +- src/shared/util.h | 7 +- vcpkg-configuration.json | 2 +- 18 files changed, 392 insertions(+), 906 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c538a949..49f4aa8e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,8 +24,8 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v3 with: - version: 6.7.0 - modules: + version: 6.7.1 + modules: qtpositioning qtwebchannel qtwebengine qtwebsockets cache: true - uses: actions/checkout@v4 diff --git a/src/env.cpp b/src/env.cpp index 9e67ef884..5dab489ed 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -1081,7 +1081,7 @@ std::wstring safeVersion() { try { // this can throw - return MOShared::createVersionInfo().displayString(3).toStdWString() + L"-"; + return MOShared::createVersionInfo().string().toStdWString() + L"-"; } catch (...) { return {}; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cb6031ed1..20a849f49 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -639,9 +639,10 @@ MainWindow::~MainWindow() void MainWindow::updateWindowTitle(const APIUserAccount& user) { //"\xe2\x80\x93" is an "em dash", a longer "-" - QString title = QString("%1 \xe2\x80\x93 Mod Organizer v%2") - .arg(m_OrganizerCore.managedGame()->displayGameName(), - m_OrganizerCore.getVersion().displayString(3)); + QString title = + QString("%1 \xe2\x80\x93 Mod Organizer v%2") + .arg(m_OrganizerCore.managedGame()->displayGameName(), + m_OrganizerCore.getVersion().string(Version::FormatCondensed)); if (!user.name().isEmpty()) { const QString premium = (user.type() == APIUserAccountTypes::Premium ? "*" : ""); @@ -1039,7 +1040,8 @@ void MainWindow::checkForProblemsImpl() void MainWindow::about() { - AboutDialog(m_OrganizerCore.getVersion().displayString(3), this).exec(); + AboutDialog(m_OrganizerCore.getVersion().string(Version::FormatCondensed), this) + .exec(); } void MainWindow::createEndorseMenu() @@ -2163,8 +2165,9 @@ void MainWindow::processUpdates() auto& settings = m_OrganizerCore.settings(); const auto earliest = QVersionNumber::fromString("2.1.2").normalized(); - const auto lastVersion = settings.version().value_or(earliest); - const auto currentVersion = m_OrganizerCore.getVersion().asQVersionNumber(); + const auto lastVersion = settings.version().value_or(earliest); + const auto currentVersion = + QVersionNumber::fromString(m_OrganizerCore.getVersion().string()).normalized(); m_LastVersion = lastVersion; @@ -2967,8 +2970,7 @@ void MainWindow::actionEndorseMO() QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { NexusInterface::instance().requestToggleEndorsement( game->gameShortName(), game->nexusModOrganizerID(), - m_OrganizerCore.getVersion().canonicalString(), true, this, QVariant(), - QString()); + m_OrganizerCore.getVersion().string(), true, this, QVariant(), QString()); } } @@ -2989,8 +2991,7 @@ void MainWindow::actionWontEndorseMO() QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { NexusInterface::instance().requestToggleEndorsement( game->gameShortName(), game->nexusModOrganizerID(), - m_OrganizerCore.getVersion().canonicalString(), false, this, QVariant(), - QString()); + m_OrganizerCore.getVersion().string(), false, this, QVariant(), QString()); } } diff --git a/src/moapplication.cpp b/src/moapplication.cpp index f06a85d1c..f621d32a9 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -203,8 +203,8 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) log::debug("command line: '{}'", QString::fromWCharArray(GetCommandLineW())); log::info("starting Mod Organizer version {} revision {} in {}, usvfs: {}", - createVersionInfo().displayString(3), GITID, - QCoreApplication::applicationDirPath(), MOShared::getUsvfsVersionString()); + createVersionInfo().string(), GITID, QCoreApplication::applicationDirPath(), + MOShared::getUsvfsVersionString()); if (multiProcess.secondary()) { log::debug("another instance of MO is running but --multiple was given"); diff --git a/src/modinfo.h b/src/modinfo.h index d9e87ae05..bbb1aae3c 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -732,7 +732,7 @@ class ModInfo : public QObject, public MOBase::IModInterface * * @note Currently, this changes the color of the cell under the "Notes" column. */ - virtual void setColor(QColor color) {} + virtual void setColor([[maybe_unused]] QColor color) {} /** * @brief Adds the information that a file has been installed into this mod. diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 359b7e413..ecd33cb67 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -274,9 +274,8 @@ NexusInterface::NexusInterface(Settings* s) : m_PluginContainer(nullptr) g_instance = this; m_User.limits(defaultAPILimits()); - m_MOVersion = createVersionInfo(); - m_AccessManager = new NXMAccessManager(this, s, m_MOVersion.displayString(3)); + m_AccessManager = new NXMAccessManager(this, s, createVersionInfo().string()); m_DiskCache = new QNetworkDiskCache(this); diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 5fab222f1..f6efef1d9 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -681,7 +681,6 @@ private slots: NXMAccessManager* m_AccessManager; std::list m_ActiveRequest; QQueue m_RequestQueue; - MOBase::VersionInfo m_MOVersion; PluginContainer* m_PluginContainer; APIUserAccount m_User; }; diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 46169a5ad..5715889bd 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -2164,43 +2164,6 @@ Right now the only case I know of where this needs to be overwritten is for the - - FindDialog - - - Find - - - - - Find what: - - - - - - Search term - - - - - - Find next occurence from current file position. - - - - - &Find Next - - - - - - - Close - - - ForcedLoadDialog @@ -2280,139 +2243,6 @@ Right now the only case I know of where this needs to be overwritten is for the - - GamebryoModDataContent - - - Plugins (ESP/ESM/ESL) - - - - - Optional Plugins - - - - - Interface - - - - - Meshes - - - - - Bethesda Archive - - - - - Scripts (Papyrus) - - - - - Script Extender Plugin - - - - - Script Extender Files - - - - - SkyProc Patcher - - - - - Sound or Music - - - - - Textures - - - - - MCM Configuration - - - - - INI Files - - - - - FaceGen Data - - - - - ModGroup Files - - - - - GamebryoSaveGameInfoWidget - - - Save # - - - - - Character - - - - - Level - - - - - Location - - - - - Date - - - - - Has Script Extender Data - - - - - Missing ESPs - - - - - - - None - - - - - Missing ESHs - - - - - Missing ESLs - - - GeneralConflictsTab @@ -2936,100 +2766,6 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - - MOBase::FilterWidget - - - Filter options - - - - - Use regular expressions - - - - - Use regular expressions in filters - - - - - Case sensitive - - - - - Make regular expressions case sensitive (/i) - leave "(/i)" verbatim - - - - - Extended - - - - - Ignores unescaped whitespace in regular expressions (/x) - leave "(/x)" verbatim - - - - - Keep selection in view - - - - - Scroll to keep the current selection in view after filtering - - - - - MOBase::TextViewer - - - Save changes? - - - - - Do you want to save changes to %1? - - - - - failed to write to %1 - - - - - file not found: %1 - - - - - Save - - - - - MOBase::TutorialControl - - - Tutorial failed to start, please check "mo_interface.log" for details. - - - - - MOBase::TutorialManager - - - tutorial manager not set up yet - - - MOMultiProcess @@ -3267,7 +3003,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Sort the plugins using LOOT. @@ -3401,7 +3137,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Name @@ -3659,7 +3395,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Endorse Mod Organizer @@ -3770,142 +3506,142 @@ Error: %1 - + There are notifications to read - + There are no notifications - + Endorse - + Won't Endorse - + First Steps Translation strings for tutorial names - + Conflict Resolution - + Overview - + Help on UI - + Documentation - - + + Game Support Wiki - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help" menu. - + Never ask to show tutorials - + Do you know how to mod this game? Do you need to learn? There's a game support wiki available! Click OK to open the wiki. In the future, you can access this link from the "Help" menu. - + Category Setup - + Please choose how to handle the default category setup. If you've already connected to Nexus, you can automatically import Nexus categories for this game (if applicable). Otherwise, use the old Mod Organizer default category structure, or leave the categories blank (for manual setup). - - + + &Import Nexus Categories - + Use &Old Category Defaults - + Do &Nothing - + This is your first time running version 2.5 or higher with an old MO2 instance. The category system now relies on an updated system to map Nexus categories. In order to assign Nexus categories automatically, you will need to import the Nexus categories for the currently managed game and map them to your preferred category structure. @@ -3916,321 +3652,321 @@ As a final option, you can disable Nexus category mapping altogether, which can - + &Open Categories Dialog - + &Disable Nexus Mappings - + &Close - + &Don't show this again - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + Update available - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + None of your %1 mods appear to have had recent file updates. - + All of your mods have been checked recently. We restrict update checks to help preserve your available API requests. - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Error %1: Request to Nexus failed: %2 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + Remove '%1' from the toolbar - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file @@ -5774,32 +5510,32 @@ Please enter a name: NexusInterface - + Please pick the mod ID for "%1" - + You must authorize MO2 in Settings -> Nexus to use the Nexus API. - + You've exceeded the Nexus API rate limit and requests are now being throttled. Your next batch of requests will be available in approximately %1 minutes and %2 seconds. - + Aborting download: Either you clicked on a premium-only link and your account is not premium, or the download link was generated by a different account than the one stored in Mod Organizer. - + empty response - + invalid response @@ -6266,168 +6002,168 @@ Continue? PluginList - + Name - + Priority - + Mod Index - + Flags - - + + unknown - + Name of the plugin - + Emblems to highlight things that might require attention. - + Load priority of plugins. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - + Determines the formids of objects originating from this mods. - + failed to update esp info for file %1 (source id: %2), error: %3 - + Plugin not found: %1 - + Origin - + This plugin can't be disabled or moved (enforced by the game). - + This plugin can't be disabled (enforced by the game). - + Author - + Description - + Missing Masters - + Enabled Masters - + Loads Archives - + There are Archives connected to this plugin. Their assets will be added to your game, overwriting in case of conflicts following the plugin order. Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin) - + Loads INI settings - + There is an ini file connected to this plugin. Its settings will be added to your game settings, overwriting in case of conflicts. - + This %1 is flagged as a light plugin (ESL). It will adhere to the %1 load order but the records will be loaded in ESL space (FE/FF). You can have up to 4096 light plugins in addition to other plugin types. - + This ESM is flagged as a medium plugin (ESH). It adheres to the ESM load order but loads records in ESH space (FD). You can have 256 medium plugins in addition to other plugin types. - + WARNING: This plugin is both light and medium flagged. This could indicate that the file was saved improperly and may have mismatched record references. Use it at your own risk. - + This is a dummy plugin. It contains no records and is typically used to load a paired archive file. - + Light plugins (ESL) are not supported by this game. - + This game does not currently permit custom plugin loading. There may be manual workarounds. - + Incompatible with %1 - + Depends on missing %1 - + Warning - + Error - + failed to restore load order for %1 @@ -7249,13 +6985,13 @@ p, li { white-space: pre-wrap; } - - - - - - - + + + + + + + Cancel @@ -7504,8 +7240,6 @@ Destination: - - Error @@ -7618,23 +7352,23 @@ Destination: - + Please use "Help" from the toolbar to get usage instructions to all elements - + Visit %1 on Nexus - - + + <Manage...> - + failed to parse profile %1: %2 @@ -7782,12 +7516,12 @@ Destination: - + failed to access %1 - + failed to set file time %1 @@ -8089,196 +7823,196 @@ Example: - + This error typically happens because an antivirus has deleted critical files from Mod Organizer's installation folder or has made them generally inaccessible. Add an exclusion for Mod Organizer's installation folder in your antivirus, reinstall Mod Organizer and try again. - + This error typically happens because an antivirus is preventing Mod Organizer from starting programs. Add an exclusion for Mod Organizer's installation folder in your antivirus and try again. - + The file '%1' does not exist. - + The working directory '%1' does not exist. + - + - Cannot start Steam - + The path to the Steam executable cannot be found. You might try reinstalling Steam. - - - + + + Continue without starting Steam - - + + The program may fail to launch. - + Cannot launch program - - - + + + Cannot start %1 - + Cannot launch helper - - + + Elevation required - + This program is requesting to run as administrator but Mod Organizer itself is not running as administrator. Running programs as administrator is typically unnecessary as long as the game and Mod Organizer have been installed outside "Program Files". You can restart Mod Organizer as administrator and try launching the program again. - - + + Restart Mod Organizer as administrator - - + + You must allow "helper.exe" to make changes to the system. - + Launch Steam - + This program requires Steam - + Mod Organizer has detected that this program likely requires Steam to be running to function properly. - + Start Steam - - + + The program might fail to run. - + Steam is running as administrator - + Running Steam as administrator is typically unnecessary and can cause problems when Mod Organizer itself is not running as administrator. You can restart Mod Organizer as administrator and try launching the program again. - - - + + + Continue - + Event Log not running - + The Event Log service is not running - + The Windows Event Log service is not running. This can prevent USVFS from running properly and your mods may not be recognized by the program being launched. - - + + Your mods might not work. - + Blacklisted program - + The program %1 is blacklisted - + The program you are attempting to launch is blacklisted in the virtual filesystem. This will likely prevent it from seeing any mods, INI files or any other virtualized files. - + Change the blacklist - + Waiting - + Please press OK once you're logged into steam. - + Select binary - + Binary @@ -8337,217 +8071,6 @@ You can restart Mod Organizer as administrator and try launching the program aga Exit Now - - - - Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. - - - - - %1, #%2, Level %3, %4 - - - - - failed to open %1 - - - - - wrong file format - expected %1 got '%2' for %3 - - - - - failed to query registry path (preflight): %1 - - - - - failed to query registry path (read): %1 - - - - - invalid nxm-link: %1 - - - - - Filter - - - - - One of the following plugins must be enabled: %1. - - - - - This plugin can only be enabled if the '%1' plugin is installed and enabled. - - - - - This plugin can only be enabled for the following game(s): %1. - - - - - - - - - - - INI file is read-only - - - - - - Mod Organizer is attempting to write to "%1" which is currently set to read-only. - - - - - - Clear the read-only flag - - - - - - Allow the write once - - - - - - The file will be set to read-only again. - - - - - - Skip this file - - - - - You can reset these choices by clicking "Reset Dialog Choices" in the General tab of the Settings - - - - - Always ask - - - - - - Remember my choice - - - - - Remember my choice for %1 - - - - - removal of "%1" failed: %2 - - - - - removal of "%1" failed - - - - - "%1" doesn't exist (remove) - - - - - Error %1 - - - - - - You have an invalid custom browser command in the settings. - - - - - - failed to create directory "%1" - - - - - - failed to copy "%1" to "%2" - - - - - %1 B - - - - - %1 KB - - - - - %1 MB - - - - - %1 GB - - - - - %1 TB - - - - - %1 B/s - - - - - %1 KB/s - - - - - %1 MB/s - - - - - %1 GB/s - - - - - %1 TB/s - - - - - Failed to save '%1', could not create a temporary file: %2 (error %3) - - QueryOverwriteDialog @@ -8594,19 +8117,6 @@ p, li { white-space: pre-wrap; } - - QuestionBoxMemory - - - Remember selection - - - - - Remember selection only for %1 - - - SaveTextAsDialog @@ -8705,42 +8215,42 @@ p, li { white-space: pre-wrap; } SelfUpdater - + Download failed - + Failed to find correct download, please try again later. - + Update - + Download in progress - + Download failed: %1 - + Failed to install update: %1 - + Failed to start %1: %2 - + Error @@ -9722,62 +9232,6 @@ programs you are intentionally running. - - TaskDialog - - - Dialog - - - - - icon - - - - - dummy main text - - - - - dummy content text - - - - - dummy button - - - - - dummy checkbox - - - - - Details - - - - - TextViewer - - - Log Viewer - - - - - Placeholder - - - - - Show Whitespace - - - TransferSavesDialog @@ -10526,25 +9980,4 @@ Please open the "Nexus" tab. - - uibase - - - h - Time remaining hours - - - - - m - Time remaining minutes - - - - - s - Time remaining seconds - - - diff --git a/src/organizercore.cpp b/src/organizercore.cpp index a8926dc65..88166c8e6 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -646,7 +646,7 @@ QString OrganizerCore::modsPath() const return QDir::fromNativeSeparators(m_Settings.paths().mods()); } -MOBase::VersionInfo OrganizerCore::appVersion() const +MOBase::Version OrganizerCore::version() const { return m_Updater.getVersion(); } diff --git a/src/organizercore.h b/src/organizercore.h index 923adc12d..fe80da240 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -1,14 +1,32 @@ #ifndef ORGANIZERCORE_H #define ORGANIZERCORE_H +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "downloadmanager.h" #include "envdump.h" -#include "executableinfo.h" #include "executableslist.h" -#include "guessedvalue.h" #include "installationmanager.h" -#include "memoizedlock.h" -#include "moddatacontent.h" #include "modinfo.h" #include "modlist.h" #include "moshortcut.h" @@ -18,22 +36,6 @@ #include "settings.h" #include "uilocker.h" #include "usvfsconnector.h" -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include class ModListSortProxy; class PluginListSortProxy; @@ -270,7 +272,7 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose std::vector enabledArchives(); - MOBase::VersionInfo getVersion() const { return m_Updater.getVersion(); } + MOBase::Version getVersion() const { return m_Updater.getVersion(); } // return the plugin container // @@ -358,7 +360,7 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose QString overwritePath() const; QString basePath() const; QString modsPath() const; - MOBase::VersionInfo appVersion() const; + MOBase::Version version() const; MOBase::IPluginGame* getGame(const QString& gameName) const; MOBase::IModInterface* createMod(MOBase::GuessedValue& name); void modDataChanged(MOBase::IModInterface* mod); diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 6edbdbab7..22a8a0234 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -114,9 +114,53 @@ QString OrganizerProxy::modsPath() const return m_Proxied->modsPath(); } +Version OrganizerProxy::version() const +{ + return m_Proxied->version(); +} + VersionInfo OrganizerProxy::appVersion() const { - return m_Proxied->appVersion(); + const auto version = m_Proxied->version(); + const int major = version.major(), minor = version.minor(), + subminor = version.patch(); + int subsubminor = 0; + VersionInfo::ReleaseType infoReleaseType = VersionInfo::RELEASE_FINAL; + + // make a copy + auto prereleases = version.preReleases(); + + if (!prereleases.empty()) { + // check if the first pre-release entry is a number + if (prereleases.front().index() == 0) { + subsubminor = std::get(prereleases.front()); + prereleases.erase(prereleases.begin()); + } + + if (!prereleases.empty()) { + const auto releaseType = std::get(prereleases.front()); + switch (releaseType) { + case Version::Development: + infoReleaseType = VersionInfo::RELEASE_PREALPHA; + break; + case Version::Alpha: + infoReleaseType = VersionInfo::RELEASE_ALPHA; + break; + case Version::Beta: + infoReleaseType = VersionInfo::RELEASE_BETA; + break; + case Version::ReleaseCandidate: + infoReleaseType = VersionInfo::RELEASE_CANDIDATE; + break; + default: + infoReleaseType = VersionInfo::RELEASE_PREALPHA; + } + } + + // there is no way to differentiate two pre-releases? + } + + return VersionInfo(major, minor, subminor, subsubminor, infoReleaseType); } IPluginGame* OrganizerProxy::getGame(const QString& gameName) const diff --git a/src/organizerproxy.h b/src/organizerproxy.h index fbae7cde6..2c70cb2c3 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -29,69 +29,67 @@ class OrganizerProxy : public MOBase::IOrganizer MOBase::IPlugin* plugin() const { return m_Plugin; } public: // IOrganizer interface - virtual MOBase::IModRepositoryBridge* createNexusBridge() const; - virtual QString profileName() const; - virtual QString profilePath() const; - virtual QString downloadsPath() const; - virtual QString overwritePath() const; - virtual QString basePath() const; - virtual QString modsPath() const; - virtual MOBase::VersionInfo appVersion() const; - virtual MOBase::IPluginGame* getGame(const QString& gameName) const; - virtual MOBase::IModInterface* createMod(MOBase::GuessedValue& name); - virtual void modDataChanged(MOBase::IModInterface* mod); - virtual QVariant persistent(const QString& pluginName, const QString& key, - const QVariant& def = QVariant()) const; - virtual void setPersistent(const QString& pluginName, const QString& key, - const QVariant& value, bool sync = true); - virtual QString pluginDataPath() const; - virtual MOBase::IModInterface* installMod(const QString& fileName, - const QString& nameSuggestion = QString()); - virtual QString resolvePath(const QString& fileName) const; - virtual QStringList listDirectories(const QString& directoryName) const; - virtual QStringList + MOBase::IModRepositoryBridge* createNexusBridge() const override; + QString profileName() const override; + QString profilePath() const override; + QString downloadsPath() const override; + QString overwritePath() const override; + QString basePath() const override; + QString modsPath() const override; + MOBase::Version version() const override; + MOBase::VersionInfo appVersion() const override; + MOBase::IPluginGame* getGame(const QString& gameName) const override; + MOBase::IModInterface* createMod(MOBase::GuessedValue& name) override; + void modDataChanged(MOBase::IModInterface* mod) override; + QVariant persistent(const QString& pluginName, const QString& key, + const QVariant& def = QVariant()) const override; + void setPersistent(const QString& pluginName, const QString& key, + const QVariant& value, bool sync = true) override; + QString pluginDataPath() const override; + MOBase::IModInterface* installMod(const QString& fileName, + const QString& nameSuggestion = QString()); + QString resolvePath(const QString& fileName) const override; + QStringList listDirectories(const QString& directoryName) const override; + QStringList findFiles(const QString& path, const std::function& filter) const override; - virtual QStringList findFiles(const QString& path, - const QStringList& globFilters) const override; - virtual QStringList getFileOrigins(const QString& fileName) const override; - virtual QList + QStringList findFiles(const QString& path, + const QStringList& globFilters) const override; + QStringList getFileOrigins(const QString& fileName) const override; + QList findFileInfos(const QString& path, const std::function& filter) const override; - virtual std::shared_ptr virtualFileTree() const override; - - virtual MOBase::IDownloadManager* downloadManager() const override; - virtual MOBase::IPluginList* pluginList() const override; - virtual MOBase::IModList* modList() const override; - virtual MOBase::IProfile* profile() const override; - virtual MOBase::IGameFeatures* gameFeatures() const override; - - virtual HANDLE startApplication(const QString& executable, - const QStringList& args = QStringList(), - const QString& cwd = "", const QString& profile = "", - const QString& forcedCustomOverwrite = "", - bool ignoreCustomOverwrite = false); - virtual bool waitForApplication(HANDLE handle, bool refresh = true, - LPDWORD exitCode = nullptr) const; - virtual void refresh(bool saveChanges); - - virtual bool onAboutToRun(const std::function& func) override; - virtual bool onAboutToRun(const std::function& func) override; - virtual bool + std::shared_ptr virtualFileTree() const override; + + MOBase::IDownloadManager* downloadManager() const override; + MOBase::IPluginList* pluginList() const override; + MOBase::IModList* modList() const override; + MOBase::IProfile* profile() const override; + MOBase::IGameFeatures* gameFeatures() const override; + + HANDLE startApplication(const QString& executable, + const QStringList& args = QStringList(), + const QString& cwd = "", const QString& profile = "", + const QString& forcedCustomOverwrite = "", + bool ignoreCustomOverwrite = false) override; + bool waitForApplication(HANDLE handle, bool refresh = true, + LPDWORD exitCode = nullptr) const override; + void refresh(bool saveChanges) override; + + bool onAboutToRun(const std::function& func) override; + bool onAboutToRun(const std::function& func) override; + bool onFinishedRun(const std::function& func) override; - virtual bool + bool onUserInterfaceInitialized(std::function const& func) override; - virtual bool onNextRefresh(const std::function& func, - bool immediateIfPossible) override; - virtual bool - onProfileCreated(std::function const& func) override; - virtual bool onProfileRenamed( - std::function const& - func) override; - virtual bool - onProfileRemoved(std::function const& func) override; - virtual bool onProfileChanged( + bool onNextRefresh(const std::function& func, + bool immediateIfPossible) override; + bool onProfileCreated(std::function const& func) override; + bool onProfileRenamed(std::function const& func) override; + bool onProfileRemoved(std::function const& func) override; + bool onProfileChanged( std::function const& func) override; // Plugin related: diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 7762c9004..43992e707 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -69,10 +69,9 @@ using namespace MOBase; using namespace MOShared; SelfUpdater::SelfUpdater(NexusInterface* nexusInterface) - : m_Parent(nullptr), m_Interface(nexusInterface), m_Reply(nullptr), m_Attempts(3) -{ - m_MOVersion = createVersionInfo(); -} + : m_Parent(nullptr), m_MOVersion(createVersionInfo()), m_Interface(nexusInterface), + m_Reply(nullptr), m_Attempts(3) +{} SelfUpdater::~SelfUpdater() {} @@ -115,7 +114,8 @@ void SelfUpdater::testForUpdate(const Settings& settings) QJsonObject release = releaseVal.toObject(); if (!release["draft"].toBool() && (Settings::instance().usePrereleases() || !release["prerelease"].toBool())) { - auto version = VersionInfo(release["tag_name"].toString()); + auto version = Version::parse(release["tag_name"].toString(), + Version::ParseMode::MO2); mreleases[version] = release; } } @@ -132,14 +132,13 @@ void SelfUpdater::testForUpdate(const Settings& settings) m_UpdateCandidates.insert(p); } } - log::info("update available: {} -> {}", - this->m_MOVersion.displayString(3), lastKey.displayString(3)); + log::info("update available: {} -> {}", this->m_MOVersion, lastKey); emit updateAvailable(); } else if (lastKey < this->m_MOVersion) { // this could happen if the user switches from using prereleases to // stable builds. Should we downgrade? log::debug("This version is newer than the latest released one: {} -> {}", - this->m_MOVersion.displayString(3), lastKey.displayString(3)); + this->m_MOVersion, lastKey); } } }); @@ -158,7 +157,7 @@ void SelfUpdater::startUpdate() auto latestRelease = m_UpdateCandidates.begin()->second; UpdateDialog dialog(m_Parent); - dialog.setVersions(MOShared::createVersionInfo().displayString(3), + dialog.setVersions(MOShared::createVersionInfo().string(), latestRelease["tag_name"].toString()); // We concatenate release details. We only include pre-release if those are diff --git a/src/selfupdater.h b/src/selfupdater.h index 612e70238..b39340849 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -22,8 +22,6 @@ along with Mod Organizer. If not, see . #include -#include - class Archive; class NexusInterface; class PluginContainer; @@ -42,6 +40,8 @@ class QNetworkReply; class QProgressDialog; class Settings; +#include + #include "github.h" /** @@ -99,7 +99,7 @@ class SelfUpdater : public QObject /** * @return current version of Mod Organizer **/ - MOBase::VersionInfo getVersion() const { return m_MOVersion; } + MOBase::Version getVersion() const { return m_MOVersion; } signals: @@ -135,7 +135,7 @@ private slots: private: QWidget* m_Parent; - MOBase::VersionInfo m_MOVersion; + MOBase::Version m_MOVersion; NexusInterface* m_Interface; QFile m_UpdateFile; QNetworkReply* m_Reply; @@ -147,7 +147,7 @@ private slots: // Map from version to release, in decreasing order (first element is the latest // release): - using CandidatesMap = std::map>; + using CandidatesMap = std::map>; CandidatesMap m_UpdateCandidates; }; diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 7acaa190b..265c9db90 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -111,7 +111,7 @@ void GeneralSettingsTab::addLanguages() QString languageString = QString("%1 (%2)") .arg(locale.nativeLanguageName()) - .arg(locale.nativeCountryName()); + .arg(locale.nativeTerritoryName()); if (locale.language() == QLocale::Chinese) { if (languageCode == "zh_TW") { diff --git a/src/shared/util.cpp b/src/shared/util.cpp index d3c6e2011..0d9788a29 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -208,10 +208,12 @@ std::wstring GetFileVersionString(const std::wstring& fileName) } } -VersionInfo createVersionInfo() +Version createVersionInfo() { VS_FIXEDFILEINFO version = GetFileVersion(env::thisProcessPath().native()); + std::optional releaseType; + if (version.dwFileFlags & VS_FF_PRERELEASE) { // Pre-release builds need annotating QString versionString = @@ -227,21 +229,29 @@ VersionInfo createVersionInfo() } } + if (!noLetters) { + // trust the string to make sense + return Version::parse(versionString, Version::ParseMode::MO2); + } + if (noLetters) { - // Default to pre-alpha when release type is unspecified - return VersionInfo( - version.dwFileVersionMS >> 16, version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16, version.dwFileVersionLS & 0xFFFF, - VersionInfo::RELEASE_PREALPHA); + // default to development when release type is unspecified + releaseType = Version::Development; } else { - // Trust the string to make sense - return VersionInfo(versionString); } - } else { - // Non-pre-release builds just need their version numbers reading - return VersionInfo(version.dwFileVersionMS >> 16, version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16, version.dwFileVersionLS & 0xFFFF); } + + const int major = version.dwFileVersionMS >> 16, + minor = version.dwFileVersionMS & 0xFFFF, + patch = version.dwFileVersionLS >> 16, + subpatch = version.dwFileVersionLS & 0xFFFF; + + std::vector> prereleases; + if (releaseType) { + prereleases.push_back(*releaseType); + } + + return Version(major, minor, patch, subpatch, std::move(prereleases)); } QString getUsvfsDLLVersion() diff --git a/src/shared/util.h b/src/shared/util.h index 1bc6bd47a..4764643e1 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -21,9 +21,10 @@ along with Mod Organizer. If not, see . #define UTIL_H #include -#include #include -#include + +#include +#include class Executable; @@ -48,7 +49,7 @@ std::wstring ToLowerCopy(std::wstring_view text); bool CaseInsensitiveEqual(const std::wstring& lhs, const std::wstring& rhs); -MOBase::VersionInfo createVersionInfo(); +MOBase::Version createVersionInfo(); QString getUsvfsVersionString(); void SetThisThreadName(const QString& s); diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json index 0f3cef6ea..6a56decd9 100644 --- a/vcpkg-configuration.json +++ b/vcpkg-configuration.json @@ -14,7 +14,7 @@ { "kind": "git", "repository": "https://github.com/ModOrganizer2/vcpkg-registry", - "baseline": "210f6e8f6eaefd6abfdf685f7deeb6dabdc78512", + "baseline": "a1cd2ddbf1afb836419a5f1a9f70d6378fc41df2", "packages": ["mo2-*", "7zip", "usvfs"] } ] From 54a7e938782548290e2115924c66a9e15cb4f270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sun, 13 Oct 2024 09:51:33 +0200 Subject: [PATCH 03/26] Move mo2-cmake to standalone feature. --- vcpkg.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcpkg.json b/vcpkg.json index 4a8c5c2c4..013a13873 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -12,7 +12,6 @@ "boost-thread", "boost-uuid", "lz4", - "mo2-cmake", "mo2-libbsarch", "zlib" ], @@ -20,6 +19,7 @@ "standalone": { "description": "Build Standalone.", "dependencies": [ + "mo2-cmake", "mo2-archive", "mo2-bsatk", "mo2-esptk", From 1d5a40738ca8cb2613135bc28d69befa9fe0cfcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 20 May 2022 14:44:18 +0200 Subject: [PATCH 04/26] Start implementing the theme manager. --- src/CMakeLists.txt | 10 +- src/extensionwatcher.h | 31 ++++ src/main.cpp | 1 + src/mainwindow.cpp | 21 +-- src/mainwindow.h | 7 +- src/moapplication.cpp | 195 ++----------------------- src/moapplication.h | 20 +-- src/settings.cpp | 4 +- src/settings.h | 6 +- src/settingsdialog.cpp | 6 +- src/settingsdialog.h | 5 +- src/settingsdialogtheme.cpp | 53 ++++--- src/settingsdialogtheme.h | 7 +- src/thememanager.cpp | 282 ++++++++++++++++++++++++++++++++++++ src/thememanager.h | 114 +++++++++++++++ 15 files changed, 512 insertions(+), 250 deletions(-) create mode 100644 src/extensionwatcher.h create mode 100644 src/thememanager.cpp create mode 100644 src/thememanager.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 10e71e6b4..2833f575a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -132,8 +132,6 @@ mo2_add_filter(NAME src/core GROUPS nexusinterface nxmaccessmanager organizercore - game_features - plugincontainer apiuseraccount processrunner qdirfiletree @@ -141,6 +139,14 @@ mo2_add_filter(NAME src/core GROUPS uilocker ) +mo2_add_filter(NAME src/extensions GROUPS + game_features + plugincontainer + thememanager + translationmanager + extensionconsumer +) + mo2_add_filter(NAME src/dialogs GROUPS aboutdialog activatemodsdialog diff --git a/src/extensionwatcher.h b/src/extensionwatcher.h new file mode 100644 index 000000000..54846e746 --- /dev/null +++ b/src/extensionwatcher.h @@ -0,0 +1,31 @@ +#ifndef EXTENSIONWATCHER_H +#define EXTENSIONWATCHER_H + +#include + +// an extension watcher is a class that watches extensions get loaded/unloaded, +// typically to extract information from theme that are needed by MO2 +// +class ExtensionWatcher +{ +public: + // called when a new extension is found and loaded + // + virtual void extensionLoaded(MOBase::IExtension const& extension) = 0; + + // called when a new extension is unloaded + // + virtual void extensionUnloaded(MOBase::IExtension const& extension) = 0; + + // called when a new extension is disabled + // + virtual void extensionEnabled(MOBase::IExtension const& extension) = 0; + + // called when a new extension is disabled + // + virtual void extensionDisabled(MOBase::IExtension const& extension) = 0; + + virtual ~ExtensionWatcher() {} +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index de1578593..b6642394a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include "multiprocess.h" #include "organizercore.h" #include "shared/util.h" +#include "thememanager.h" #include "thread_utils.h" #include #include diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 20a849f49..86294b798 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -230,12 +230,13 @@ void setFilterShortcuts(QWidget* widget, QLineEdit* edit) } MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginContainer& pluginContainer, QWidget* parent) + PluginContainer& pluginContainer, ThemeManager& themeManager, + QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_WasVisible(false), m_FirstPaint(true), m_linksSeparator(nullptr), m_Tutorial(this, "MainWindow"), m_OldProfileIndex(-1), m_OldExecutableIndex(-1), m_CategoryFactory(CategoryFactory::instance()), m_OrganizerCore(organizerCore), - m_PluginContainer(pluginContainer), + m_PluginContainer(pluginContainer), m_ThemeManager(themeManager), m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)), m_LinkToolbar(nullptr), m_LinkDesktop(nullptr), m_LinkStartMenu(nullptr), m_NumberOfProblems(0), m_ProblemsCheckRequired(false) @@ -386,8 +387,8 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(&m_OrganizerCore.settings(), SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString))); - connect(&m_OrganizerCore.settings(), SIGNAL(styleChanged(QString)), this, - SIGNAL(styleChanged(QString))); + connect(&m_OrganizerCore.settings(), &Settings::themeChanged, this, + &MainWindow::themeChanged); connect(m_OrganizerCore.updater(), SIGNAL(restart()), this, SLOT(close())); connect(m_OrganizerCore.updater(), SIGNAL(updateAvailable()), this, @@ -1232,7 +1233,7 @@ void MainWindow::showEvent(QShowEvent* event) // by connecting the event here, changing the style setting will first be // handled by MOApplication, and then in updateStyle(), at which point the // stylesheet has already been set correctly - connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString))); + connect(this, &MainWindow::themeChanged, this, &MainWindow::updateStyle); // only the first time the window becomes visible m_Tutorial.registerControl(); @@ -1935,7 +1936,7 @@ void MainWindow::updateBSAList(const QStringList& defaultArchives, }; for (FileEntryPtr current : files) { - QFileInfo fileInfo(ToQString(current->getName().c_str())); + QFileInfo fileInfo(ToQString(current->getName())); if (fileInfo.suffix().toLower() == "bsa" || fileInfo.suffix().toLower() == "ba2") { int index = activeArchives.indexOf(fileInfo.fileName()); @@ -2719,7 +2720,7 @@ void MainWindow::on_actionSettings_triggered() const bool oldCheckForUpdates = settings.checkForUpdates(); const int oldMaxDumps = settings.diagnostics().maxCoreDumps(); - SettingsDialog dialog(&m_PluginContainer, settings, this); + SettingsDialog dialog(&m_PluginContainer, m_ThemeManager, settings, this); dialog.exec(); auto e = dialog.exitNeeded(); @@ -3665,12 +3666,14 @@ void MainWindow::on_actionChange_Game_triggered() void MainWindow::setCategoryListVisible(bool visible) { + using namespace std::literals; + if (visible) { ui->categoriesGroup->show(); - ui->displayCategoriesBtn->setText(ToQString(L"\u00ab")); + ui->displayCategoriesBtn->setText(ToQString(L"\u00ab"sv)); } else { ui->categoriesGroup->hide(); - ui->displayCategoriesBtn->setText(ToQString(L"\u00bb")); + ui->displayCategoriesBtn->setText(ToQString(L"\u00bb"sv)); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index b3f4233aa..f06fec820 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -34,6 +34,7 @@ along with Mod Organizer. If not, see . #include "modlistsortproxy.h" #include "plugincontainer.h" #include "shared/fileregisterfwd.h" +#include "thememanager.h" class Executable; class CategoryFactory; @@ -126,7 +127,8 @@ class MainWindow : public QMainWindow, public IUserInterface public: explicit MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginContainer& pluginContainer, QWidget* parent = 0); + PluginContainer& pluginContainer, ThemeManager& manager, + QWidget* parent = 0); ~MainWindow(); void processUpdates(); @@ -166,7 +168,7 @@ public slots: /** * @brief emitted when the selected style changes */ - void styleChanged(const QString& styleFile); + void themeChanged(const QString& themeIdentifier); void checkForProblemsDone(); @@ -298,6 +300,7 @@ private slots: OrganizerCore& m_OrganizerCore; PluginContainer& m_PluginContainer; + ThemeManager& m_ThemeManager; QString m_CurrentLanguage; std::vector m_Translators; diff --git a/src/moapplication.cpp b/src/moapplication.cpp index f621d32a9..52a96e438 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -31,12 +31,12 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "shared/appconfig.h" #include "shared/util.h" +#include "thememanager.h" #include "thread_utils.h" #include "tutorialmanager.h" #include #include #include -#include #include #include #include @@ -57,55 +57,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; -// style proxy that changes the appearance of drop indicators -// -class ProxyStyle : public QProxyStyle -{ -public: - ProxyStyle(QStyle* baseStyle = 0) : QProxyStyle(baseStyle) {} - - void drawPrimitive(PrimitiveElement element, const QStyleOption* option, - QPainter* painter, const QWidget* widget) const override - { - if (element == QStyle::PE_IndicatorItemViewItemDrop) { - - // 0. Fix a bug that made the drop indicator sometimes appear on top - // of the mod list when selecting a mod. - if (option->rect.height() == 0 && option->rect.bottomRight() == QPoint(-1, -1)) { - return; - } - - // 1. full-width drop indicator - QRect rect(option->rect); - if (auto* view = qobject_cast(widget)) { - rect.setLeft(view->indentation()); - rect.setRight(widget->width()); - } - - // 2. stylish drop indicator - painter->setRenderHint(QPainter::Antialiasing, true); - - QColor col(option->palette.windowText().color()); - QPen pen(col); - pen.setWidth(2); - col.setAlpha(50); - - painter->setPen(pen); - painter->setBrush(QBrush(col)); - if (rect.height() == 0) { - QPoint tri[3] = {rect.topLeft(), rect.topLeft() + QPoint(-5, 5), - rect.topLeft() + QPoint(-5, -5)}; - painter->drawPolygon(tri, 3); - painter->drawLine(rect.topLeft(), rect.topRight()); - } else { - painter->drawRoundedRect(rect, 5, 5); - } - } else { - QProxyStyle::drawPrimitive(element, option, painter, widget); - } - } -}; - // This adds the `dlls` directory to the path so the dlls can be found. How // MO is able to find dlls in there is a bit convoluted: // @@ -149,15 +100,6 @@ MOApplication::MOApplication(int& argc, char** argv) : QApplication(argc, argv) TimeThis tt("MOApplication()"); qputenv("QML_DISABLE_DISK_CACHE", "true"); - - connect(&m_styleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file) { - log::debug("style file '{}' changed, reloading", file); - updateStyle(file); - }); - - m_defaultStyle = "windowsvista"; - updateStyle(m_defaultStyle); - addDllsToPath(); } OrganizerCore& MOApplication::core() @@ -268,6 +210,8 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) tt.start("MOApplication::doOneRun() plugins"); log::debug("initializing plugins"); + m_themes = std::make_unique(this); + m_plugins = std::make_unique(m_core.get()); m_plugins->loadPlugins(); @@ -330,25 +274,26 @@ int MOApplication::run(MOMultiProcess& multiProcess) m_core.get()); // styling - if (!setStyleFile(m_settings->interface().styleName().value_or(""))) { + if (!m_themes->load(m_settings->interface().themeName().value_or("").toStdString())) { // disable invalid stylesheet - m_settings->interface().setStyleName(""); + m_settings->interface().setThemeName(""); } int res = 1; { tt.start("MOApplication::doOneRun() MainWindow setup"); - MainWindow mainWindow(*m_settings, *m_core, *m_plugins); + MainWindow mainWindow(*m_settings, *m_core, *m_plugins, *m_themes); // the nexus interface can show dialogs, make sure they're parented to the // main window m_nexus->getAccessManager()->setTopLevelWidget(&mainWindow); + // TODO: connect( - &mainWindow, &MainWindow::styleChanged, this, - [this](auto&& file) { - setStyleFile(file); + &mainWindow, &MainWindow::themeChanged, this, + [this](auto&& themeIdentifier) { + m_themes->load(themeIdentifier.toStdString()); }, Qt::QueuedConnection); @@ -523,31 +468,6 @@ void MOApplication::resetForRestart() m_instance = {}; } -bool MOApplication::setStyleFile(const QString& styleName) -{ - // remove all files from watch - QStringList currentWatch = m_styleWatcher.files(); - if (currentWatch.count() != 0) { - m_styleWatcher.removePaths(currentWatch); - } - // set new stylesheet or clear it - if (styleName.length() != 0) { - QString styleSheetName = applicationDirPath() + "/" + - MOBase::ToQString(AppConfig::stylesheetsPath()) + "/" + - styleName; - if (QFile::exists(styleSheetName)) { - m_styleWatcher.addPath(styleSheetName); - updateStyle(styleSheetName); - } else { - updateStyle(styleName); - } - } else { - setStyle(new ProxyStyle(QStyleFactory::create(m_defaultStyle))); - setStyleSheet(""); - } - return true; -} - bool MOApplication::notify(QObject* receiver, QEvent* event) { try { @@ -565,101 +485,6 @@ bool MOApplication::notify(QObject* receiver, QEvent* event) } } -namespace -{ -QStringList extractTopStyleSheetComments(QFile& stylesheet) -{ - if (!stylesheet.open(QFile::ReadOnly)) { - log::error("failed to open stylesheet file {}", stylesheet.fileName()); - return {}; - } - ON_BLOCK_EXIT([&stylesheet]() { - stylesheet.close(); - }); - - QStringList topComments; - - while (true) { - const auto byteLine = stylesheet.readLine(); - if (byteLine.isNull()) { - break; - } - - const auto line = QString(byteLine).trimmed(); - - // skip empty lines - if (line.isEmpty()) { - continue; - } - - // only handle single line comments - if (!line.startsWith("/*")) { - break; - } - - topComments.push_back(line.mid(2, line.size() - 4).trimmed()); - } - - return topComments; -} - -QString extractBaseStyleFromStyleSheet(QFile& stylesheet, const QString& defaultStyle) -{ - // read the first line of the files that are either empty or comments - // - const auto topLines = extractTopStyleSheetComments(stylesheet); - - const auto factoryStyles = QStyleFactory::keys(); - - QString style = defaultStyle; - - for (const auto& line : topLines) { - if (!line.startsWith("mo2-base-style")) { - continue; - } - - const auto parts = line.split(":"); - if (parts.size() != 2) { - log::warn("found invalid top-comment for mo2 in {}: {}", stylesheet.fileName(), - line); - continue; - } - - const auto tmpStyle = parts[1].trimmed(); - const auto index = factoryStyles.indexOf(tmpStyle, 0, Qt::CaseInsensitive); - if (index == -1) { - log::warn("base style '{}' from style '{}' not found", tmpStyle, - stylesheet.fileName(), line); - continue; - } - - style = factoryStyles[index]; - log::info("found base style '{}' for style '{}'", style, stylesheet.fileName()); - break; - } - - return style; -} - -} // namespace - -void MOApplication::updateStyle(const QString& fileName) -{ - if (QStyleFactory::keys().contains(fileName)) { - setStyleSheet(""); - setStyle(new ProxyStyle(QStyleFactory::create(fileName))); - } else { - QFile stylesheet(fileName); - if (stylesheet.exists()) { - setStyle(new ProxyStyle(QStyleFactory::create( - extractBaseStyleFromStyleSheet(stylesheet, m_defaultStyle)))); - setStyleSheet(QString("file:///%1").arg(fileName)); - } else { - log::warn("invalid stylesheet: {}", fileName); - } - } -} - MOSplash::MOSplash(const Settings& settings, const QString& dataPath, const MOBase::IPluginGame* game) { diff --git a/src/moapplication.h b/src/moapplication.h index 498242f3e..e7ad983a8 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -24,12 +24,15 @@ along with Mod Organizer. If not, see . #include #include -class Settings; -class MOMultiProcess; +#include "thememanager.h" + class Instance; -class PluginContainer; -class OrganizerCore; +class MOMultiProcess; class NexusInterface; +class OrganizerCore; +class PluginContainer; +class Settings; +class ThemeManager; namespace MOBase { @@ -70,21 +73,14 @@ class MOApplication : public QApplication // bool notify(QObject* receiver, QEvent* event) override; -public slots: - bool setStyleFile(const QString& style); - -private slots: - void updateStyle(const QString& fileName); - private: - QFileSystemWatcher m_styleWatcher; - QString m_defaultStyle; std::unique_ptr m_modules; std::unique_ptr m_instance; std::unique_ptr m_settings; std::unique_ptr m_nexus; std::unique_ptr m_plugins; + std::unique_ptr m_themes; std::unique_ptr m_core; void externalMessage(const QString& message); diff --git a/src/settings.cpp b/src/settings.cpp index d88311ebe..5ece014b1 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2136,12 +2136,12 @@ void InterfaceSettings::setLockGUI(bool b) set(m_Settings, "Settings", "lock_gui", b); } -std::optional InterfaceSettings::styleName() const +std::optional InterfaceSettings::themeName() const { return getOptional(m_Settings, "Settings", "style"); } -void InterfaceSettings::setStyleName(const QString& name) +void InterfaceSettings::setThemeName(const QString& name) { set(m_Settings, "Settings", "style", name); } diff --git a/src/settings.h b/src/settings.h index bd58e10d4..f12fbb1b1 100644 --- a/src/settings.h +++ b/src/settings.h @@ -592,8 +592,8 @@ class InterfaceSettings // filename of the theme // - std::optional styleName() const; - void setStyleName(const QString& name); + std::optional themeName() const; + void setThemeName(const QString& name); // whether to use collapsible separators when possible // @@ -901,7 +901,7 @@ public slots: // these are fired from outside the settings, mostly by the settings dialog // void languageChanged(const QString& newLanguage); - void styleChanged(const QString& newStyle); + void themeChanged(const QString& themeIdentifier); private: static Settings* s_Instance; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 425a1cb80..e79847cc8 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -30,7 +30,8 @@ along with Mod Organizer. If not, see . using namespace MOBase; -SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, Settings& settings, +SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, + ThemeManager const& manager, Settings& settings, QWidget* parent) : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog), m_settings(settings), m_exit(Exit::None), m_pluginContainer(pluginContainer) @@ -39,7 +40,8 @@ SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, Settings& setti m_tabs.push_back( std::unique_ptr(new GeneralSettingsTab(settings, *this))); - m_tabs.push_back(std::unique_ptr(new ThemeSettingsTab(settings, *this))); + m_tabs.push_back( + std::unique_ptr(new ThemeSettingsTab(settings, manager, *this))); m_tabs.push_back( std::unique_ptr(new ModListSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new PathsSettingsTab(settings, *this))); diff --git a/src/settingsdialog.h b/src/settingsdialog.h index eca0b2662..ad45bc6f9 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "shared/util.h" #include "tutorabledialog.h" +class ThemeManager; class PluginContainer; class Settings; class SettingsDialog; @@ -63,8 +64,8 @@ class SettingsDialog : public MOBase::TutorableDialog friend class SettingsTab; public: - explicit SettingsDialog(PluginContainer* pluginContainer, Settings& settings, - QWidget* parent = 0); + explicit SettingsDialog(PluginContainer* pluginContainer, ThemeManager const& manager, + Settings& settings, QWidget* parent = 0); ~SettingsDialog(); diff --git a/src/settingsdialogtheme.cpp b/src/settingsdialogtheme.cpp index 75bcdb598..57f6936cd 100644 --- a/src/settingsdialogtheme.cpp +++ b/src/settingsdialogtheme.cpp @@ -9,10 +9,12 @@ using namespace MOBase; -ThemeSettingsTab::ThemeSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) +ThemeSettingsTab::ThemeSettingsTab(Settings& s, ThemeManager const& manager, + SettingsDialog& d) + : SettingsTab(s, d) { // style - addStyles(); + addStyles(manager); selectStyle(); // colors @@ -21,61 +23,56 @@ ThemeSettingsTab::ThemeSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab QObject::connect(ui->resetColorsBtn, &QPushButton::clicked, [&] { ui->colorTable->resetColors(); }); - - QObject::connect(ui->exploreStyles, &QPushButton::clicked, [&] { - onExploreStyles(); - }); } void ThemeSettingsTab::update() { // style - const QString oldStyle = settings().interface().styleName().value_or(""); + const QString oldStyle = settings().interface().themeName().value_or(""); const QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); if (oldStyle != newStyle) { - settings().interface().setStyleName(newStyle); - emit settings().styleChanged(newStyle); + settings().interface().setThemeName(newStyle); + emit settings().themeChanged(newStyle); } // colors ui->colorTable->commitColors(); } -void ThemeSettingsTab::addStyles() +void ThemeSettingsTab::addStyles(ThemeManager const& manager) { ui->styleBox->addItem("None", ""); - for (auto&& key : QStyleFactory::keys()) { - ui->styleBox->addItem(key, key); - } - ui->styleBox->insertSeparator(ui->styleBox->count()); + auto themes = manager.themes(); - QDirIterator iter(QCoreApplication::applicationDirPath() + "/" + - QString::fromStdWString(AppConfig::stylesheetsPath()), - QStringList("*.qss"), QDir::Files); + std::sort(themes.begin(), themes.end(), [&manager](auto&& lhs, auto&& rhs) { + if (manager.isBuiltIn(lhs) == manager.isBuiltIn(rhs)) { + return lhs->name() < rhs->name(); + } else { + // put built-in before others + return manager.isBuiltIn(rhs) < manager.isBuiltIn(lhs); + } + }); - while (iter.hasNext()) { - iter.next(); + bool separator = true; + for (auto&& theme : themes) { + if (separator && !manager.isBuiltIn(theme)) { + ui->styleBox->insertSeparator(ui->styleBox->count()); + separator = false; + } - ui->styleBox->addItem(iter.fileInfo().completeBaseName(), iter.fileName()); + ui->styleBox->addItem(ToQString(theme->identifier()), ToQString(theme->name())); } } void ThemeSettingsTab::selectStyle() { const int currentID = - ui->styleBox->findData(settings().interface().styleName().value_or("")); + ui->styleBox->findData(settings().interface().themeName().value_or("")); if (currentID != -1) { ui->styleBox->setCurrentIndex(currentID); } } - -void ThemeSettingsTab::onExploreStyles() -{ - QString ssPath = QCoreApplication::applicationDirPath() + "/" + - ToQString(AppConfig::stylesheetsPath()); - shell::Explore(ssPath); -} diff --git a/src/settingsdialogtheme.h b/src/settingsdialogtheme.h index 2a53dc38c..2ca5c9458 100644 --- a/src/settingsdialogtheme.h +++ b/src/settingsdialogtheme.h @@ -5,18 +5,19 @@ #include "settings.h" #include "settingsdialog.h" +#include "thememanager.h" class ThemeSettingsTab : public SettingsTab { public: - ThemeSettingsTab(Settings& settings, SettingsDialog& dialog); + ThemeSettingsTab(Settings& settings, ThemeManager const& manager, + SettingsDialog& dialog); void update() override; private: - void addStyles(); + void addStyles(ThemeManager const& manager); void selectStyle(); - void onExploreStyles(); }; #endif // SETTINGSDIALOGGENERAL_H diff --git a/src/thememanager.cpp b/src/thememanager.cpp new file mode 100644 index 000000000..aefc449e3 --- /dev/null +++ b/src/thememanager.cpp @@ -0,0 +1,282 @@ +#include "thememanager.h" + +#include +#include +#include + +#include +#include + +#include "shared/appconfig.h" + +using namespace MOBase; + +// style proxy that changes the appearance of drop indicators +// +class ProxyStyle : public QProxyStyle +{ +public: + ProxyStyle(QStyle* baseStyle = 0) : QProxyStyle(baseStyle) {} + + void drawPrimitive(PrimitiveElement element, const QStyleOption* option, + QPainter* painter, const QWidget* widget) const override + { + if (element == QStyle::PE_IndicatorItemViewItemDrop) { + + // 0. Fix a bug that made the drop indicator sometimes appear on top + // of the mod list when selecting a mod. + if (option->rect.height() == 0 && option->rect.bottomRight() == QPoint(-1, -1)) { + return; + } + + // 1. full-width drop indicator + QRect rect(option->rect); + if (auto* view = qobject_cast(widget)) { + rect.setLeft(view->indentation()); + rect.setRight(widget->width()); + } + + // 2. stylish drop indicator + painter->setRenderHint(QPainter::Antialiasing, true); + + QColor col(option->palette.windowText().color()); + QPen pen(col); + pen.setWidth(2); + col.setAlpha(50); + + painter->setPen(pen); + painter->setBrush(QBrush(col)); + if (rect.height() == 0) { + QPoint tri[3] = {rect.topLeft(), rect.topLeft() + QPoint(-5, 5), + rect.topLeft() + QPoint(-5, -5)}; + painter->drawPolygon(tri, 3); + painter->drawLine(rect.topLeft(), rect.topRight()); + } else { + painter->drawRoundedRect(rect, 5, 5); + } + } else { + QProxyStyle::drawPrimitive(element, option, painter, widget); + } + } +}; + +ThemeManager::ThemeManager(QApplication* application) : m_app{application} +{ + // add built-in themes + addQtThemes(); + + // TODO: remove this + addOldFormatThemes(); + + // find the default theme + m_defaultTheme = + m_baseThemesByIdentifier.at(m_app->style()->objectName().toStdString()); + + // for ease, we set the empty identifier to the default theme + m_baseThemesByIdentifier[""] = m_defaultTheme; + + // load the default theme + load(m_defaultTheme); + + // connect the style watcher + m_app->connect(&m_watcher, &QFileSystemWatcher::fileChanged, [this](auto&&) { + reload(); + }); +} + +// TODO: remove this +void ThemeManager::addOldFormatThemes() +{ + QDirIterator iter(QCoreApplication::applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::stylesheetsPath()), + QStringList("*.qss"), QDir::Files); + + while (iter.hasNext()) { + iter.next(); + registerTheme( + std::make_shared(iter.fileInfo().completeBaseName().toStdString(), + iter.fileInfo().baseName().toStdString(), + iter.fileInfo().filesystemFilePath())); + } +} + +bool ThemeManager::load(std::shared_ptr theme) +{ + // no theme -> default + if (!theme) { + theme = m_defaultTheme; + } + + // do not reload the current theme + if (theme == m_currentTheme) { + return true; + } + + // set the current theme + m_currentTheme = theme; + + if (isBuiltIn(theme)) { + loadQtTheme(theme->identifier()); + watchThemeFiles(nullptr); + } else { + loadExtensionTheme(theme); + watchThemeFiles(theme); + } + + return true; +} + +bool ThemeManager::load(std::string_view themeIdentifier) +{ + auto it = m_baseThemesByIdentifier.find(themeIdentifier); + if (it == m_baseThemesByIdentifier.end()) { + log::error("theme '{}' not found", themeIdentifier); + return false; + } + + return load(it->second); +} + +void ThemeManager::loadQtTheme(std::string_view themeIdentifier) +{ + m_app->setStyleSheet(""); + m_app->setStyle(new ProxyStyle(QStyleFactory::create(ToQString(themeIdentifier)))); +} + +void ThemeManager::loadExtensionTheme(std::shared_ptr const& theme) +{ + // load the default theme + m_app->setStyle( + new ProxyStyle(QStyleFactory::create(ToQString(m_defaultTheme->identifier())))); + + // build the stylesheet and set it + m_app->setStyleSheet(buildStyleSheet(theme)); +} + +void ThemeManager::unload() +{ + // load the default style + load(m_defaultTheme); +} + +void ThemeManager::reload() +{ + // cannot reload if there is no theme or builtin themes + if (!m_currentTheme || m_currentTheme->stylesheet().empty()) { + return; + } + + if (isBuiltIn(m_currentTheme)) { + loadQtTheme(m_currentTheme->identifier()); + } else { + loadExtensionTheme(m_currentTheme); + } +} + +void ThemeManager::registerTheme(std::shared_ptr const& theme) +{ + // two themes with same identifier, skip (+ warn) + auto it = m_baseThemesByIdentifier.find(theme->identifier()); + if (it != m_baseThemesByIdentifier.end()) { + log::warn("found existing theme with identifier '{}', skipping", + theme->identifier()); + return; + } + + m_baseThemes.push_back(theme); + m_baseThemesByIdentifier[theme->identifier()] = theme; +} + +void ThemeManager::addQtThemes() +{ + for (const auto& key : QStyleFactory::keys()) { + registerTheme(std::make_shared(key.toStdString(), key.toStdString(), + std::filesystem::path{})); + } +} + +QString ThemeManager::buildStyleSheet(std::shared_ptr const& theme) const +{ + // TODO: + return QString("file:///%1").arg(ToQString(absolute(theme->stylesheet()).native())); +} + +void ThemeManager::watchThemeFiles(std::shared_ptr const& theme) +{ + // clear previous files + QStringList currentWatch = m_watcher.files(); + if (currentWatch.count() != 0) { + m_watcher.removePaths(currentWatch); + } + + if (!theme) { + return; + } + + // find theme files + QStringList themeFiles; + + themeFiles.append(ToQString(absolute(theme->stylesheet()).native())); + + for (auto&& themeExtension : m_globalExtensions) { + themeFiles.append(ToQString(absolute(themeExtension->stylesheet()).native())); + } + + for (auto&& themeExtension : m_themeExtensionsByIdentifier[theme->identifier()]) { + themeFiles.append(ToQString(absolute(themeExtension->stylesheet()).native())); + } + + // add all files + m_watcher.addPaths(themeFiles); +} + +void ThemeManager::extensionLoaded(IExtension const& extension) +{ + for (const auto& theme : extension.themes()) { + registerTheme(theme); + } + + for (const auto& themeExtension : extension.themeExtensions()) { + const auto identifier = themeExtension->baseIdentifier(); + + if (identifier.has_value()) { + m_themeExtensionsByIdentifier[*identifier].push_back(themeExtension); + } else { + m_globalExtensions.push_back(themeExtension); + } + } +} + +void ThemeManager::extensionUnloaded(IExtension const& extension) +{ + // remove theme, unload if needed + for (const auto& theme : extension.themes()) { + if (m_currentTheme == theme) { + unload(); + } + + if (std::erase(m_baseThemes, theme) > 0) { + m_themeExtensionsByIdentifier.erase(theme->identifier()); + } + } + + for (const auto& themeExtension : extension.themeExtensions()) { + std::erase(m_globalExtensions, themeExtension); + + if (themeExtension->baseIdentifier().has_value() && + m_themeExtensionsByIdentifier.contains(*themeExtension->baseIdentifier())) { + std::erase(m_themeExtensionsByIdentifier[*themeExtension->baseIdentifier()], + themeExtension); + } + } +} + +void ThemeManager::extensionEnabled(IExtension const& extension) +{ + extensionLoaded(extension); +} + +void ThemeManager::extensionDisabled(IExtension const& extension) +{ + extensionUnloaded(extension); +} diff --git a/src/thememanager.h b/src/thememanager.h new file mode 100644 index 000000000..9ecc10e80 --- /dev/null +++ b/src/thememanager.h @@ -0,0 +1,114 @@ +#ifndef THEMEMANAGER_H +#define THEMEMANAGER_H + +#include +#include + +#include "extensionwatcher.h" + +class ThemeManager : public ExtensionWatcher +{ +public: + ThemeManager(QApplication* application); + + // retrieve the list of available themes + // + const auto& themes() const { return m_baseThemes; } + + // load the given theme + // + bool load(std::shared_ptr theme); + bool load(std::string_view themeIdentifier); + + // unload the current theme + // + void unload(); + + // retrieve the current theme, if there is one + // + auto currentTheme() const { return m_currentTheme; } + + // check if the given theme is a built-in Qt theme (not from an extension) + // + bool isBuiltIn(std::shared_ptr const& theme) const + { + return theme->stylesheet().empty(); + } + +public: // ExtensionWatcher + void extensionLoaded(MOBase::IExtension const& extension) override; + void extensionUnloaded(MOBase::IExtension const& extension) override; + void extensionEnabled(MOBase::IExtension const& extension) override; + void extensionDisabled(MOBase::IExtension const& extension) override; + +private: + // reload the current style + // + void reload(); + + // register a theme + // + void registerTheme(std::shared_ptr const& theme); + + // add built-in themes + // + void addQtThemes(); + + // load a Qt theme + // + void loadQtTheme(std::string_view identifier); + + // load an extension theme + // + void loadExtensionTheme(std::shared_ptr const& theme); + + // build a stylesheet for a theme + // + QString buildStyleSheet(std::shared_ptr const& theme) const; + + // watch files for the given theme (can be nullptr to stop watching) + // + void watchThemeFiles(std::shared_ptr const& theme); + + // [deprecated] add themes for the stylesheets folder + // + [[deprecated]] void addOldFormatThemes(); + +private: + // TODO: move these two elsewhere + struct string_equal : std::equal_to + { + using is_transparent = std::true_type; + }; + + struct string_hash : std::hash + { + using is_transparent = std::true_type; + }; + + // application and file system watcher + QApplication* m_app; + QFileSystemWatcher m_watcher; + + // the default current theme + std::shared_ptr m_defaultTheme; + std::shared_ptr m_currentTheme; + + // the list of base themes + std::vector> m_baseThemes; + std::unordered_map, string_hash, + string_equal> + m_baseThemesByIdentifier; + + // the list of theme extensions per theme identifier, for extension that + // have identifiers + std::unordered_map>, + string_hash, string_equal> + m_themeExtensionsByIdentifier; + + // theme extensions for all themes + std::vector> m_globalExtensions; +}; + +#endif From f8ea6dedd002ae2e01b08dec8a873567f20be29f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 20 May 2022 15:23:43 +0200 Subject: [PATCH 05/26] Implement theme extensions. --- src/thememanager.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++-- src/thememanager.h | 5 ++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/thememanager.cpp b/src/thememanager.cpp index aefc449e3..f8f15c6ae 100644 --- a/src/thememanager.cpp +++ b/src/thememanager.cpp @@ -195,10 +195,66 @@ void ThemeManager::addQtThemes() } } +namespace +{ +QString readWholeFile(std::filesystem::path const& path) +{ + QFile file(path); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + return {}; + } + + return QTextStream(&file).readAll(); +} +} // namespace + QString ThemeManager::buildStyleSheet(std::shared_ptr const& theme) const { - // TODO: - return QString("file:///%1").arg(ToQString(absolute(theme->stylesheet()).native())); + // create the base stylesheet + QString stylesheet = patchStyleSheet(readWholeFile(theme->stylesheet()), + theme->stylesheet().parent_path()); + + for (auto&& globalExtension : m_globalExtensions) { + stylesheet += "\n" + patchStyleSheet(readWholeFile(globalExtension->stylesheet()), + globalExtension->stylesheet().parent_path()); + } + + auto it = m_themeExtensionsByIdentifier.find(theme->identifier()); + if (it != m_themeExtensionsByIdentifier.end()) { + for (auto&& themExtension : m_themeExtensionsByIdentifier.at(theme->identifier())) { + stylesheet += "\n" + patchStyleSheet(readWholeFile(themExtension->stylesheet()), + themExtension->stylesheet().parent_path()); + } + } + + return stylesheet; +} + +QString ThemeManager::patchStyleSheet(QString stylesheet, + std::filesystem::path const& folder) const +{ + // we try to extract url() from the stylesheet and replace them + QRegularExpression urlRegex(R"re((:|\s+)url\("?([^")]+)"?\))re"); + + QString newStyleSheet = ""; + while (!stylesheet.isEmpty()) { + auto match = urlRegex.match(stylesheet); + + if (match.hasMatch()) { + QFileInfo path(match.captured(2)); + if (path.isRelative()) { + path = QFileInfo(QDir(folder), match.captured(2)); + } + newStyleSheet += stylesheet.left(match.capturedStart()) + match.captured(1) + + "url(\"" + path.absoluteFilePath() + "\")"; + stylesheet = stylesheet.mid(match.capturedEnd()); + } else { + newStyleSheet += stylesheet; + stylesheet = ""; + } + } + + return newStyleSheet; } void ThemeManager::watchThemeFiles(std::shared_ptr const& theme) diff --git a/src/thememanager.h b/src/thememanager.h index 9ecc10e80..7c48de9c7 100644 --- a/src/thememanager.h +++ b/src/thememanager.h @@ -66,6 +66,11 @@ class ThemeManager : public ExtensionWatcher // QString buildStyleSheet(std::shared_ptr const& theme) const; + // patch the given stylesheet by replacing url() to be relative to the given folder + // + QString patchStyleSheet(QString stylesheet, + std::filesystem::path const& folder) const; + // watch files for the given theme (can be nullptr to stop watching) // void watchThemeFiles(std::shared_ptr const& theme); From 17e54ba34798b5462d1a6758f410a4e0d92231f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 20 May 2022 17:07:57 +0200 Subject: [PATCH 06/26] Implement translation manager. --- src/iuserinterface.h | 2 - src/mainwindow.cpp | 51 ++------ src/mainwindow.h | 13 +- src/moapplication.cpp | 5 +- src/moapplication.h | 2 + src/settingsdialog.cpp | 13 +- src/settingsdialog.h | 8 +- src/settingsdialoggeneral.cpp | 60 ++------- src/settingsdialoggeneral.h | 6 +- src/thememanager.cpp | 2 +- src/thememanager.h | 2 + src/translationmanager.cpp | 222 ++++++++++++++++++++++++++++++++++ src/translationmanager.h | 83 +++++++++++++ 13 files changed, 357 insertions(+), 112 deletions(-) create mode 100644 src/translationmanager.cpp create mode 100644 src/translationmanager.h diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 7ddc545d0..01a6e7902 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -13,8 +13,6 @@ class IUserInterface public: virtual void registerModPage(MOBase::IPluginModPage* modPage) = 0; - virtual void installTranslator(const QString& name) = 0; - virtual bool closeWindow() = 0; virtual void setWindowEnabled(bool enabled) = 0; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 86294b798..c2a1e36e0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -231,12 +231,13 @@ void setFilterShortcuts(QWidget* widget, QLineEdit* edit) MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, PluginContainer& pluginContainer, ThemeManager& themeManager, - QWidget* parent) + TranslationManager& translationManager, QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_WasVisible(false), m_FirstPaint(true), m_linksSeparator(nullptr), m_Tutorial(this, "MainWindow"), m_OldProfileIndex(-1), m_OldExecutableIndex(-1), m_CategoryFactory(CategoryFactory::instance()), m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer), m_ThemeManager(themeManager), + m_TranslationManager(translationManager), m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)), m_LinkToolbar(nullptr), m_LinkDesktop(nullptr), m_LinkStartMenu(nullptr), m_NumberOfProblems(0), m_ProblemsCheckRequired(false) @@ -263,7 +264,7 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, MOShared::SetThisThreadName("main"); ui->setupUi(this); - languageChange(settings.interface().language()); + onLanguageChanged(settings.interface().language()); ui->statusBar->setup(ui, settings); { @@ -385,8 +386,8 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(m_OrganizerCore.directoryRefresher(), SIGNAL(error(QString)), this, SLOT(showError(QString))); - connect(&m_OrganizerCore.settings(), SIGNAL(languageChanged(QString)), this, - SLOT(languageChange(QString))); + connect(&m_OrganizerCore.settings(), &Settings::languageChanged, this, + &MainWindow::onLanguageChanged); connect(&m_OrganizerCore.settings(), &Settings::themeChanged, this, &MainWindow::themeChanged); @@ -499,9 +500,6 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, [=](auto&& message) { showMessage(message); }); - for (const QString& fileName : m_PluginContainer.pluginFileNames()) { - installTranslator(QFileInfo(fileName).baseName()); - } updateModPageMenu(); @@ -2720,7 +2718,8 @@ void MainWindow::on_actionSettings_triggered() const bool oldCheckForUpdates = settings.checkForUpdates(); const int oldMaxDumps = settings.diagnostics().maxCoreDumps(); - SettingsDialog dialog(&m_PluginContainer, m_ThemeManager, settings, this); + SettingsDialog dialog(&m_PluginContainer, m_ThemeManager, m_TranslationManager, + settings, this); dialog.exec(); auto e = dialog.exitNeeded(); @@ -2861,42 +2860,10 @@ void MainWindow::on_actionNexus_triggered() shell::Open(QUrl(NexusInterface::instance().getGameURL(gameName))); } -void MainWindow::installTranslator(const QString& name) +void MainWindow::onLanguageChanged(const QString& newLanguage) { - QTranslator* translator = new QTranslator(this); - QString fileName = name + "_" + m_CurrentLanguage; - if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { - if (m_CurrentLanguage.contains(QRegularExpression("^.*_(EN|en)(-.*)?$"))) { - log::debug("localization file %s not found", fileName); - } // we don't actually expect localization files for English (en, en-us, en-uk, and - // any variation thereof) - } - - qApp->installTranslator(translator); - m_Translators.push_back(translator); -} + m_TranslationManager.load(newLanguage.toStdString()); -void MainWindow::languageChange(const QString& newLanguage) -{ - for (QTranslator* trans : m_Translators) { - qApp->removeTranslator(trans); - } - m_Translators.clear(); - - m_CurrentLanguage = newLanguage; - - installTranslator("qt"); - installTranslator("qtbase"); - installTranslator(ToQString(AppConfig::translationPrefix())); - installTranslator("uibase"); - - // TODO: this will probably be changed once extension come out - installTranslator("game_gamebryo"); - installTranslator("game_creation"); - - for (const QString& fileName : m_PluginContainer.pluginFileNames()) { - installTranslator(QFileInfo(fileName).baseName()); - } ui->retranslateUi(this); log::debug("loaded language {}", newLanguage); diff --git a/src/mainwindow.h b/src/mainwindow.h index f06fec820..8e0ba5284 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -35,6 +35,7 @@ along with Mod Organizer. If not, see . #include "plugincontainer.h" #include "shared/fileregisterfwd.h" #include "thememanager.h" +#include "translationmanager.h" class Executable; class CategoryFactory; @@ -127,8 +128,8 @@ class MainWindow : public QMainWindow, public IUserInterface public: explicit MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginContainer& pluginContainer, ThemeManager& manager, - QWidget* parent = 0); + PluginContainer& pluginContainer, ThemeManager& themeManager, + TranslationManager& translationManager, QWidget* parent = 0); ~MainWindow(); void processUpdates(); @@ -141,8 +142,6 @@ class MainWindow : public QMainWindow, public IUserInterface void saveArchiveList(); - void installTranslator(const QString& name); - void displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) override; @@ -301,9 +300,7 @@ private slots: OrganizerCore& m_OrganizerCore; PluginContainer& m_PluginContainer; ThemeManager& m_ThemeManager; - - QString m_CurrentLanguage; - std::vector m_Translators; + TranslationManager& m_TranslationManager; std::unique_ptr m_IntegratedBrowser; @@ -347,7 +344,7 @@ private slots: void linkDesktop(); void linkMenu(); - void languageChange(const QString& newLanguage); + void onLanguageChanged(const QString& newLanguage); void windowTutorialFinished(const QString& windowName); diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 52a96e438..53a815667 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -210,7 +210,8 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) tt.start("MOApplication::doOneRun() plugins"); log::debug("initializing plugins"); - m_themes = std::make_unique(this); + m_themes = std::make_unique(this); + m_translations = std::make_unique(this); m_plugins = std::make_unique(m_core.get()); m_plugins->loadPlugins(); @@ -283,7 +284,7 @@ int MOApplication::run(MOMultiProcess& multiProcess) { tt.start("MOApplication::doOneRun() MainWindow setup"); - MainWindow mainWindow(*m_settings, *m_core, *m_plugins, *m_themes); + MainWindow mainWindow(*m_settings, *m_core, *m_plugins, *m_themes, *m_translations); // the nexus interface can show dialogs, make sure they're parented to the // main window diff --git a/src/moapplication.h b/src/moapplication.h index e7ad983a8..8f7572240 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include #include "thememanager.h" +#include "translationmanager.h" class Instance; class MOMultiProcess; @@ -81,6 +82,7 @@ class MOApplication : public QApplication std::unique_ptr m_nexus; std::unique_ptr m_plugins; std::unique_ptr m_themes; + std::unique_ptr m_translations; std::unique_ptr m_core; void externalMessage(const QString& message); diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index e79847cc8..abe54d242 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -31,17 +31,18 @@ along with Mod Organizer. If not, see . using namespace MOBase; SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, - ThemeManager const& manager, Settings& settings, - QWidget* parent) + ThemeManager const& themeManager, + TranslationManager const& translationManager, + Settings& settings, QWidget* parent) : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog), m_settings(settings), m_exit(Exit::None), m_pluginContainer(pluginContainer) { ui->setupUi(this); - m_tabs.push_back( - std::unique_ptr(new GeneralSettingsTab(settings, *this))); - m_tabs.push_back( - std::unique_ptr(new ThemeSettingsTab(settings, manager, *this))); + m_tabs.push_back(std::unique_ptr( + new GeneralSettingsTab(settings, translationManager, *this))); + m_tabs.push_back(std::unique_ptr( + new ThemeSettingsTab(settings, themeManager, *this))); m_tabs.push_back( std::unique_ptr(new ModListSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new PathsSettingsTab(settings, *this))); diff --git a/src/settingsdialog.h b/src/settingsdialog.h index ad45bc6f9..60f69ccf8 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -23,10 +23,12 @@ along with Mod Organizer. If not, see . #include "shared/util.h" #include "tutorabledialog.h" -class ThemeManager; class PluginContainer; class Settings; class SettingsDialog; +class ThemeManager; +class TranslationManager; + namespace Ui { class SettingsDialog; @@ -64,7 +66,9 @@ class SettingsDialog : public MOBase::TutorableDialog friend class SettingsTab; public: - explicit SettingsDialog(PluginContainer* pluginContainer, ThemeManager const& manager, + explicit SettingsDialog(PluginContainer* pluginContainer, + ThemeManager const& themeManager, + TranslationManager const& translationManager, Settings& settings, QWidget* parent = 0); ~SettingsDialog(); diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 265c9db90..108c4792a 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -8,11 +8,13 @@ using namespace MOBase; -GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) +GeneralSettingsTab::GeneralSettingsTab(Settings& s, + TranslationManager const& translationManager, + SettingsDialog& d) : SettingsTab(s, d) { // language - addLanguages(); + addLanguages(translationManager); selectLanguage(); // download list @@ -84,54 +86,18 @@ void GeneralSettingsTab::update() ui->doubleClickPreviews->isChecked()); } -void GeneralSettingsTab::addLanguages() +void GeneralSettingsTab::addLanguages(TranslationManager const& manager) { - // matches the end of filenames for something like "_en.qm" or "_zh_CN.qm" - const QString pattern = QString::fromStdWString(AppConfig::translationPrefix()) + - "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; + auto translations = manager.translations(); - const QRegularExpression exp(QRegularExpression::anchoredPattern(pattern)); - - QDirIterator iter(QCoreApplication::applicationDirPath() + "/translations", - QDir::Files); - - std::vector> languages; - - while (iter.hasNext()) { - iter.next(); - - const QString file = iter.fileName(); - auto match = exp.match(file); - if (!match.hasMatch()) { - continue; - } - - const QString languageCode = match.captured(1); - const QLocale locale(languageCode); - - QString languageString = QString("%1 (%2)") - .arg(locale.nativeLanguageName()) - .arg(locale.nativeTerritoryName()); - - if (locale.language() == QLocale::Chinese) { - if (languageCode == "zh_TW") { - languageString = "Chinese (Traditional)"; - } else { - languageString = "Chinese (Simplified)"; - } - } - - languages.push_back({languageString, match.captured(1)}); - } - - if (!ui->languageBox->findText("English")) { - languages.push_back({QString("English"), QString("en_US")}); - } - - std::sort(languages.begin(), languages.end()); + std::sort(translations.begin(), translations.end(), [](auto&& lhs, auto&& rhs) { + return std::forward_as_tuple(lhs->language(), lhs->identifier()) < + std::forward_as_tuple(rhs->language(), rhs->identifier()); + }); - for (const auto& lang : languages) { - ui->languageBox->addItem(lang.first, lang.second); + for (const auto& translation : translations) { + ui->languageBox->addItem(ToQString(translation->language()), + ToQString(translation->identifier())); } } diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index 6c1ee8670..2c2987cc0 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -4,16 +4,18 @@ #include "plugincontainer.h" #include "settings.h" #include "settingsdialog.h" +#include "translationmanager.h" class GeneralSettingsTab : public SettingsTab { public: - GeneralSettingsTab(Settings& settings, SettingsDialog& dialog); + GeneralSettingsTab(Settings& settings, TranslationManager const& translationManager, + SettingsDialog& dialog); void update(); private: - void addLanguages(); + void addLanguages(TranslationManager const& translationManager); void selectLanguage(); void resetDialogs(); diff --git a/src/thememanager.cpp b/src/thememanager.cpp index f8f15c6ae..db06008b5 100644 --- a/src/thememanager.cpp +++ b/src/thememanager.cpp @@ -312,7 +312,7 @@ void ThemeManager::extensionUnloaded(IExtension const& extension) } if (std::erase(m_baseThemes, theme) > 0) { - m_themeExtensionsByIdentifier.erase(theme->identifier()); + m_baseThemesByIdentifier.erase(theme->identifier()); } } diff --git a/src/thememanager.h b/src/thememanager.h index 7c48de9c7..69789a4f7 100644 --- a/src/thememanager.h +++ b/src/thememanager.h @@ -4,6 +4,8 @@ #include #include +#include + #include "extensionwatcher.h" class ThemeManager : public ExtensionWatcher diff --git a/src/translationmanager.cpp b/src/translationmanager.cpp new file mode 100644 index 000000000..b10e7ed68 --- /dev/null +++ b/src/translationmanager.cpp @@ -0,0 +1,222 @@ +#include "translationmanager.h" + +#include +#include +#include + +#include +#include + +#include "shared/appconfig.h" + +using namespace MOBase; + +TranslationManager::TranslationManager(QApplication* application) : m_app{application} +{ + // TODO: remove this + addOldFormatTranslations(); + + registerTranslation(std::make_shared( + "en_US", "English", std::vector{})); + + // specific translation to allow load("") to actually unload + m_translationByLanguage[""] = nullptr; +} + +// TODO: remove this +void TranslationManager::addOldFormatTranslations() +{ + const QRegularExpression mainTranslationPattern(QRegularExpression::anchoredPattern( + QString::fromStdWString(AppConfig::translationPrefix()) + + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm")); + + // extract the main translations + QDirIterator iter(QCoreApplication::applicationDirPath() + "/translations", + QDir::Files); + while (iter.hasNext()) { + iter.next(); + + const QString file = iter.fileName(); + auto match = mainTranslationPattern.match(file); + if (!match.hasMatch()) { + continue; + } + + const QString languageCode = match.captured(1); + const QLocale locale(languageCode); + + QString languageString = QString("%1 (%2)") + .arg(locale.nativeLanguageName()) + .arg(locale.nativeCountryName()); + + if (locale.language() == QLocale::Chinese) { + if (languageCode == "zh_TW") { + languageString = "Chinese (Traditional)"; + } else { + languageString = "Chinese (Simplified)"; + } + } + + std::vector qm_files{iter.fileInfo().filesystemFilePath()}; + + if (auto qt_path = qm_files[0].parent_path() / ("qt_" + languageCode.toStdString()); + exists(qt_path)) { + qm_files.push_back(qt_path); + } + + if (auto qtbase_path = + qm_files[0].parent_path() / ("qtbase_" + languageCode.toStdString()); + exists(qtbase_path)) { + qm_files.push_back(qtbase_path); + } + + registerTranslation(std::make_shared( + languageCode.toStdString(), languageString.toStdString(), qm_files)); + } + + // lookup each file except for main and Qt and add an extension for them + for (auto& [code, translation] : m_translationByLanguage) { + QDirIterator iter(QCoreApplication::applicationDirPath() + "/translations", + {ToQString("*_" + code + ".qm")}, QDir::Files); + + while (iter.hasNext()) { + iter.next(); + const auto filename = iter.fileName(); + + // skip main files + if (filename.startsWith(AppConfig::translationPrefix()) || + filename.startsWith("qt")) { + continue; + } + + m_translationExtensions[code].push_back(std::make_shared( + code, std::vector{iter.fileInfo().filesystemFilePath()})); + } + } +} + +bool TranslationManager::load(std::shared_ptr translation) +{ + // no translation -> abort + if (!translation) { + unload(); + return true; + } + + // do not reload the current translation + if (translation == m_currentTranslation) { + return true; + } + + // unload previous translations + unload(); + + // set the current translation + m_currentTranslation = translation; + + // retrieve all files + std::vector qm_files = translation->files(); + { + auto it = m_translationExtensions.find(m_currentTranslation->identifier()); + if (it != m_translationExtensions.end()) { + for (auto&& translationExtension : it->second) { + const auto& ext_files = translationExtension->files(); + qm_files.insert(qm_files.end(), ext_files.begin(), ext_files.end()); + } + } + } + + // add translators + for (const auto& qm_file : qm_files) { + auto translator = std::make_unique(); + if (translator->load(ToQString(absolute(qm_file).native()))) { + m_app->installTranslator(translator.get()); + m_translators.push_back(std::move(translator)); + } else { + log::warn("failed to load translation from '{}'", qm_file.native()); + } + } + + return true; +} + +bool TranslationManager::load(std::string_view language) +{ + auto it = m_translationByLanguage.find(language); + if (it == m_translationByLanguage.end()) { + log::error("translation for '{}' not found", language); + return false; + } + + return load(it->second); +} + +void TranslationManager::unload() +{ + // remove translators from application + for (auto&& translator : m_translators) { + m_app->removeTranslator(translator.get()); + } + m_translators.clear(); + + // unset current translation + m_currentTranslation = nullptr; +} + +void TranslationManager::registerTranslation( + std::shared_ptr const& translation) +{ + // two translations with same identifier, skip (+ warn) + auto it = m_translationByLanguage.find(translation->identifier()); + if (it != m_translationByLanguage.end()) { + log::warn("found existing translation with identifier '{}', skipping", + translation->identifier()); + return; + } + + m_translations.push_back(translation); + m_translationByLanguage[translation->identifier()] = translation; +} + +void TranslationManager::extensionLoaded(IExtension const& extension) +{ + for (const auto& translation : extension.translations()) { + registerTranslation(translation); + } + + for (const auto& translationExtension : extension.translationExtensions()) { + const auto identifier = translationExtension->baseIdentifier(); + m_translationExtensions[identifier].push_back(translationExtension); + } +} + +void TranslationManager::extensionUnloaded(IExtension const& extension) +{ + // remove translation, unload if needed + for (const auto& translation : extension.translations()) { + if (m_currentTranslation == translation) { + unload(); + } + + if (std::erase(m_translations, translation) > 0) { + m_translationByLanguage.erase(translation->identifier()); + } + } + + for (const auto& translationExtension : extension.translationExtensions()) { + if (m_translationExtensions.contains(translationExtension->baseIdentifier())) { + std::erase(m_translationExtensions[translationExtension->baseIdentifier()], + translationExtension); + } + } +} + +void TranslationManager::extensionEnabled(IExtension const& extension) +{ + extensionLoaded(extension); +} + +void TranslationManager::extensionDisabled(IExtension const& extension) +{ + extensionUnloaded(extension); +} diff --git a/src/translationmanager.h b/src/translationmanager.h new file mode 100644 index 000000000..ccb0eb08e --- /dev/null +++ b/src/translationmanager.h @@ -0,0 +1,83 @@ +#ifndef TRANSLATIONMANAGER_H +#define TRANSLATIONMANAGER_H + +#include +#include + +#include + +#include "extensionwatcher.h" + +class TranslationManager : public ExtensionWatcher +{ +public: + TranslationManager(QApplication* application); + + // retrieve the list of available translations + // + const auto& translations() const { return m_translations; } + + // load the given translation + // + bool load(std::shared_ptr translation); + bool load(std::string_view language); + + // unload the current translation + // + void unload(); + + // retrieve the current language, if there is one + // + auto currentTranslation() const { return m_currentTranslation; } + +public: // ExtensionWatcher + void extensionLoaded(MOBase::IExtension const& extension) override; + void extensionUnloaded(MOBase::IExtension const& extension) override; + void extensionEnabled(MOBase::IExtension const& extension) override; + void extensionDisabled(MOBase::IExtension const& extension) override; + +private: + // register a translation + // + void + registerTranslation(std::shared_ptr const& translation); + + // [deprecated] add themes for the translations folder + // + [[deprecated]] void addOldFormatTranslations(); + +private: + // TODO: move these two elsewhere + struct string_equal : std::equal_to + { + using is_transparent = std::true_type; + }; + + struct string_hash : std::hash + { + using is_transparent = std::true_type; + }; + + // application + QApplication* m_app; + + // installed translators + std::vector> m_translators; + + // the current translation + std::shared_ptr m_currentTranslation; + + // the list of base translations + std::vector> m_translations; + std::unordered_map, + string_hash, string_equal> + m_translationByLanguage; + + // the list of translations extensions + std::unordered_map>, + string_hash, string_equal> + m_translationExtensions; +}; + +#endif From 9d9370100cea9b699c3a7826bac50ed5946cc2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 23 May 2022 12:49:27 +0200 Subject: [PATCH 07/26] Fix after uibase changes. --- src/thememanager.cpp | 46 +++++++++++--------------------------- src/thememanager.h | 9 +------- src/translationmanager.cpp | 6 ++--- src/translationmanager.h | 2 +- 4 files changed, 18 insertions(+), 45 deletions(-) diff --git a/src/thememanager.cpp b/src/thememanager.cpp index db06008b5..7fe3eb791 100644 --- a/src/thememanager.cpp +++ b/src/thememanager.cpp @@ -214,16 +214,10 @@ QString ThemeManager::buildStyleSheet(std::shared_ptr const& theme) QString stylesheet = patchStyleSheet(readWholeFile(theme->stylesheet()), theme->stylesheet().parent_path()); - for (auto&& globalExtension : m_globalExtensions) { - stylesheet += "\n" + patchStyleSheet(readWholeFile(globalExtension->stylesheet()), - globalExtension->stylesheet().parent_path()); - } - - auto it = m_themeExtensionsByIdentifier.find(theme->identifier()); - if (it != m_themeExtensionsByIdentifier.end()) { - for (auto&& themExtension : m_themeExtensionsByIdentifier.at(theme->identifier())) { - stylesheet += "\n" + patchStyleSheet(readWholeFile(themExtension->stylesheet()), - themExtension->stylesheet().parent_path()); + for (auto&& themeAddition : m_additions) { + if (themeAddition->isAdditionFor(*theme)) { + stylesheet += "\n" + patchStyleSheet(readWholeFile(themeAddition->stylesheet()), + themeAddition->stylesheet().parent_path()); } } @@ -274,12 +268,10 @@ void ThemeManager::watchThemeFiles(std::shared_ptr const& theme) themeFiles.append(ToQString(absolute(theme->stylesheet()).native())); - for (auto&& themeExtension : m_globalExtensions) { - themeFiles.append(ToQString(absolute(themeExtension->stylesheet()).native())); - } - - for (auto&& themeExtension : m_themeExtensionsByIdentifier[theme->identifier()]) { - themeFiles.append(ToQString(absolute(themeExtension->stylesheet()).native())); + for (auto&& themeAddition : m_additions) { + if (themeAddition->isAdditionFor(*theme)) { + themeFiles.append(ToQString(absolute(themeAddition->stylesheet()).native())); + } } // add all files @@ -292,15 +284,9 @@ void ThemeManager::extensionLoaded(IExtension const& extension) registerTheme(theme); } - for (const auto& themeExtension : extension.themeExtensions()) { - const auto identifier = themeExtension->baseIdentifier(); - - if (identifier.has_value()) { - m_themeExtensionsByIdentifier[*identifier].push_back(themeExtension); - } else { - m_globalExtensions.push_back(themeExtension); - } - } + const auto& themeAdditions = extension.themeAdditions(); + m_additions.insert(m_additions.end(), std::begin(themeAdditions), + std::end(themeAdditions)); } void ThemeManager::extensionUnloaded(IExtension const& extension) @@ -316,14 +302,8 @@ void ThemeManager::extensionUnloaded(IExtension const& extension) } } - for (const auto& themeExtension : extension.themeExtensions()) { - std::erase(m_globalExtensions, themeExtension); - - if (themeExtension->baseIdentifier().has_value() && - m_themeExtensionsByIdentifier.contains(*themeExtension->baseIdentifier())) { - std::erase(m_themeExtensionsByIdentifier[*themeExtension->baseIdentifier()], - themeExtension); - } + for (const auto& themeAddition : extension.themeAdditions()) { + std::erase(m_additions, themeAddition); } } diff --git a/src/thememanager.h b/src/thememanager.h index 69789a4f7..2cf146444 100644 --- a/src/thememanager.h +++ b/src/thememanager.h @@ -107,15 +107,8 @@ class ThemeManager : public ExtensionWatcher string_equal> m_baseThemesByIdentifier; - // the list of theme extensions per theme identifier, for extension that - // have identifiers - std::unordered_map>, - string_hash, string_equal> - m_themeExtensionsByIdentifier; - // theme extensions for all themes - std::vector> m_globalExtensions; + std::vector> m_additions; }; #endif diff --git a/src/translationmanager.cpp b/src/translationmanager.cpp index b10e7ed68..6e805b5a9 100644 --- a/src/translationmanager.cpp +++ b/src/translationmanager.cpp @@ -89,7 +89,7 @@ void TranslationManager::addOldFormatTranslations() continue; } - m_translationExtensions[code].push_back(std::make_shared( + m_translationExtensions[code].push_back(std::make_shared( code, std::vector{iter.fileInfo().filesystemFilePath()})); } } @@ -184,7 +184,7 @@ void TranslationManager::extensionLoaded(IExtension const& extension) registerTranslation(translation); } - for (const auto& translationExtension : extension.translationExtensions()) { + for (const auto& translationExtension : extension.translationAdditions()) { const auto identifier = translationExtension->baseIdentifier(); m_translationExtensions[identifier].push_back(translationExtension); } @@ -203,7 +203,7 @@ void TranslationManager::extensionUnloaded(IExtension const& extension) } } - for (const auto& translationExtension : extension.translationExtensions()) { + for (const auto& translationExtension : extension.translationAdditions()) { if (m_translationExtensions.contains(translationExtension->baseIdentifier())) { std::erase(m_translationExtensions[translationExtension->baseIdentifier()], translationExtension); diff --git a/src/translationmanager.h b/src/translationmanager.h index ccb0eb08e..5b7a3f887 100644 --- a/src/translationmanager.h +++ b/src/translationmanager.h @@ -75,7 +75,7 @@ class TranslationManager : public ExtensionWatcher // the list of translations extensions std::unordered_map>, + std::vector>, string_hash, string_equal> m_translationExtensions; }; From 646723cac7f330f83eb8c55e68ce5283b3e83c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 23 May 2022 19:29:33 +0200 Subject: [PATCH 08/26] Load themes and translations from extensions/. --- src/CMakeLists.txt | 9 ++---- src/createinstancedialog.cpp | 6 ++-- src/extensionmanager.cpp | 48 +++++++++++++++++++++++++++ src/extensionmanager.h | 63 ++++++++++++++++++++++++++++++++++++ src/extensionwatcher.h | 11 ++++--- src/main.cpp | 1 - src/mainwindow.cpp | 2 +- src/moapplication.cpp | 9 ++++++ src/moapplication.h | 3 +- src/modlistview.cpp | 15 +++++---- src/settingsdialogtheme.cpp | 2 +- src/thememanager.cpp | 63 ++++++++++++++++++++++++++++-------- src/thememanager.h | 16 ++++++--- src/translationmanager.cpp | 55 +++++++++++++++++++++++-------- src/translationmanager.h | 16 ++++++--- 15 files changed, 257 insertions(+), 62 deletions(-) create mode 100644 src/extensionmanager.cpp create mode 100644 src/extensionmanager.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2833f575a..e06c3a9b2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,12 +52,6 @@ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.debug.qt6" CONFIGURATIONS Debug RENAME dlls.manifest) -if (NOT MO2_SKIP_STYLESHEETS_INSTALL) - install( - DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/stylesheets" - DESTINATION ${_bin}) -endif() - if (NOT MO2_SKIP_TUTORIALS_INSTALL) install( DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tutorials" @@ -144,7 +138,8 @@ mo2_add_filter(NAME src/extensions GROUPS plugincontainer thememanager translationmanager - extensionconsumer + extensionmanager + extensionwatcher ) mo2_add_filter(NAME src/dialogs GROUPS diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index d50a2063c..e02d84dab 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -133,13 +133,13 @@ CreateInstanceDialog::CreateInstanceDialog(const PluginContainer& pc, Settings* addShortcutAction(QKeySequence::Find, Actions::Find); - addShortcut(Qt::ALT + Qt::Key_Left, [&] { + addShortcut(Qt::ALT | Qt::Key_Left, [&] { back(); }); - addShortcut(Qt::ALT + Qt::Key_Right, [&] { + addShortcut(Qt::ALT | Qt::Key_Right, [&] { next(false); }); - addShortcut(Qt::CTRL + Qt::Key_Return, [&] { + addShortcut(Qt::CTRL | Qt::Key_Return, [&] { next(); }); diff --git a/src/extensionmanager.cpp b/src/extensionmanager.cpp new file mode 100644 index 000000000..75211e0e7 --- /dev/null +++ b/src/extensionmanager.cpp @@ -0,0 +1,48 @@ +#include "extensionmanager.h" + +#include + +using namespace MOBase; +namespace fs = std::filesystem; + +void ExtensionManager::loadExtensions(fs::path const& directory) +{ + for (const auto& entry : fs::directory_iterator{directory}) { + if (entry.is_directory()) { + auto extension = ExtensionFactory::loadExtension(entry.path()); + + if (extension) { + // check if we have a duplicate identifier + const auto it = std::find_if( + m_extensions.begin(), m_extensions.end(), [&extension](const auto& value) { + return value->metadata().identifier().compare( + extension->metadata().identifier(), Qt::CaseInsensitive) == 0; + }); + if (it != m_extensions.end()) { + log::error("an extension '{}' already exists", + extension->metadata().identifier()); + continue; + } + + log::debug("extension correctly loaded from '{}': {}, {}", + entry.path().native(), extension->metadata().identifier(), + extension->metadata().type()); + + triggerWatchers(*extension); + m_extensions.push_back(std::move(extension)); + } + } + } +} + +void ExtensionManager::triggerWatchers(const MOBase::IExtension& extension) const +{ + boost::fusion::for_each(m_watchers, [&extension](auto& watchers) { + using KeyType = typename std::decay_t::first_type; + if (auto* p = dynamic_cast(&extension)) { + for (auto& watcher : watchers.second) { + watcher->extensionLoaded(*p); + } + } + }); +} diff --git a/src/extensionmanager.h b/src/extensionmanager.h new file mode 100644 index 000000000..5c662446e --- /dev/null +++ b/src/extensionmanager.h @@ -0,0 +1,63 @@ +#ifndef EXTENSIONMANAGER_H +#define EXTENSIONMANAGER_H + +#include + +#include +#include +#include + +#include + +#include "extensionwatcher.h" + +class ExtensionManager +{ +public: + // load all extensions from the given directory + // + // trigger all currently registered watchers + // + void loadExtensions(std::filesystem::path const& directory); + + // retrieve the list of currently loaded extensions + // + const auto& extensions() const { return m_extensions; } + + // register an object implementing one or many watcher classes + // + template + void registerWatcher(Watcher& watcher) + { + using WatcherType = std::decay_t; + boost::fusion::for_each(m_watchers, [&watcher](auto& watchers) { + using KeyType = + ExtensionWatcher::first_type>; + if constexpr (std::is_base_of_v) { + watchers.second.push_back(&watcher); + } + }); + } + +private: + // trigger appropriate watchers for the given extension + // + void triggerWatchers(const MOBase::IExtension& extension) const; + +private: + std::vector> m_extensions; + + using WatcherMap = boost::fusion::map< + boost::fusion::pair*>>, + boost::fusion::pair*>>, + boost::fusion::pair*>>, + boost::fusion::pair*>>>; + + WatcherMap m_watchers; +}; + +#endif diff --git a/src/extensionwatcher.h b/src/extensionwatcher.h index 54846e746..5dd47259b 100644 --- a/src/extensionwatcher.h +++ b/src/extensionwatcher.h @@ -6,24 +6,27 @@ // an extension watcher is a class that watches extensions get loaded/unloaded, // typically to extract information from theme that are needed by MO2 // +template class ExtensionWatcher { + static_assert(std::is_base_of_v); + public: // called when a new extension is found and loaded // - virtual void extensionLoaded(MOBase::IExtension const& extension) = 0; + virtual void extensionLoaded(ExtensionType const& extension) = 0; // called when a new extension is unloaded // - virtual void extensionUnloaded(MOBase::IExtension const& extension) = 0; + virtual void extensionUnloaded(ExtensionType const& extension) = 0; // called when a new extension is disabled // - virtual void extensionEnabled(MOBase::IExtension const& extension) = 0; + virtual void extensionEnabled(ExtensionType const& extension) = 0; // called when a new extension is disabled // - virtual void extensionDisabled(MOBase::IExtension const& extension) = 0; + virtual void extensionDisabled(ExtensionType const& extension) = 0; virtual ~ExtensionWatcher() {} }; diff --git a/src/main.cpp b/src/main.cpp index b6642394a..02aa2a183 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,7 +40,6 @@ int run(int argc, char* argv[]) // must be after logging TimeThis tt("main() multiprocess"); - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); MOApplication app(argc, argv); // check if the command line wants to run something right now diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c2a1e36e0..bd814804f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2904,7 +2904,7 @@ void MainWindow::motdReceived(const QString& motd) // don't show motd after 5 seconds, may be annoying. Hopefully the user's // internet connection is faster next time if (m_StartTime.secsTo(QTime::currentTime()) < 5) { - uint hash = qHash(motd); + unsigned int hash = static_cast(qHash(motd)); if (hash != m_OrganizerCore.settings().motdHash()) { MotDDialog dialog(motd); dialog.exec(); diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 53a815667..c6f564782 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "moapplication.h" #include "commandline.h" +#include "extensionmanager.h" #include "instancemanager.h" #include "loglist.h" #include "mainwindow.h" @@ -213,6 +214,14 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) m_themes = std::make_unique(this); m_translations = std::make_unique(this); + m_extensions = std::make_unique(); + m_extensions->registerWatcher(*m_themes); + m_extensions->registerWatcher(*m_translations); + + m_extensions->loadExtensions( + QDir(QCoreApplication::applicationDirPath() + "/extensions") + .filesystemAbsolutePath()); + m_plugins = std::make_unique(m_core.get()); m_plugins->loadPlugins(); diff --git a/src/moapplication.h b/src/moapplication.h index 8f7572240..533337942 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include #include +#include "extensionmanager.h" #include "thememanager.h" #include "translationmanager.h" @@ -33,7 +34,6 @@ class NexusInterface; class OrganizerCore; class PluginContainer; class Settings; -class ThemeManager; namespace MOBase { @@ -80,6 +80,7 @@ class MOApplication : public QApplication std::unique_ptr m_instance; std::unique_ptr m_settings; std::unique_ptr m_nexus; + std::unique_ptr m_extensions; std::unique_ptr m_plugins; std::unique_ptr m_themes; std::unique_ptr m_translations; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 81e87c5ee..cef6f737c 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -1136,7 +1136,7 @@ void ModListView::setHighlightedMods(const std::vector& pluginIndi QColor ModListView::markerColor(const QModelIndex& index) const { unsigned int modIndex = index.data(ModList::IndexRole).toInt(); - bool highligth = m_markers.highlight.find(modIndex) != m_markers.highlight.end(); + bool highlight = m_markers.highlight.find(modIndex) != m_markers.highlight.end(); bool overwrite = m_markers.overwrite.find(modIndex) != m_markers.overwrite.end(); bool archiveOverwrite = m_markers.archiveOverwrite.find(modIndex) != m_markers.archiveOverwrite.end(); @@ -1149,7 +1149,7 @@ QColor ModListView::markerColor(const QModelIndex& index) const bool archiveLooseOverwritten = m_markers.archiveLooseOverwritten.find(modIndex) != m_markers.archiveLooseOverwritten.end(); - if (highligth) { + if (highlight) { return Settings::instance().colors().modlistContainsPlugin(); } else if (overwritten || archiveLooseOverwritten) { return Settings::instance().colors().modlistOverwritingLoose(); @@ -1187,8 +1187,8 @@ QColor ModListView::markerColor(const QModelIndex& index) const a += color.alpha(); } - return QColor(r / colors.size(), g / colors.size(), b / colors.size(), - a / colors.size()); + const int ncolors = static_cast(colors.size()); + return QColor(r / ncolors, g / ncolors, b / ncolors, a / ncolors); } return QColor(); @@ -1391,9 +1391,10 @@ void ModListView::dropEvent(QDropEvent* event) { // from Qt source QModelIndex index; - if (viewport()->rect().contains(event->pos())) { - index = indexAt(event->pos()); - if (!index.isValid() || !visualRect(index).contains(event->pos())) + const auto position = event->position().toPoint(); + if (viewport()->rect().contains(position)) { + index = indexAt(position); + if (!index.isValid() || !visualRect(index).contains(position)) index = QModelIndex(); } diff --git a/src/settingsdialogtheme.cpp b/src/settingsdialogtheme.cpp index 57f6936cd..49dd72668 100644 --- a/src/settingsdialogtheme.cpp +++ b/src/settingsdialogtheme.cpp @@ -63,7 +63,7 @@ void ThemeSettingsTab::addStyles(ThemeManager const& manager) separator = false; } - ui->styleBox->addItem(ToQString(theme->identifier()), ToQString(theme->name())); + ui->styleBox->addItem(ToQString(theme->name()), ToQString(theme->identifier())); } } diff --git a/src/thememanager.cpp b/src/thememanager.cpp index 7fe3eb791..cc9dfa008 100644 --- a/src/thememanager.cpp +++ b/src/thememanager.cpp @@ -65,12 +65,15 @@ ThemeManager::ThemeManager(QApplication* application) : m_app{application} // add built-in themes addQtThemes(); - // TODO: remove this - addOldFormatThemes(); - - // find the default theme - m_defaultTheme = - m_baseThemesByIdentifier.at(m_app->style()->objectName().toStdString()); + // find the default theme - this might be a built-in Qt theme, or null, in which case + // we just create a default theme + if (auto it = + m_baseThemesByIdentifier.find(m_app->style()->objectName().toStdString()); + it != m_baseThemesByIdentifier.end()) { + m_defaultTheme = it->second; + } else { + m_defaultTheme = std::make_shared("", "", std::filesystem::path{}); + } // for ease, we set the empty identifier to the default theme m_baseThemesByIdentifier[""] = m_defaultTheme; @@ -278,18 +281,14 @@ void ThemeManager::watchThemeFiles(std::shared_ptr const& theme) m_watcher.addPaths(themeFiles); } -void ThemeManager::extensionLoaded(IExtension const& extension) +void ThemeManager::extensionLoaded(ThemeExtension const& extension) { for (const auto& theme : extension.themes()) { registerTheme(theme); } - - const auto& themeAdditions = extension.themeAdditions(); - m_additions.insert(m_additions.end(), std::begin(themeAdditions), - std::end(themeAdditions)); } -void ThemeManager::extensionUnloaded(IExtension const& extension) +void ThemeManager::extensionUnloaded(ThemeExtension const& extension) { // remove theme, unload if needed for (const auto& theme : extension.themes()) { @@ -301,18 +300,54 @@ void ThemeManager::extensionUnloaded(IExtension const& extension) m_baseThemesByIdentifier.erase(theme->identifier()); } } +} + +void ThemeManager::extensionEnabled(ThemeExtension const& extension) +{ + extensionLoaded(extension); +} +void ThemeManager::extensionDisabled(ThemeExtension const& extension) +{ + extensionUnloaded(extension); +} + +void ThemeManager::extensionLoaded(PluginExtension const& extension) +{ + bool needReload = false; + for (const auto& themeAddition : extension.themeAdditions()) { + m_additions.push_back(themeAddition); + + // check if the addition is for the current theme + needReload = needReload || themeAddition->isAdditionFor(*m_currentTheme); + } + + if (needReload) { + reload(); + } +} + +void ThemeManager::extensionUnloaded(PluginExtension const& extension) +{ + bool needReload = false; for (const auto& themeAddition : extension.themeAdditions()) { std::erase(m_additions, themeAddition); + + // check if the addition is for the current theme + needReload = needReload || themeAddition->isAdditionFor(*m_currentTheme); + } + + if (needReload) { + reload(); } } -void ThemeManager::extensionEnabled(IExtension const& extension) +void ThemeManager::extensionEnabled(PluginExtension const& extension) { extensionLoaded(extension); } -void ThemeManager::extensionDisabled(IExtension const& extension) +void ThemeManager::extensionDisabled(PluginExtension const& extension) { extensionUnloaded(extension); } diff --git a/src/thememanager.h b/src/thememanager.h index 2cf146444..a5211ba2c 100644 --- a/src/thememanager.h +++ b/src/thememanager.h @@ -8,7 +8,8 @@ #include "extensionwatcher.h" -class ThemeManager : public ExtensionWatcher +class ThemeManager : public ExtensionWatcher, + public ExtensionWatcher { public: ThemeManager(QApplication* application); @@ -38,10 +39,15 @@ class ThemeManager : public ExtensionWatcher } public: // ExtensionWatcher - void extensionLoaded(MOBase::IExtension const& extension) override; - void extensionUnloaded(MOBase::IExtension const& extension) override; - void extensionEnabled(MOBase::IExtension const& extension) override; - void extensionDisabled(MOBase::IExtension const& extension) override; + void extensionLoaded(MOBase::ThemeExtension const& extension) override; + void extensionUnloaded(MOBase::ThemeExtension const& extension) override; + void extensionEnabled(MOBase::ThemeExtension const& extension) override; + void extensionDisabled(MOBase::ThemeExtension const& extension) override; + + void extensionLoaded(MOBase::PluginExtension const& extension) override; + void extensionUnloaded(MOBase::PluginExtension const& extension) override; + void extensionEnabled(MOBase::PluginExtension const& extension) override; + void extensionDisabled(MOBase::PluginExtension const& extension) override; private: // reload the current style diff --git a/src/translationmanager.cpp b/src/translationmanager.cpp index 6e805b5a9..339209417 100644 --- a/src/translationmanager.cpp +++ b/src/translationmanager.cpp @@ -14,7 +14,7 @@ using namespace MOBase; TranslationManager::TranslationManager(QApplication* application) : m_app{application} { // TODO: remove this - addOldFormatTranslations(); + // addOldFormatTranslations(); registerTranslation(std::make_shared( "en_US", "English", std::vector{})); @@ -178,19 +178,14 @@ void TranslationManager::registerTranslation( m_translationByLanguage[translation->identifier()] = translation; } -void TranslationManager::extensionLoaded(IExtension const& extension) +void TranslationManager::extensionLoaded(TranslationExtension const& extension) { for (const auto& translation : extension.translations()) { registerTranslation(translation); } - - for (const auto& translationExtension : extension.translationAdditions()) { - const auto identifier = translationExtension->baseIdentifier(); - m_translationExtensions[identifier].push_back(translationExtension); - } } -void TranslationManager::extensionUnloaded(IExtension const& extension) +void TranslationManager::extensionUnloaded(TranslationExtension const& extension) { // remove translation, unload if needed for (const auto& translation : extension.translations()) { @@ -202,21 +197,55 @@ void TranslationManager::extensionUnloaded(IExtension const& extension) m_translationByLanguage.erase(translation->identifier()); } } +} +void TranslationManager::extensionEnabled(TranslationExtension const& extension) +{ + extensionLoaded(extension); +} + +void TranslationManager::extensionDisabled(TranslationExtension const& extension) +{ + extensionUnloaded(extension); +} + +void TranslationManager::extensionLoaded(PluginExtension const& extension) +{ for (const auto& translationExtension : extension.translationAdditions()) { - if (m_translationExtensions.contains(translationExtension->baseIdentifier())) { - std::erase(m_translationExtensions[translationExtension->baseIdentifier()], - translationExtension); + const auto identifier = translationExtension->baseIdentifier(); + m_translationExtensions[identifier].push_back(translationExtension); + } +} + +void TranslationManager::extensionUnloaded(PluginExtension const& extension) +{ + for (const auto& translationAddition : extension.translationAdditions()) { + if (m_translationExtensions.contains(translationAddition->baseIdentifier())) { + std::erase(m_translationExtensions[translationAddition->baseIdentifier()], + translationAddition); + } + + // unload translator if there is one + const auto& files = translationAddition->files(); + const auto it = std::find_if( + m_translators.begin(), m_translators.end(), [&files](const auto& translator) { + const auto path = QFileInfo(translator->filePath()).filesystemFilePath(); + return std::find(files.begin(), files.end(), path) != files.end(); + }); + + if (it != m_translators.end()) { + m_app->removeTranslator(it->get()); + m_translators.erase(it); } } } -void TranslationManager::extensionEnabled(IExtension const& extension) +void TranslationManager::extensionEnabled(PluginExtension const& extension) { extensionLoaded(extension); } -void TranslationManager::extensionDisabled(IExtension const& extension) +void TranslationManager::extensionDisabled(PluginExtension const& extension) { extensionUnloaded(extension); } diff --git a/src/translationmanager.h b/src/translationmanager.h index 5b7a3f887..0afab7a82 100644 --- a/src/translationmanager.h +++ b/src/translationmanager.h @@ -8,7 +8,8 @@ #include "extensionwatcher.h" -class TranslationManager : public ExtensionWatcher +class TranslationManager : public ExtensionWatcher, + public ExtensionWatcher { public: TranslationManager(QApplication* application); @@ -31,10 +32,15 @@ class TranslationManager : public ExtensionWatcher auto currentTranslation() const { return m_currentTranslation; } public: // ExtensionWatcher - void extensionLoaded(MOBase::IExtension const& extension) override; - void extensionUnloaded(MOBase::IExtension const& extension) override; - void extensionEnabled(MOBase::IExtension const& extension) override; - void extensionDisabled(MOBase::IExtension const& extension) override; + void extensionLoaded(MOBase::TranslationExtension const& extension) override; + void extensionUnloaded(MOBase::TranslationExtension const& extension) override; + void extensionEnabled(MOBase::TranslationExtension const& extension) override; + void extensionDisabled(MOBase::TranslationExtension const& extension) override; + + void extensionLoaded(MOBase::PluginExtension const& extension) override; + void extensionUnloaded(MOBase::PluginExtension const& extension) override; + void extensionEnabled(MOBase::PluginExtension const& extension) override; + void extensionDisabled(MOBase::PluginExtension const& extension) override; private: // register a translation From 1993fee880038402da6dc5376a862f1b55ee2959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Tue, 24 May 2022 17:08:51 +0200 Subject: [PATCH 09/26] Update plugin manager. --- src/CMakeLists.txt | 3 + src/categoriesdialog.h | 2 - src/commandline.cpp | 3 +- src/createinstancedialog.cpp | 4 +- src/createinstancedialog.h | 9 +- src/createinstancedialogpages.cpp | 4 +- src/createinstancedialogpages.h | 2 +- src/datatab.cpp | 6 +- src/datatab.h | 7 +- src/disableproxyplugindialog.cpp | 34 - src/disableproxyplugindialog.h | 44 - src/disableproxyplugindialog.ui | 174 ---- src/downloadmanager.cpp | 4 +- src/downloadmanager.h | 4 +- src/extensionmanager.cpp | 24 + src/extensionmanager.h | 21 +- src/filetree.cpp | 5 +- src/filetree.h | 6 +- src/filterlist.cpp | 2 +- src/game_features.cpp | 21 +- src/game_features.h | 6 +- src/inibakery.cpp | 50 ++ src/inibakery.h | 24 + src/installationmanager.cpp | 20 +- src/installationmanager.h | 6 +- src/instancemanager.cpp | 27 +- src/instancemanager.h | 14 +- src/instancemanagerdialog.cpp | 13 +- src/instancemanagerdialog.h | 6 +- src/mainwindow.cpp | 59 +- src/mainwindow.h | 6 +- src/moapplication.cpp | 5 +- src/moapplication.h | 6 +- src/modinfo.cpp | 12 +- src/modinfo.h | 4 +- src/modinfodialog.cpp | 12 +- src/modinfodialog.h | 6 +- src/modinfodialogconflicts.cpp | 4 +- src/modinfodialogconflictsmodels.cpp | 4 +- src/modinfodialogconflictsmodels.h | 4 +- src/modinfodialogfiletree.cpp | 4 +- src/modinfodialogfwd.h | 4 +- src/modinfodialogimages.cpp | 2 +- src/modinfodialogimages.h | 1 - src/modinfodialognexus.cpp | 4 +- src/modinfodialogtab.cpp | 6 +- src/modinfodialogtab.h | 10 +- src/modinforegular.cpp | 7 +- src/modlist.cpp | 12 +- src/modlist.h | 8 +- src/modlistbypriorityproxy.cpp | 14 +- src/modlistviewactions.cpp | 4 +- src/nexusinterface.cpp | 12 +- src/nexusinterface.h | 8 +- src/organizercore.cpp | 67 +- src/organizercore.h | 12 +- src/organizerproxy.cpp | 15 +- src/organizerproxy.h | 8 +- src/plugincontainer.cpp | 1245 -------------------------- src/plugincontainer.h | 505 ----------- src/pluginmanager.cpp | 703 +++++++++++++++ src/pluginmanager.h | 353 ++++++++ src/previewgenerator.cpp | 12 +- src/previewgenerator.h | 6 +- src/problemsdialog.cpp | 12 +- src/problemsdialog.h | 6 +- src/proxyqt.cpp | 70 ++ src/proxyqt.h | 32 + src/selfupdater.cpp | 6 +- src/selfupdater.h | 4 +- src/settingsdialog.cpp | 11 +- src/settingsdialog.h | 7 +- src/settingsdialoggeneral.h | 1 - src/settingsdialogplugins.cpp | 180 +--- src/settingsdialogplugins.h | 5 +- 75 files changed, 1600 insertions(+), 2423 deletions(-) delete mode 100644 src/disableproxyplugindialog.cpp delete mode 100644 src/disableproxyplugindialog.h delete mode 100644 src/disableproxyplugindialog.ui create mode 100644 src/inibakery.cpp create mode 100644 src/inibakery.h delete mode 100644 src/plugincontainer.cpp delete mode 100644 src/plugincontainer.h create mode 100644 src/pluginmanager.cpp create mode 100644 src/pluginmanager.h create mode 100644 src/proxyqt.cpp create mode 100644 src/proxyqt.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e06c3a9b2..9b2081dad 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -122,6 +122,7 @@ mo2_add_filter(NAME src/categories GROUPS mo2_add_filter(NAME src/core GROUPS archivefiletree githubpp + inibakery installationmanager nexusinterface nxmaccessmanager @@ -140,6 +141,8 @@ mo2_add_filter(NAME src/extensions GROUPS translationmanager extensionmanager extensionwatcher + pluginmanager + proxyqt ) mo2_add_filter(NAME src/dialogs GROUPS diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index 94f390b07..b8465d667 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -21,7 +21,6 @@ along with Mod Organizer. If not, see . #define CATEGORIESDIALOG_H #include "categories.h" -#include "plugincontainer.h" #include "tutorabledialog.h" #include @@ -71,7 +70,6 @@ private slots: private: Ui::CategoriesDialog* ui; - PluginContainer* m_PluginContainer; int m_ContextRow; int m_HighestID; diff --git a/src/commandline.cpp b/src/commandline.cpp index 1d44e0435..0d3061684 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -828,8 +828,9 @@ std::optional ReloadPluginCommand::runPostOrganizer(OrganizerCore& core) QDir(qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())) .absoluteFilePath(name); + // TODO: reload extension, not plugin log::debug("reloading plugin from {}", filepath); - core.pluginContainer().reloadPlugin(filepath); + // core.pluginManager().reloadPlugin(filepath); return {}; } diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index e02d84dab..fc9c3ce69 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -95,7 +95,7 @@ class DirectoryCreator std::vector m_created; }; -CreateInstanceDialog::CreateInstanceDialog(const PluginContainer& pc, Settings* s, +CreateInstanceDialog::CreateInstanceDialog(const PluginManager& pc, Settings* s, QWidget* parent) : QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), m_settings(s), m_switching(false), m_singlePage(false) @@ -161,7 +161,7 @@ Ui::CreateInstanceDialog* CreateInstanceDialog::getUI() return ui.get(); } -const PluginContainer& CreateInstanceDialog::pluginContainer() +const PluginManager& CreateInstanceDialog::pluginManager() { return m_pc; } diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 4495cc78e..515cd14d2 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -16,7 +16,7 @@ namespace cid class Page; } -class PluginContainer; +class PluginManager; class Settings; // this is a wizard for creating a new instance, it is made out of Page objects, @@ -90,13 +90,12 @@ class CreateInstanceDialog : public QDialog ProfileSettings profileSettings; }; - CreateInstanceDialog(const PluginContainer& pc, Settings* s, - QWidget* parent = nullptr); + CreateInstanceDialog(const PluginManager& pc, Settings* s, QWidget* parent = nullptr); ~CreateInstanceDialog(); Ui::CreateInstanceDialog* getUI(); - const PluginContainer& pluginContainer(); + const PluginManager& pluginManager(); Settings* settings(); // disables all the pages except for the given one, used on startup when some @@ -185,7 +184,7 @@ class CreateInstanceDialog : public QDialog private: std::unique_ptr ui; - const PluginContainer& m_pc; + const PluginManager& m_pc; Settings* m_settings; std::vector> m_pages; QString m_originalNext; diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 66f4c31cc..147b906b8 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -1,7 +1,7 @@ #include "createinstancedialogpages.h" #include "filesystemutilities.h" #include "instancemanager.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "settings.h" #include "settingsdialognexus.h" #include "shared/appconfig.h" @@ -55,7 +55,7 @@ void PlaceholderLabel::setVisible(bool b) } Page::Page(CreateInstanceDialog& dlg) - : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()), m_skip(false), + : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginManager()), m_skip(false), m_firstActivation(true) {} diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index ce5256354..8af91f6a2 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -117,7 +117,7 @@ class Page protected: Ui::CreateInstanceDialog* ui; CreateInstanceDialog& m_dlg; - const PluginContainer& m_pc; + const PluginManager& m_pc; bool m_skip; bool m_firstActivation; diff --git a/src/datatab.cpp b/src/datatab.cpp index dc8419cf1..cc05e83d4 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -14,9 +14,9 @@ using namespace MOBase; // in mainwindow.cpp QString UnmanagedModName(); -DataTab::DataTab(OrganizerCore& core, PluginContainer& pc, QWidget* parent, +DataTab::DataTab(OrganizerCore& core, PluginManager& pc, QWidget* parent, Ui::MainWindow* mwui) - : m_core(core), m_pluginContainer(pc), m_parent(parent), + : m_core(core), m_pluginManager(pc), m_parent(parent), ui{mwui->tabWidget, mwui->dataTab, mwui->dataTabRefresh, @@ -26,7 +26,7 @@ DataTab::DataTab(OrganizerCore& core, PluginContainer& pc, QWidget* parent, mwui->dataTabShowHiddenFiles}, m_needUpdate(true) { - m_filetree.reset(new FileTree(core, m_pluginContainer, ui.tree)); + m_filetree.reset(new FileTree(core, m_pluginManager, ui.tree)); m_filter.setUseSourceSort(true); m_filter.setFilterColumn(FileTreeModel::FileName); m_filter.setEdit(mwui->dataTabFilter); diff --git a/src/datatab.h b/src/datatab.h index 0bc0d83e6..c1d258c9f 100644 --- a/src/datatab.h +++ b/src/datatab.h @@ -14,7 +14,7 @@ class MainWindow; } class OrganizerCore; class Settings; -class PluginContainer; +class PluginManager; class FileTree; namespace MOShared @@ -27,8 +27,7 @@ class DataTab : public QObject Q_OBJECT; public: - DataTab(OrganizerCore& core, PluginContainer& pc, QWidget* parent, - Ui::MainWindow* ui); + DataTab(OrganizerCore& core, PluginManager& pc, QWidget* parent, Ui::MainWindow* ui); void saveState(Settings& s) const; void restoreState(const Settings& s); @@ -58,7 +57,7 @@ class DataTab : public QObject }; OrganizerCore& m_core; - PluginContainer& m_pluginContainer; + PluginManager& m_pluginManager; QWidget* m_parent; DataTabUi ui; std::unique_ptr m_filetree; diff --git a/src/disableproxyplugindialog.cpp b/src/disableproxyplugindialog.cpp deleted file mode 100644 index a99b0a073..000000000 --- a/src/disableproxyplugindialog.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "disableproxyplugindialog.h" - -#include "ui_disableproxyplugindialog.h" - -using namespace MOBase; - -DisableProxyPluginDialog::DisableProxyPluginDialog( - MOBase::IPlugin* proxyPlugin, std::vector const& required, - QWidget* parent) - : QDialog(parent), ui(new Ui::DisableProxyPluginDialog) -{ - ui->setupUi(this); - - ui->topLabel->setText(QObject::tr("Disabling the '%1' plugin will prevent the " - "following %2 plugin(s) from working:", - "", required.size()) - .arg(proxyPlugin->localizedName()) - .arg(required.size())); - - connect(ui->noBtn, &QPushButton::clicked, this, &QDialog::reject); - connect(ui->yesBtn, &QPushButton::clicked, this, &QDialog::accept); - - ui->requiredPlugins->setSelectionMode(QAbstractItemView::NoSelection); - ui->requiredPlugins->setRowCount(required.size()); - for (int i = 0; i < required.size(); ++i) { - ui->requiredPlugins->setItem(i, 0, - new QTableWidgetItem(required[i]->localizedName())); - ui->requiredPlugins->setItem(i, 1, - new QTableWidgetItem(required[i]->description())); - ui->requiredPlugins->setRowHeight(i, 9); - } - ui->requiredPlugins->verticalHeader()->setVisible(false); - ui->requiredPlugins->sortByColumn(0, Qt::AscendingOrder); -} diff --git a/src/disableproxyplugindialog.h b/src/disableproxyplugindialog.h deleted file mode 100644 index 421697b67..000000000 --- a/src/disableproxyplugindialog.h +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (C) 2020 Mikaël Capelle. 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 . -*/ - -#ifndef DISABLEPROXYPLUGINDIALOG_H -#define DISABLEPROXYPLUGINDIALOG_H - -#include - -#include "ipluginproxy.h" - -namespace Ui -{ -class DisableProxyPluginDialog; -} - -class DisableProxyPluginDialog : public QDialog -{ -public: - DisableProxyPluginDialog(MOBase::IPlugin* proxyPlugin, - std::vector const& required, - QWidget* parent = nullptr); - -private slots: - - Ui::DisableProxyPluginDialog* ui; -}; - -#endif diff --git a/src/disableproxyplugindialog.ui b/src/disableproxyplugindialog.ui deleted file mode 100644 index 9f0687879..000000000 --- a/src/disableproxyplugindialog.ui +++ /dev/null @@ -1,174 +0,0 @@ - - - DisableProxyPluginDialog - - - - 0 - 0 - 522 - 417 - - - - Really disable plugin? - - - - - - - 0 - 0 - - - - - - - - 0 - 0 - - - - - - - Qt::PlainText - - - :/MO/gui/remove - - - Qt::AlignCenter - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - - - - - Disabling the '%1' plugin will prevent the following plugins from working: - - - - - - - - - - 2 - - - true - - - - Plugin - - - - - Description - - - - - - - - Do you want to continue? You will need to restart Mod Organizer for the change to take effect. - - - - - - - - 0 - 0 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 80 - 0 - - - - Yes - - - - :/MO/gui/remove:/MO/gui/remove - - - - - - - - 0 - 0 - - - - - 80 - 0 - - - - No - - - true - - - false - - - - - - - - - - - - - diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 730803f6d..9bfc77022 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -322,9 +322,9 @@ void DownloadManager::setShowHidden(bool showHidden) refreshList(); } -void DownloadManager::setPluginContainer(PluginContainer* pluginContainer) +void DownloadManager::setPluginManager(PluginManager* pluginManager) { - m_NexusInterface->setPluginContainer(pluginContainer); + m_NexusInterface->setPluginManager(pluginManager); } void DownloadManager::refreshList() diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 1e7a626ee..28af32b73 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -49,7 +49,7 @@ class IPluginGame; } class NexusInterface; -class PluginContainer; +class PluginManager; class OrganizerCore; /*! @@ -213,7 +213,7 @@ class DownloadManager : public QObject */ void setShowHidden(bool showHidden); - void setPluginContainer(PluginContainer* pluginContainer); + void setPluginManager(PluginManager* pluginManager); /** * @brief download from an already open network connection diff --git a/src/extensionmanager.cpp b/src/extensionmanager.cpp index 75211e0e7..e894abe98 100644 --- a/src/extensionmanager.cpp +++ b/src/extensionmanager.cpp @@ -46,3 +46,27 @@ void ExtensionManager::triggerWatchers(const MOBase::IExtension& extension) cons } }); } + +const IExtension* ExtensionManager::extension(QString const& identifier) const +{ + // TODO: use a map for faster lookup + auto it = std::find_if(m_extensions.begin(), m_extensions.end(), + [&identifier](const auto& ext) { + return identifier.compare(ext->metadata().identifier(), + Qt::CaseInsensitive) == 0; + }); + + return it == m_extensions.end() ? nullptr : it->get(); +} + +bool ExtensionManager::isEnabled(MOBase::IExtension const& extension) const +{ + // TODO + return true; +} + +bool ExtensionManager::isEnabled(QString const& identifier) const +{ + const auto* e = extension(identifier); + return e ? isEnabled(*e) : false; +} diff --git a/src/extensionmanager.h b/src/extensionmanager.h index 5c662446e..5ecc05595 100644 --- a/src/extensionmanager.h +++ b/src/extensionmanager.h @@ -13,6 +13,23 @@ class ExtensionManager { +public: + // retrieve the list of currently loaded extensions + // + const auto& extensions() const { return m_extensions; } + + // retrieve the extension with the given identifier, or a null pointer if there is + // none + // + // identifier are case insensitive + // + const MOBase::IExtension* extension(QString const& identifier) const; + + // check if the given extension is enabled + // + bool isEnabled(MOBase::IExtension const& extension) const; + bool isEnabled(QString const& extension) const; + public: // load all extensions from the given directory // @@ -20,10 +37,6 @@ class ExtensionManager // void loadExtensions(std::filesystem::path const& directory); - // retrieve the list of currently loaded extensions - // - const auto& extensions() const { return m_extensions; } - // register an object implementing one or many watcher classes // template diff --git a/src/filetree.cpp b/src/filetree.cpp index 6d397f24e..12042fdc7 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -3,6 +3,7 @@ #include "filetreeitem.h" #include "filetreemodel.h" #include "organizercore.h" +#include "previewgenerator.h" #include "shared/directoryentry.h" #include "shared/fileentry.h" #include "shared/filesorigin.h" @@ -12,7 +13,7 @@ using namespace MOShared; using namespace MOBase; -bool canPreviewFile(const PluginContainer& pc, const FileEntry& file) +bool canPreviewFile(const PluginManager& pc, const FileEntry& file) { return canPreviewFile(pc, file.isFromArchive(), QString::fromStdWString(file.getName())); @@ -116,7 +117,7 @@ class MenuItem } }; -FileTree::FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree) +FileTree::FileTree(OrganizerCore& core, PluginManager& pc, QTreeView* tree) : m_core(core), m_plugins(pc), m_tree(tree), m_model(new FileTreeModel(core)) { m_tree->sortByColumn(0, Qt::AscendingOrder); diff --git a/src/filetree.h b/src/filetree.h index d9597322a..b91cd5181 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -10,7 +10,7 @@ class FileEntry; } class OrganizerCore; -class PluginContainer; +class PluginManager; class FileTreeModel; class FileTreeItem; @@ -19,7 +19,7 @@ class FileTree : public QObject Q_OBJECT; public: - FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree); + FileTree(OrganizerCore& core, PluginManager& pc, QTreeView* tree); FileTreeModel* model(); void refresh(); @@ -52,7 +52,7 @@ class FileTree : public QObject private: OrganizerCore& m_core; - PluginContainer& m_plugins; + PluginManager& m_plugins; QTreeView* m_tree; FileTreeModel* m_model; diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 3be67def3..85830306e 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -2,7 +2,7 @@ #include "categories.h" #include "categoriesdialog.h" #include "organizercore.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "settings.h" #include "ui_mainwindow.h" #include diff --git a/src/game_features.cpp b/src/game_features.cpp index 9505c8dcb..99c9d9afb 100644 --- a/src/game_features.cpp +++ b/src/game_features.cpp @@ -12,7 +12,7 @@ #include "gameplugins.h" #include "localsavegames.h" #include "organizercore.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "savegameinfo.h" #include "scriptextender.h" #include "unmanagedmods.h" @@ -132,9 +132,8 @@ class GameFeatures::CombinedModDataContent : public ModDataContent } }; -GameFeatures::GameFeatures(OrganizerCore* core, PluginContainer* plugins) - : m_pluginContainer(*plugins), - m_modDataChecker(std::make_unique()), +GameFeatures::GameFeatures(OrganizerCore* core, PluginManager* plugins) + : m_plugins(*plugins), m_modDataChecker(std::make_unique()), m_modDataContent(std::make_unique()) { // can be nullptr since the plugin container can be initialized with a Core (e.g., @@ -151,10 +150,10 @@ GameFeatures::GameFeatures(OrganizerCore* core, PluginContainer* plugins) }); }; - connect(plugins, &PluginContainer::pluginEnabled, updateFeatures); - connect(plugins, &PluginContainer::pluginDisabled, updateFeatures); - connect(plugins, &PluginContainer::pluginRegistered, updateFeatures); - connect(plugins, &PluginContainer::pluginUnregistered, + connect(plugins, &PluginManager::pluginEnabled, updateFeatures); + connect(plugins, &PluginManager::pluginDisabled, updateFeatures); + connect(plugins, &PluginManager::pluginRegistered, updateFeatures); + connect(plugins, &PluginManager::pluginUnregistered, [this, updateFeatures](MOBase::IPlugin* plugin) { // remove features from the current plugin for (auto& [_, features] : m_allFeatures) { @@ -188,20 +187,20 @@ void GameFeatures::updateCurrentFeatures(std::type_index const& index) m_currentFeatures[index].clear(); // this can occur when starting MO2, just wait for the next update - if (!m_pluginContainer.managedGame()) { + if (!m_plugins.managedGame()) { return; } for (const auto& dataFeature : features) { // registering plugin is disabled - if (!m_pluginContainer.isEnabled(dataFeature.plugin())) { + if (!m_plugins.isEnabled(dataFeature.plugin())) { continue; } // games does not match if (!dataFeature.games().isEmpty() && - !dataFeature.games().contains(m_pluginContainer.managedGame()->gameName())) { + !dataFeature.games().contains(m_plugins.managedGame()->gameName())) { continue; } diff --git a/src/game_features.h b/src/game_features.h index 55f35e377..fb42c8987 100644 --- a/src/game_features.h +++ b/src/game_features.h @@ -20,7 +20,7 @@ class IPluginGame; } // namespace MOBase class OrganizerCore; -class PluginContainer; +class PluginManager; /** * Class managing game features, either registered or from the game plugin. @@ -33,7 +33,7 @@ class GameFeatures : public QObject /** * */ - GameFeatures(OrganizerCore* core, PluginContainer* plugins); + GameFeatures(OrganizerCore* core, PluginManager* plugins); ~GameFeatures(); @@ -104,7 +104,7 @@ class GameFeatures : public QObject CombinedModDataChecker& modDataChecker() const; CombinedModDataContent& modDataContent() const; - PluginContainer& m_pluginContainer; + PluginManager& m_plugins; std::unordered_map> m_allFeatures; std::unordered_map>> diff --git a/src/inibakery.cpp b/src/inibakery.cpp new file mode 100644 index 000000000..0b58307f3 --- /dev/null +++ b/src/inibakery.cpp @@ -0,0 +1,50 @@ +#include "inibakery.h" + +#include +#include + +#include "organizercore.h" + +using namespace MOBase; + +IniBakery::IniBakery(OrganizerCore& core) : m_core{core} +{ + m_core.onAboutToRun([this](auto&&...) { + return prepareIni(); + }); +} + +bool IniBakery::prepareIni() const +{ + const auto& features = m_core.pluginManager().gameFeatures(); + + if (auto savegames = features.gameFeature()) { + savegames->prepareProfile(m_core.currentProfile()); + } + + if (auto invalidation = features.gameFeature()) { + invalidation->prepareProfile(m_core.currentProfile()); + } + + return true; +} + +MappingType IniBakery::mappings() const +{ + MappingType result; + + const auto iniFileNames = m_core.managedGame()->iniFiles(); + const IPluginGame* game = m_core.managedGame(); + + IProfile* profile = m_core.currentProfile(); + + if (profile->localSettingsEnabled()) { + for (const QString& iniFile : iniFileNames) { + result.push_back({m_core.profilePath() + "/" + QFileInfo(iniFile).fileName(), + game->documentsDirectory().absoluteFilePath(iniFile), false, + false}); + } + } + + return result; +} diff --git a/src/inibakery.h b/src/inibakery.h new file mode 100644 index 000000000..2707085aa --- /dev/null +++ b/src/inibakery.h @@ -0,0 +1,24 @@ +#ifndef INIBAKERY_H +#define INIBAKERY_H + +#include + +#include + +class OrganizerCore; + +class IniBakery +{ +public: + IniBakery(OrganizerCore& core); + + MappingType mappings() const; + +private: + bool prepareIni() const; + +private: + OrganizerCore& m_core; +}; + +#endif diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 37c86c7e4..150b677aa 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -116,9 +116,9 @@ void InstallationManager::setParentWidget(QWidget* widget) m_ParentWidget = widget; } -void InstallationManager::setPluginContainer(const PluginContainer* pluginContainer) +void InstallationManager::setPluginManager(const PluginManager* pluginManager) { - m_PluginContainer = pluginContainer; + m_PluginManager = pluginManager; } void InstallationManager::queryPassword() @@ -751,7 +751,7 @@ InstallationResult InstallationManager::install(const QString& fileName, std::shared_ptr filesTree = archiveOpen ? ArchiveFileTree::makeTree(*m_ArchiveHandler) : nullptr; - auto installers = m_PluginContainer->plugins(); + auto installers = m_PluginManager->plugins(); std::sort(installers.begin(), installers.end(), [](IPluginInstaller* lhs, IPluginInstaller* rhs) { @@ -763,7 +763,7 @@ InstallationResult InstallationManager::install(const QString& fileName, for (IPluginInstaller* installer : installers) { // don't use inactive installers (installer can't be null here but vc static code // analysis thinks it could) - if ((installer == nullptr) || !m_PluginContainer->isEnabled(installer)) { + if ((installer == nullptr) || !m_PluginManager->isEnabled(installer)) { continue; } @@ -910,8 +910,8 @@ QStringList InstallationManager::getSupportedExtensions() const { std::set supportedExtensions( {"zip", "rar", "7z", "fomod", "001"}); - for (auto* installer : m_PluginContainer->plugins()) { - if (m_PluginContainer->isEnabled(installer)) { + for (auto* installer : m_PluginManager->plugins()) { + if (m_PluginManager->isEnabled(installer)) { if (auto* installerCustom = dynamic_cast(installer)) { std::set extensions = installerCustom->supportedExtensions(); supportedExtensions.insert(extensions.begin(), extensions.end()); @@ -925,9 +925,9 @@ void InstallationManager::notifyInstallationStart(QString const& archive, bool reinstallation, ModInfo::Ptr currentMod) { - auto& installers = m_PluginContainer->plugins(); + auto& installers = m_PluginManager->plugins(); for (auto* installer : installers) { - if (m_PluginContainer->isEnabled(installer)) { + if (m_PluginManager->isEnabled(installer)) { installer->onInstallationStart(archive, reinstallation, currentMod.get()); } } @@ -936,9 +936,9 @@ void InstallationManager::notifyInstallationStart(QString const& archive, void InstallationManager::notifyInstallationEnd(const InstallationResult& result, ModInfo::Ptr newMod) { - auto& installers = m_PluginContainer->plugins(); + auto& installers = m_PluginManager->plugins(); for (auto* installer : installers) { - if (m_PluginContainer->isEnabled(installer)) { + if (m_PluginManager->isEnabled(installer)) { installer->onInstallationEnd(result.result(), newMod.get()); } } diff --git a/src/installationmanager.h b/src/installationmanager.h index e125bbe94..e24f96b73 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -35,7 +35,7 @@ along with Mod Organizer. If not, see . #include #include "modinfo.h" -#include "plugincontainer.h" +#include "pluginmanager.h" // contains installation result from the manager, internal class // for MO2 that is not forwarded to plugin @@ -129,7 +129,7 @@ class InstallationManager : public QObject, public MOBase::IInstallationManager /** * */ - void setPluginContainer(const PluginContainer* pluginContainer); + void setPluginManager(const PluginManager* pluginManager); /** * @brief update the directory where downloads are stored @@ -332,7 +332,7 @@ private slots: private: // The plugin container, mostly to check if installer are enabled or not. - const PluginContainer* m_PluginContainer; + const PluginManager* m_PluginManager; bool m_IsRunning; diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 90e9e5190..29640776a 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -20,10 +20,11 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "createinstancedialog.h" #include "createinstancedialogpages.h" +#include "extensionmanager.h" #include "filesystemutilities.h" #include "instancemanagerdialog.h" #include "nexusinterface.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "selectiondialog.h" #include "settings.h" #include "shared/appconfig.h" @@ -146,7 +147,7 @@ bool Instance::readFromIni() return true; } -Instance::SetupResults Instance::setup(PluginContainer& plugins) +Instance::SetupResults Instance::setup(PluginManager& plugins) { // read initial values from the ini if (!readFromIni()) { @@ -208,7 +209,7 @@ void Instance::setVariant(const QString& name) m_gameVariant = name; } -Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) +Instance::SetupResults Instance::getGamePlugin(PluginManager& plugins) { if (!m_gameName.isEmpty() && !m_gameDir.isEmpty()) { // normal case: both the name and dir are in the ini @@ -609,15 +610,15 @@ bool InstanceManager::allowedToChangeInstance() const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory(const QString& instanceDir, - PluginContainer& plugins) const + PluginManager& plugins) const { return const_cast( - gamePluginForDirectory(instanceDir, const_cast(plugins))); + gamePluginForDirectory(instanceDir, const_cast(plugins))); } const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory(const QString& instanceDir, - const PluginContainer& plugins) const + const PluginManager& plugins) const { const QString ini = iniPath(instanceDir); @@ -701,9 +702,13 @@ std::unique_ptr selectInstance() auto& m = InstanceManager::singleton(); // since there is no instance currently active, load plugins with a null - // OrganizerCore; see PluginContainer::initPlugin() + // OrganizerCore; see PluginManager::initPlugin() NexusInterface ni(nullptr); - PluginContainer pc(nullptr); + ExtensionManager ec; + ec.loadExtensions(QDir(QCoreApplication::applicationDirPath() + "/extensions") + .filesystemAbsolutePath()); + + PluginManager pc(ec, nullptr); pc.loadPlugins(); if (m.hasAnyInstances()) { @@ -742,7 +747,7 @@ std::unique_ptr selectInstance() // this is used below in setupInstance() when the game directory is gone or // no plugins can recognize it // -SetupInstanceResults selectGame(Instance& instance, PluginContainer& pc) +SetupInstanceResults selectGame(Instance& instance, PluginManager& pc) { CreateInstanceDialog dlg(pc, nullptr); @@ -774,7 +779,7 @@ SetupInstanceResults selectGame(Instance& instance, PluginContainer& pc) // or when a new variant has become supported by the plugin for a game the // user already has an instance for // -SetupInstanceResults selectVariant(Instance& instance, PluginContainer& pc) +SetupInstanceResults selectVariant(Instance& instance, PluginManager& pc) { CreateInstanceDialog dlg(pc, nullptr); @@ -800,7 +805,7 @@ SetupInstanceResults selectVariant(Instance& instance, PluginContainer& pc) return SetupInstanceResults::TryAgain; } -SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) +SetupInstanceResults setupInstance(Instance& instance, PluginManager& pc) { // set up the instance const auto setupResult = instance.setup(pc); diff --git a/src/instancemanager.h b/src/instancemanager.h index 176033e81..efed37c93 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -10,7 +10,7 @@ class IPluginGame; } class Settings; -class PluginContainer; +class PluginManager; // represents an instance, either global or portable // @@ -110,7 +110,7 @@ class Instance // setup() tries to recover from some errors, but can fail for a variety of // reasons, see SetupResults // - SetupResults setup(PluginContainer& plugins); + SetupResults setup(PluginManager& plugins); // overrides the game name and directory // @@ -198,7 +198,7 @@ class Instance // figures out the game plugin for this instance // - SetupResults getGamePlugin(PluginContainer& plugins); + SetupResults getGamePlugin(PluginManager& plugins); // figures out the profile name for this instance // @@ -243,11 +243,11 @@ class InstanceManager // // returns null if all of this fails // - const MOBase::IPluginGame* - gamePluginForDirectory(const QString& dir, const PluginContainer& plugins) const; + const MOBase::IPluginGame* gamePluginForDirectory(const QString& dir, + const PluginManager& plugins) const; MOBase::IPluginGame* gamePluginForDirectory(const QString& dir, - PluginContainer& plugins) const; + PluginManager& plugins) const; // clears the instance name from the registry; on restart, this will make MO // either select the portable instance if it exists, or display the instance @@ -359,6 +359,6 @@ std::unique_ptr selectInstance(); // // - if the instance has been set up correctly, returns Okay // -SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc); +SetupInstanceResults setupInstance(Instance& instance, PluginManager& pc); #endif // MODORGANIZER_INSTANCEMANAGER_INCLUDED diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index b5139e908..5a026f1fb 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -2,7 +2,7 @@ #include "createinstancedialog.h" #include "filesystemutilities.h" #include "instancemanager.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "selectiondialog.h" #include "settings.h" #include "shared/appconfig.h" @@ -17,7 +17,7 @@ using namespace MOBase; // returns the icon for the given instance or an empty 32x32 icon if the game // plugin couldn't be found // -QIcon instanceIcon(PluginContainer& pc, const Instance& i) +QIcon instanceIcon(PluginManager& pc, const Instance& i) { auto* game = InstanceManager::singleton().gamePluginForDirectory(i.directory(), pc); @@ -146,7 +146,7 @@ QString getInstanceName(QWidget* parent, const QString& title, const QString& mo InstanceManagerDialog::~InstanceManagerDialog() = default; -InstanceManagerDialog::InstanceManagerDialog(PluginContainer& pc, QWidget* parent) +InstanceManagerDialog::InstanceManagerDialog(PluginManager& pc, QWidget* parent) : QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc), m_model(nullptr), m_restartOnSelect(true) { @@ -313,7 +313,7 @@ void InstanceManagerDialog::select(std::size_t i) fillData(*ii); ui->list->selectionModel()->select( - m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), + m_filter.mapFromSource(m_filter.sourceModel()->index(static_cast(i), 0)), QItemSelectionModel::ClearAndSelect); } else { clearData(); @@ -341,7 +341,8 @@ void InstanceManagerDialog::selectActiveInstance() if (m_instances[i]->displayName() == active->displayName()) { select(i); - ui->list->scrollTo(m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0))); + ui->list->scrollTo(m_filter.mapFromSource( + m_filter.sourceModel()->index(static_cast(i), 0))); return; } @@ -455,7 +456,7 @@ void InstanceManagerDialog::rename() auto newInstance = std::make_unique(dest, false); i = newInstance.get(); - m_model->item(selIndex)->setText(newName); + m_model->item(static_cast(selIndex))->setText(newName); m_instances[selIndex] = std::move(newInstance); fillData(*i); diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 884beaa53..3a50d61aa 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -10,7 +10,7 @@ class InstanceManagerDialog; }; class Instance; -class PluginContainer; +class PluginManager; // a dialog to manage existing instances // @@ -19,7 +19,7 @@ class InstanceManagerDialog : public QDialog Q_OBJECT public: - explicit InstanceManagerDialog(PluginContainer& pc, QWidget* parent = nullptr); + explicit InstanceManagerDialog(PluginManager& pc, QWidget* parent = nullptr); ~InstanceManagerDialog(); @@ -90,7 +90,7 @@ class InstanceManagerDialog : public QDialog static const std::size_t NoSelection = -1; std::unique_ptr ui; - PluginContainer& m_pc; + PluginManager& m_pc; std::vector> m_instances; MOBase::FilterWidget m_filter; QStandardItemModel* m_model; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bd814804f..199342f57 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -230,13 +230,13 @@ void setFilterShortcuts(QWidget* widget, QLineEdit* edit) } MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginContainer& pluginContainer, ThemeManager& themeManager, + PluginManager& pluginManager, ThemeManager& themeManager, TranslationManager& translationManager, QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_WasVisible(false), m_FirstPaint(true), m_linksSeparator(nullptr), m_Tutorial(this, "MainWindow"), m_OldProfileIndex(-1), m_OldExecutableIndex(-1), m_CategoryFactory(CategoryFactory::instance()), m_OrganizerCore(organizerCore), - m_PluginContainer(pluginContainer), m_ThemeManager(themeManager), + m_PluginManager(pluginManager), m_ThemeManager(themeManager), m_TranslationManager(translationManager), m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)), m_LinkToolbar(nullptr), m_LinkDesktop(nullptr), m_LinkStartMenu(nullptr), @@ -311,7 +311,7 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, settings.geometry().restoreState(ui->espList->header()); // data tab - m_DataTab.reset(new DataTab(m_OrganizerCore, m_PluginContainer, this, ui)); + m_DataTab.reset(new DataTab(m_OrganizerCore, m_PluginManager, this, ui)); m_DataTab->restoreState(settings); connect(m_DataTab.get(), &DataTab::executablesChanged, [&] { @@ -375,8 +375,9 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, updateSortButton(); - connect(&m_PluginContainer, SIGNAL(diagnosisUpdate()), this, - SLOT(scheduleCheckForProblems())); + connect(&m_PluginManager, &PluginManager::diagnosePluginInvalidated, [this] { + scheduleCheckForProblems(); + }); connect(&m_OrganizerCore, &OrganizerCore::directoryStructureReady, this, &MainWindow::onDirectoryStructureChanged); @@ -428,21 +429,21 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(ui->actionTool->menu(), &QMenu::aboutToShow, [&] { updateToolMenu(); }); - connect(&m_PluginContainer, &PluginContainer::pluginEnabled, this, + connect(&m_PluginManager, &PluginManager::pluginEnabled, this, [this](IPlugin* plugin) { - if (m_PluginContainer.implementInterface(plugin)) { + if (m_PluginManager.implementInterface(plugin)) { updateModPageMenu(); } }); - connect(&m_PluginContainer, &PluginContainer::pluginDisabled, this, + connect(&m_PluginManager, &PluginManager::pluginDisabled, this, [this](IPlugin* plugin) { - if (m_PluginContainer.implementInterface(plugin)) { + if (m_PluginManager.implementInterface(plugin)) { updateModPageMenu(); } }); - connect(&m_PluginContainer, &PluginContainer::pluginRegistered, this, + connect(&m_PluginManager, &PluginManager::pluginRegistered, this, &MainWindow::onPluginRegistrationChanged); - connect(&m_PluginContainer, &PluginContainer::pluginUnregistered, this, + connect(&m_PluginManager, &PluginManager::pluginUnregistered, this, &MainWindow::onPluginRegistrationChanged); connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, @@ -1024,9 +1025,9 @@ void MainWindow::checkForProblemsImpl() m_ProblemsCheckRequired = false; TimeThis tt("MainWindow::checkForProblemsImpl()"); size_t numProblems = 0; - for (QObject* pluginObj : m_PluginContainer.plugins()) { + for (QObject* pluginObj : m_PluginManager.plugins()) { IPlugin* plugin = qobject_cast(pluginObj); - if (plugin == nullptr || m_PluginContainer.isEnabled(plugin)) { + if (plugin == nullptr || m_PluginManager.isEnabled(plugin)) { IPluginDiagnose* diagnose = qobject_cast(pluginObj); if (diagnose != nullptr) numProblems += diagnose->activeProblems().size(); @@ -1346,7 +1347,7 @@ void MainWindow::showEvent(QShowEvent* event) updateProblemsButton(); // notify plugins that the MO2 is ready - m_PluginContainer.startPlugins(this); + m_PluginManager.startPlugins(this); // forces a log list refresh to display startup logs // @@ -1513,7 +1514,7 @@ void MainWindow::updateToolMenu() // Clear the menu: ui->actionTool->menu()->clear(); - std::vector toolPlugins = m_PluginContainer.plugins(); + std::vector toolPlugins = m_PluginManager.plugins(); // Sort the plugins by display name std::sort(std::begin(toolPlugins), std::end(toolPlugins), @@ -1524,7 +1525,7 @@ void MainWindow::updateToolMenu() // Remove disabled plugins: toolPlugins.erase(std::remove_if(std::begin(toolPlugins), std::end(toolPlugins), [&](auto* tool) { - return !m_PluginContainer.isEnabled(tool); + return !m_PluginManager.isEnabled(tool); }), toolPlugins.end()); @@ -1616,7 +1617,7 @@ void MainWindow::updateModPageMenu() // Determine the loaded mod page plugins std::vector modPagePlugins = - m_PluginContainer.plugins(); + m_PluginManager.plugins(); // Sort the plugins by display name std::sort(std::begin(modPagePlugins), std::end(modPagePlugins), @@ -1628,7 +1629,7 @@ void MainWindow::updateModPageMenu() modPagePlugins.erase(std::remove_if(std::begin(modPagePlugins), std::end(modPagePlugins), [&](auto* tool) { - return !m_PluginContainer.isEnabled(tool); + return !m_PluginManager.isEnabled(tool); }), modPagePlugins.end()); @@ -2442,7 +2443,7 @@ void MainWindow::modInstalled(const QString& modName) void MainWindow::importCategories(bool) { NexusInterface& nexus = NexusInterface::instance(); - nexus.setPluginContainer(&m_OrganizerCore.pluginContainer()); + nexus.setPluginManager(&m_OrganizerCore.pluginManager()); nexus.requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, QVariant(), QString()); } @@ -2718,8 +2719,8 @@ void MainWindow::on_actionSettings_triggered() const bool oldCheckForUpdates = settings.checkForUpdates(); const int oldMaxDumps = settings.diagnostics().maxCoreDumps(); - SettingsDialog dialog(&m_PluginContainer, m_ThemeManager, m_TranslationManager, - settings, this); + SettingsDialog dialog(m_PluginManager, m_ThemeManager, m_TranslationManager, settings, + this); dialog.exec(); auto e = dialog.exitNeeded(); @@ -2829,7 +2830,7 @@ void MainWindow::onPluginRegistrationChanged() void MainWindow::refreshNexusCategories(CategoriesDialog* dialog) { NexusInterface& nexus = NexusInterface::instance(); - nexus.setPluginContainer(&m_PluginContainer); + nexus.setPluginManager(&m_PluginManager); if (!Settings::instance().game().plugin()->primarySources().isEmpty()) { nexus.requestGameInfo( Settings::instance().game().plugin()->primarySources().first(), dialog, @@ -3084,7 +3085,7 @@ void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int) { QString gameNameReal; - for (IPluginGame* game : m_PluginContainer.plugins()) { + for (IPluginGame* game : m_PluginManager.plugins()) { if (game->gameNexusName() == gameName) { gameNameReal = game->gameShortName(); break; @@ -3140,7 +3141,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD QList fileUpdates = resultInfo["file_updates"].toList(); QString gameNameReal; - for (IPluginGame* game : m_PluginContainer.plugins()) { + for (IPluginGame* game : m_PluginManager.plugins()) { if (game->gameNexusName() == gameName) { gameNameReal = game->gameShortName(); break; @@ -3283,7 +3284,7 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD QString gameNameReal; bool foundUpdate = false; - for (IPluginGame* game : m_PluginContainer.plugins()) { + for (IPluginGame* game : m_PluginManager.plugins()) { if (game->gameNexusName() == gameName) { gameNameReal = game->gameShortName(); break; @@ -3385,7 +3386,7 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int) { QMap gameNames; - for (auto game : m_PluginContainer.plugins()) { + for (auto game : m_PluginManager.plugins()) { gameNames[game->gameNexusName()] = game->gameShortName(); } @@ -3473,7 +3474,7 @@ void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, in // update last checked timestamp on orphaned mods as well to avoid repeating // requests QString gameNameReal; - for (IPluginGame* game : m_PluginContainer.plugins()) { + for (IPluginGame* game : m_PluginManager.plugins()) { if (game->gameNexusName() == gameName) { gameNameReal = game->gameShortName(); break; @@ -3619,7 +3620,7 @@ void MainWindow::on_actionNotifications_triggered() future.waitForFinished(); - ProblemsDialog problems(m_PluginContainer, this); + ProblemsDialog problems(m_PluginManager, this); problems.exec(); scheduleCheckForProblems(); @@ -3627,7 +3628,7 @@ void MainWindow::on_actionNotifications_triggered() void MainWindow::on_actionChange_Game_triggered() { - InstanceManagerDialog dlg(m_PluginContainer, this); + InstanceManagerDialog dlg(m_PluginManager, this); dlg.exec(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 8e0ba5284..763cf6f32 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -32,7 +32,7 @@ along with Mod Organizer. If not, see . #include "modinfo.h" #include "modlistbypriorityproxy.h" #include "modlistsortproxy.h" -#include "plugincontainer.h" +#include "pluginmanager.h" //class PluginManager; #include "shared/fileregisterfwd.h" #include "thememanager.h" #include "translationmanager.h" @@ -128,7 +128,7 @@ class MainWindow : public QMainWindow, public IUserInterface public: explicit MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginContainer& pluginContainer, ThemeManager& themeManager, + PluginManager& pluginManager, ThemeManager& themeManager, TranslationManager& translationManager, QWidget* parent = 0); ~MainWindow(); @@ -298,7 +298,7 @@ private slots: QTime m_StartTime; OrganizerCore& m_OrganizerCore; - PluginContainer& m_PluginContainer; + PluginManager& m_PluginManager; ThemeManager& m_ThemeManager; TranslationManager& m_TranslationManager; diff --git a/src/moapplication.cpp b/src/moapplication.cpp index c6f564782..a0adc9fde 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -28,6 +28,7 @@ along with Mod Organizer. If not, see . #include "nexusinterface.h" #include "nxmaccessmanager.h" #include "organizercore.h" +#include "pluginmanager.h" #include "sanitychecks.h" #include "settings.h" #include "shared/appconfig.h" @@ -222,7 +223,7 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) QDir(QCoreApplication::applicationDirPath() + "/extensions") .filesystemAbsolutePath()); - m_plugins = std::make_unique(m_core.get()); + m_plugins = std::make_unique(*m_extensions, m_core.get()); m_plugins->loadPlugins(); // instance @@ -426,7 +427,7 @@ std::unique_ptr MOApplication::getCurrentInstance(bool forceSelect) } std::optional MOApplication::setupInstanceLoop(Instance& currentInstance, - PluginContainer& pc) + PluginManager& pc) { for (;;) { const auto setupResult = setupInstance(currentInstance, pc); diff --git a/src/moapplication.h b/src/moapplication.h index 533337942..55a8b943f 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -32,7 +32,7 @@ class Instance; class MOMultiProcess; class NexusInterface; class OrganizerCore; -class PluginContainer; +class PluginManager; class Settings; namespace MOBase @@ -81,14 +81,14 @@ class MOApplication : public QApplication std::unique_ptr m_settings; std::unique_ptr m_nexus; std::unique_ptr m_extensions; - std::unique_ptr m_plugins; + std::unique_ptr m_plugins; std::unique_ptr m_themes; std::unique_ptr m_translations; std::unique_ptr m_core; void externalMessage(const QString& message); std::unique_ptr getCurrentInstance(bool forceSelect); - std::optional setupInstanceLoop(Instance& currentInstance, PluginContainer& pc); + std::optional setupInstanceLoop(Instance& currentInstance, PluginManager& pc); void purgeOldFiles(); }; diff --git a/src/modinfo.cpp b/src/modinfo.cpp index abfd9de6c..3ee958817 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -94,7 +94,7 @@ ModInfo::Ptr ModInfo::createFrom(const QDir& dir, OrganizerCore& core) } else { result = ModInfo::Ptr(new ModInfoRegular(dir, core)); } - result->m_Index = s_Collection.size(); + result->m_Index = static_cast(s_Collection.size()); s_Collection.push_back(result); return result; } @@ -106,7 +106,7 @@ ModInfo::Ptr ModInfo::createFromPlugin(const QString& modName, const QString& es QMutexLocker locker(&s_Mutex); ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(modName, espName, bsaNames, modType, core)); - result->m_Index = s_Collection.size(); + result->m_Index = static_cast(s_Collection.size()); s_Collection.push_back(result); return result; } @@ -115,7 +115,7 @@ ModInfo::Ptr ModInfo::createFromOverwrite(OrganizerCore& core) { QMutexLocker locker(&s_Mutex); ModInfo::Ptr overwrite = ModInfo::Ptr(new ModInfoOverwrite(core)); - overwrite->m_Index = s_Collection.size(); + overwrite->m_Index = static_cast(s_Collection.size()); s_Collection.push_back(overwrite); return overwrite; } @@ -250,7 +250,7 @@ void ModInfo::updateFromDisc(const QString& modsDirectory, OrganizerCore& core, } auto* game = core.managedGame(); - auto& features = core.pluginContainer().gameFeatures(); + auto& features = core.pluginManager().gameFeatures(); auto unmanaged = features.gameFeature(); if (unmanaged != nullptr) { for (const QString& modName : unmanaged->mods(!displayForeign)) { @@ -296,7 +296,7 @@ void ModInfo::updateIndices() ModInfo::ModInfo(OrganizerCore& core) : m_PrimaryCategory(-1), m_Core(core) {} -bool ModInfo::checkAllForUpdate(PluginContainer* pluginContainer, QObject* receiver) +bool ModInfo::checkAllForUpdate(PluginManager* pluginManager, QObject* receiver) { bool updatesAvailable = true; @@ -315,7 +315,7 @@ bool ModInfo::checkAllForUpdate(PluginContainer* pluginContainer, QObject* recei // Detect invalid source games for (auto itr = games.begin(); itr != games.end();) { - auto gamePlugins = pluginContainer->plugins(); + auto gamePlugins = pluginManager->plugins(); IPluginGame* gamePlugin = qApp->property("managed_game").value(); for (auto plugin : gamePlugins) { if (plugin != nullptr && diff --git a/src/modinfo.h b/src/modinfo.h index bbb1aae3c..c43a40c84 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -25,7 +25,7 @@ along with Mod Organizer. If not, see . #include "versioninfo.h" class OrganizerCore; -class PluginContainer; +class PluginManager; class QDir; class QDateTime; @@ -215,7 +215,7 @@ class ModInfo : public QObject, public MOBase::IModInterface * * @return true if any mods are checked for update. */ - static bool checkAllForUpdate(PluginContainer* pluginContainer, QObject* receiver); + static bool checkAllForUpdate(PluginManager* pluginManager, QObject* receiver); /** * diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 1798e9870..f9e480e85 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -27,7 +27,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogtextfiles.h" #include "modlistview.h" #include "organizercore.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "shared/directoryentry.h" #include "shared/filesorigin.h" #include "ui_modinfodialog.h" @@ -39,11 +39,11 @@ namespace fs = std::filesystem; const int max_scan_for_context_menu = 50; -bool canPreviewFile(const PluginContainer& pluginContainer, bool isArchive, +bool canPreviewFile(const PluginManager& pluginManager, bool isArchive, const QString& filename) { const auto ext = QFileInfo(filename).suffix().toLower(); - return pluginContainer.previewGenerator().previewSupported(ext, isArchive); + return pluginManager.previewGenerator().previewSupported(ext, isArchive); } bool isExecutableFilename(const QString& filename) @@ -164,11 +164,11 @@ bool ModInfoDialog::TabInfo::isVisible() const return (realPos != -1); } -ModInfoDialog::ModInfoDialog(OrganizerCore& core, PluginContainer& plugin, +ModInfoDialog::ModInfoDialog(OrganizerCore& core, PluginManager& plugins, ModInfo::Ptr mod, ModListView* modListView, QWidget* parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_core(core), - m_plugin(plugin), m_modListView(modListView), m_initialTab(ModInfoTabIDs::None), + m_plugins(plugins), m_modListView(modListView), m_initialTab(ModInfoTabIDs::None), m_arrangingTabs(false) { ui->setupUi(this); @@ -218,7 +218,7 @@ template std::unique_ptr createTab(ModInfoDialog& d, ModInfoTabIDs id) { return std::make_unique(ModInfoDialogTabContext( - d.m_core, d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); + d.m_core, d.m_plugins, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); } void ModInfoDialog::createTabs() diff --git a/src/modinfodialog.h b/src/modinfodialog.h index e894efb0c..cb74c6f02 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -34,7 +34,7 @@ namespace MOShared class FilesOrigin; } -class PluginContainer; +class PluginManager; class OrganizerCore; class Settings; class ModInfoDialogTab; @@ -56,7 +56,7 @@ class ModInfoDialog : public MOBase::TutorableDialog ModInfoTabIDs index); public: - ModInfoDialog(OrganizerCore& core, PluginContainer& plugin, ModInfo::Ptr mod, + ModInfoDialog(OrganizerCore& core, PluginManager& plugins, ModInfo::Ptr mod, ModListView* view, QWidget* parent = nullptr); ~ModInfoDialog(); @@ -123,7 +123,7 @@ class ModInfoDialog : public MOBase::TutorableDialog std::unique_ptr ui; OrganizerCore& m_core; - PluginContainer& m_plugin; + PluginManager& m_plugins; ModListView* m_modListView; ModInfo::Ptr m_mod; std::vector m_tabs; diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 715214b33..e47f94147 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -221,7 +221,7 @@ void ConflictsTab::activateItems(QTreeView* tree) forEachInSelection(tree, [&](const ConflictItem* item) { const auto path = item->fileName(); - if (tryPreview && canPreviewFile(plugin(), item->isArchive(), path)) { + if (tryPreview && canPreviewFile(plugins(), item->isArchive(), path)) { previewItem(item); } else { openItem(item, false); @@ -424,7 +424,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) enableUnhide = item->canUnhide(); enableRun = item->canRun(); enableOpen = item->canOpen(); - enablePreview = item->canPreview(plugin()); + enablePreview = item->canPreview(plugins()); enableExplore = item->canExplore(); enableGoto = item->hasAlts(); } else { diff --git a/src/modinfodialogconflictsmodels.cpp b/src/modinfodialogconflictsmodels.cpp index a1806e61f..d2a6b18fe 100644 --- a/src/modinfodialogconflictsmodels.cpp +++ b/src/modinfodialogconflictsmodels.cpp @@ -73,9 +73,9 @@ bool ConflictItem::canOpen() const return canOpenFile(isArchive(), fileName()); } -bool ConflictItem::canPreview(PluginContainer& pluginContainer) const +bool ConflictItem::canPreview(PluginManager& pluginManager) const { - return canPreviewFile(pluginContainer, isArchive(), fileName()); + return canPreviewFile(pluginManager, isArchive(), fileName()); } bool ConflictItem::canExplore() const diff --git a/src/modinfodialogconflictsmodels.h b/src/modinfodialogconflictsmodels.h index 263723285..851c440e3 100644 --- a/src/modinfodialogconflictsmodels.h +++ b/src/modinfodialogconflictsmodels.h @@ -1,6 +1,6 @@ #include "shared/fileentry.h" -class PluginContainer; +class PluginManager; class ConflictItem { @@ -25,7 +25,7 @@ class ConflictItem bool canUnhide() const; bool canRun() const; bool canOpen() const; - bool canPreview(PluginContainer& pluginContainer) const; + bool canPreview(PluginManager& pluginManager) const; bool canExplore() const; private: diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 140b813d3..76bb28a22 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -178,7 +178,7 @@ void FileTreeTab::onActivated() const auto path = m_fs->filePath(selection); const auto tryPreview = core().settings().interface().doubleClicksOpenPreviews(); - if (tryPreview && canPreviewFile(plugin(), false, path)) { + if (tryPreview && canPreviewFile(plugins(), false, path)) { onPreview(); } else { onOpen(); @@ -446,7 +446,7 @@ void FileTreeTab::onContextMenu(const QPoint& pos) } } - enablePreview = canPreviewFile(plugin(), false, fileName); + enablePreview = canPreviewFile(plugins(), false, fileName); enableExplore = canExploreFile(false, fileName); enableHide = canHideFile(false, fileName); enableUnhide = canUnhideFile(false, fileName); diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index 086263236..ee3cac219 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -21,9 +21,9 @@ enum class ModInfoTabIDs Filetree }; -class PluginContainer; +class PluginManager; -bool canPreviewFile(const PluginContainer& pluginContainer, bool isArchive, +bool canPreviewFile(const PluginManager& pluginManager, bool isArchive, const QString& filename); bool canRunFile(bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 7b250e1e8..877e7f30e 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -261,7 +261,7 @@ void ImagesTab::select(std::size_t i, Visibility v) ui->imagesPath->setText(QDir::toNativeSeparators(f->path())); ui->imagesExplore->setEnabled(true); - if (plugin().previewGenerator().previewSupported( + if (plugins().previewGenerator().previewSupported( QFileInfo(f->path()).suffix().toLower(), false)) ui->previewPluginButton->setEnabled(true); else diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index e7311aa0a..e35d1648e 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -4,7 +4,6 @@ #include "filterwidget.h" #include "modinfodialogtab.h" #include "organizercore.h" -#include "plugincontainer.h" #include using namespace MOBase; diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 485f138a9..a99b79829 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -100,7 +100,7 @@ void NexusTab::update() if (core().managedGame()->validShortNames().size() == 0) { ui->sourceGame->setDisabled(true); } else { - for (auto game : plugin().plugins()) { + for (auto game : plugins().plugins()) { for (QString gameName : core().managedGame()->validShortNames()) { if (game->gameShortName().compare(gameName, Qt::CaseInsensitive) == 0) { ui->sourceGame->addItem(game->gameName(), game->gameShortName()); @@ -345,7 +345,7 @@ void NexusTab::onSourceGameChanged() return; } - for (auto game : plugin().plugins()) { + for (auto game : plugins().plugins()) { if (game->gameName() == ui->sourceGame->currentText()) { mod().setGameName(game->gameShortName()); mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index c443a389b..96e901668 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -5,7 +5,7 @@ #include "ui_modinfodialog.h" ModInfoDialogTab::ModInfoDialogTab(ModInfoDialogTabContext cx) - : ui(cx.ui), m_core(cx.core), m_plugin(cx.plugin), m_parent(cx.parent), + : ui(cx.ui), m_core(cx.core), m_plugins(cx.plugins), m_parent(cx.parent), m_origin(cx.origin), m_tabID(cx.id), m_hasData(false), m_firstActivation(true) {} @@ -112,9 +112,9 @@ OrganizerCore& ModInfoDialogTab::core() return m_core; } -PluginContainer& ModInfoDialogTab::plugin() +PluginManager& ModInfoDialogTab::plugins() { - return m_plugin; + return m_plugins; } QWidget* ModInfoDialogTab::parentWidget() diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index dff1732d0..40706c022 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -21,17 +21,17 @@ class OrganizerCore; struct ModInfoDialogTabContext { OrganizerCore& core; - PluginContainer& plugin; + PluginManager& plugins; QWidget* parent; Ui::ModInfoDialog* ui; ModInfoTabIDs id; ModInfoPtr mod; MOShared::FilesOrigin* origin; - ModInfoDialogTabContext(OrganizerCore& core, PluginContainer& plugin, QWidget* parent, + ModInfoDialogTabContext(OrganizerCore& core, PluginManager& plugins, QWidget* parent, Ui::ModInfoDialog* ui, ModInfoTabIDs id, ModInfoPtr mod, MOShared::FilesOrigin* origin) - : core(core), plugin(plugin), parent(parent), ui(ui), id(id), mod(mod), + : core(core), plugins(plugins), parent(parent), ui(ui), id(id), mod(mod), origin(origin) {} }; @@ -227,7 +227,7 @@ class ModInfoDialogTab : public QObject ModInfoDialogTab(ModInfoDialogTabContext cx); OrganizerCore& core(); - PluginContainer& plugin(); + PluginManager& plugins(); QWidget* parentWidget(); // emits originModified @@ -254,7 +254,7 @@ class ModInfoDialogTab : public QObject OrganizerCore& m_core; // plugin - PluginContainer& m_plugin; + PluginManager& m_plugins; // current mod, never null ModInfoPtr m_mod; diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index d3ff65472..d7239bf36 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -4,7 +4,7 @@ #include "messagedialog.h" #include "moddatacontent.h" #include "organizercore.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "report.h" #include "settings.h" #include @@ -34,8 +34,7 @@ ModInfoRegular::ModInfoRegular(const QDir& path, OrganizerCore& core) m_GameName(core.managedGame()->gameShortName()), m_IsAlternate(false), m_Converted(false), m_Validated(false), m_MetaInfoChanged(false), m_EndorsedState(EndorsedState::ENDORSED_UNKNOWN), - m_TrackedState(TrackedState::TRACKED_UNKNOWN), - m_NexusBridge(&core.pluginContainer()) + m_TrackedState(TrackedState::TRACKED_UNKNOWN), m_NexusBridge() { m_CreationTime = QFileInfo(path.absolutePath()).birthTime(); // read out the meta-file for information @@ -704,7 +703,7 @@ std::vector ModInfoRegular::getFlags() const std::set ModInfoRegular::doGetContents() const { auto contentFeature = - m_Core.pluginContainer().gameFeatures().gameFeature(); + m_Core.pluginManager().gameFeatures().gameFeature(); if (contentFeature) { auto result = contentFeature->getContentsFor(fileTree()); diff --git a/src/modlist.cpp b/src/modlist.cpp index 8945dea11..2a85ebebb 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -60,10 +60,10 @@ along with Mod Organizer. If not, see . using namespace MOBase; -ModList::ModList(PluginContainer* pluginContainer, OrganizerCore* organizer) +ModList::ModList(PluginManager* pluginManager, OrganizerCore* organizer) : QAbstractItemModel(organizer), m_Organizer(organizer), m_Profile(nullptr), m_NexusInterface(nullptr), m_Modified(false), m_InNotifyChange(false), - m_FontMetrics(QFont()), m_PluginContainer(pluginContainer) + m_FontMetrics(QFont()), m_PluginManager(pluginManager) { m_LastCheck.start(); } @@ -218,8 +218,8 @@ QVariant ModList::data(const QModelIndex& modelIndex, int role) const return QVariant(); } } else if (column == COL_GAME) { - if (m_PluginContainer != nullptr) { - for (auto game : m_PluginContainer->plugins()) { + if (m_PluginManager != nullptr) { + for (auto game : m_PluginManager->plugins()) { if (game->gameShortName().compare(modInfo->gameName(), Qt::CaseInsensitive) == 0) return game->gameName(); @@ -726,9 +726,9 @@ void ModList::changeModPriority(int sourceIndex, int newPriority) emit modPrioritiesChanged({index(sourceIndex, 0)}); } -void ModList::setPluginContainer(PluginContainer* pluginContianer) +void ModList::setPluginManager(PluginManager* pluginContianer) { - m_PluginContainer = pluginContianer; + m_PluginManager = pluginContianer; } bool ModList::modInfoAboutToChange(ModInfo::Ptr info) diff --git a/src/modlist.h b/src/modlist.h index 112070e12..cf05351b6 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -41,7 +41,7 @@ along with Mod Organizer. If not, see . #include class QSortFilterProxyModel; -class PluginContainer; +class PluginManager; class OrganizerCore; class ModListDropInfo; @@ -106,7 +106,7 @@ class ModList : public QAbstractItemModel * @todo ensure this view works without a profile set, otherwise there are *intransparent dependencies on the initialisation order **/ - ModList(PluginContainer* pluginContainer, OrganizerCore* parent); + ModList(PluginManager* pluginManager, OrganizerCore* parent); ~ModList(); @@ -136,7 +136,7 @@ class ModList : public QAbstractItemModel void changeModPriority(int sourceIndex, int newPriority); void changeModPriority(std::vector sourceIndices, int newPriority); - void setPluginContainer(PluginContainer* pluginContainer); + void setPluginManager(PluginManager* pluginContainer); bool modInfoAboutToChange(ModInfo::Ptr info); void modInfoChanged(ModInfo::Ptr info); @@ -414,7 +414,7 @@ public slots: QElapsedTimer m_LastCheck; - PluginContainer* m_PluginContainer; + PluginManager* m_PluginManager; }; #endif // MODLIST_H diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index ba53ee749..bc0def559 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -131,8 +131,8 @@ void ModListByPriorityProxy::onModelLayoutChanged(const QList(idx.internalPointer()); - toPersistent.append( - createIndex(item->parent->childIndex(item), idx.column(), item)); + toPersistent.append(createIndex(static_cast(item->parent->childIndex(item)), + idx.column(), item)); } changePersistentIndexList(persistent, toPersistent); @@ -171,7 +171,8 @@ QModelIndex ModListByPriorityProxy::mapFromSource(const QModelIndex& sourceIndex } auto* item = m_IndexToItem.at(sourceIndex.row()).get(); - return createIndex(item->parent->childIndex(item), sourceIndex.column(), item); + return createIndex(static_cast(item->parent->childIndex(item)), + sourceIndex.column(), item); } QModelIndex ModListByPriorityProxy::mapToSource(const QModelIndex& proxyIndex) const @@ -187,13 +188,13 @@ QModelIndex ModListByPriorityProxy::mapToSource(const QModelIndex& proxyIndex) c int ModListByPriorityProxy::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { - return m_Root.children.size(); + return static_cast(m_Root.children.size()); } auto* item = static_cast(parent.internalPointer()); if (item->mod->isSeparator()) { - return item->children.size(); + return static_cast(item->children.size()); } return 0; @@ -216,7 +217,8 @@ QModelIndex ModListByPriorityProxy::parent(const QModelIndex& child) const return QModelIndex(); } - return createIndex(item->parent->parent->childIndex(item->parent), 0, item->parent); + return createIndex(static_cast(item->parent->parent->childIndex(item->parent)), + 0, item->parent); } bool ModListByPriorityProxy::hasChildren(const QModelIndex& parent) const diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index a38e876b1..35b4dfdc1 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -225,7 +225,7 @@ void ModListViewActions::checkModsForUpdates() const bool checkingModsForUpdate = false; if (NexusInterface::instance().getAccessManager()->validated()) { checkingModsForUpdate = - ModInfo::checkAllForUpdate(&m_core.pluginContainer(), m_receiver); + ModInfo::checkAllForUpdate(&m_core.pluginManager(), m_receiver); NexusInterface::instance().requestEndorsementInfo(m_receiver, QVariant(), QString()); NexusInterface::instance().requestTrackingInfo(m_receiver, QVariant(), QString()); @@ -568,7 +568,7 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, } else { modInfo->saveMeta(); - ModInfoDialog dialog(m_core, m_core.pluginContainer(), modInfo, m_view, m_parent); + ModInfoDialog dialog(m_core, m_core.pluginManager(), modInfo, m_view, m_parent); connect(&dialog, &ModInfoDialog::originModified, this, &ModListViewActions::originModified); connect(&dialog, &ModInfoDialog::modChanged, [=](unsigned int index) { diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index ecd33cb67..c6fd59dd3 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -46,7 +46,7 @@ void throttledWarning(const APIUserAccount& user) APIUserAccount::ThrottleThreshold, user.remainingRequests()); } -NexusBridge::NexusBridge(PluginContainer* pluginContainer, const QString& subModule) +NexusBridge::NexusBridge(const QString& subModule) : m_Interface(&NexusInterface::instance()), m_SubModule(subModule) {} @@ -268,7 +268,7 @@ NexusInterface::parseLimits(const QList& headers) static NexusInterface* g_instance = nullptr; -NexusInterface::NexusInterface(Settings* s) : m_PluginContainer(nullptr) +NexusInterface::NexusInterface(Settings* s) : m_PluginManager(nullptr) { MO_ASSERT(!g_instance); g_instance = this; @@ -435,7 +435,7 @@ NexusInterface::getGameChoices(const MOBase::IPluginGame* game) choices.push_back( std::pair(game->gameShortName(), game->gameName())); for (QString gameName : game->validShortNames()) { - for (auto gamePlugin : m_PluginContainer->plugins()) { + for (auto gamePlugin : m_PluginManager->plugins()) { if (gamePlugin->gameShortName().compare(gameName, Qt::CaseInsensitive) == 0) { choices.push_back(std::pair(gamePlugin->gameShortName(), gamePlugin->gameName())); @@ -456,9 +456,9 @@ bool NexusInterface::isModURL(int modID, const QString& url) const return QUrl(alt) == QUrl(url); } -void NexusInterface::setPluginContainer(PluginContainer* pluginContainer) +void NexusInterface::setPluginManager(PluginManager* pluginContainer) { - m_PluginContainer = pluginContainer; + m_PluginManager = pluginContainer; } int NexusInterface::requestDescription(QString gameName, int modID, QObject* receiver, @@ -806,7 +806,7 @@ int NexusInterface::requestInfoFromMd5(QString gameName, QByteArray& hash, IPluginGame* NexusInterface::getGame(QString gameName) const { - auto gamePlugins = m_PluginContainer->plugins(); + auto gamePlugins = m_PluginManager->plugins(); IPluginGame* gamePlugin = qApp->property("managed_game").value(); for (auto plugin : gamePlugins) { if (plugin != nullptr && diff --git a/src/nexusinterface.h b/src/nexusinterface.h index f6efef1d9..abe92e6d4 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -21,7 +21,7 @@ along with Mod Organizer. If not, see . #define NEXUSINTERFACE_H #include "apiuseraccount.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include #include @@ -58,7 +58,7 @@ class NexusBridge : public MOBase::IModRepositoryBridge Q_OBJECT public: - NexusBridge(PluginContainer* pluginContainer, const QString& subModule = ""); + NexusBridge(const QString& subModule = ""); /** * @brief request description for a mod @@ -561,7 +561,7 @@ class NexusInterface : public QObject */ bool isModURL(int modID, QString const& url) const; - void setPluginContainer(PluginContainer* pluginContainer); + void setPluginManager(PluginManager* pluginManager); signals: @@ -681,7 +681,7 @@ private slots: NXMAccessManager* m_AccessManager; std::list m_ActiveRequest; QQueue m_RequestQueue; - PluginContainer* m_PluginContainer; + PluginManager* m_PluginManager; APIUserAccount m_User; }; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 88166c8e6..93b7ec5ef 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -18,7 +18,7 @@ #include "modrepositoryfileinfo.h" #include "nexusinterface.h" #include "nxmaccessmanager.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "previewdialog.h" #include "profile.h" #include "shared/appconfig.h" @@ -73,6 +73,7 @@ #include +#include "inibakery.h" #include "organizerproxy.h" using namespace MOShared; @@ -91,9 +92,9 @@ QStringList toStringList(InputIterator current, InputIterator end) } OrganizerCore::OrganizerCore(Settings& settings) - : m_UserInterface(nullptr), m_PluginContainer(nullptr), m_GamePlugin(nullptr), + : m_UserInterface(nullptr), m_PluginManager(nullptr), m_GamePlugin(nullptr), m_CurrentProfile(nullptr), m_Settings(settings), - m_Updater(&NexusInterface::instance()), m_ModList(m_PluginContainer, this), + m_Updater(&NexusInterface::instance()), m_ModList(m_PluginManager, this), m_PluginList(*this), m_DirectoryRefresher(new DirectoryRefresher(this, settings.refreshThreadCount())), m_DirectoryStructure(new DirectoryEntry(L"data", nullptr, 0)), @@ -104,6 +105,9 @@ OrganizerCore::OrganizerCore(Settings& settings) m_ArchivesInit(false), m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) { + // need to initialize here for aboutToRun() to be callable + m_IniBakery = std::make_unique(*this); + env::setHandleCloserThreadCount(settings.refreshThreadCount()); m_DownloadManager.setOutputDirectory(m_Settings.paths().downloads(), false); @@ -204,7 +208,7 @@ void OrganizerCore::storeSettings() void OrganizerCore::updateExecutablesList() { - if (m_PluginContainer == nullptr) { + if (m_PluginManager == nullptr) { log::error("can't update executables list now"); return; } @@ -247,27 +251,27 @@ void OrganizerCore::checkForUpdates() } } -void OrganizerCore::connectPlugins(PluginContainer* container) +void OrganizerCore::connectPlugins(PluginManager* manager) { - m_PluginContainer = container; - m_Updater.setPluginContainer(m_PluginContainer); - m_InstallationManager.setPluginContainer(m_PluginContainer); - m_DownloadManager.setPluginContainer(m_PluginContainer); - m_ModList.setPluginContainer(m_PluginContainer); + m_PluginManager = manager; + m_Updater.setPluginManager(m_PluginManager); + m_InstallationManager.setPluginManager(m_PluginManager); + m_DownloadManager.setPluginManager(m_PluginManager); + m_ModList.setPluginManager(m_PluginManager); if (!m_GameName.isEmpty()) { - m_GamePlugin = m_PluginContainer->game(m_GameName); + m_GamePlugin = m_PluginManager->game(m_GameName); emit managedGameChanged(m_GamePlugin); } - connect(m_PluginContainer, &PluginContainer::pluginEnabled, [&](IPlugin* plugin) { + connect(m_PluginManager, &PluginManager::pluginEnabled, [&](IPlugin* plugin) { m_PluginEnabled(plugin); }); - connect(m_PluginContainer, &PluginContainer::pluginDisabled, [&](IPlugin* plugin) { + connect(m_PluginManager, &PluginManager::pluginDisabled, [&](IPlugin* plugin) { m_PluginDisabled(plugin); }); - connect(&m_PluginContainer->gameFeatures(), &GameFeatures::modDataContentUpdated, + connect(&m_PluginManager->gameFeatures(), &GameFeatures::modDataContentUpdated, [this](ModDataContent const* contentFeature) { if (contentFeature) { m_Contents = ModDataContentHolder(contentFeature->getAllContents()); @@ -605,7 +609,7 @@ void OrganizerCore::setCurrentProfile(const QString& profileName) MOBase::IModRepositoryBridge* OrganizerCore::createNexusBridge() const { - return new NexusBridge(m_PluginContainer); + return new NexusBridge(); } QString OrganizerCore::profileName() const @@ -653,7 +657,7 @@ MOBase::Version OrganizerCore::version() const MOBase::IPluginGame* OrganizerCore::getGame(const QString& name) const { - for (IPluginGame* game : m_PluginContainer->plugins()) { + for (IPluginGame* game : m_PluginManager->plugins()) { if (game != nullptr && game->gameShortName().compare(name, Qt::CaseInsensitive) == 0) return game; @@ -963,7 +967,7 @@ QStringList OrganizerCore::getFileOrigins(const QString& fileName) const if (file.get() != nullptr) { result.append( ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin()).getName())); - foreach (const auto& i, file->getAlternatives()) { + for (const auto& i : file->getAlternatives()) { result.append( ToQString(m_DirectoryStructure->getOriginByID(i.originID()).getName())); } @@ -1062,7 +1066,7 @@ bool OrganizerCore::previewFileWithAlternatives(QWidget* parent, QString fileNam if (QFile::exists(filePath)) { // it's very possible the file doesn't exist, because it's inside an archive. we // don't support that - QWidget* wid = m_PluginContainer->previewGenerator().genPreview(filePath); + QWidget* wid = m_PluginManager->previewGenerator().genPreview(filePath); if (wid == nullptr) { reportError(tr("failed to generate preview for %1").arg(filePath)); } else { @@ -1149,7 +1153,7 @@ bool OrganizerCore::previewFile(QWidget* parent, const QString& originName, PreviewDialog preview(path, parent); - QWidget* wid = m_PluginContainer->previewGenerator().genPreview(path); + QWidget* wid = m_PluginManager->previewGenerator().genPreview(path); if (wid == nullptr) { reportError(tr("Failed to generate preview for %1").arg(path)); return false; @@ -1452,11 +1456,11 @@ void OrganizerCore::loggedInAction(QWidget* parent, std::function f) void OrganizerCore::requestDownload(const QUrl& url, QNetworkReply* reply) { - if (!m_PluginContainer) { + if (!m_PluginManager) { return; } - for (IPluginModPage* modPage : m_PluginContainer->plugins()) { - if (m_PluginContainer->isEnabled(modPage)) { + for (IPluginModPage* modPage : m_PluginManager->plugins()) { + if (m_PluginManager->isEnabled(modPage)) { ModRepositoryFileInfo* fileInfo = new ModRepositoryFileInfo(); if (modPage->handlesDownload(url, reply->url(), *fileInfo)) { fileInfo->repository = modPage->name(); @@ -1502,14 +1506,14 @@ void OrganizerCore::requestDownload(const QUrl& url, QNetworkReply* reply) } } -PluginContainer& OrganizerCore::pluginContainer() const +PluginManager& OrganizerCore::pluginManager() const { - return *m_PluginContainer; + return *m_PluginManager; } GameFeatures& OrganizerCore::gameFeatures() const { - return pluginContainer().gameFeatures(); + return pluginManager().gameFeatures(); } IPluginGame const* OrganizerCore::managedGame() const @@ -1519,7 +1523,7 @@ IPluginGame const* OrganizerCore::managedGame() const IOrganizer const* OrganizerCore::managedGameOrganizer() const { - return m_PluginContainer->requirements(m_GamePlugin).m_Organizer; + return m_PluginManager->details(m_GamePlugin).proxy(); } std::vector OrganizerCore::enabledArchives() @@ -2086,10 +2090,17 @@ std::vector OrganizerCore::fileMapping(const QString& profileName, true, customOverwrite.isEmpty()}); } + // ini bakery + { + const auto iniBakeryMapping = m_IniBakery->mappings(); + result.reserve(result.size() + iniBakeryMapping.size()); + result.insert(result.end(), iniBakeryMapping.begin(), iniBakeryMapping.end()); + } + for (MOBase::IPluginFileMapper* mapper : - m_PluginContainer->plugins()) { + m_PluginManager->plugins()) { IPlugin* plugin = dynamic_cast(mapper); - if (m_PluginContainer->isEnabled(plugin)) { + if (m_PluginManager->isEnabled(plugin)) { MappingType pluginMap = mapper->mappings(); result.reserve(result.size() + pluginMap.size()); result.insert(result.end(), pluginMap.begin(), pluginMap.end()); diff --git a/src/organizercore.h b/src/organizercore.h index fe80da240..31da3b757 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -37,12 +37,13 @@ #include "uilocker.h" #include "usvfsconnector.h" +class IniBakery; class ModListSortProxy; class PluginListSortProxy; class Profile; class IUserInterface; class GameFeatures; -class PluginContainer; +class PluginManager; class DirectoryRefresher; namespace MOBase @@ -246,7 +247,7 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose ~OrganizerCore(); void setUserInterface(IUserInterface* ui); - void connectPlugins(PluginContainer* container); + void connectPlugins(PluginManager* manager); void setManagedGame(MOBase::IPluginGame* game); @@ -274,9 +275,9 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose MOBase::Version getVersion() const { return m_Updater.getVersion(); } - // return the plugin container + // return the plugin manager // - PluginContainer& pluginContainer() const; + PluginManager& pluginManager() const; // return the game features GameFeatures& gameFeatures() const; @@ -528,7 +529,8 @@ private slots: private: IUserInterface* m_UserInterface; - PluginContainer* m_PluginContainer; + PluginManager* m_PluginManager; + std::unique_ptr m_IniBakery; QString m_GameName; MOBase::IPluginGame* m_GamePlugin; ModDataContentHolder m_Contents; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 22a8a0234..959940773 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -5,8 +5,8 @@ #include "glob_matching.h" #include "modlistproxy.h" #include "organizercore.h" -#include "plugincontainer.h" #include "pluginlistproxy.h" +#include "pluginmanager.h" #include "proxyutils.h" #include "settings.h" #include "shared/appconfig.h" @@ -18,17 +18,16 @@ using namespace MOBase; using namespace MOShared; -OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, - PluginContainer* pluginContainer, +OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, PluginManager* pluginManager, MOBase::IPlugin* plugin) - : m_Proxied(organizer), m_PluginContainer(pluginContainer), m_Plugin(plugin), + : m_Proxied(organizer), m_PluginManager(pluginManager), m_Plugin(plugin), m_DownloadManagerProxy( std::make_unique(this, organizer->downloadManager())), m_ModListProxy(std::make_unique(this, organizer->modList())), m_PluginListProxy( std::make_unique(this, organizer->pluginList())), m_GameFeaturesProxy( - std::make_unique(this, pluginContainer->gameFeatures())) + std::make_unique(this, pluginManager->gameFeatures())) {} OrganizerProxy::~OrganizerProxy() @@ -81,7 +80,7 @@ void OrganizerProxy::disconnectSignals() IModRepositoryBridge* OrganizerProxy::createNexusBridge() const { - return new NexusBridge(m_PluginContainer, m_Plugin->name()); + return new NexusBridge(m_Plugin->name()); } QString OrganizerProxy::profileName() const @@ -180,12 +179,12 @@ void OrganizerProxy::modDataChanged(IModInterface* mod) bool OrganizerProxy::isPluginEnabled(QString const& pluginName) const { - return m_PluginContainer->isEnabled(pluginName); + return m_PluginManager->isEnabled(pluginName); } bool OrganizerProxy::isPluginEnabled(IPlugin* plugin) const { - return m_PluginContainer->isEnabled(plugin); + return m_PluginManager->isEnabled(plugin); } QVariant OrganizerProxy::pluginSetting(const QString& pluginName, diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 2c70cb2c3..ba5efe455 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -9,7 +9,7 @@ #include "organizercore.h" class GameFeaturesProxy; -class PluginContainer; +class PluginManager; class DownloadManagerProxy; class ModListProxy; class PluginListProxy; @@ -18,7 +18,7 @@ class OrganizerProxy : public MOBase::IOrganizer { public: - OrganizerProxy(OrganizerCore* organizer, PluginContainer* pluginContainer, + OrganizerProxy(OrganizerCore* organizer, PluginManager* pluginManager, MOBase::IPlugin* plugin); ~OrganizerProxy(); @@ -115,7 +115,7 @@ class OrganizerProxy : public MOBase::IOrganizer protected: // The container needs access to some callbacks to simulate startup. - friend class PluginContainer; + friend class PluginManager; /** * @brief Connect the signals from this proxy and all the child proxies (plugin list, @@ -132,7 +132,7 @@ class OrganizerProxy : public MOBase::IOrganizer private: OrganizerCore* m_Proxied; - PluginContainer* m_PluginContainer; + PluginManager* m_PluginManager; MOBase::IPlugin* m_Plugin; diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp deleted file mode 100644 index c73d45666..000000000 --- a/src/plugincontainer.cpp +++ /dev/null @@ -1,1245 +0,0 @@ -#include "plugincontainer.h" -#include "iuserinterface.h" -#include "organizercore.h" -#include "organizerproxy.h" -#include "report.h" -#include "shared/appconfig.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace MOBase; -using namespace MOShared; - -namespace bf = boost::fusion; - -// Welcome to the wonderful world of MO2 plugin management! -// -// We'll start by the C++ side. -// -// There are 9 types of MO2 plugins, two of which cannot be standalone: IPluginDiagnose -// and IPluginFileMapper. This means that you can have a class implementing IPluginGame, -// IPluginDiagnose and IPluginFileMapper. It is not possible for a class to implement -// two full plugin types (e.g. IPluginPreview and IPluginTool). -// -// Plugins are fetch as QObject initially and must be "qobject-casted" to the right -// type. -// -// Plugins are stored in the PluginContainer class in various C++ containers: there is a -// vector that stores all the plugin as QObject, multiple vectors that stores the plugin -// of each types, a map to find IPlugin object from their names or from IPluginDiagnose -// or IFileMapper (since these do not inherit IPlugin, they cannot be downcasted). -// -// Requirements for plugins are stored in m_Requirements: -// - IPluginGame cannot be enabled by user. A game plugin is considered enable only if -// it is -// the one corresponding to the currently managed games. -// - If a plugin has a master plugin (IPlugin::master()), it cannot be enabled/disabled -// by users, -// and will follow the enabled/disabled state of its parent. -// - Each plugin has an "enabled" setting stored in persistence. If the setting does -// not exist, -// the plugin's enabledByDefault is used instead. -// - A plugin is considered disabled if the setting is false. -// - If the setting is true, a plugin is considered disabled if one of its -// requirements is not met. -// - Users cannot enable a plugin if one of its requirements is not met. -// -// Now let's move to the Proxy side... Or the as of now, the Python side. -// -// Proxied plugins are much more annoying because they can implement all interfaces, and -// are given to MO2 as separate plugins... A Python class implementing IPluginGame and -// IPluginDiagnose will be seen by MO2 as two separate QObject, and they will all have -// the same name. -// -// When a proxied plugin is registered, a few things must be taken care of: -// - There can only be one plugin mapped to a name in the PluginContainer class, so we -// keep the -// plugin corresponding to the most relevant class (see PluginTypeOrder), e.g. if the -// class inherits both IPluginGame and IPluginFileMapper, we map the name to the C++ -// QObject corresponding to the IPluginGame. -// - When a proxied plugin implements multiple interfaces, the IPlugin corresponding to -// the most -// important interface is set as the parent (hidden) of the other IPlugin through -// PluginRequirements. This way, the plugin are managed together (enabled/disabled -// state). The "fake" children plugins will not be returned by -// PluginRequirements::children(). -// - Since each interface corresponds to a different QObject, we need to take care not -// to call -// IPlugin::init() on each QObject, but only on the first one. -// -// All the proxied plugins are linked to the proxy plugin by PluginRequirements. If the -// proxy plugin is disabled, the proxied plugins are not even loaded so not visible in -// the plugin management tab. - -template -struct PluginTypeName; - -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Plugin"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Diagnose"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Game"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Installer"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Mod Page"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Preview"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Tool"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Proxy"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("File Mapper"); } -}; - -QStringList PluginContainer::pluginInterfaces() -{ - // Find all the names: - QStringList names; - boost::mp11::mp_for_each([&names](const auto* p) { - using plugin_type = std::decay_t; - auto name = PluginTypeName::value(); - if (!name.isEmpty()) { - names.append(name); - } - }); - - return names; -} - -// PluginRequirementProxy - -const std::set PluginRequirements::s_CorePlugins{"INI Bakery"}; - -PluginRequirements::PluginRequirements(PluginContainer* pluginContainer, - MOBase::IPlugin* plugin, OrganizerProxy* proxy, - MOBase::IPluginProxy* pluginProxy) - : m_PluginContainer(pluginContainer), m_Plugin(plugin), m_PluginProxy(pluginProxy), - m_Master(nullptr), m_Organizer(proxy) -{ - // There are a lots of things we cannot set here (e.g. m_Master) because we do not - // know the order plugins are loaded. -} - -void PluginRequirements::fetchRequirements() -{ - m_Requirements = m_Plugin->requirements(); -} - -IPluginProxy* PluginRequirements::proxy() const -{ - return m_PluginProxy; -} - -std::vector PluginRequirements::proxied() const -{ - std::vector children; - if (dynamic_cast(m_Plugin)) { - for (auto* obj : m_PluginContainer->plugins()) { - auto* plugin = qobject_cast(obj); - if (plugin && m_PluginContainer->requirements(plugin).proxy() == m_Plugin) { - children.push_back(plugin); - } - } - } - return children; -} - -IPlugin* PluginRequirements::master() const -{ - // If we have a m_Master, it was forced and thus override the default master(). - if (m_Master) { - return m_Master; - } - - if (m_Plugin->master().isEmpty()) { - return nullptr; - } - - return m_PluginContainer->plugin(m_Plugin->master()); -} - -void PluginRequirements::setMaster(IPlugin* master) -{ - m_Master = master; -} - -std::vector PluginRequirements::children() const -{ - std::vector children; - for (auto* obj : m_PluginContainer->plugins()) { - auto* plugin = qobject_cast(obj); - - // Not checking master() but requirements().master() due to "hidden" - // masters. - // If the master has the same name as the plugin, this is a "hidden" - // master, we do not add it here. - if (plugin && m_PluginContainer->requirements(plugin).master() == m_Plugin && - plugin->name() != m_Plugin->name()) { - children.push_back(plugin); - } - } - return children; -} - -std::vector PluginRequirements::problems() const -{ - std::vector result; - for (auto& requirement : m_Requirements) { - if (auto p = requirement->check(m_Organizer)) { - result.push_back(*p); - } - } - return result; -} - -bool PluginRequirements::canEnable() const -{ - return problems().empty(); -} - -bool PluginRequirements::isCorePlugin() const -{ - // Let's consider game plugins as "core": - if (m_PluginContainer->implementInterface(m_Plugin)) { - return true; - } - - return s_CorePlugins.contains(m_Plugin->name()); -} - -bool PluginRequirements::hasRequirements() const -{ - return !m_Requirements.empty(); -} - -QStringList PluginRequirements::requiredGames() const -{ - // We look for a "GameDependencyRequirement" - There can be only one since otherwise - // it'd mean that the plugin requires two games at once. - for (auto& requirement : m_Requirements) { - if (auto* gdep = - dynamic_cast(requirement.get())) { - return gdep->gameNames(); - } - } - - return {}; -} - -std::vector PluginRequirements::requiredFor() const -{ - std::vector required; - std::set visited; - requiredFor(required, visited); - return required; -} - -void PluginRequirements::requiredFor(std::vector& required, - std::set& visited) const -{ - // Handle cyclic dependencies. - if (visited.contains(m_Plugin)) { - return; - } - visited.insert(m_Plugin); - - for (auto& [plugin, requirements] : m_PluginContainer->m_Requirements) { - - // If the plugin is not enabled, discard: - if (!m_PluginContainer->isEnabled(plugin)) { - continue; - } - - // Check the requirements: - for (auto& requirement : requirements.m_Requirements) { - - // We check for plugin dependency. Game dependency are not checked this way. - if (auto* pdep = - dynamic_cast(requirement.get())) { - - // Check if at least one of the plugin in the requirements is enabled (except - // this one): - bool oneEnabled = false; - for (auto& pluginName : pdep->pluginNames()) { - if (pluginName != m_Plugin->name() && - m_PluginContainer->isEnabled(pluginName)) { - oneEnabled = true; - break; - } - } - - // No plugin enabled found, so the plugin requires this plugin: - if (!oneEnabled) { - required.push_back(plugin); - requirements.requiredFor(required, visited); - break; - } - } - } - } -} - -// PluginContainer - -PluginContainer::PluginContainer(OrganizerCore* organizer) - : m_Organizer(organizer), m_UserInterface(nullptr), - m_GameFeatures(std::make_unique(organizer, this)), - m_PreviewGenerator(*this) -{} - -PluginContainer::~PluginContainer() -{ - m_Organizer = nullptr; - unloadPlugins(); -} - -void PluginContainer::startPlugins(IUserInterface* userInterface) -{ - m_UserInterface = userInterface; - startPluginsImpl(plugins()); -} - -QStringList PluginContainer::implementedInterfaces(IPlugin* plugin) const -{ - // We need a QObject to be able to qobject_cast<> to the plugin types: - QObject* oPlugin = as_qobject(plugin); - - if (!oPlugin) { - return {}; - } - - return implementedInterfaces(oPlugin); -} - -QStringList PluginContainer::implementedInterfaces(QObject* oPlugin) const -{ - // Find all the names: - QStringList names; - boost::mp11::mp_for_each([oPlugin, &names](const auto* p) { - using plugin_type = std::decay_t; - if (qobject_cast(oPlugin)) { - auto name = PluginTypeName::value(); - if (!name.isEmpty()) { - names.append(name); - } - } - }); - - // If the plugin implements at least one interface other than IPlugin, remove IPlugin: - if (names.size() > 1) { - names.removeAll(PluginTypeName::value()); - } - - return names; -} - -QString PluginContainer::topImplementedInterface(IPlugin* plugin) const -{ - auto interfaces = implementedInterfaces(plugin); - return interfaces.isEmpty() ? "" : interfaces[0]; -} - -bool PluginContainer::isBetterInterface(QObject* lhs, QObject* rhs) const -{ - int count = 0, lhsIdx = -1, rhsIdx = -1; - boost::mp11::mp_for_each([&](const auto* p) { - using plugin_type = std::decay_t; - if (lhsIdx < 0 && qobject_cast(lhs)) { - lhsIdx = count; - } - if (rhsIdx < 0 && qobject_cast(rhs)) { - rhsIdx = count; - } - ++count; - }); - return lhsIdx < rhsIdx; -} - -QStringList PluginContainer::pluginFileNames() const -{ - QStringList result; - for (QPluginLoader* loader : m_PluginLoaders) { - result.append(loader->fileName()); - } - std::vector proxyList = bf::at_key(m_Plugins); - for (IPluginProxy* proxy : proxyList) { - QStringList proxiedPlugins = - proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + - ToQString(AppConfig::pluginPath())); - result.append(proxiedPlugins); - } - return result; -} - -QObject* PluginContainer::as_qobject(MOBase::IPlugin* plugin) const -{ - // Find the correspond QObject - Can this be done safely with a cast? - auto& objects = bf::at_key(m_Plugins); - auto it = - std::find_if(std::begin(objects), std::end(objects), [plugin](QObject* obj) { - return qobject_cast(obj) == plugin; - }); - - if (it == std::end(objects)) { - return nullptr; - } - - return *it; -} - -bool PluginContainer::initPlugin(IPlugin* plugin, IPluginProxy* pluginProxy, - bool skipInit) -{ - // when MO has no instance loaded, init() is not called on plugins, except - // for proxy plugins, where init() is called with a null IOrganizer - // - // after proxies are initialized, instantiate() is called for all the plugins - // they've discovered, but as for regular plugins, init() won't be - // called on them if m_OrganizerCore is null - - if (plugin == nullptr) { - return false; - } - - OrganizerProxy* proxy = nullptr; - if (m_Organizer) { - proxy = new OrganizerProxy(m_Organizer, this, plugin); - proxy->setParent(as_qobject(plugin)); - } - - // Check if it is a proxy plugin: - bool isProxy = dynamic_cast(plugin); - - auto [it, bl] = m_Requirements.emplace( - plugin, PluginRequirements(this, plugin, proxy, pluginProxy)); - - if (!m_Organizer && !isProxy) { - return true; - } - - if (skipInit) { - return true; - } - - if (!plugin->init(proxy)) { - log::warn("plugin failed to initialize"); - return false; - } - - // Update requirements: - it->second.fetchRequirements(); - - return true; -} - -void PluginContainer::registerGame(IPluginGame* game) -{ - m_SupportedGames.insert({game->gameName(), game}); -} - -void PluginContainer::unregisterGame(MOBase::IPluginGame* game) -{ - m_SupportedGames.erase(game->gameName()); -} - -IPlugin* PluginContainer::registerPlugin(QObject* plugin, const QString& filepath, - MOBase::IPluginProxy* pluginProxy) -{ - - // generic treatment for all plugins - IPlugin* pluginObj = qobject_cast(plugin); - if (pluginObj == nullptr) { - log::debug("PluginContainer::registerPlugin() called with a non IPlugin QObject."); - return nullptr; - } - - // If we already a plugin with this name: - bool skipInit = false; - auto& mapNames = bf::at_key(m_AccessPlugins); - if (mapNames.contains(pluginObj->name())) { - - IPlugin* other = mapNames[pluginObj->name()]; - - // If both plugins are from the same proxy and the same file, this is usually - // ok (in theory some one could write two different classes from the same Python - // file/module): - if (pluginProxy && m_Requirements.at(other).proxy() == pluginProxy && - this->filepath(other) == QDir::cleanPath(filepath)) { - - // Plugin has already been initialized: - skipInit = true; - - if (isBetterInterface(plugin, as_qobject(other))) { - log::debug( - "replacing plugin '{}' with interfaces [{}] by one with interfaces [{}]", - pluginObj->name(), implementedInterfaces(other).join(", "), - implementedInterfaces(plugin).join(", ")); - bf::at_key(m_AccessPlugins)[pluginObj->name()] = pluginObj; - } - } else { - log::warn("Trying to register two plugins with the name '{}' (from {} and {}), " - "the second one will not be registered.", - pluginObj->name(), this->filepath(other), QDir::cleanPath(filepath)); - return nullptr; - } - } else { - bf::at_key(m_AccessPlugins)[pluginObj->name()] = pluginObj; - } - - // Storing the original QObject* is a bit of a hack as I couldn't figure out any - // way to cast directly between IPlugin* and IPluginDiagnose* - bf::at_key(m_Plugins).push_back(plugin); - - plugin->setProperty("filepath", QDir::cleanPath(filepath)); - plugin->setParent(this); - - if (m_Organizer) { - m_Organizer->settings().plugins().registerPlugin(pluginObj); - } - - { // diagnosis plugin - IPluginDiagnose* diagnose = qobject_cast(plugin); - if (diagnose != nullptr) { - bf::at_key(m_Plugins).push_back(diagnose); - bf::at_key(m_AccessPlugins)[diagnose] = pluginObj; - diagnose->onInvalidated([&]() { - emit diagnosisUpdate(); - }); - } - } - { // file mapper plugin - IPluginFileMapper* mapper = qobject_cast(plugin); - if (mapper != nullptr) { - bf::at_key(m_Plugins).push_back(mapper); - bf::at_key(m_AccessPlugins)[mapper] = pluginObj; - } - } - { // mod page plugin - IPluginModPage* modPage = qobject_cast(plugin); - if (initPlugin(modPage, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(modPage); - emit pluginRegistered(modPage); - return modPage; - } - } - { // game plugin - IPluginGame* game = qobject_cast(plugin); - if (game) { - game->detectGame(); - if (initPlugin(game, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(game); - registerGame(game); - emit pluginRegistered(game); - return game; - } - } - } - { // tool plugins - IPluginTool* tool = qobject_cast(plugin); - if (initPlugin(tool, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(tool); - emit pluginRegistered(tool); - return tool; - } - } - { // installer plugins - IPluginInstaller* installer = qobject_cast(plugin); - if (initPlugin(installer, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(installer); - if (m_Organizer) { - installer->setInstallationManager(m_Organizer->installationManager()); - } - emit pluginRegistered(installer); - return installer; - } - } - { // preview plugins - IPluginPreview* preview = qobject_cast(plugin); - if (initPlugin(preview, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(preview); - return preview; - } - } - { // proxy plugins - IPluginProxy* proxy = qobject_cast(plugin); - if (initPlugin(proxy, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(proxy); - emit pluginRegistered(proxy); - - QStringList filepaths = - proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + - ToQString(AppConfig::pluginPath())); - for (const QString& filepath : filepaths) { - loadProxied(filepath, proxy); - } - return proxy; - } - } - - { // dummy plugins - // only initialize these, no processing otherwise - IPlugin* dummy = qobject_cast(plugin); - if (initPlugin(dummy, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(dummy); - emit pluginRegistered(dummy); - return dummy; - } - } - - return nullptr; -} - -IPluginGame* PluginContainer::managedGame() const -{ - // TODO: This const_cast is safe but ugly. Most methods require a IPlugin*, so - // returning a const-version if painful. This should be fixed by making methods accept - // a const IPlugin* instead, but there are a few tricks with qobject_cast and const. - return m_Organizer ? const_cast(m_Organizer->managedGame()) : nullptr; -} - -bool PluginContainer::isEnabled(IPlugin* plugin) const -{ - // Check if it's a game plugin: - if (implementInterface(plugin)) { - return plugin == m_Organizer->managedGame(); - } - - // Check the master, if any: - auto& requirements = m_Requirements.at(plugin); - - if (requirements.master()) { - return isEnabled(requirements.master()); - } - - // Check if the plugin is enabled: - if (!m_Organizer->persistent(plugin->name(), "enabled", plugin->enabledByDefault()) - .toBool()) { - return false; - } - - // Check the requirements: - return m_Requirements.at(plugin).canEnable(); -} - -void PluginContainer::setEnabled(MOBase::IPlugin* plugin, bool enable, - bool dependencies) -{ - // If required, disable dependencies: - if (!enable && dependencies) { - for (auto* p : requirements(plugin).requiredFor()) { - // No need to "recurse" here since requiredFor already does it. - setEnabled(p, false, false); - } - } - - // Always disable/enable child plugins: - for (auto* p : requirements(plugin).children()) { - // "Child" plugin should have no dependencies. - setEnabled(p, enable, false); - } - - m_Organizer->setPersistent(plugin->name(), "enabled", enable, true); - - if (enable) { - emit pluginEnabled(plugin); - } else { - emit pluginDisabled(plugin); - } -} - -MOBase::IPlugin* PluginContainer::plugin(QString const& pluginName) const -{ - auto& map = bf::at_key(m_AccessPlugins); - auto it = map.find(pluginName); - if (it == std::end(map)) { - return nullptr; - } - return it->second; -} - -MOBase::IPlugin* PluginContainer::plugin(MOBase::IPluginDiagnose* diagnose) const -{ - auto& map = bf::at_key(m_AccessPlugins); - auto it = map.find(diagnose); - if (it == std::end(map)) { - return nullptr; - } - return it->second; -} - -MOBase::IPlugin* PluginContainer::plugin(MOBase::IPluginFileMapper* mapper) const -{ - auto& map = bf::at_key(m_AccessPlugins); - auto it = map.find(mapper); - if (it == std::end(map)) { - return nullptr; - } - return it->second; -} - -bool PluginContainer::isEnabled(QString const& pluginName) const -{ - IPlugin* p = plugin(pluginName); - return p ? isEnabled(p) : false; -} -bool PluginContainer::isEnabled(MOBase::IPluginDiagnose* diagnose) const -{ - IPlugin* p = plugin(diagnose); - return p ? isEnabled(p) : false; -} -bool PluginContainer::isEnabled(MOBase::IPluginFileMapper* mapper) const -{ - IPlugin* p = plugin(mapper); - return p ? isEnabled(p) : false; -} - -const PluginRequirements& PluginContainer::requirements(IPlugin* plugin) const -{ - return m_Requirements.at(plugin); -} - -OrganizerProxy* PluginContainer::organizerProxy(MOBase::IPlugin* plugin) const -{ - return requirements(plugin).m_Organizer; -} - -MOBase::IPluginProxy* PluginContainer::pluginProxy(MOBase::IPlugin* plugin) const -{ - return requirements(plugin).proxy(); -} - -QString PluginContainer::filepath(MOBase::IPlugin* plugin) const -{ - return as_qobject(plugin)->property("filepath").toString(); -} - -IPluginGame* PluginContainer::game(const QString& name) const -{ - auto iter = m_SupportedGames.find(name); - if (iter != m_SupportedGames.end()) { - return iter->second; - } else { - return nullptr; - } -} - -void PluginContainer::startPluginsImpl(const std::vector& plugins) const -{ - // setUserInterface() - if (m_UserInterface) { - for (auto* plugin : plugins) { - if (auto* proxy = qobject_cast(plugin)) { - proxy->setParentWidget(m_UserInterface->mainWindow()); - } - if (auto* modPage = qobject_cast(plugin)) { - modPage->setParentWidget(m_UserInterface->mainWindow()); - } - if (auto* tool = qobject_cast(plugin)) { - tool->setParentWidget(m_UserInterface->mainWindow()); - } - if (auto* installer = qobject_cast(plugin)) { - installer->setParentWidget(m_UserInterface->mainWindow()); - } - } - } - - // Trigger initial callbacks, e.g. onUserInterfaceInitialized and onProfileChanged. - if (m_Organizer) { - for (auto* object : plugins) { - auto* plugin = qobject_cast(object); - auto* oproxy = organizerProxy(plugin); - oproxy->connectSignals(); - oproxy->m_ProfileChanged(nullptr, m_Organizer->currentProfile()); - - if (m_UserInterface) { - oproxy->m_UserInterfaceInitialized(m_UserInterface->mainWindow()); - } - } - } -} - -std::vector PluginContainer::loadProxied(const QString& filepath, - IPluginProxy* proxy) -{ - std::vector proxiedPlugins; - - try { - // We get a list of matching plugins as proxies can return multiple plugins - // per file and do not have a good way of supporting multiple inheritance. - QList matchingPlugins = proxy->load(filepath); - - // We are going to group plugin by names and "fix" them later: - std::map> proxiedByNames; - - for (QObject* proxiedPlugin : matchingPlugins) { - if (proxiedPlugin != nullptr) { - - if (IPlugin* proxied = registerPlugin(proxiedPlugin, filepath, proxy); - proxied) { - log::debug("loaded plugin '{}' from '{}' - [{}]", proxied->name(), - QFileInfo(filepath).fileName(), - implementedInterfaces(proxied).join(", ")); - - // Store the plugin for later: - proxiedPlugins.push_back(proxiedPlugin); - proxiedByNames[proxied->name()].push_back(proxied); - } else { - log::warn("plugin \"{}\" failed to load. If this plugin is for an older " - "version of MO " - "you have to update it or delete it if no update exists.", - filepath); - } - } - } - - // Fake masters: - for (auto& [name, proxiedPlugins] : proxiedByNames) { - if (proxiedPlugins.size() > 1) { - auto it = std::min_element(std::begin(proxiedPlugins), std::end(proxiedPlugins), - [&](auto const& lhs, auto const& rhs) { - return isBetterInterface(as_qobject(lhs), - as_qobject(rhs)); - }); - - for (auto& proxiedPlugin : proxiedPlugins) { - if (proxiedPlugin != *it) { - m_Requirements.at(proxiedPlugin).setMaster(*it); - } - } - } - } - } catch (const std::exception& e) { - reportError( - QObject::tr("failed to initialize plugin %1: %2").arg(filepath).arg(e.what())); - } - - return proxiedPlugins; -} - -QObject* PluginContainer::loadQtPlugin(const QString& filepath) -{ - std::unique_ptr pluginLoader(new QPluginLoader(filepath, this)); - if (pluginLoader->instance() == nullptr) { - m_FailedPlugins.push_back(filepath); - log::error("failed to load plugin {}: {}", filepath, pluginLoader->errorString()); - } else { - QObject* object = pluginLoader->instance(); - if (IPlugin* plugin = registerPlugin(object, filepath, nullptr); plugin) { - log::debug("loaded plugin '{}' from '{}' - [{}]", plugin->name(), - QFileInfo(filepath).fileName(), - implementedInterfaces(plugin).join(", ")); - m_PluginLoaders.push_back(pluginLoader.release()); - return object; - } else { - m_FailedPlugins.push_back(filepath); - log::warn("plugin '{}' failed to load (may be outdated)", filepath); - } - } - return nullptr; -} - -std::optional PluginContainer::isQtPluginFolder(const QString& filepath) const -{ - - if (!QFileInfo(filepath).isDir()) { - return {}; - } - - QDirIterator iter(filepath, QDir::Files | QDir::NoDotAndDotDot); - while (iter.hasNext()) { - iter.next(); - const auto filePath = iter.filePath(); - - // not a library, skip - if (!QLibrary::isLibrary(filePath)) { - continue; - } - - // check if we have proper metadata - this does not load the plugin (metaData() - // should be very lightweight) - const QPluginLoader loader(filePath); - if (!loader.metaData().isEmpty()) { - return filePath; - } - } - - return {}; -} - -void PluginContainer::loadPlugin(QString const& filepath) -{ - std::vector plugins; - if (QFileInfo(filepath).isFile() && QLibrary::isLibrary(filepath)) { - QObject* plugin = loadQtPlugin(filepath); - if (plugin) { - plugins.push_back(plugin); - } - } else if (auto p = isQtPluginFolder(filepath)) { - QObject* plugin = loadQtPlugin(*p); - if (plugin) { - plugins.push_back(plugin); - } - } else { - // We need to check if this can be handled by a proxy. - for (auto* proxy : this->plugins()) { - auto filepaths = proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + - ToQString(AppConfig::pluginPath())); - if (filepaths.contains(filepath)) { - plugins = loadProxied(filepath, proxy); - break; - } - } - } - - for (auto* plugin : plugins) { - emit pluginRegistered(qobject_cast(plugin)); - } - - startPluginsImpl(plugins); -} - -void PluginContainer::unloadPlugin(MOBase::IPlugin* plugin, QObject* object) -{ - if (auto* game = qobject_cast(object)) { - - if (game == managedGame()) { - throw Exception("cannot unload the plugin for the currently managed game"); - } - - unregisterGame(game); - } - - // We need to remove from the m_Plugins maps BEFORE unloading from the proxy - // otherwise the qobject_cast to check the plugin type will not work. - bf::for_each(m_Plugins, [object](auto& t) { - using type = typename std::decay_t::value_type; - - // We do not want to remove from QObject since we are iterating over them. - if constexpr (!std::is_same{}) { - auto itp = - std::find(t.second.begin(), t.second.end(), qobject_cast(object)); - if (itp != t.second.end()) { - t.second.erase(itp); - } - } - }); - - emit pluginUnregistered(plugin); - - // Remove from the members. - if (auto* diagnose = qobject_cast(object)) { - bf::at_key(m_AccessPlugins).erase(diagnose); - } - if (auto* mapper = qobject_cast(object)) { - bf::at_key(m_AccessPlugins).erase(mapper); - } - - auto& mapNames = bf::at_key(m_AccessPlugins); - if (mapNames.contains(plugin->name())) { - mapNames.erase(plugin->name()); - } - - m_Organizer->settings().plugins().unregisterPlugin(plugin); - - // Force disconnection of the signals from the proxies. This is a safety - // operations since those signals should be disconnected when the proxies - // are destroyed anyway. - organizerProxy(plugin)->disconnectSignals(); - - // Is this a proxied plugin? - auto* proxy = pluginProxy(plugin); - - if (proxy) { - proxy->unload(filepath(plugin)); - } else { - // We need to find the loader. - auto it = std::find_if(m_PluginLoaders.begin(), m_PluginLoaders.end(), - [object](auto* loader) { - return loader->instance() == object; - }); - - if (it != m_PluginLoaders.end()) { - if (!(*it)->unload()) { - log::error("failed to unload {}: {}", (*it)->fileName(), (*it)->errorString()); - } - delete *it; - m_PluginLoaders.erase(it); - } else { - log::error("loader for plugin {} does not exist, cannot unload", plugin->name()); - } - } - - object->deleteLater(); - - // Do this at the end. - m_Requirements.erase(plugin); -} - -void PluginContainer::unloadPlugin(QString const& filepath) -{ - // We need to find all the plugins from the given path and - // unload them: - QString cleanPath = QDir::cleanPath(filepath); - auto& objects = bf::at_key(m_Plugins); - for (auto it = objects.begin(); it != objects.end();) { - auto* plugin = qobject_cast(*it); - if (this->filepath(plugin) == filepath) { - unloadPlugin(plugin, *it); - it = objects.erase(it); - } else { - ++it; - } - } -} - -void PluginContainer::reloadPlugin(QString const& filepath) -{ - unloadPlugin(filepath); - loadPlugin(filepath); -} - -void PluginContainer::unloadPlugins() -{ - if (m_Organizer) { - // this will clear several structures that can hold on to pointers to - // plugins, as well as read the plugin blacklist from the ini file, which - // is used in loadPlugins() below to skip plugins - // - // note that the first thing loadPlugins() does is call unloadPlugins(), - // so this makes sure the blacklist is always available - m_Organizer->settings().plugins().clearPlugins(); - } - - bf::for_each(m_Plugins, [](auto& t) { - t.second.clear(); - }); - bf::for_each(m_AccessPlugins, [](auto& t) { - t.second.clear(); - }); - m_Requirements.clear(); - - while (!m_PluginLoaders.empty()) { - QPluginLoader* loader = m_PluginLoaders.back(); - m_PluginLoaders.pop_back(); - if ((loader != nullptr) && !loader->unload()) { - log::debug("failed to unload {}: {}", loader->fileName(), loader->errorString()); - } - delete loader; - } -} - -void PluginContainer::loadPlugins() -{ - TimeThis tt("PluginContainer::loadPlugins()"); - - unloadPlugins(); - - for (QObject* plugin : QPluginLoader::staticInstances()) { - registerPlugin(plugin, "", nullptr); - } - - QFile loadCheck; - QString skipPlugin; - - if (m_Organizer) { - loadCheck.setFileName(qApp->property("dataPath").toString() + - "/plugin_loadcheck.tmp"); - - if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) { - // oh, there was a failed plugin load last time. Find out which plugin was loaded - // last - QString fileName; - while (!loadCheck.atEnd()) { - fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed(); - } - - log::warn("loadcheck file found for plugin '{}'", fileName); - - MOBase::TaskDialog dlg; - - const auto Skip = QMessageBox::Ignore; - const auto Blacklist = QMessageBox::Cancel; - const auto Load = QMessageBox::Ok; - - const auto r = - dlg.title(tr("Plugin error")) - .main(tr("Mod Organizer failed to load the plugin '%1' last time it was " - "started.") - .arg(fileName)) - .content(tr( - "The plugin can be skipped for this session, blacklisted, " - "or loaded normally, in which case it might fail again. Blacklisted " - "plugins can be re-enabled later in the settings.")) - .icon(QMessageBox::Warning) - .button({tr("Skip this plugin"), Skip}) - .button({tr("Blacklist this plugin"), Blacklist}) - .button({tr("Load this plugin"), Load}) - .exec(); - - switch (r) { - case Skip: - log::warn("user wants to skip plugin '{}'", fileName); - skipPlugin = fileName; - break; - - case Blacklist: - log::warn("user wants to blacklist plugin '{}'", fileName); - m_Organizer->settings().plugins().addBlacklist(fileName); - break; - - case Load: - log::warn("user wants to load plugin '{}' anyway", fileName); - break; - } - - loadCheck.close(); - } - - loadCheck.open(QIODevice::WriteOnly); - } - - QString pluginPath = - qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); - log::debug("looking for plugins in {}", QDir::toNativeSeparators(pluginPath)); - QDirIterator iter(pluginPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); - - while (iter.hasNext()) { - iter.next(); - - if (skipPlugin == iter.fileName()) { - log::debug("plugin \"{}\" skipped for this session", iter.fileName()); - continue; - } - - if (m_Organizer) { - if (m_Organizer->settings().plugins().blacklisted(iter.fileName())) { - log::debug("plugin \"{}\" blacklisted", iter.fileName()); - continue; - } - } - - if (loadCheck.isOpen()) { - loadCheck.write(iter.fileName().toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); - } - - QString filepath = iter.filePath(); - if (QLibrary::isLibrary(filepath)) { - loadQtPlugin(filepath); - } else if (auto p = isQtPluginFolder(filepath)) { - loadQtPlugin(*p); - } - } - - if (skipPlugin.isEmpty()) { - // remove the load check file on success - if (loadCheck.isOpen()) { - loadCheck.remove(); - } - } else { - // remember the plugin for next time - if (loadCheck.isOpen()) { - loadCheck.close(); - } - - log::warn("user skipped plugin '{}', remembering in loadcheck", skipPlugin); - loadCheck.open(QIODevice::WriteOnly); - loadCheck.write(skipPlugin.toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); - } - - bf::at_key(m_Plugins).push_back(this); - - if (m_Organizer) { - bf::at_key(m_Plugins).push_back(m_Organizer); - m_Organizer->connectPlugins(this); - } -} - -std::vector PluginContainer::activeProblems() const -{ - std::vector problems; - if (m_FailedPlugins.size()) { - problems.push_back(PROBLEM_PLUGINSNOTLOADED); - } - return problems; -} - -QString PluginContainer::shortDescription(unsigned int key) const -{ - switch (key) { - case PROBLEM_PLUGINSNOTLOADED: { - return tr("Some plugins could not be loaded"); - } break; - default: { - return tr("Description missing"); - } break; - } -} - -QString PluginContainer::fullDescription(unsigned int key) const -{ - switch (key) { - case PROBLEM_PLUGINSNOTLOADED: { - QString result = - tr("The following plugins could not be loaded. The reason may be missing " - "dependencies (i.e. python) or an outdated version:") + - "
    "; - for (const QString& plugin : m_FailedPlugins) { - result += "
  • " + plugin + "
  • "; - } - result += "
      "; - return result; - } break; - default: { - return tr("Description missing"); - } break; - } -} - -bool PluginContainer::hasGuidedFix(unsigned int) const -{ - return false; -} - -void PluginContainer::startGuidedFix(unsigned int) const {} diff --git a/src/plugincontainer.h b/src/plugincontainer.h deleted file mode 100644 index 4854954d3..000000000 --- a/src/plugincontainer.h +++ /dev/null @@ -1,505 +0,0 @@ -#ifndef PLUGINCONTAINER_H -#define PLUGINCONTAINER_H - -#include "previewgenerator.h" - -class OrganizerCore; -class IUserInterface; - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef Q_MOC_RUN -#include -#include -#include -#endif // Q_MOC_RUN -#include -#include - -#include "game_features.h" - -class OrganizerProxy; - -/** - * @brief Class that wrap multiple requirements for a plugin together. THis - * class owns the requirements. - */ -class PluginRequirements -{ -public: - /** - * @return true if the plugin can be enabled (all requirements are met). - */ - bool canEnable() const; - - /** - * @return true if this is a core plugin, i.e. a plugin that should not be - * manually enabled or disabled by the user. - */ - bool isCorePlugin() const; - - /** - * @return true if this plugin has requirements (satisfied or not). - */ - bool hasRequirements() const; - - /** - * @return the proxy that created this plugin, if any. - */ - MOBase::IPluginProxy* proxy() const; - - /** - * @return the list of plugins this plugin proxies (if it's a proxy plugin). - */ - std::vector proxied() const; - - /** - * @return the master of this plugin, if any. - */ - MOBase::IPlugin* master() const; - - /** - * @return the plugins this plugin is master of. - */ - std::vector children() const; - - /** - * @return the list of problems to be resolved before enabling the plugin. - */ - std::vector problems() const; - - /** - * @return the name of the games (gameName()) this plugin can be used with, or an - * empty list if this plugin does not require particular games. - */ - QStringList requiredGames() const; - - /** - * @return the list of plugins currently enabled that would have to be disabled - * if this plugin was disabled. - */ - std::vector requiredFor() const; - -private: - // The list of "Core" plugins. - static const std::set s_CorePlugins; - - // Accumulator version for requiredFor() to avoid infinite recursion. - void requiredFor(std::vector& required, - std::set& visited) const; - - // Retrieve the requirements from the underlying plugin, take ownership on them - // and store them. We cannot do this in the constructor because we want to have a - // constructed object before calling init(). - void fetchRequirements(); - - // Set the master for this plugin. This is required to "fake" masters for proxied - // plugins. - void setMaster(MOBase::IPlugin* master); - - friend class OrganizerCore; - friend class PluginContainer; - - PluginContainer* m_PluginContainer; - MOBase::IPlugin* m_Plugin; - MOBase::IPluginProxy* m_PluginProxy; - MOBase::IPlugin* m_Master; - std::vector> m_Requirements; - OrganizerProxy* m_Organizer; - std::vector m_RequiredFor; - - PluginRequirements(PluginContainer* pluginContainer, MOBase::IPlugin* plugin, - OrganizerProxy* proxy, MOBase::IPluginProxy* pluginProxy); -}; - -/** - * - */ -class PluginContainer : public QObject, public MOBase::IPluginDiagnose -{ - - Q_OBJECT - Q_INTERFACES(MOBase::IPluginDiagnose) - -private: - using PluginMap = boost::fusion::map< - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>>; - - using AccessPluginMap = boost::fusion::map< - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>>; - - static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; - - /** - * This typedefs defines the order of plugin interface. This is increasing order of - * importance". - * - * @note IPlugin is the less important interface, followed by IPluginDiagnose and - * IPluginFileMapper as those are usually implemented together with another - * interface. Other interfaces are in a alphabetical order since it is unlikely a - * plugin will implement multiple ones. - */ - using PluginTypeOrder = boost::mp11::mp_transform< - std::add_pointer_t, - boost::mp11::mp_list< - MOBase::IPluginGame, MOBase::IPluginInstaller, MOBase::IPluginModPage, - MOBase::IPluginPreview, MOBase::IPluginProxy, MOBase::IPluginTool, - MOBase::IPluginDiagnose, MOBase::IPluginFileMapper, MOBase::IPlugin>>; - - static_assert(boost::mp11::mp_size::value == - boost::mp11::mp_size::value - 1); - -public: - /** - * @brief Retrieved the (localized) names of the various plugin interfaces. - * - * @return the (localized) names of the various plugin interfaces. - */ - static QStringList pluginInterfaces(); - -public: - PluginContainer(OrganizerCore* organizer); - virtual ~PluginContainer(); - - /** - * @brief Start the plugins. - * - * This function should not be called before MO2 is ready and plugins can be - * started, and will do the following: - * - connect the callbacks of the plugins, - * - set the parent widget for plugins that can have one, - * - notify plugins that MO2 has been started, including: - * - triggering a call to the "profile changed" callback for the initial profile, - * - triggering a call to the "user interface initialized" callback. - * - * @param userInterface The main user interface to use for the plugins. - */ - void startPlugins(IUserInterface* userInterface); - - /** - * @brief Load, unload or reload the plugin at the given path. - * - */ - void loadPlugin(QString const& filepath); - void unloadPlugin(QString const& filepath); - void reloadPlugin(QString const& filepath); - - /** - * @brief Load all plugins. - * - */ - void loadPlugins(); - - /** - * @brief Retrieve the list of plugins of the given type. - * - * @return the list of plugins of the specified type. - * - * @tparam T The type of plugin to retrieve. - */ - template - const std::vector& plugins() const - { - typename boost::fusion::result_of::at_key::type temp = - boost::fusion::at_key(m_Plugins); - return temp; - } - - /** - * @brief Check if a plugin implement a given interface. - * - * @param plugin The plugin to check. - * - * @return true if the plugin implements the interface, false otherwise. - * - * @tparam The interface type. - */ - template - bool implementInterface(MOBase::IPlugin* plugin) const - { - // We need a QObject to be able to qobject_cast<> to the plugin types: - QObject* oPlugin = as_qobject(plugin); - - if (!oPlugin) { - return false; - } - - return qobject_cast(oPlugin); - } - - /** - * @brief Retrieve a plugin from its name or a corresponding non-IPlugin - * interface. - * - * @param t Name of the plugin to retrieve, or non-IPlugin interface. - * - * @return the corresponding plugin, or a null pointer. - * - * @note It is possible to have multiple plugins for the same name when - * dealing with proxied plugins (e.g. Python), in which case the - * most important one will be returned, as specified in PluginTypeOrder. - */ - MOBase::IPlugin* plugin(QString const& pluginName) const; - MOBase::IPlugin* plugin(MOBase::IPluginDiagnose* diagnose) const; - MOBase::IPlugin* plugin(MOBase::IPluginFileMapper* mapper) const; - - /** - * @brief Find the game plugin corresponding to the given name. - * - * @param name The name of the game to find a plugin for (as returned by - * IPluginGame::gameName()). - * - * @return the game plugin for the given name, or a null pointer if no - * plugin exists for this game. - */ - MOBase::IPluginGame* game(const QString& name) const; - - /** - * @return the IPlugin interface to the currently managed game. - */ - MOBase::IPluginGame* managedGame() const; - - /** - * @brief Check if the given plugin is enabled. - * - * @param plugin The plugin to check. - * - * @return true if the plugin is enabled, false otherwise. - */ - bool isEnabled(MOBase::IPlugin* plugin) const; - - // These are friendly methods that called isEnabled(plugin(arg)). - bool isEnabled(QString const& pluginName) const; - bool isEnabled(MOBase::IPluginDiagnose* diagnose) const; - bool isEnabled(MOBase::IPluginFileMapper* mapper) const; - - /** - * @brief Enable or disable a plugin. - * - * @param plugin The plugin to enable or disable. - * @param enable true to enable, false to disable. - * @param dependencies If true and enable is false, dependencies will also - * be disabled (see PluginRequirements::requiredFor). - */ - void setEnabled(MOBase::IPlugin* plugin, bool enable, bool dependencies = true); - - /** - * @brief Retrieve the requirements for the given plugin. - * - * @param plugin The plugin to retrieve the requirements for. - * - * @return the requirements (as proxy) for the given plugin. - */ - const PluginRequirements& requirements(MOBase::IPlugin* plugin) const; - - /** - * @brief Retrieved the (localized) names of interfaces implemented by the given - * plugin. - * - * @param plugin The plugin to retrieve interface for. - * - * @return the (localized) names of interfaces implemented by this plugin. - */ - QStringList implementedInterfaces(MOBase::IPlugin* plugin) const; - - /** - * @brief Return the (localized) name of the most important interface implemented by - * the given plugin. - * - * The order of interfaces is defined in X. - * - * @param plugin The plugin to retrieve the interface for. - * - * @return the (localized) name of the most important interface implemented by this - * plugin. - */ - QString topImplementedInterface(MOBase::IPlugin* plugin) const; - - /** - * @return the game features. - */ - GameFeatures& gameFeatures() const { return *m_GameFeatures; } - - /** - * @return the preview generator. - */ - const PreviewGenerator& previewGenerator() const { return m_PreviewGenerator; } - - /** - * @return the list of plugin file names, including proxied plugins. - */ - QStringList pluginFileNames() const; - -public: // IPluginDiagnose interface - virtual std::vector activeProblems() const; - virtual QString shortDescription(unsigned int key) const; - virtual QString fullDescription(unsigned int key) const; - virtual bool hasGuidedFix(unsigned int key) const; - virtual void startGuidedFix(unsigned int key) const; - -signals: - - /** - * @brief Emitted when plugins are enabled or disabled. - */ - void pluginEnabled(MOBase::IPlugin*); - void pluginDisabled(MOBase::IPlugin*); - - /** - * @brief Emitted when plugins are registered or unregistered. - */ - void pluginRegistered(MOBase::IPlugin*); - void pluginUnregistered(MOBase::IPlugin*); - - void diagnosisUpdate(); - -private: - friend class PluginRequirements; - - // Unload all the plugins. - void unloadPlugins(); - - // Retrieve the organizer proxy for the given plugin. - OrganizerProxy* organizerProxy(MOBase::IPlugin* plugin) const; - - // Retrieve the proxy plugin that instantiated the given plugin, or a null pointer - // if the plugin was not instantiated by a proxy. - MOBase::IPluginProxy* pluginProxy(MOBase::IPlugin* plugin) const; - - // Retrieve the path to the file or folder corresponding to the plugin. - QString filepath(MOBase::IPlugin* plugin) const; - - // Load plugins from the given filepath using the given proxy. - std::vector loadProxied(const QString& filepath, - MOBase::IPluginProxy* proxy); - - // Load the Qt plugin from the given file. - QObject* loadQtPlugin(const QString& filepath); - - // check if a plugin is folder containing a Qt plugin, it is, return the path to the - // DLL containing the plugin in the folder, otherwise return an empty optional - // - // a Qt plugin folder is a folder with a DLL containing a library (not in a - // subdirectory), if multiple plugins are present, only the first one is returned - // - // extra DLLs are ignored by Qt so can be present in the folder - // - std::optional isQtPluginFolder(const QString& filepath) const; - - // See startPlugins for more details. This is simply an intermediate function - // that can be used when loading plugins after initialization. This uses the - // user interface in m_UserInterface. - void startPluginsImpl(const std::vector& plugins) const; - - /** - * @brief Unload the given plugin. - * - * This function is not public because it's kind of dangerous trying to unload - * plugin directly since some plugins are linked together. - * - * @param plugin The plugin to unload/unregister. - * @param object The QObject corresponding to the plugin. - */ - void unloadPlugin(MOBase::IPlugin* plugin, QObject* object); - - /** - * @brief Retrieved the (localized) names of interfaces implemented by the given - * plugin. - * - * @param plugin The plugin to retrieve interface for. - * - * @return the (localized) names of interfaces implemented by this plugin. - * - * @note This function can be used to get implemented interfaces before registering - * a plugin. - */ - QStringList implementedInterfaces(QObject* plugin) const; - - /** - * @brief Check if a plugin implements a "better" interface than another - * one, as specified by PluginTypeOrder. - * - * @param lhs, rhs The plugin to compare. - * - * @return true if the left plugin implements a better interface than the right - * one, false otherwise (or if both implements the same interface). - */ - bool isBetterInterface(QObject* lhs, QObject* rhs) const; - - /** - * @brief Find the QObject* corresponding to the given plugin. - * - * @param plugin The plugin to find the QObject* for. - * - * @return a QObject* for the given plugin. - */ - QObject* as_qobject(MOBase::IPlugin* plugin) const; - - /** - * @brief Initialize a plugin. - * - * @param plugin The plugin to initialize. - * @param proxy The proxy that created this plugin (can be null). - * @param skipInit If true, IPlugin::init() will not be called, regardless - * of the state of the container. - * - * @return true if the plugin was initialized correctly, false otherwise. - */ - bool initPlugin(MOBase::IPlugin* plugin, MOBase::IPluginProxy* proxy, bool skipInit); - - void registerGame(MOBase::IPluginGame* game); - void unregisterGame(MOBase::IPluginGame* game); - - MOBase::IPlugin* registerPlugin(QObject* pluginObj, const QString& fileName, - MOBase::IPluginProxy* proxy); - - // Core organizer, can be null (e.g. on first MO2 startup). - OrganizerCore* m_Organizer; - - // Main user interface, can be null until MW has been initialized. - IUserInterface* m_UserInterface; - - // Game features - std::unique_ptr m_GameFeatures; - - PluginMap m_Plugins; - - // This maps allow access to IPlugin* from name or diagnose/mapper object. - AccessPluginMap m_AccessPlugins; - - std::map m_Requirements; - - std::map m_SupportedGames; - QStringList m_FailedPlugins; - std::vector m_PluginLoaders; - - PreviewGenerator m_PreviewGenerator; - - QFile m_PluginsCheck; -}; - -#endif // PLUGINCONTAINER_H diff --git a/src/pluginmanager.cpp b/src/pluginmanager.cpp new file mode 100644 index 000000000..bbbb50a02 --- /dev/null +++ b/src/pluginmanager.cpp @@ -0,0 +1,703 @@ +#include "pluginmanager.h" + +#include +#include + +#include +#include +#include +#include + +#include "extensionmanager.h" +#include "iuserinterface.h" +#include "organizercore.h" +#include "organizerproxy.h" +#include "previewgenerator.h" +#include "proxyqt.h" + +using namespace MOBase; +namespace bf = boost::fusion; + +// localized names + +template +struct PluginTypeName; + +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Plugin"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Diagnose"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Game"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Installer"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Mod Page"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Preview"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Tool"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("File Mapper"); } +}; + +// PluginDetails + +PluginDetails::PluginDetails(PluginManager* manager, PluginExtension const& extension, + IPlugin* plugin, OrganizerProxy* proxy) + : m_manager(manager), m_extension(&extension), m_plugin(plugin), m_organizer(proxy) +{} + +void PluginDetails::fetchRequirements() +{ + m_requirements = m_plugin->requirements(); +} + +std::vector PluginDetails::problems() const +{ + std::vector result; + for (auto& requirement : m_requirements) { + if (auto p = requirement->check(m_organizer)) { + result.push_back(*p); + } + } + return result; +} + +bool PluginDetails::canEnable() const +{ + return problems().empty(); +} + +bool PluginDetails::hasRequirements() const +{ + return !m_requirements.empty(); +} + +QStringList PluginDetails::requiredGames() const +{ + // We look for a "GameDependencyRequirement" - There can be only one since otherwise + // it'd mean that the plugin requires two games at once. + for (auto& requirement : m_requirements) { + if (auto* gdep = + dynamic_cast(requirement.get())) { + return gdep->gameNames(); + } + } + + return {}; +} + +// PluginManager + +QStringList PluginManager::pluginInterfaces() +{ + // Find all the names: + QStringList names; + boost::mp11::mp_for_each([&names](const auto* p) { + using plugin_type = std::decay_t; + auto name = PluginTypeName::value(); + if (!name.isEmpty()) { + names.append(name); + } + }); + + return names; +} + +PluginManager::PluginManager(ExtensionManager const& manager, OrganizerCore* core) + : m_extensions{manager}, m_core{core}, + m_gameFeatures(std::make_unique(core, this)) +{ + m_loaders = makeLoaders(); +} + +QStringList PluginManager::implementedInterfaces(IPlugin* plugin) const +{ + // we need a QObject to be able to qobject_cast<> to the plugin types + QObject* oPlugin = as_qobject(plugin); + + if (!oPlugin) { + return {}; + } + + return implementedInterfaces(oPlugin); +} + +QStringList PluginManager::implementedInterfaces(QObject* oPlugin) const +{ + // Find all the names: + QStringList names; + boost::mp11::mp_for_each([oPlugin, &names](const auto* p) { + using plugin_type = std::decay_t; + if (qobject_cast(oPlugin)) { + auto name = PluginTypeName::value(); + if (!name.isEmpty()) { + names.append(name); + } + } + }); + + // If the plugin implements at least one interface other than IPlugin, remove IPlugin: + if (names.size() > 1) { + names.removeAll(PluginTypeName::value()); + } + + return names; +} + +QString PluginManager::topImplementedInterface(IPlugin* plugin) const +{ + auto interfaces = implementedInterfaces(plugin); + return interfaces.isEmpty() ? "" : interfaces[0]; +} + +bool PluginManager::isBetterInterface(QObject* lhs, QObject* rhs) const +{ + int count = 0, lhsIdx = -1, rhsIdx = -1; + boost::mp11::mp_for_each([&](const auto* p) { + using plugin_type = std::decay_t; + if (lhsIdx < 0 && qobject_cast(lhs)) { + lhsIdx = count; + } + if (rhsIdx < 0 && qobject_cast(rhs)) { + rhsIdx = count; + } + ++count; + }); + return lhsIdx < rhsIdx; +} + +MOBase::IPluginGame* PluginManager::game(const QString& name) const +{ + auto iter = m_supportedGames.find(name); + if (iter != m_supportedGames.end()) { + return iter->second; + } else { + return nullptr; + } +} + +MOBase::IPluginGame* PluginManager::managedGame() const +{ + // TODO: this const_cast is safe but ugly + // + // most methods require a IPlugin*, so returning a const-version if painful, this + // should be fixed by making methods accept a const IPlugin* instead, but there are a + // few tricks with qobject_cast and const + // + return m_core ? const_cast(m_core->managedGame()) : nullptr; +} + +MOBase::IPlugin* PluginManager::plugin(QString const& pluginName) const +{ + auto& map = bf::at_key(m_accessPlugins); + auto it = map.find(pluginName); + if (it == std::end(map)) { + return nullptr; + } + return it->second; +} + +MOBase::IPlugin* PluginManager::plugin(MOBase::IPluginDiagnose* diagnose) const +{ + auto& map = bf::at_key(m_accessPlugins); + auto it = map.find(diagnose); + if (it == std::end(map)) { + return nullptr; + } + return it->second; +} + +MOBase::IPlugin* PluginManager::plugin(MOBase::IPluginFileMapper* mapper) const +{ + auto& map = bf::at_key(m_accessPlugins); + auto it = map.find(mapper); + if (it == std::end(map)) { + return nullptr; + } + return it->second; +} + +bool PluginManager::isEnabled(MOBase::IPlugin* plugin) const +{ + // check if it is a game plugin + if (implementInterface(plugin)) { + return plugin == m_core->managedGame(); + } + + return m_extensions.isEnabled(details(plugin).extension()); +} + +bool PluginManager::isEnabled(QString const& pluginName) const +{ + IPlugin* p = plugin(pluginName); + return p ? isEnabled(p) : false; +} + +bool PluginManager::isEnabled(MOBase::IPluginDiagnose* diagnose) const +{ + IPlugin* p = plugin(diagnose); + return p ? isEnabled(p) : false; +} + +bool PluginManager::isEnabled(MOBase::IPluginFileMapper* mapper) const +{ + IPlugin* p = plugin(mapper); + return p ? isEnabled(p) : false; +} + +QObject* PluginManager::as_qobject(MOBase::IPlugin* plugin) const +{ + // Find the correspond QObject - Can this be done safely with a cast? + auto& objects = bf::at_key(m_plugins); + auto it = + std::find_if(std::begin(objects), std::end(objects), [plugin](QObject* obj) { + return qobject_cast(obj) == plugin; + }); + + if (it == std::end(objects)) { + return nullptr; + } + + return *it; +} + +bool PluginManager::initPlugin(PluginExtension const& extension, IPlugin* plugin, + bool skipInit) +{ + // when MO has no instance loaded, init() is not called on plugins, except + // for proxy plugins, where init() is called with a null IOrganizer + // + // after proxies are initialized, instantiate() is called for all the plugins + // they've discovered, but as for regular plugins, init() won't be + // called on them if m_OrganizerCore is null + // + + if (plugin == nullptr) { + return false; + } + + OrganizerProxy* proxy = nullptr; + if (m_core) { + proxy = new OrganizerProxy(m_core, this, plugin); + proxy->setParent(as_qobject(plugin)); + } + + auto [it, bl] = + m_details.emplace(plugin, PluginDetails(this, extension, plugin, proxy)); + + if (!m_core || skipInit) { + return true; + } + + if (!plugin->init(proxy)) { + log::warn("plugin failed to initialize"); + return false; + } + + // Update requirements: + it->second.fetchRequirements(); + + return true; +} + +IPlugin* PluginManager::registerPlugin(const PluginExtension& extension, + QObject* plugin, + QList const& pluginGroup) +{ + // generic treatment for all plugins + IPlugin* pluginObj = qobject_cast(plugin); + if (pluginObj == nullptr) { + log::debug("PluginContainer::registerPlugin() called with a non IPlugin QObject."); + return nullptr; + } + + // we check if there is already a plugin with this name, if there is one, it must be + // from the same group + bool skipInit = false; + auto& mapNames = bf::at_key(m_accessPlugins); + if (mapNames.contains(pluginObj->name())) { + + IPlugin* other = mapNames[pluginObj->name()]; + + // if both plugins are from the same group that's ok, we just need to skip + // initialization + if (pluginGroup.contains(as_qobject(other))) { + + // plugin has already been initialized + skipInit = true; + + if (isBetterInterface(plugin, as_qobject(other))) { + log::debug( + "replacing plugin '{}' with interfaces [{}] by one with interfaces [{}]", + pluginObj->name(), implementedInterfaces(other).join(", "), + implementedInterfaces(plugin).join(", ")); + bf::at_key(m_accessPlugins)[pluginObj->name()] = pluginObj; + } + } else { + log::warn("trying to register two plugins with the name '{}' (from {} and {}), " + "the second one will not be registered", + pluginObj->name(), details(other).extension().metadata().name(), + extension.metadata().name()); + return nullptr; + } + } else { + bf::at_key(m_accessPlugins)[pluginObj->name()] = pluginObj; + } + + // storing the original QObject* is a bit of a hack as I couldn't figure out any + // way to cast directly between IPlugin* and IPluginDiagnose* + bf::at_key(m_plugins).push_back(plugin); + m_allPlugins.push_back(pluginObj); + + plugin->setParent(this); + + if (m_core) { + m_core->settings().plugins().registerPlugin(pluginObj); + } + + { // diagnosis plugin + IPluginDiagnose* diagnose = qobject_cast(plugin); + if (diagnose != nullptr) { + bf::at_key(m_plugins).push_back(diagnose); + bf::at_key(m_accessPlugins)[diagnose] = pluginObj; + diagnose->onInvalidated([&, diagnose]() { + emit diagnosePluginInvalidated(diagnose); + }); + } + } + + { // file mapper plugin + IPluginFileMapper* mapper = qobject_cast(plugin); + if (mapper != nullptr) { + bf::at_key(m_plugins).push_back(mapper); + bf::at_key(m_accessPlugins)[mapper] = pluginObj; + } + } + + { // mod page plugin + IPluginModPage* modPage = qobject_cast(plugin); + if (initPlugin(extension, modPage, skipInit)) { + bf::at_key(m_plugins).push_back(modPage); + emit pluginRegistered(modPage); + return modPage; + } + } + + { // game plugin + IPluginGame* game = qobject_cast(plugin); + if (game) { + game->detectGame(); + if (initPlugin(extension, game, skipInit)) { + bf::at_key(m_plugins).push_back(game); + registerGame(game); + emit pluginRegistered(game); + return game; + } + } + } + + { // tool plugins + IPluginTool* tool = qobject_cast(plugin); + if (initPlugin(extension, tool, skipInit)) { + bf::at_key(m_plugins).push_back(tool); + emit pluginRegistered(tool); + return tool; + } + } + + { // installer plugins + IPluginInstaller* installer = qobject_cast(plugin); + if (initPlugin(extension, installer, skipInit)) { + bf::at_key(m_plugins).push_back(installer); + if (m_core) { + installer->setInstallationManager(m_core->installationManager()); + } + emit pluginRegistered(installer); + return installer; + } + } + + { // preview plugins + IPluginPreview* preview = qobject_cast(plugin); + if (initPlugin(extension, preview, skipInit)) { + bf::at_key(m_plugins).push_back(preview); + return preview; + } + } + + { // dummy plugins + // only initialize these, no processing otherwise + IPlugin* dummy = qobject_cast(plugin); + if (initPlugin(extension, dummy, skipInit)) { + bf::at_key(m_plugins).push_back(dummy); + emit pluginRegistered(dummy); + return dummy; + } + } + + return nullptr; +} + +void PluginManager::loadPlugins() +{ + // TODO: order based on dependencies + for (auto& extension : m_extensions.extensions()) { + if (auto* pluginExtension = dynamic_cast(extension.get())) { + loadPlugins(*pluginExtension); + } + } +} + +bool PluginManager::loadPlugins(const MOBase::PluginExtension& extension) +{ + unloadPlugins(); + + // load plugins + QList> objects; + for (auto& loader : m_loaders) { + objects.append(loader->load(extension)); + } + + for (auto& objectGroup : objects) { + for (auto* object : objectGroup) { + registerPlugin(extension, object, objectGroup); + } + } + + // TODO: move this elsewhere, e.g., in core + if (m_core) { + bf::at_key(m_plugins).push_back(m_core); + m_core->connectPlugins(this); + } + return true; +} + +void PluginManager::unloadPlugin(MOBase::IPlugin* plugin, QObject* object) +{ + if (auto* game = qobject_cast(object)) { + + if (game == managedGame()) { + throw Exception("cannot unload the plugin for the currently managed game"); + } + + unregisterGame(game); + } + + // we need to remove from the m_plugins maps BEFORE unloading from the proxy + // otherwise the qobject_cast to check the plugin type will not work + bf::for_each(m_plugins, [object](auto& t) { + using type = typename std::decay_t::value_type; + + // we do not want to remove from QObject since we are iterating over them + if constexpr (!std::is_same{}) { + auto itp = + std::find(t.second.begin(), t.second.end(), qobject_cast(object)); + if (itp != t.second.end()) { + t.second.erase(itp); + } + } + }); + + emit pluginUnregistered(plugin); + + // remove from the members + if (auto* diagnose = qobject_cast(object)) { + bf::at_key(m_accessPlugins).erase(diagnose); + } + if (auto* mapper = qobject_cast(object)) { + bf::at_key(m_accessPlugins).erase(mapper); + } + + auto& mapNames = bf::at_key(m_accessPlugins); + if (mapNames.contains(plugin->name())) { + mapNames.erase(plugin->name()); + } + + m_core->settings().plugins().unregisterPlugin(plugin); + + // force disconnection of the signals from the proxies + // + // this is a safety operations since those signals should be disconnected when the + // proxies are destroyed anyway + // + details(plugin).m_organizer->disconnectSignals(); + + // do this at the end + m_details.erase(plugin); +} + +bool PluginManager::unloadPlugins(const MOBase::PluginExtension& extension) +{ + std::vector objectsToDelete; + + // first we clear the internal structures, disconnect signales, etc. + { + auto& objects = bf::at_key(m_plugins); + for (auto it = objects.begin(); it != objects.end();) { + auto* plugin = qobject_cast(*it); + if (&details(plugin).extension() == &extension) { + unloadPlugin(plugin, *it); + objectsToDelete.push_back(*it); + it = objects.erase(it); + } else { + ++it; + } + } + } + + // then we let the loader unload the plugin + for (auto& loader : m_loaders) { + loader->unload(extension); + } + + // manual delete (for safety) + for (auto* object : objectsToDelete) { + object->deleteLater(); + } + + return true; +} + +void PluginManager::unloadPlugins() +{ + if (m_core) { + // this will clear several structures that can hold on to pointers to + // plugins, as well as read the plugin blacklist from the ini file, which + // is used in loadPlugins() below to skip plugins + // + // note that the first thing loadPlugins() does is call unloadPlugins(), + // so this makes sure the blacklist is always available + m_core->settings().plugins().clearPlugins(); + } + + bf::for_each(m_plugins, [](auto& t) { + t.second.clear(); + }); + bf::for_each(m_accessPlugins, [](auto& t) { + t.second.clear(); + }); + + m_details.clear(); + m_supportedGames.clear(); + + for (auto& loader : m_loaders) { + // TODO: + // loader->unloadAll(); + } +} + +bool PluginManager::reloadPlugins(const MOBase::PluginExtension& extension) +{ + unloadPlugins(extension); + return loadPlugins(extension); +} + +void PluginManager::registerGame(MOBase::IPluginGame* game) +{ + m_supportedGames.insert({game->gameName(), game}); +} + +void PluginManager::unregisterGame(MOBase::IPluginGame* game) +{ + m_supportedGames.erase(game->gameName()); +} + +void PluginManager::startPlugins(IUserInterface* userInterface) +{ + m_userInterface = userInterface; + startPluginsImpl(plugins()); +} + +void PluginManager::startPluginsImpl(const std::vector& plugins) const +{ + if (m_userInterface) { + for (auto* plugin : plugins) { + if (auto* modPage = qobject_cast(plugin)) { + modPage->setParentWidget(m_userInterface->mainWindow()); + } + if (auto* tool = qobject_cast(plugin)) { + tool->setParentWidget(m_userInterface->mainWindow()); + } + if (auto* installer = qobject_cast(plugin)) { + installer->setParentWidget(m_userInterface->mainWindow()); + } + } + } + + // Trigger initial callbacks, e.g. onUserInterfaceInitialized and onProfileChanged. + if (m_core) { + for (auto* object : plugins) { + auto* plugin = qobject_cast(object); + auto* oproxy = details(plugin).m_organizer; + oproxy->connectSignals(); + oproxy->m_ProfileChanged(nullptr, m_core->currentProfile()); + + if (m_userInterface) { + oproxy->m_UserInterfaceInitialized(m_userInterface->mainWindow()); + } + } + } +} + +PluginManager::PluginLoaderDeleter::PluginLoaderDeleter(QPluginLoader* qPluginLoader) + : m_qPluginLoader(qPluginLoader) +{} + +void PluginManager::PluginLoaderDeleter::operator()(MOBase::IPluginLoader* loader) const +{ + // if there is a QPluginLoader, the loader is responsible for unloading the plugin + if (m_qPluginLoader) { + m_qPluginLoader->unload(); + delete m_qPluginLoader; + } else { + delete loader; + } +} + +std::vector PluginManager::makeLoaders() +{ + std::vector loaders; + + // create the Qt loader + loaders.push_back(PluginLoaderPtr(new ProxyQtLoader(), PluginLoaderDeleter{})); + + // load the python proxy + { + auto pluginLoader = std::make_unique( + QCoreApplication::applicationDirPath() + "/proxies/python/python_proxy.dll", + this); + + if (auto* object = pluginLoader->instance(); object) { + auto loader = qobject_cast(object); + loaders.push_back( + PluginLoaderPtr(loader, PluginLoaderDeleter{pluginLoader.release()})); + } + } + + return loaders; +} diff --git a/src/pluginmanager.h b/src/pluginmanager.h new file mode 100644 index 000000000..90b385a13 --- /dev/null +++ b/src/pluginmanager.h @@ -0,0 +1,353 @@ +#ifndef PLUGINMANAGER_H +#define PLUGINMANAGER_H + +#include + +#ifndef Q_MOC_RUN +#include +#include +#include +#endif // Q_MOC_RUN + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "game_features.h" +#include "previewgenerator.h" + +class ExtensionManager; +class IUserInterface; +class OrganizerCore; +class OrganizerProxy; +class PluginManager; + +// class containing extra useful information for plugins +// +class PluginDetails +{ +public: + // check if the plugin can be enabled (all requirements are met) + // + bool canEnable() const; + + // check if this plugin has requirements (satisfied or not) + // + bool hasRequirements() const; + + // the organizer proxy for this plugin + // + const auto* proxy() const { return m_organizer; } + + // the extension containing this plugin + // + const MOBase::PluginExtension& extension() const { return *m_extension; } + + // retrieve the list of problems to be resolved before enabling the plugin + // + std::vector problems() const; + + // retrieve the name of the games (gameName()) this plugin can be used with, or an + // empty list if this plugin does not require particular games. + // + QStringList requiredGames() const; + +private: + // retrieve the requirements from the underlying plugin, take ownership on them and + // store them + // + // we cannot do this in the constructor because we want to have a constructed object + // before calling init() + // + void fetchRequirements(); + + friend class PluginManager; + + PluginManager* m_manager; + MOBase::IPlugin* m_plugin; + const MOBase::PluginExtension* m_extension; + std::vector> m_requirements; + OrganizerProxy* m_organizer; + + PluginDetails(PluginManager* manager, MOBase::PluginExtension const& extension, + MOBase::IPlugin* plugin, OrganizerProxy* proxy); +}; + +// manager for plugins +// +class PluginManager : public QObject +{ + Q_OBJECT +public: + // retrieve the (localized) names of the various plugin interfaces + // + static QStringList pluginInterfaces(); + +public: + PluginManager(ExtensionManager const& manager, OrganizerCore* core); + +public: // access + // retrieve the list of plugins of a given type + // + // - if no type is specified, return the list of all plugins as IPlugin + // - if IPlugin is specified, returns only plugins that only extends IPlugin + // + // + template + const auto& plugins() const + { + if constexpr (std::is_void_v) { + + return m_allPlugins; + } else { + return boost::fusion::at_key(m_plugins); + } + } + + // retrieve the details for the given plugin + // + const auto& details(MOBase::IPlugin* plugin) const { return m_details.at(plugin); } + + // retrieve the (localized) names of interfaces implemented by the given plugin + // + QStringList implementedInterfaces(MOBase::IPlugin* plugin) const; + + // retrieve the (localized) name of the most important interface implemented by the + // given plugin + // + QString topImplementedInterface(MOBase::IPlugin* plugin) const; + + // retrieve a plugin from its name or a corresponding non-IPlugin interface + // + MOBase::IPlugin* plugin(QString const& pluginName) const; + MOBase::IPlugin* plugin(MOBase::IPluginDiagnose* diagnose) const; + MOBase::IPlugin* plugin(MOBase::IPluginFileMapper* mapper) const; + + // find the game plugin corresponding to the given name, returns a null pointer if no + // game exists + // + MOBase::IPluginGame* game(const QString& name) const; + + // retrieve the IPlugin interface to the currently managed game. + // + MOBase::IPluginGame* managedGame() const; + + // retrieve the game features + // + GameFeatures& gameFeatures() const { return *m_gameFeatures; } + + // retrieve the preview generator + // + const PreviewGenerator& previewGenerator() const { return *m_previews; } + +public: // checks + // check if a plugin implement a given plugin interface + // + template + bool implementInterface(MOBase::IPlugin* plugin) const + { + // we need a QObject to be able to qobject_cast<> to the plugin types + QObject* oPlugin = as_qobject(plugin); + + if (!oPlugin) { + return false; + } + + return qobject_cast(oPlugin); + } + + // check if a plugin is enabled + // + bool isEnabled(MOBase::IPlugin* plugin) const; + bool isEnabled(QString const& pluginName) const; + bool isEnabled(MOBase::IPluginDiagnose* diagnose) const; + bool isEnabled(MOBase::IPluginFileMapper* mapper) const; + +public: // load + // load all plugins from the extension manager + // + void loadPlugins(); + + // load plugins from the given extension + // + bool loadPlugins(const MOBase::PluginExtension& extension); + bool unloadPlugins(const MOBase::PluginExtension& extension); + bool reloadPlugins(const MOBase::PluginExtension& extension); + + // start the plugins + // + // this function should not be called before MO2 is ready and plugins can be started, + // and will do the following: + // - connect the callbacks of the plugins, + // - set the parent widget for plugins that can have one, + // - notify plugins that MO2 has been started, including: + // - triggering a call to the "profile changed" callback for the initial profile, + // - triggering a call to the "user interface initialized" callback. + // + void startPlugins(IUserInterface* userInterface); + +signals: + + // emitted when plugins are enabled or disabled + // + void pluginEnabled(MOBase::IPlugin*); + void pluginDisabled(MOBase::IPlugin*); + + // emitted when plugins are registered or unregistered + // + void pluginRegistered(MOBase::IPlugin*); + void pluginUnregistered(MOBase::IPlugin*); + + // enmitted when a diagnose plugin invalidates() itself + // + void diagnosePluginInvalidated(MOBase::IPluginDiagnose*); + +private: + friend class PluginDetails; + +private: + using PluginMap = boost::fusion::map< + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>>; + + using AccessPluginMap = boost::fusion::map< + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>>; + + // type defining the order of plugin interface, in increasing order of importance + // + // IPlugin is the less important interface, followed by IPluginDiagnose and + // IPluginFileMapper as those are usually implemented together with another interface + // + // other interfaces are in a alphabetical order since it is unlikely a plugin will + // implement multiple ones + // + using PluginTypeOrder = boost::mp11::mp_transform< + std::add_pointer_t, + boost::mp11::mp_list>; + static_assert(boost::mp11::mp_size::value == + boost::mp11::mp_size::value - 1); + +private: + // retrieve the (localized) names of interfaces implemented by the given plugin + // + // this function can be used to get implemented interfaces before registering a plugin + // + QStringList implementedInterfaces(QObject* plugin) const; + + // check if the left plugin implements a "better" interface than the right one, as + // specified by PluginTypeOrder + // + bool isBetterInterface(QObject* lhs, QObject* rhs) const; + + // find the QObject* corresponding to the given plugin + // + QObject* as_qobject(MOBase::IPlugin* plugin) const; + + // see startPlugins for more details + // + // this is simply an intermediate function that can be used when loading plugins after + // initialization which uses the user interface in m_userInterface + // + void startPluginsImpl(const std::vector& plugins) const; + + // unload the given plugin + // + // this function is not public because it's kind of dangerous trying to unload plugin + // directly since some plugins are linked together + // + void unloadPlugin(MOBase::IPlugin* plugin, QObject* object); + + // unload all plugins + // + void unloadPlugins(); + + // register/unregister a game plugin + // + void registerGame(MOBase::IPluginGame* game); + void unregisterGame(MOBase::IPluginGame* game); + + // initialize a plugin and creates approriate PluginDetails for it + // + bool initPlugin(MOBase::PluginExtension const& extension, MOBase::IPlugin* plugin, + bool skipInit); + + // register a plugin for the given extension + // + MOBase::IPlugin* registerPlugin(const MOBase::PluginExtension& extension, + QObject* pluginObj, + QList const& pluginGroup); + +private: + struct PluginLoaderDeleter + { + PluginLoaderDeleter(QPluginLoader* qPluginLoader = nullptr); + + void operator()(MOBase::IPluginLoader* loader) const; + + private: + QPluginLoader* m_qPluginLoader; + }; + + using PluginLoaderPtr = std::unique_ptr; + + // create the loaders + // + std::vector makeLoaders(); + +private: + const ExtensionManager& m_extensions; + + // core organizer, can be null (e.g. on first MO2 startup). + OrganizerCore* m_core; + + // main user interface, can be null until MW has been initialized. + IUserInterface* m_userInterface; + + // Game features + std::unique_ptr m_gameFeatures; + + // plugin loaders + std::vector m_loaders; + + PluginMap m_plugins; + std::vector m_allPlugins; + + // this maps allow access to IPlugin* from name or diagnose/mapper object, and from + // game + AccessPluginMap m_accessPlugins; + std::map m_supportedGames; + + // details for plugins + std::map m_details; + + // the preview generator + PreviewGenerator* m_previews; +}; + +#endif diff --git a/src/previewgenerator.cpp b/src/previewgenerator.cpp index dad41c2fa..ed2eb7c8a 100644 --- a/src/previewgenerator.cpp +++ b/src/previewgenerator.cpp @@ -25,12 +25,12 @@ along with Mod Organizer. If not, see . #include #include -#include "plugincontainer.h" +#include "pluginmanager.h" using namespace MOBase; -PreviewGenerator::PreviewGenerator(const PluginContainer& pluginContainer) - : m_PluginContainer(pluginContainer) +PreviewGenerator::PreviewGenerator(const PluginManager& pluginManager) + : m_PluginManager(pluginManager) { m_MaxSize = QGuiApplication::primaryScreen()->size() * 0.8; } @@ -38,7 +38,7 @@ PreviewGenerator::PreviewGenerator(const PluginContainer& pluginContainer) bool PreviewGenerator::previewSupported(const QString& fileExtension, const bool& isArchive) const { - auto& previews = m_PluginContainer.plugins(); + auto& previews = m_PluginManager.plugins(); for (auto* preview : previews) { if (preview->supportedExtensions().contains(fileExtension)) { if (!isArchive) @@ -53,9 +53,9 @@ bool PreviewGenerator::previewSupported(const QString& fileExtension, QWidget* PreviewGenerator::genPreview(const QString& fileName) const { const QString ext = QFileInfo(fileName).suffix().toLower(); - auto& previews = m_PluginContainer.plugins(); + auto& previews = m_PluginManager.plugins(); for (auto* preview : previews) { - if (m_PluginContainer.isEnabled(preview) && + if (m_PluginManager.isEnabled(preview) && preview->supportedExtensions().contains(ext)) { return preview->genFilePreview(fileName, m_MaxSize); } diff --git a/src/previewgenerator.h b/src/previewgenerator.h index 15d652f62..cb82824d7 100644 --- a/src/previewgenerator.h +++ b/src/previewgenerator.h @@ -26,12 +26,12 @@ along with Mod Organizer. If not, see . #include #include -class PluginContainer; +class PluginManager; class PreviewGenerator { public: - PreviewGenerator(const PluginContainer& pluginContainer); + PreviewGenerator(const PluginManager& pluginManager); bool previewSupported(const QString& fileExtension, const bool& isArchive) const; @@ -40,7 +40,7 @@ class PreviewGenerator QWidget* genArchivePreview(const QByteArray& fileData, const QString& fileName) const; private: - const PluginContainer& m_PluginContainer; + const PluginManager& m_PluginManager; QSize m_MaxSize; }; diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 1b049fcdf..5a7bb7360 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -7,12 +7,12 @@ #include #include -#include "plugincontainer.h" +#include "pluginmanager.h" using namespace MOBase; -ProblemsDialog::ProblemsDialog(const PluginContainer& pluginContainer, QWidget* parent) - : QDialog(parent), ui(new Ui::ProblemsDialog), m_PluginContainer(pluginContainer), +ProblemsDialog::ProblemsDialog(const PluginManager& pluginManager, QWidget* parent) + : QDialog(parent), ui(new Ui::ProblemsDialog), m_PluginManager(pluginManager), m_hasProblems(false) { ui->setupUi(this); @@ -42,13 +42,13 @@ void ProblemsDialog::runDiagnosis() m_hasProblems = false; ui->problemsWidget->clear(); - for (IPluginDiagnose* diagnose : m_PluginContainer.plugins()) { - if (!m_PluginContainer.isEnabled(diagnose)) { + for (IPluginDiagnose* diagnose : m_PluginManager.plugins()) { + if (!m_PluginManager.isEnabled(diagnose)) { continue; } std::vector activeProblems = diagnose->activeProblems(); - foreach (unsigned int key, activeProblems) { + for (const auto key : activeProblems) { QTreeWidgetItem* newItem = new QTreeWidgetItem(); newItem->setText(0, diagnose->shortDescription(key)); newItem->setData(0, Qt::UserRole, diagnose->fullDescription(key)); diff --git a/src/problemsdialog.h b/src/problemsdialog.h index 288f50491..25ab83763 100644 --- a/src/problemsdialog.h +++ b/src/problemsdialog.h @@ -10,14 +10,14 @@ namespace Ui class ProblemsDialog; } -class PluginContainer; +class PluginManager; class ProblemsDialog : public QDialog { Q_OBJECT public: - explicit ProblemsDialog(PluginContainer const& pluginContainer, QWidget* parent = 0); + explicit ProblemsDialog(PluginManager const& pluginContainer, QWidget* parent = 0); ~ProblemsDialog(); // also saves and restores geometry @@ -37,7 +37,7 @@ private slots: private: Ui::ProblemsDialog* ui; - const PluginContainer& m_PluginContainer; + const PluginManager& m_PluginManager; bool m_hasProblems; }; diff --git a/src/proxyqt.cpp b/src/proxyqt.cpp new file mode 100644 index 000000000..b1ec86dac --- /dev/null +++ b/src/proxyqt.cpp @@ -0,0 +1,70 @@ +#include "proxyqt.h" + +#include + +using namespace MOBase; + +void ProxyQtLoader::QPluginLoaderDeleter::operator()(QPluginLoader* loader) const +{ + if (loader) { + loader->unload(); + delete loader; + } +} + +ProxyQtLoader::ProxyQtLoader() {} + +bool ProxyQtLoader::initialize(QString& errorMessage) +{ + return true; +} + +QList> ProxyQtLoader::load(const MOBase::PluginExtension& extension) +{ + QList> plugins; + + // TODO - retrieve plugins from extension instead of listing them + + QDirIterator iter(QDir(extension.directory(), {}, QDir::NoSort, + QDir::Files | QDir::NoDotAndDotDot)); + while (iter.hasNext()) { + iter.next(); + const auto filePath = iter.filePath(); + + // not a library, skip + if (!QLibrary::isLibrary(filePath)) { + continue; + } + + // check if we have proper metadata - this does not load the plugin (metaData() + // should be very lightweight) + auto loader = QPluginLoaderPtr(new QPluginLoader(filePath)); + if (loader->metaData().isEmpty()) { + log::debug("no metadata found in '{}', skipping", filePath); + continue; + } + + QObject* instance = loader->instance(); + if (!instance) { + log::warn("failed to load plugin from '{}', skipping", filePath); + continue; + } + + m_loaders[&extension].push_back(std::move(loader)); + plugins.push_back({instance}); + } + + return plugins; +} + +void ProxyQtLoader::unload(const MOBase::PluginExtension& extension) +{ + if (auto it = m_loaders.find(&extension); it != m_loaders.end()) { + m_loaders.erase(it); + } +} + +void ProxyQtLoader::unloadAll() +{ + m_loaders.clear(); +} diff --git a/src/proxyqt.h b/src/proxyqt.h new file mode 100644 index 000000000..80c7fb95b --- /dev/null +++ b/src/proxyqt.h @@ -0,0 +1,32 @@ +#ifndef PROXYQTLOADER_H +#define PROXYQTLOADER_H + +#include + +#include + +class ProxyQtLoader : public MOBase::IPluginLoader +{ + Q_OBJECT + Q_INTERFACES(MOBase::IPluginLoader) + Q_PLUGIN_METADATA(IID "org.mo2.ProxyQt") + +public: + ProxyQtLoader(); + + bool initialize(QString& errorMessage) override; + QList> load(const MOBase::PluginExtension& extension) override; + void unload(const MOBase::PluginExtension& extension) override; + void unloadAll() override; + +private: + struct QPluginLoaderDeleter + { + void operator()(QPluginLoader*) const; + }; + using QPluginLoaderPtr = std::unique_ptr; + + std::map> m_loaders; +}; + +#endif diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 43992e707..d41b43ed0 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -26,7 +26,7 @@ along with Mod Organizer. If not, see . #include "nexusinterface.h" #include "nxmaccessmanager.h" #include "organizercore.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "settings.h" #include "shared/util.h" #include "updatedialog.h" @@ -80,9 +80,9 @@ void SelfUpdater::setUserInterface(QWidget* widget) m_Parent = widget; } -void SelfUpdater::setPluginContainer(PluginContainer* pluginContainer) +void SelfUpdater::setPluginManager(PluginManager* pluginManager) { - m_Interface->setPluginContainer(pluginContainer); + m_Interface->setPluginManager(pluginManager); } void SelfUpdater::testForUpdate(const Settings& settings) diff --git a/src/selfupdater.h b/src/selfupdater.h index b39340849..0db43cb98 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -24,7 +24,7 @@ along with Mod Organizer. If not, see . class Archive; class NexusInterface; -class PluginContainer; +class PluginManager; namespace MOBase { class IPluginGame; @@ -83,7 +83,7 @@ class SelfUpdater : public QObject void setUserInterface(QWidget* widget); - void setPluginContainer(PluginContainer* pluginContainer); + void setPluginManager(PluginManager* pluginManager); /** * @brief request information about the current version diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index abe54d242..1db59117e 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -30,12 +30,12 @@ along with Mod Organizer. If not, see . using namespace MOBase; -SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, +SettingsDialog::SettingsDialog(PluginManager& pluginManager, ThemeManager const& themeManager, TranslationManager const& translationManager, Settings& settings, QWidget* parent) : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog), - m_settings(settings), m_exit(Exit::None), m_pluginContainer(pluginContainer) + m_settings(settings), m_exit(Exit::None), m_pluginManager(&pluginManager) { ui->setupUi(this); @@ -50,16 +50,11 @@ SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, std::unique_ptr(new DiagnosticsSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new NexusSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr( - new PluginsSettingsTab(settings, m_pluginContainer, *this))); + new PluginsSettingsTab(settings, *m_pluginManager, *this))); m_tabs.push_back( std::unique_ptr(new WorkaroundsSettingsTab(settings, *this))); } -PluginContainer* SettingsDialog::pluginContainer() -{ - return m_pluginContainer; -} - QWidget* SettingsDialog::parentWidgetForDialogs() { if (isVisible()) { diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 60f69ccf8..25a6bf9d9 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -23,7 +23,7 @@ along with Mod Organizer. If not, see . #include "shared/util.h" #include "tutorabledialog.h" -class PluginContainer; +class PluginManager; class Settings; class SettingsDialog; class ThemeManager; @@ -66,7 +66,7 @@ class SettingsDialog : public MOBase::TutorableDialog friend class SettingsTab; public: - explicit SettingsDialog(PluginContainer* pluginContainer, + explicit SettingsDialog(PluginManager& pluginManager, ThemeManager const& themeManager, TranslationManager const& translationManager, Settings& settings, QWidget* parent = 0); @@ -79,7 +79,6 @@ class SettingsDialog : public MOBase::TutorableDialog */ QString getColoredButtonStyleSheet() const; - PluginContainer* pluginContainer(); QWidget* parentWidgetForDialogs(); void setExitNeeded(ExitFlags e); @@ -95,7 +94,7 @@ public slots: Settings& m_settings; std::vector> m_tabs; ExitFlags m_exit; - PluginContainer* m_pluginContainer; + PluginManager* m_pluginManager; }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index 2c2987cc0..ec1744b43 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -1,7 +1,6 @@ #ifndef SETTINGSDIALOGGENERAL_H #define SETTINGSDIALOGGENERAL_H -#include "plugincontainer.h" #include "settings.h" #include "settingsdialog.h" #include "translationmanager.h" diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index db00ca081..6853c4155 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -3,69 +3,52 @@ #include "ui_settingsdialog.h" #include -#include "disableproxyplugindialog.h" #include "organizercore.h" -#include "plugincontainer.h" +#include "pluginmanager.h" using namespace MOBase; -PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginContainer, +struct PluginExtensionComparator +{ + bool operator()(const PluginExtension* lhs, const PluginExtension* rhs) const + { + return lhs->metadata().name().compare(rhs->metadata().name(), Qt::CaseInsensitive); + } +}; + +PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginManager& pluginManager, SettingsDialog& d) - : SettingsTab(s, d), m_pluginContainer(pluginContainer) + : SettingsTab(s, d), m_pluginManager(&pluginManager) { ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); - - // Create top-level tree widget: - QStringList pluginInterfaces = m_pluginContainer->pluginInterfaces(); - pluginInterfaces.sort(Qt::CaseInsensitive); - std::map topItems; - for (QString interfaceName : pluginInterfaces) { - auto* item = new QTreeWidgetItem(ui->pluginsList, {interfaceName}); - item->setFlags(item->flags() & ~Qt::ItemIsSelectable); - auto font = item->font(0); - font.setBold(true); - item->setFont(0, font); - topItems[interfaceName] = item; - item->setExpanded(true); - item->setFlags(item->flags() & ~Qt::ItemIsSelectable); - } ui->pluginsList->setHeaderHidden(true); // display plugin settings - QSet handledNames; - for (IPlugin* plugin : settings().plugins().plugins()) { - if (handledNames.contains(plugin->name()) || - m_pluginContainer->requirements(plugin).master()) { - continue; - } + std::map, PluginExtensionComparator> + pluginsPerExtension; + + for (IPlugin* plugin : pluginManager.plugins()) { + pluginsPerExtension[&pluginManager.details(plugin).extension()].push_back(plugin); + } + + for (auto& [extension, plugins] : pluginsPerExtension) { + + QTreeWidgetItem* extensionItem = new QTreeWidgetItem(); + extensionItem->setData(0, Qt::DisplayRole, extension->metadata().name()); + ui->pluginsList->addTopLevelItem(extensionItem); - QTreeWidgetItem* listItem = new QTreeWidgetItem( - topItems.at(m_pluginContainer->topImplementedInterface(plugin))); - listItem->setData(0, Qt::DisplayRole, plugin->localizedName()); - listItem->setData(0, PluginRole, QVariant::fromValue((void*)plugin)); - listItem->setData(0, SettingsRole, settings().plugins().settings(plugin->name())); - listItem->setData(0, DescriptionsRole, - settings().plugins().descriptions(plugin->name())); + QSet handledNames; // Handle child item: - auto children = m_pluginContainer->requirements(plugin).children(); - for (auto* child : children) { - QTreeWidgetItem* childItem = new QTreeWidgetItem(listItem); - childItem->setData(0, Qt::DisplayRole, child->localizedName()); - childItem->setData(0, PluginRole, QVariant::fromValue((void*)child)); - childItem->setData(0, SettingsRole, settings().plugins().settings(child->name())); - childItem->setData(0, DescriptionsRole, - settings().plugins().descriptions(child->name())); - - handledNames.insert(child->name()); - } + for (auto* plugin : plugins) { + if (handledNames.contains(plugin->name())) { + continue; + } - handledNames.insert(plugin->name()); - } + QTreeWidgetItem* pluginItem = new QTreeWidgetItem(extensionItem); + pluginItem->setData(0, Qt::DisplayRole, plugin->localizedName()); - for (auto& [k, item] : topItems) { - if (item->childCount() == 0) { - item->setHidden(true); + handledNames.insert(plugin->name()); } } @@ -82,9 +65,6 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta [&](auto* current, auto* previous) { on_pluginsList_currentItemChanged(current, previous); }); - QObject::connect(ui->enabledCheckbox, &QCheckBox::clicked, [&](bool checked) { - on_checkboxEnabled_clicked(checked); - }); QShortcut* delShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); @@ -95,8 +75,8 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta filterPluginList(); }); - updateListItems(); - filterPluginList(); + // updateListItems(); + // filterPluginList(); } void PluginsSettingsTab::updateListItems() @@ -107,8 +87,8 @@ void PluginsSettingsTab::updateListItems() auto* item = topLevelItem->child(j); auto* plugin = this->plugin(item); - bool inactive = !m_pluginContainer->implementInterface(plugin) && - !m_pluginContainer->isEnabled(plugin); + bool inactive = !m_pluginManager->implementInterface(plugin) && + !m_pluginManager->isEnabled(plugin); auto font = item->font(0); font.setItalic(inactive); @@ -138,11 +118,6 @@ void PluginsSettingsTab::filterPluginList() bool match = m_filter.matches([plugin](const QRegularExpression& regex) { return regex.match(plugin->localizedName()).hasMatch(); }); - for (auto* child : m_pluginContainer->requirements(plugin).children()) { - match = match || m_filter.matches([child](const QRegularExpression& regex) { - return regex.match(child->localizedName()).hasMatch(); - }); - } if (match) { found = true; @@ -209,77 +184,6 @@ void PluginsSettingsTab::closing() storeSettings(ui->pluginsList->currentItem()); } -void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) -{ - // Retrieve the plugin: - auto* item = ui->pluginsList->currentItem(); - if (!item || !item->data(0, PluginRole).isValid()) { - return; - } - IPlugin* plugin = this->plugin(item); - const auto& requirements = m_pluginContainer->requirements(plugin); - - // User wants to enable: - if (checked) { - m_pluginContainer->setEnabled(plugin, true, false); - } else { - // Custom check for proxy + current game: - if (m_pluginContainer->implementInterface(plugin)) { - - // Current game: - auto* game = m_pluginContainer->managedGame(); - if (m_pluginContainer->requirements(game).proxy() == plugin) { - QMessageBox::warning(parentWidget(), QObject::tr("Cannot disable plugin"), - QObject::tr("The '%1' plugin is used by the current game " - "plugin and cannot disabled.") - .arg(plugin->localizedName()), - QMessageBox::Ok); - ui->enabledCheckbox->setChecked(true); - return; - } - - // Check the proxied plugins: - auto proxied = requirements.proxied(); - if (!proxied.empty()) { - DisableProxyPluginDialog dialog(plugin, proxied, parentWidget()); - if (dialog.exec() != QDialog::Accepted) { - ui->enabledCheckbox->setChecked(true); - return; - } - } - } - - // Check if the plugins is required for other plugins: - auto requiredFor = requirements.requiredFor(); - if (!requiredFor.empty()) { - QStringList pluginNames; - for (auto& p : requiredFor) { - pluginNames.append(p->localizedName()); - } - pluginNames.sort(); - QString message = - QObject::tr("

      Disabling the '%1' plugin will also disable the following " - "plugins:

        %1

      Do you want to continue?

      ") - .arg(plugin->localizedName()) - .arg("
    • " + pluginNames.join("
    • ") + "
    • "); - if (QMessageBox::warning(parentWidget(), QObject::tr("Really disable plugin?"), - message, - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { - ui->enabledCheckbox->setChecked(true); - return; - } - } - m_pluginContainer->setEnabled(plugin, false, true); - } - - // Proxy was disabled / enabled, need restart: - if (m_pluginContainer->implementInterface(plugin)) { - dialog().setExitNeeded(Exit::Restart); - } - - updateListItems(); -} - void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) { @@ -298,21 +202,15 @@ void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem* curr // Checkbox, do not show for children or game plugins, disable // if the plugin cannot be enabled. ui->enabledCheckbox->setVisible( - !m_pluginContainer->implementInterface(plugin) && + !m_pluginManager->implementInterface(plugin) && plugin->master().isEmpty()); - bool enabled = m_pluginContainer->isEnabled(plugin); - auto& requirements = m_pluginContainer->requirements(plugin); + bool enabled = m_pluginManager->isEnabled(plugin); + auto& requirements = m_pluginManager->details(plugin); auto problems = requirements.problems(); - if (m_pluginContainer->requirements(plugin).isCorePlugin()) { - ui->enabledCheckbox->setDisabled(true); - ui->enabledCheckbox->setToolTip( - QObject::tr("This plugin is required for Mod Organizer to work properly and " - "cannot be disabled.")); - } // Plugin is enable or can be enabled. - else if (enabled || problems.empty()) { + if (enabled || problems.empty()) { ui->enabledCheckbox->setDisabled(false); ui->enabledCheckbox->setToolTip(""); ui->enabledCheckbox->setChecked(enabled); diff --git a/src/settingsdialogplugins.h b/src/settingsdialogplugins.h index 416f457ae..218bd7469 100644 --- a/src/settingsdialogplugins.h +++ b/src/settingsdialogplugins.h @@ -9,7 +9,7 @@ class PluginsSettingsTab : public SettingsTab { public: - PluginsSettingsTab(Settings& settings, PluginContainer* pluginContainer, + PluginsSettingsTab(Settings& settings, PluginManager& pluginManager, SettingsDialog& dialog); void update(); @@ -18,7 +18,6 @@ class PluginsSettingsTab : public SettingsTab private: void on_pluginsList_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); - void on_checkboxEnabled_clicked(bool checked); void deleteBlacklistItem(); void storeSettings(QTreeWidgetItem* pluginItem); @@ -49,7 +48,7 @@ private slots: }; private: - PluginContainer* m_pluginContainer; + PluginManager* m_pluginManager; MOBase::FilterWidget m_filter; }; From 62fe0a4c13ef8263853f11f80fc8a37fe71d457e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Tue, 24 May 2022 17:09:13 +0200 Subject: [PATCH 10/26] Remove plugin container from source group. --- src/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9b2081dad..fe647e964 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -136,7 +136,6 @@ mo2_add_filter(NAME src/core GROUPS mo2_add_filter(NAME src/extensions GROUPS game_features - plugincontainer thememanager translationmanager extensionmanager From a02fabc06febef206a0ccc235ca9b23700ee7442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Tue, 24 May 2022 22:04:14 +0200 Subject: [PATCH 11/26] Update for extensions. --- src/pluginmanager.cpp | 9 +++++---- src/settingsdialogplugins.cpp | 16 ++++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/pluginmanager.cpp b/src/pluginmanager.cpp index bbbb50a02..79e3460a7 100644 --- a/src/pluginmanager.cpp +++ b/src/pluginmanager.cpp @@ -465,6 +465,8 @@ IPlugin* PluginManager::registerPlugin(const PluginExtension& extension, void PluginManager::loadPlugins() { + unloadPlugins(); + // TODO: order based on dependencies for (auto& extension : m_extensions.extensions()) { if (auto* pluginExtension = dynamic_cast(extension.get())) { @@ -475,7 +477,7 @@ void PluginManager::loadPlugins() bool PluginManager::loadPlugins(const MOBase::PluginExtension& extension) { - unloadPlugins(); + unloadPlugins(extension); // load plugins QList> objects; @@ -606,14 +608,13 @@ void PluginManager::unloadPlugins() m_supportedGames.clear(); for (auto& loader : m_loaders) { - // TODO: - // loader->unloadAll(); + loader->unloadAll(); } } bool PluginManager::reloadPlugins(const MOBase::PluginExtension& extension) { - unloadPlugins(extension); + // load plugin already unload(), so no need to manually do it here return loadPlugins(extension); } diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 6853c4155..359964dc0 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -159,14 +159,14 @@ IPlugin* PluginsSettingsTab::plugin(QTreeWidgetItem* pluginItem) const void PluginsSettingsTab::update() { // transfer plugin settings to in-memory structure - for (int i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { - auto* topLevelItem = ui->pluginsList->topLevelItem(i); - for (int j = 0; j < topLevelItem->childCount(); ++j) { - auto* item = topLevelItem->child(j); - settings().plugins().setSettings(plugin(item)->name(), - item->data(0, SettingsRole).toMap()); - } - } + // for (int i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { + // auto* topLevelItem = ui->pluginsList->topLevelItem(i); + // for (int j = 0; j < topLevelItem->childCount(); ++j) { + // auto* item = topLevelItem->child(j); + // settings().plugins().setSettings(plugin(item)->name(), + // item->data(0, SettingsRole).toMap()); + // } + //} // set plugin blacklist QStringList names; From 64f9c998cd2b5885d518b17f45f4ca19b0796dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 26 May 2022 19:39:50 +0200 Subject: [PATCH 12/26] Track the master plugin in a plugin group. --- src/pluginmanager.cpp | 43 ++++++++++++++++++++++++++++++----- src/pluginmanager.h | 5 ++++ src/settingsdialogplugins.cpp | 9 +++----- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/pluginmanager.cpp b/src/pluginmanager.cpp index 79e3460a7..594bd1107 100644 --- a/src/pluginmanager.cpp +++ b/src/pluginmanager.cpp @@ -249,6 +249,12 @@ bool PluginManager::isEnabled(MOBase::IPlugin* plugin) const return plugin == m_core->managedGame(); } + // check the master of the group + const auto& d = details(plugin); + if (d.master() && d.master() != plugin) { + return isEnabled(d.master()); + } + return m_extensions.isEnabled(details(plugin).extension()); } @@ -486,8 +492,26 @@ bool PluginManager::loadPlugins(const MOBase::PluginExtension& extension) } for (auto& objectGroup : objects) { + + // safety for min_element + if (objectGroup.isEmpty()) { + continue; + } + + // find the best interface + auto it = std::min_element(std::begin(objectGroup), std::end(objectGroup), + [&](auto const& lhs, auto const& rhs) { + return isBetterInterface(lhs, rhs); + }); + IPlugin* master = qobject_cast(*it); + + // register plugins in the group for (auto* object : objectGroup) { - registerPlugin(extension, object, objectGroup); + IPlugin* plugin = registerPlugin(extension, object, objectGroup); + + if (plugin) { + m_details.at(plugin).m_master = master; + } } } @@ -689,14 +713,21 @@ std::vector PluginManager::makeLoaders() // load the python proxy { - auto pluginLoader = std::make_unique( - QCoreApplication::applicationDirPath() + "/proxies/python/python_proxy.dll", - this); + const QString proxyPath = + QCoreApplication::applicationDirPath() + "/proxies/python"; + auto pluginLoader = + std::make_unique(proxyPath + "/python_proxy.dll", this); if (auto* object = pluginLoader->instance(); object) { auto loader = qobject_cast(object); - loaders.push_back( - PluginLoaderPtr(loader, PluginLoaderDeleter{pluginLoader.release()})); + QString errorMessage; + + if (loader->initialize(errorMessage)) { + loaders.push_back( + PluginLoaderPtr(loader, PluginLoaderDeleter{pluginLoader.release()})); + } else { + log::error("failed to initialize proxy from '{}': {}", proxyPath, errorMessage); + } } } diff --git a/src/pluginmanager.h b/src/pluginmanager.h index 90b385a13..9ffa6c2e8 100644 --- a/src/pluginmanager.h +++ b/src/pluginmanager.h @@ -48,6 +48,10 @@ class PluginDetails // const auto* proxy() const { return m_organizer; } + // the "master" of the group this plugin belongs to + // + MOBase::IPlugin* master() const { return m_master; } + // the extension containing this plugin // const MOBase::PluginExtension& extension() const { return *m_extension; } @@ -75,6 +79,7 @@ class PluginDetails PluginManager* m_manager; MOBase::IPlugin* m_plugin; const MOBase::PluginExtension* m_extension; + MOBase::IPlugin* m_master; std::vector> m_requirements; OrganizerProxy* m_organizer; diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 359964dc0..1a6d0c90d 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -37,18 +37,15 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginManager& pluginManager extensionItem->setData(0, Qt::DisplayRole, extension->metadata().name()); ui->pluginsList->addTopLevelItem(extensionItem); - QSet handledNames; - - // Handle child item: for (auto* plugin : plugins) { - if (handledNames.contains(plugin->name())) { + + // only show master + if (pluginManager.details(plugin).master() != plugin) { continue; } QTreeWidgetItem* pluginItem = new QTreeWidgetItem(extensionItem); pluginItem->setData(0, Qt::DisplayRole, plugin->localizedName()); - - handledNames.insert(plugin->name()); } } From d35ffb9751141848cfe292901285bc24af63ca56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sun, 16 Jul 2023 13:33:11 +0200 Subject: [PATCH 13/26] Setup theme extensions properly. --- CMakeLists.txt | 1 + themes/CMakeLists.txt | 11 + .../dark-theme}/dark.qss | 790 +++++++++--------- themes/dark-theme/metadata.json | 13 + .../dracula-theme}/dracula.qss | 0 themes/dracula-theme/metadata.json | 13 + themes/make-metadata.ps1 | 25 + themes/nighteyes-theme/metadata.json | 13 + .../nighteyes-theme/nigheyes.qss | 0 .../Parchment/checkbox-alt-checked.png | Bin .../checkbox-alt-unchecked-hover.png | Bin .../Parchment/checkbox-alt-unchecked.png | Bin .../Parchment/checkbox-checked-disabled.png | Bin .../Parchment/checkbox-checked-hover.png | Bin .../Parchment/checkbox-checked.png | Bin .../Parchment/checkbox-disabled.png | Bin .../Parchment/checkbox-hover.png | Bin .../parchment-theme}/Parchment/checkbox.png | Bin themes/parchment-theme/metadata.json | 13 + .../parchment-theme/parchment.qss | 0 themes/skyrim-theme/metadata.json | 13 + .../skyrim-theme}/skyrim.qss | 0 .../skyrim-theme}/skyrim/arrow-down.png | Bin .../skyrim-theme}/skyrim/arrow-left.png | Bin .../skyrim-theme}/skyrim/arrow-right.png | Bin .../skyrim-theme}/skyrim/arrow-up.png | Bin .../skyrim-theme}/skyrim/border-image.png | Bin .../skyrim-theme}/skyrim/border-image1.png | Bin .../skyrim-theme}/skyrim/border-image2.png | Bin .../skyrim-theme}/skyrim/branch-opened.png | Bin .../skyrim/button-big-border.png | Bin .../skyrim-theme}/skyrim/button-border.png | Bin .../skyrim/button-checked-border.png | Bin .../skyrim/checkbox-alt-checked.png | Bin .../skyrim/checkbox-alt-unchecked-hover.png | Bin .../skyrim/checkbox-alt-unchecked.png | Bin .../skyrim/checkbox-checked-disabled.png | Bin .../skyrim/checkbox-checked-hover.png | Bin .../skyrim-theme}/skyrim/checkbox-checked.png | Bin .../skyrim/checkbox-disabled.png | Bin .../skyrim-theme}/skyrim/checkbox-hover.png | Bin .../skyrim-theme}/skyrim/checkbox.png | Bin .../skyrim/context-menu-separator.png | Bin .../skyrim/progress-bar-border.png | Bin .../skyrim/progress-bar-chunk.png | Bin .../skyrim-theme}/skyrim/radio-checked.png | Bin .../skyrim-theme}/skyrim/radio-hover.png | Bin .../skyrim-theme}/skyrim/radio.png | Bin .../skyrim-theme}/skyrim/scrollbar-down.png | Bin .../skyrim/scrollbar-horizontal.png | Bin .../skyrim-theme}/skyrim/scrollbar-left.png | Bin .../skyrim-theme}/skyrim/scrollbar-right.png | Bin .../skyrim-theme}/skyrim/scrollbar-up.png | Bin .../skyrim/scrollbar-vertical.png | Bin .../skyrim-theme}/skyrim/separator.png | Bin .../skyrim-theme}/skyrim/slider-border.png | Bin .../skyrim-theme}/skyrim/slider-handle.png | Bin themes/vs15-themes/metadata.json | 37 + .../vs15-themes/vs15 Dark-Blue.qss | 0 .../vs15-themes}/vs15 Dark-Green.qss | 0 .../vs15-themes}/vs15 Dark-Orange.qss | 0 .../vs15-themes}/vs15 Dark-Pink.qss | 0 .../vs15-themes}/vs15 Dark-Purple.qss | 0 .../vs15-themes}/vs15 Dark-Red.qss | 0 .../vs15-themes}/vs15 Dark-Yellow.qss | 0 .../vs15-themes}/vs15/branch-closed.png | Bin .../vs15-themes}/vs15/branch-open.png | Bin .../vs15/checkbox-check-disabled.png | Bin .../vs15-themes}/vs15/checkbox-check.png | Bin .../vs15-themes}/vs15/combobox-down.png | Bin .../vs15/scrollbar-down-disabled.png | Bin .../vs15/scrollbar-down-hover.png | Bin .../vs15-themes}/vs15/scrollbar-down.png | Bin .../vs15/scrollbar-left-disabled.png | Bin .../vs15/scrollbar-left-hover.png | Bin .../vs15-themes}/vs15/scrollbar-left.png | Bin .../vs15/scrollbar-right-disabled.png | Bin .../vs15/scrollbar-right-hover.png | Bin .../vs15-themes}/vs15/scrollbar-right.png | Bin .../vs15/scrollbar-up-disabled.png | Bin .../vs15-themes}/vs15/scrollbar-up-hover.png | Bin .../vs15-themes}/vs15/scrollbar-up.png | Bin .../vs15-themes}/vs15/sort-asc.png | Bin .../vs15-themes}/vs15/sort-desc.png | Bin .../vs15-themes}/vs15/spinner-down.png | Bin .../vs15-themes}/vs15/spinner-up.png | Bin .../vs15/sub-menu-arrow-hover.png | Bin .../vs15-themes}/vs15/sub-menu-arrow.png | Bin 88 files changed, 534 insertions(+), 395 deletions(-) create mode 100644 themes/CMakeLists.txt rename {src/stylesheets => themes/dark-theme}/dark.qss (95%) create mode 100644 themes/dark-theme/metadata.json rename {src/stylesheets => themes/dracula-theme}/dracula.qss (100%) create mode 100644 themes/dracula-theme/metadata.json create mode 100644 themes/make-metadata.ps1 create mode 100644 themes/nighteyes-theme/metadata.json rename src/stylesheets/Night Eyes.qss => themes/nighteyes-theme/nigheyes.qss (100%) rename {src/stylesheets => themes/parchment-theme}/Parchment/checkbox-alt-checked.png (100%) rename {src/stylesheets => themes/parchment-theme}/Parchment/checkbox-alt-unchecked-hover.png (100%) rename {src/stylesheets => themes/parchment-theme}/Parchment/checkbox-alt-unchecked.png (100%) rename {src/stylesheets => themes/parchment-theme}/Parchment/checkbox-checked-disabled.png (100%) rename {src/stylesheets => themes/parchment-theme}/Parchment/checkbox-checked-hover.png (100%) rename {src/stylesheets => themes/parchment-theme}/Parchment/checkbox-checked.png (100%) rename {src/stylesheets => themes/parchment-theme}/Parchment/checkbox-disabled.png (100%) rename {src/stylesheets => themes/parchment-theme}/Parchment/checkbox-hover.png (100%) rename {src/stylesheets => themes/parchment-theme}/Parchment/checkbox.png (100%) create mode 100644 themes/parchment-theme/metadata.json rename src/stylesheets/Parchment v1.1 by Bob.qss => themes/parchment-theme/parchment.qss (100%) create mode 100644 themes/skyrim-theme/metadata.json rename {src/stylesheets => themes/skyrim-theme}/skyrim.qss (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/arrow-down.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/arrow-left.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/arrow-right.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/arrow-up.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/border-image.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/border-image1.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/border-image2.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/branch-opened.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/button-big-border.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/button-border.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/button-checked-border.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/checkbox-alt-checked.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/checkbox-alt-unchecked-hover.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/checkbox-alt-unchecked.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/checkbox-checked-disabled.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/checkbox-checked-hover.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/checkbox-checked.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/checkbox-disabled.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/checkbox-hover.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/checkbox.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/context-menu-separator.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/progress-bar-border.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/progress-bar-chunk.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/radio-checked.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/radio-hover.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/radio.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/scrollbar-down.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/scrollbar-horizontal.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/scrollbar-left.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/scrollbar-right.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/scrollbar-up.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/scrollbar-vertical.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/separator.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/slider-border.png (100%) rename {src/stylesheets => themes/skyrim-theme}/skyrim/slider-handle.png (100%) create mode 100644 themes/vs15-themes/metadata.json rename src/stylesheets/vs15 Dark.qss => themes/vs15-themes/vs15 Dark-Blue.qss (100%) rename {src/stylesheets => themes/vs15-themes}/vs15 Dark-Green.qss (100%) rename {src/stylesheets => themes/vs15-themes}/vs15 Dark-Orange.qss (100%) rename {src/stylesheets => themes/vs15-themes}/vs15 Dark-Pink.qss (100%) rename {src/stylesheets => themes/vs15-themes}/vs15 Dark-Purple.qss (100%) rename {src/stylesheets => themes/vs15-themes}/vs15 Dark-Red.qss (100%) rename {src/stylesheets => themes/vs15-themes}/vs15 Dark-Yellow.qss (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/branch-closed.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/branch-open.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/checkbox-check-disabled.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/checkbox-check.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/combobox-down.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/scrollbar-down-disabled.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/scrollbar-down-hover.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/scrollbar-down.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/scrollbar-left-disabled.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/scrollbar-left-hover.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/scrollbar-left.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/scrollbar-right-disabled.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/scrollbar-right-hover.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/scrollbar-right.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/scrollbar-up-disabled.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/scrollbar-up-hover.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/scrollbar-up.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/sort-asc.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/sort-desc.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/spinner-down.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/spinner-up.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/sub-menu-arrow-hover.png (100%) rename {src/stylesheets => themes/vs15-themes}/vs15/sub-menu-arrow.png (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85ba23f05..bb8591a49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ else() endif() add_subdirectory(src) +add_subdirectory(themes) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT organizer) diff --git a/themes/CMakeLists.txt b/themes/CMakeLists.txt new file mode 100644 index 000000000..18871c885 --- /dev/null +++ b/themes/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.16) + +file(GLOB theme_directories + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + LIST_DIRECTORIES TRUE "*") + +foreach(theme_directory ${theme_directories}) + if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${theme_directory}") + install(DIRECTORY ${theme_directory} DESTINATION bin/extensions) + endif() +endforeach() diff --git a/src/stylesheets/dark.qss b/themes/dark-theme/dark.qss similarity index 95% rename from src/stylesheets/dark.qss rename to themes/dark-theme/dark.qss index b7a185e55..e37e44ba4 100644 --- a/src/stylesheets/dark.qss +++ b/themes/dark-theme/dark.qss @@ -1,395 +1,395 @@ -QToolTip -{ - border: 1px solid black; - color: #D9E6EA; - background-color: #2F3031; - padding: 1px; - border-radius: 3px; - opacity: 255; -} - -QWidget -{ - color: #E9E6E4; - background-color: #2F3031; -} - -QWidget:disabled -{ - color: #757676; - background-color: #292A2B; -} - -QAbstractItemView -{ - background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #484F53, stop: 0.7 #656666, stop: 1 #484F53); -} - -QLineEdit -{ - background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #2D3330, stop: 0.9 #484F53, stop: 1 #2D3330); - padding: 1px; - border-style: solid; - border: 1px solid #1e1e1e; - border-radius: 5; -} - -QPushButton -{ - color: #D9E6EA; - background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); - border-width: 2px; - border-color: #1F2021; - border-style: solid; - border-radius: 6; - padding: 3px; - padding-left: 15px; - padding-right: 15px; -} - -QPushButton:pressed -{ - background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #484F53, stop: 1 #697670); -} - -QPushButton:checked -{ - border-width: 1px; - border-color: #3EA0CA; -} - -QComboBox -{ - selection-background-color: #D9E6EA; - background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #9AA6A4, stop: 1 #484F53); - border: 2px solid #1D2320; - height: 20px; - border-radius: 5px; -} - -QComboBox:hover,QPushButton:hover -{ - border: 2px solid #3EA0CA; -} - -QComboBox:on -{ - padding-top: 3px; - padding-left: 4px; - background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); - selection-background-color: #80B5C3; -} - -QComboBox::drop-down -{ - subcontrol-origin: padding; - subcontrol-position: top right; - width: 15px; - - border-left-width: 0px; - border-left-color: darkgray; - border-left-style: solid; - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; -} - -QComboBox::down-arrow -{ - image: url(:/stylesheet/combobox-down.png); -} - -QScrollBar:horizontal -{ - border: 1px solid #1F2021; - background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0.0 #1D2320, stop: 0.2 #1F2021, stop: 1 #484F53); - height: 14px; - margin: 1px 16px 1px 16px; -} - -QScrollBar::handle:horizontal -{ - background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #3EA0CA, stop: 0.5 #427683, stop: 1 #3EA0CA); - min-height: 20px; - border-radius: 4px; -} - -QScrollBar::add-line:horizontal -{ - border: 1px solid #1b1b19; - border-radius: 2px; - background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #3EA0CA, stop: 1 #427683); - width: 14px; - subcontrol-position: right; - subcontrol-origin: margin; -} - -QScrollBar::sub-line:horizontal -{ - border: 1px solid #1b1b19; - border-radius: 2px; - background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #3EA0CA, stop: 1 #427683); - width: 14px; - subcontrol-position: left; - subcontrol-origin: margin; -} - -QScrollBar::right-arrow:horizontal, QScrollBar::left-arrow:horizontal -{ - border: 1px solid black; - width: 1px; - height: 1px; - background: white; -} - -QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal -{ - background: none; -} - -QScrollBar:vertical -{ - border: 1px solid #1F2021; - background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0.0 #1D2320, stop: 0.2 #1F2021, stop: 1 #484F53); - width: 14px; - margin: 16px 1px 16px 1px; -} - -QScrollBar::handle:vertical -{ - background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #3EA0CA, stop: 0.5 #427683, stop: 1 #3EA0CA); - min-height: 20px; - border-radius: 4px; -} - -QScrollBar::add-line:vertical -{ - border: 1px solid #1b1b19; - border-radius: 2px; - background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #3EA0CA, stop: 1 #427683); - height: 14px; - subcontrol-position: bottom; - subcontrol-origin: margin; -} - -QScrollBar::sub-line:vertical -{ - border: 1px solid #1b1b19; - border-radius: 2px; - background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #3EA0CA, stop: 1 #427683); - height: 14px; - subcontrol-position: top; - subcontrol-origin: margin; -} - -QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical -{ - border: 1px solid black; - width: 1px; - height: 1px; - background: white; -} - -QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical -{ - background: none; -} - -QTextEdit -{ - background-color: #484F53; -} - -QPlainTextEdit -{ - background-color: #484F53; -} - -QWebView -{ - background-color: #484F53; -} - -QHeaderView::section -{ - background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #484F53, stop:0.5 #757676, stop:1 #484F53); - color: white; - padding-left: 4px; - border: 1px solid #2D3330; - border-radius: 2px; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; -} - -QCheckBox:disabled -{ - color: #414141; -} - -QMenu::separator -{ - height: 2px; - background-color: #484F53; - color: white; - padding-left: 4px; - margin-left: 10px; - margin-right: 5px; -} - -QMenu::item -{ - padding: 2px 25px 2px 20px; - border: 1px solid transparent; -} - -QMenu::item:selected -{ - background-color: #3c4b54; - border-color: #3EA0CA; -} - -QMenuBar::item:selected { - background-color: #3c4b54; - border-color: #3EA0CA; -} - -QStatusBar::item {border: None;} - -QProgressBar -{ - border: 2px solid grey; - border-radius: 5px; - text-align: center; -} - -QProgressBar::chunk -{ - background-color: #427683; -} - -QTabBar::tab -{ - color: #E9E6E4; - border: 1px solid #444; - border-bottom-style: none; - background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0.8 #1D2320, stop:0.2 #757676); - padding-left: 10px; - padding-right: 10px; - padding-top: 3px; - padding-bottom: 2px; - margin-right: -1px; - border-top-left-radius: 4px; - border-top-right-radius: 4px; -} - -QTabWidget::pane -{ - border: 1px solid #444; - top: 1px; -} - -QTabBar::tab:last -{ - margin-right: 0px; -} - -QTabBar::tab:first -{ - margin-left: 0px; -} - -QTabBar::tab:!selected -{ - color: #E9E6E4; - border-bottom-style: solid; - margin-top: 3px; - background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0.8 #1D2320, stop:0.4 #484F53); -} - -QTabBar::tab:disabled -{ - color: #757676; - border-bottom-style: solid; - margin-top: 3px; - background-color: #484F53; -} -QTabBar::tab:selected -{ - border-top-left-radius: 3px; - border-top-right-radius: 3px; - margin-bottom: 0px; -} - -QTabBar::tab:!selected:hover -{ - border-top-left-radius: 6px; - border-top-right-radius: 6px; - background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:0.4 #343434, stop:0.2 #343434, stop:0.1 #3EA0CA); -} - -QToolButton -{ - border:2px ridge #757676; - border-radius: 6px; - margin: 3px; - padding-left: 8px; - padding-right: 8px; - padding-top: 0px; - padding-bottom: 2px; -} - -QToolButton:hover -{ - border: 2px ridge #757676; - background-color: #484F53; - border-radius: 6px; - margin: 3px; - padding-left: 8px; - padding-right: 8px; - padding-top: 0px; - padding-bottom: 2px; -} - -QTreeView, QListView - { - color: #E9E6E4; - background-color: #3F4041; - alternate-background-color: #2F3031; -} - -QAbstractItemView[filtered=true] { - border: 2px solid #f00 !important; -} - -QTreeView::branch:has-children:!has-siblings:closed, -QTreeView::branch:closed:has-children:has-siblings -{ - border-image: none; - image: url(:/stylesheet/branch-closed.png); -} - -QTreeView::branch:open:has-children:!has-siblings, -QTreeView::branch:open:has-children:has-siblings -{ - border-image: none; - image: url(:/stylesheet/branch-open.png); -} - -DownloadListView QLabel#installLabel { - color: none; -} - -DownloadListView[downloadView=standard]::item { - padding: 16px; -} - -DownloadListView[downloadView=compact]::item { - padding: 4px; -} - -LinkLabel { - qproperty-linkColor: #3399FF; -} - -QLineEdit[valid-filter=false] { - background-color: #661111 !important; -} +QToolTip +{ + border: 1px solid black; + color: #D9E6EA; + background-color: #2F3031; + padding: 1px; + border-radius: 3px; + opacity: 255; +} + +QWidget +{ + color: #E9E6E4; + background-color: #2F3031; +} + +QWidget:disabled +{ + color: #757676; + background-color: #292A2B; +} + +QAbstractItemView +{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #484F53, stop: 0.7 #656666, stop: 1 #484F53); +} + +QLineEdit +{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #2D3330, stop: 0.9 #484F53, stop: 1 #2D3330); + padding: 1px; + border-style: solid; + border: 1px solid #1e1e1e; + border-radius: 5; +} + +QPushButton +{ + color: #D9E6EA; + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); + border-width: 2px; + border-color: #1F2021; + border-style: solid; + border-radius: 6; + padding: 3px; + padding-left: 15px; + padding-right: 15px; +} + +QPushButton:pressed +{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #484F53, stop: 1 #697670); +} + +QPushButton:checked +{ + border-width: 1px; + border-color: #3EA0CA; +} + +QComboBox +{ + selection-background-color: #D9E6EA; + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #9AA6A4, stop: 1 #484F53); + border: 2px solid #1D2320; + height: 20px; + border-radius: 5px; +} + +QComboBox:hover,QPushButton:hover +{ + border: 2px solid #3EA0CA; +} + +QComboBox:on +{ + padding-top: 3px; + padding-left: 4px; + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); + selection-background-color: #80B5C3; +} + +QComboBox::drop-down +{ + subcontrol-origin: padding; + subcontrol-position: top right; + width: 15px; + + border-left-width: 0px; + border-left-color: darkgray; + border-left-style: solid; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +QComboBox::down-arrow +{ + image: url(:/stylesheet/combobox-down.png); +} + +QScrollBar:horizontal +{ + border: 1px solid #1F2021; + background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0.0 #1D2320, stop: 0.2 #1F2021, stop: 1 #484F53); + height: 14px; + margin: 1px 16px 1px 16px; +} + +QScrollBar::handle:horizontal +{ + background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #3EA0CA, stop: 0.5 #427683, stop: 1 #3EA0CA); + min-height: 20px; + border-radius: 4px; +} + +QScrollBar::add-line:horizontal +{ + border: 1px solid #1b1b19; + border-radius: 2px; + background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #3EA0CA, stop: 1 #427683); + width: 14px; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal +{ + border: 1px solid #1b1b19; + border-radius: 2px; + background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #3EA0CA, stop: 1 #427683); + width: 14px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::right-arrow:horizontal, QScrollBar::left-arrow:horizontal +{ + border: 1px solid black; + width: 1px; + height: 1px; + background: white; +} + +QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal +{ + background: none; +} + +QScrollBar:vertical +{ + border: 1px solid #1F2021; + background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0.0 #1D2320, stop: 0.2 #1F2021, stop: 1 #484F53); + width: 14px; + margin: 16px 1px 16px 1px; +} + +QScrollBar::handle:vertical +{ + background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #3EA0CA, stop: 0.5 #427683, stop: 1 #3EA0CA); + min-height: 20px; + border-radius: 4px; +} + +QScrollBar::add-line:vertical +{ + border: 1px solid #1b1b19; + border-radius: 2px; + background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #3EA0CA, stop: 1 #427683); + height: 14px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:vertical +{ + border: 1px solid #1b1b19; + border-radius: 2px; + background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #3EA0CA, stop: 1 #427683); + height: 14px; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical +{ + border: 1px solid black; + width: 1px; + height: 1px; + background: white; +} + +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical +{ + background: none; +} + +QTextEdit +{ + background-color: #484F53; +} + +QPlainTextEdit +{ + background-color: #484F53; +} + +QWebView +{ + background-color: #484F53; +} + +QHeaderView::section +{ + background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #484F53, stop:0.5 #757676, stop:1 #484F53); + color: white; + padding-left: 4px; + border: 1px solid #2D3330; + border-radius: 2px; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} + +QCheckBox:disabled +{ + color: #414141; +} + +QMenu::separator +{ + height: 2px; + background-color: #484F53; + color: white; + padding-left: 4px; + margin-left: 10px; + margin-right: 5px; +} + +QMenu::item +{ + padding: 2px 25px 2px 20px; + border: 1px solid transparent; +} + +QMenu::item:selected +{ + background-color: #3c4b54; + border-color: #3EA0CA; +} + +QMenuBar::item:selected { + background-color: #3c4b54; + border-color: #3EA0CA; +} + +QStatusBar::item {border: None;} + +QProgressBar +{ + border: 2px solid grey; + border-radius: 5px; + text-align: center; +} + +QProgressBar::chunk +{ + background-color: #427683; +} + +QTabBar::tab +{ + color: #E9E6E4; + border: 1px solid #444; + border-bottom-style: none; + background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0.8 #1D2320, stop:0.2 #757676); + padding-left: 10px; + padding-right: 10px; + padding-top: 3px; + padding-bottom: 2px; + margin-right: -1px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +QTabWidget::pane +{ + border: 1px solid #444; + top: 1px; +} + +QTabBar::tab:last +{ + margin-right: 0px; +} + +QTabBar::tab:first +{ + margin-left: 0px; +} + +QTabBar::tab:!selected +{ + color: #E9E6E4; + border-bottom-style: solid; + margin-top: 3px; + background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0.8 #1D2320, stop:0.4 #484F53); +} + +QTabBar::tab:disabled +{ + color: #757676; + border-bottom-style: solid; + margin-top: 3px; + background-color: #484F53; +} +QTabBar::tab:selected +{ + border-top-left-radius: 3px; + border-top-right-radius: 3px; + margin-bottom: 0px; +} + +QTabBar::tab:!selected:hover +{ + border-top-left-radius: 6px; + border-top-right-radius: 6px; + background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:0.4 #343434, stop:0.2 #343434, stop:0.1 #3EA0CA); +} + +QToolButton +{ + border:2px ridge #757676; + border-radius: 6px; + margin: 3px; + padding-left: 8px; + padding-right: 8px; + padding-top: 0px; + padding-bottom: 2px; +} + +QToolButton:hover +{ + border: 2px ridge #757676; + background-color: #484F53; + border-radius: 6px; + margin: 3px; + padding-left: 8px; + padding-right: 8px; + padding-top: 0px; + padding-bottom: 2px; +} + +QTreeView, QListView + { + color: #E9E6E4; + background-color: #3F4041; + alternate-background-color: #2F3031; +} + +QAbstractItemView[filtered=true] { + border: 2px solid #f00 !important; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings +{ + border-image: none; + image: url(:/stylesheet/branch-closed.png); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings +{ + border-image: none; + image: url(:/stylesheet/branch-open.png); +} + +DownloadListView QLabel#installLabel { + color: none; +} + +DownloadListView[downloadView=standard]::item { + padding: 16px; +} + +DownloadListView[downloadView=compact]::item { + padding: 4px; +} + +LinkLabel { + qproperty-linkColor: #3399FF; +} + +QLineEdit[valid-filter=false] { + background-color: #661111 !important; +} diff --git a/themes/dark-theme/metadata.json b/themes/dark-theme/metadata.json new file mode 100644 index 000000000..0bd00fa70 --- /dev/null +++ b/themes/dark-theme/metadata.json @@ -0,0 +1,13 @@ +{ + "identifier": "dark-theme", + "type": "theme", + "name": "Dark Theme", + "description": "Dark theme for ModOrganizer2.", + "version": "1.0.0", + "themes": { + "dark": { + "name": "Dark", + "path": "dark.qss" + } + } +} diff --git a/src/stylesheets/dracula.qss b/themes/dracula-theme/dracula.qss similarity index 100% rename from src/stylesheets/dracula.qss rename to themes/dracula-theme/dracula.qss diff --git a/themes/dracula-theme/metadata.json b/themes/dracula-theme/metadata.json new file mode 100644 index 000000000..4e1cb8543 --- /dev/null +++ b/themes/dracula-theme/metadata.json @@ -0,0 +1,13 @@ +{ + "identifier": "dracula-theme", + "type": "theme", + "name": "Dracula Theme", + "description": "Dracula theme for ModOrganizer2.", + "version": "1.0.0", + "themes": { + "dracula": { + "name": "Dracula", + "path": "dracula.qss" + } + } +} diff --git a/themes/make-metadata.ps1 b/themes/make-metadata.ps1 new file mode 100644 index 000000000..4143291c8 --- /dev/null +++ b/themes/make-metadata.ps1 @@ -0,0 +1,25 @@ +$fixNames = @{ + dark = "Dark"; + dracula = "Dracula"; + nighteyes = "Night Eyes"; + parchment = "Parchment"; + skyrim = "Skyrim"; +} + +Get-ChildItem -Directory -Exclude "vs15" | ForEach-Object { + $name = $fixNames[$_.Name]; + $data = [ordered]@{ + identifier = $_.Name + "-theme"; + type = "theme"; + name = "$name Theme"; + description = "$name theme for ModOrganizer2."; + version = "1.0.0"; + themes = @{ + $_.Name = [ordered]@{ + name = $name; + path = (Get-ChildItem $_ -Filter "*.qss")[0].Name; + } + }; + } + ConvertTo-Json -InputObject $data | Set-Content (Join-Path $_ "metadata.json") +} diff --git a/themes/nighteyes-theme/metadata.json b/themes/nighteyes-theme/metadata.json new file mode 100644 index 000000000..f8d8b3261 --- /dev/null +++ b/themes/nighteyes-theme/metadata.json @@ -0,0 +1,13 @@ +{ + "identifier": "nighteyes-theme", + "type": "theme", + "name": "Night Eyes Theme", + "description": "Night Eyes theme for ModOrganizer2.", + "version": "1.0.0", + "themes": { + "nighteyes": { + "name": "Night Eyes", + "path": "nigheyes.qss" + } + } +} diff --git a/src/stylesheets/Night Eyes.qss b/themes/nighteyes-theme/nigheyes.qss similarity index 100% rename from src/stylesheets/Night Eyes.qss rename to themes/nighteyes-theme/nigheyes.qss diff --git a/src/stylesheets/Parchment/checkbox-alt-checked.png b/themes/parchment-theme/Parchment/checkbox-alt-checked.png similarity index 100% rename from src/stylesheets/Parchment/checkbox-alt-checked.png rename to themes/parchment-theme/Parchment/checkbox-alt-checked.png diff --git a/src/stylesheets/Parchment/checkbox-alt-unchecked-hover.png b/themes/parchment-theme/Parchment/checkbox-alt-unchecked-hover.png similarity index 100% rename from src/stylesheets/Parchment/checkbox-alt-unchecked-hover.png rename to themes/parchment-theme/Parchment/checkbox-alt-unchecked-hover.png diff --git a/src/stylesheets/Parchment/checkbox-alt-unchecked.png b/themes/parchment-theme/Parchment/checkbox-alt-unchecked.png similarity index 100% rename from src/stylesheets/Parchment/checkbox-alt-unchecked.png rename to themes/parchment-theme/Parchment/checkbox-alt-unchecked.png diff --git a/src/stylesheets/Parchment/checkbox-checked-disabled.png b/themes/parchment-theme/Parchment/checkbox-checked-disabled.png similarity index 100% rename from src/stylesheets/Parchment/checkbox-checked-disabled.png rename to themes/parchment-theme/Parchment/checkbox-checked-disabled.png diff --git a/src/stylesheets/Parchment/checkbox-checked-hover.png b/themes/parchment-theme/Parchment/checkbox-checked-hover.png similarity index 100% rename from src/stylesheets/Parchment/checkbox-checked-hover.png rename to themes/parchment-theme/Parchment/checkbox-checked-hover.png diff --git a/src/stylesheets/Parchment/checkbox-checked.png b/themes/parchment-theme/Parchment/checkbox-checked.png similarity index 100% rename from src/stylesheets/Parchment/checkbox-checked.png rename to themes/parchment-theme/Parchment/checkbox-checked.png diff --git a/src/stylesheets/Parchment/checkbox-disabled.png b/themes/parchment-theme/Parchment/checkbox-disabled.png similarity index 100% rename from src/stylesheets/Parchment/checkbox-disabled.png rename to themes/parchment-theme/Parchment/checkbox-disabled.png diff --git a/src/stylesheets/Parchment/checkbox-hover.png b/themes/parchment-theme/Parchment/checkbox-hover.png similarity index 100% rename from src/stylesheets/Parchment/checkbox-hover.png rename to themes/parchment-theme/Parchment/checkbox-hover.png diff --git a/src/stylesheets/Parchment/checkbox.png b/themes/parchment-theme/Parchment/checkbox.png similarity index 100% rename from src/stylesheets/Parchment/checkbox.png rename to themes/parchment-theme/Parchment/checkbox.png diff --git a/themes/parchment-theme/metadata.json b/themes/parchment-theme/metadata.json new file mode 100644 index 000000000..4e53ba407 --- /dev/null +++ b/themes/parchment-theme/metadata.json @@ -0,0 +1,13 @@ +{ + "identifier": "parchment-theme", + "type": "theme", + "name": "Parchment Theme", + "description": "Parchment theme for ModOrganizer2.", + "version": "1.0.0", + "themes": { + "parchment": { + "name": "Parchment", + "path": "parchment.qss" + } + } +} diff --git a/src/stylesheets/Parchment v1.1 by Bob.qss b/themes/parchment-theme/parchment.qss similarity index 100% rename from src/stylesheets/Parchment v1.1 by Bob.qss rename to themes/parchment-theme/parchment.qss diff --git a/themes/skyrim-theme/metadata.json b/themes/skyrim-theme/metadata.json new file mode 100644 index 000000000..b28358b1b --- /dev/null +++ b/themes/skyrim-theme/metadata.json @@ -0,0 +1,13 @@ +{ + "identifier": "skyrim-theme", + "type": "theme", + "name": "Skyrim Theme", + "description": "Skyrim theme for ModOrganizer2.", + "version": "1.0.0", + "themes": { + "skyrim": { + "name": "Skyrim", + "path": "skyrim.qss" + } + } +} diff --git a/src/stylesheets/skyrim.qss b/themes/skyrim-theme/skyrim.qss similarity index 100% rename from src/stylesheets/skyrim.qss rename to themes/skyrim-theme/skyrim.qss diff --git a/src/stylesheets/skyrim/arrow-down.png b/themes/skyrim-theme/skyrim/arrow-down.png similarity index 100% rename from src/stylesheets/skyrim/arrow-down.png rename to themes/skyrim-theme/skyrim/arrow-down.png diff --git a/src/stylesheets/skyrim/arrow-left.png b/themes/skyrim-theme/skyrim/arrow-left.png similarity index 100% rename from src/stylesheets/skyrim/arrow-left.png rename to themes/skyrim-theme/skyrim/arrow-left.png diff --git a/src/stylesheets/skyrim/arrow-right.png b/themes/skyrim-theme/skyrim/arrow-right.png similarity index 100% rename from src/stylesheets/skyrim/arrow-right.png rename to themes/skyrim-theme/skyrim/arrow-right.png diff --git a/src/stylesheets/skyrim/arrow-up.png b/themes/skyrim-theme/skyrim/arrow-up.png similarity index 100% rename from src/stylesheets/skyrim/arrow-up.png rename to themes/skyrim-theme/skyrim/arrow-up.png diff --git a/src/stylesheets/skyrim/border-image.png b/themes/skyrim-theme/skyrim/border-image.png similarity index 100% rename from src/stylesheets/skyrim/border-image.png rename to themes/skyrim-theme/skyrim/border-image.png diff --git a/src/stylesheets/skyrim/border-image1.png b/themes/skyrim-theme/skyrim/border-image1.png similarity index 100% rename from src/stylesheets/skyrim/border-image1.png rename to themes/skyrim-theme/skyrim/border-image1.png diff --git a/src/stylesheets/skyrim/border-image2.png b/themes/skyrim-theme/skyrim/border-image2.png similarity index 100% rename from src/stylesheets/skyrim/border-image2.png rename to themes/skyrim-theme/skyrim/border-image2.png diff --git a/src/stylesheets/skyrim/branch-opened.png b/themes/skyrim-theme/skyrim/branch-opened.png similarity index 100% rename from src/stylesheets/skyrim/branch-opened.png rename to themes/skyrim-theme/skyrim/branch-opened.png diff --git a/src/stylesheets/skyrim/button-big-border.png b/themes/skyrim-theme/skyrim/button-big-border.png similarity index 100% rename from src/stylesheets/skyrim/button-big-border.png rename to themes/skyrim-theme/skyrim/button-big-border.png diff --git a/src/stylesheets/skyrim/button-border.png b/themes/skyrim-theme/skyrim/button-border.png similarity index 100% rename from src/stylesheets/skyrim/button-border.png rename to themes/skyrim-theme/skyrim/button-border.png diff --git a/src/stylesheets/skyrim/button-checked-border.png b/themes/skyrim-theme/skyrim/button-checked-border.png similarity index 100% rename from src/stylesheets/skyrim/button-checked-border.png rename to themes/skyrim-theme/skyrim/button-checked-border.png diff --git a/src/stylesheets/skyrim/checkbox-alt-checked.png b/themes/skyrim-theme/skyrim/checkbox-alt-checked.png similarity index 100% rename from src/stylesheets/skyrim/checkbox-alt-checked.png rename to themes/skyrim-theme/skyrim/checkbox-alt-checked.png diff --git a/src/stylesheets/skyrim/checkbox-alt-unchecked-hover.png b/themes/skyrim-theme/skyrim/checkbox-alt-unchecked-hover.png similarity index 100% rename from src/stylesheets/skyrim/checkbox-alt-unchecked-hover.png rename to themes/skyrim-theme/skyrim/checkbox-alt-unchecked-hover.png diff --git a/src/stylesheets/skyrim/checkbox-alt-unchecked.png b/themes/skyrim-theme/skyrim/checkbox-alt-unchecked.png similarity index 100% rename from src/stylesheets/skyrim/checkbox-alt-unchecked.png rename to themes/skyrim-theme/skyrim/checkbox-alt-unchecked.png diff --git a/src/stylesheets/skyrim/checkbox-checked-disabled.png b/themes/skyrim-theme/skyrim/checkbox-checked-disabled.png similarity index 100% rename from src/stylesheets/skyrim/checkbox-checked-disabled.png rename to themes/skyrim-theme/skyrim/checkbox-checked-disabled.png diff --git a/src/stylesheets/skyrim/checkbox-checked-hover.png b/themes/skyrim-theme/skyrim/checkbox-checked-hover.png similarity index 100% rename from src/stylesheets/skyrim/checkbox-checked-hover.png rename to themes/skyrim-theme/skyrim/checkbox-checked-hover.png diff --git a/src/stylesheets/skyrim/checkbox-checked.png b/themes/skyrim-theme/skyrim/checkbox-checked.png similarity index 100% rename from src/stylesheets/skyrim/checkbox-checked.png rename to themes/skyrim-theme/skyrim/checkbox-checked.png diff --git a/src/stylesheets/skyrim/checkbox-disabled.png b/themes/skyrim-theme/skyrim/checkbox-disabled.png similarity index 100% rename from src/stylesheets/skyrim/checkbox-disabled.png rename to themes/skyrim-theme/skyrim/checkbox-disabled.png diff --git a/src/stylesheets/skyrim/checkbox-hover.png b/themes/skyrim-theme/skyrim/checkbox-hover.png similarity index 100% rename from src/stylesheets/skyrim/checkbox-hover.png rename to themes/skyrim-theme/skyrim/checkbox-hover.png diff --git a/src/stylesheets/skyrim/checkbox.png b/themes/skyrim-theme/skyrim/checkbox.png similarity index 100% rename from src/stylesheets/skyrim/checkbox.png rename to themes/skyrim-theme/skyrim/checkbox.png diff --git a/src/stylesheets/skyrim/context-menu-separator.png b/themes/skyrim-theme/skyrim/context-menu-separator.png similarity index 100% rename from src/stylesheets/skyrim/context-menu-separator.png rename to themes/skyrim-theme/skyrim/context-menu-separator.png diff --git a/src/stylesheets/skyrim/progress-bar-border.png b/themes/skyrim-theme/skyrim/progress-bar-border.png similarity index 100% rename from src/stylesheets/skyrim/progress-bar-border.png rename to themes/skyrim-theme/skyrim/progress-bar-border.png diff --git a/src/stylesheets/skyrim/progress-bar-chunk.png b/themes/skyrim-theme/skyrim/progress-bar-chunk.png similarity index 100% rename from src/stylesheets/skyrim/progress-bar-chunk.png rename to themes/skyrim-theme/skyrim/progress-bar-chunk.png diff --git a/src/stylesheets/skyrim/radio-checked.png b/themes/skyrim-theme/skyrim/radio-checked.png similarity index 100% rename from src/stylesheets/skyrim/radio-checked.png rename to themes/skyrim-theme/skyrim/radio-checked.png diff --git a/src/stylesheets/skyrim/radio-hover.png b/themes/skyrim-theme/skyrim/radio-hover.png similarity index 100% rename from src/stylesheets/skyrim/radio-hover.png rename to themes/skyrim-theme/skyrim/radio-hover.png diff --git a/src/stylesheets/skyrim/radio.png b/themes/skyrim-theme/skyrim/radio.png similarity index 100% rename from src/stylesheets/skyrim/radio.png rename to themes/skyrim-theme/skyrim/radio.png diff --git a/src/stylesheets/skyrim/scrollbar-down.png b/themes/skyrim-theme/skyrim/scrollbar-down.png similarity index 100% rename from src/stylesheets/skyrim/scrollbar-down.png rename to themes/skyrim-theme/skyrim/scrollbar-down.png diff --git a/src/stylesheets/skyrim/scrollbar-horizontal.png b/themes/skyrim-theme/skyrim/scrollbar-horizontal.png similarity index 100% rename from src/stylesheets/skyrim/scrollbar-horizontal.png rename to themes/skyrim-theme/skyrim/scrollbar-horizontal.png diff --git a/src/stylesheets/skyrim/scrollbar-left.png b/themes/skyrim-theme/skyrim/scrollbar-left.png similarity index 100% rename from src/stylesheets/skyrim/scrollbar-left.png rename to themes/skyrim-theme/skyrim/scrollbar-left.png diff --git a/src/stylesheets/skyrim/scrollbar-right.png b/themes/skyrim-theme/skyrim/scrollbar-right.png similarity index 100% rename from src/stylesheets/skyrim/scrollbar-right.png rename to themes/skyrim-theme/skyrim/scrollbar-right.png diff --git a/src/stylesheets/skyrim/scrollbar-up.png b/themes/skyrim-theme/skyrim/scrollbar-up.png similarity index 100% rename from src/stylesheets/skyrim/scrollbar-up.png rename to themes/skyrim-theme/skyrim/scrollbar-up.png diff --git a/src/stylesheets/skyrim/scrollbar-vertical.png b/themes/skyrim-theme/skyrim/scrollbar-vertical.png similarity index 100% rename from src/stylesheets/skyrim/scrollbar-vertical.png rename to themes/skyrim-theme/skyrim/scrollbar-vertical.png diff --git a/src/stylesheets/skyrim/separator.png b/themes/skyrim-theme/skyrim/separator.png similarity index 100% rename from src/stylesheets/skyrim/separator.png rename to themes/skyrim-theme/skyrim/separator.png diff --git a/src/stylesheets/skyrim/slider-border.png b/themes/skyrim-theme/skyrim/slider-border.png similarity index 100% rename from src/stylesheets/skyrim/slider-border.png rename to themes/skyrim-theme/skyrim/slider-border.png diff --git a/src/stylesheets/skyrim/slider-handle.png b/themes/skyrim-theme/skyrim/slider-handle.png similarity index 100% rename from src/stylesheets/skyrim/slider-handle.png rename to themes/skyrim-theme/skyrim/slider-handle.png diff --git a/themes/vs15-themes/metadata.json b/themes/vs15-themes/metadata.json new file mode 100644 index 000000000..0f6ea552d --- /dev/null +++ b/themes/vs15-themes/metadata.json @@ -0,0 +1,37 @@ +{ + "identifier": "vs15-dark-themes", + "type": "theme", + "name": "VS15 Themes", + "description": "Set of dark themes, inspired by Visual Studio, for ModOrganizer2.", + "version": "1.0.0", + "themes": { + "vs15-dark-blue": { + "name": "VS15 - Dark Blue", + "path": "vs15 Dark-Blue.qss" + }, + "vs15-dark-green": { + "name": "VS15 - Dark Green", + "path": "vs15 Dark-Green.qss" + }, + "vs15-dark-orange": { + "name": "VS15 - Dark Orange", + "path": "vs15 Dark-Orange.qss" + }, + "vs15-dark-pink": { + "name": "VS15 - Dark Pink", + "path": "vs15 Dark-Pink.qss" + }, + "vs15-dark-purple": { + "name": "VS15 - Dark Purple", + "path": "vs15 Dark-Purple.qss" + }, + "vs15-dark-red": { + "name": "VS15 - Dark Red", + "path": "vs15 Dark-Red.qss" + }, + "vs15-dark-yellow": { + "name": "VS15 - Dark Yellow", + "path": "vs15 Dark-Yellow.qss" + } + } +} diff --git a/src/stylesheets/vs15 Dark.qss b/themes/vs15-themes/vs15 Dark-Blue.qss similarity index 100% rename from src/stylesheets/vs15 Dark.qss rename to themes/vs15-themes/vs15 Dark-Blue.qss diff --git a/src/stylesheets/vs15 Dark-Green.qss b/themes/vs15-themes/vs15 Dark-Green.qss similarity index 100% rename from src/stylesheets/vs15 Dark-Green.qss rename to themes/vs15-themes/vs15 Dark-Green.qss diff --git a/src/stylesheets/vs15 Dark-Orange.qss b/themes/vs15-themes/vs15 Dark-Orange.qss similarity index 100% rename from src/stylesheets/vs15 Dark-Orange.qss rename to themes/vs15-themes/vs15 Dark-Orange.qss diff --git a/src/stylesheets/vs15 Dark-Pink.qss b/themes/vs15-themes/vs15 Dark-Pink.qss similarity index 100% rename from src/stylesheets/vs15 Dark-Pink.qss rename to themes/vs15-themes/vs15 Dark-Pink.qss diff --git a/src/stylesheets/vs15 Dark-Purple.qss b/themes/vs15-themes/vs15 Dark-Purple.qss similarity index 100% rename from src/stylesheets/vs15 Dark-Purple.qss rename to themes/vs15-themes/vs15 Dark-Purple.qss diff --git a/src/stylesheets/vs15 Dark-Red.qss b/themes/vs15-themes/vs15 Dark-Red.qss similarity index 100% rename from src/stylesheets/vs15 Dark-Red.qss rename to themes/vs15-themes/vs15 Dark-Red.qss diff --git a/src/stylesheets/vs15 Dark-Yellow.qss b/themes/vs15-themes/vs15 Dark-Yellow.qss similarity index 100% rename from src/stylesheets/vs15 Dark-Yellow.qss rename to themes/vs15-themes/vs15 Dark-Yellow.qss diff --git a/src/stylesheets/vs15/branch-closed.png b/themes/vs15-themes/vs15/branch-closed.png similarity index 100% rename from src/stylesheets/vs15/branch-closed.png rename to themes/vs15-themes/vs15/branch-closed.png diff --git a/src/stylesheets/vs15/branch-open.png b/themes/vs15-themes/vs15/branch-open.png similarity index 100% rename from src/stylesheets/vs15/branch-open.png rename to themes/vs15-themes/vs15/branch-open.png diff --git a/src/stylesheets/vs15/checkbox-check-disabled.png b/themes/vs15-themes/vs15/checkbox-check-disabled.png similarity index 100% rename from src/stylesheets/vs15/checkbox-check-disabled.png rename to themes/vs15-themes/vs15/checkbox-check-disabled.png diff --git a/src/stylesheets/vs15/checkbox-check.png b/themes/vs15-themes/vs15/checkbox-check.png similarity index 100% rename from src/stylesheets/vs15/checkbox-check.png rename to themes/vs15-themes/vs15/checkbox-check.png diff --git a/src/stylesheets/vs15/combobox-down.png b/themes/vs15-themes/vs15/combobox-down.png similarity index 100% rename from src/stylesheets/vs15/combobox-down.png rename to themes/vs15-themes/vs15/combobox-down.png diff --git a/src/stylesheets/vs15/scrollbar-down-disabled.png b/themes/vs15-themes/vs15/scrollbar-down-disabled.png similarity index 100% rename from src/stylesheets/vs15/scrollbar-down-disabled.png rename to themes/vs15-themes/vs15/scrollbar-down-disabled.png diff --git a/src/stylesheets/vs15/scrollbar-down-hover.png b/themes/vs15-themes/vs15/scrollbar-down-hover.png similarity index 100% rename from src/stylesheets/vs15/scrollbar-down-hover.png rename to themes/vs15-themes/vs15/scrollbar-down-hover.png diff --git a/src/stylesheets/vs15/scrollbar-down.png b/themes/vs15-themes/vs15/scrollbar-down.png similarity index 100% rename from src/stylesheets/vs15/scrollbar-down.png rename to themes/vs15-themes/vs15/scrollbar-down.png diff --git a/src/stylesheets/vs15/scrollbar-left-disabled.png b/themes/vs15-themes/vs15/scrollbar-left-disabled.png similarity index 100% rename from src/stylesheets/vs15/scrollbar-left-disabled.png rename to themes/vs15-themes/vs15/scrollbar-left-disabled.png diff --git a/src/stylesheets/vs15/scrollbar-left-hover.png b/themes/vs15-themes/vs15/scrollbar-left-hover.png similarity index 100% rename from src/stylesheets/vs15/scrollbar-left-hover.png rename to themes/vs15-themes/vs15/scrollbar-left-hover.png diff --git a/src/stylesheets/vs15/scrollbar-left.png b/themes/vs15-themes/vs15/scrollbar-left.png similarity index 100% rename from src/stylesheets/vs15/scrollbar-left.png rename to themes/vs15-themes/vs15/scrollbar-left.png diff --git a/src/stylesheets/vs15/scrollbar-right-disabled.png b/themes/vs15-themes/vs15/scrollbar-right-disabled.png similarity index 100% rename from src/stylesheets/vs15/scrollbar-right-disabled.png rename to themes/vs15-themes/vs15/scrollbar-right-disabled.png diff --git a/src/stylesheets/vs15/scrollbar-right-hover.png b/themes/vs15-themes/vs15/scrollbar-right-hover.png similarity index 100% rename from src/stylesheets/vs15/scrollbar-right-hover.png rename to themes/vs15-themes/vs15/scrollbar-right-hover.png diff --git a/src/stylesheets/vs15/scrollbar-right.png b/themes/vs15-themes/vs15/scrollbar-right.png similarity index 100% rename from src/stylesheets/vs15/scrollbar-right.png rename to themes/vs15-themes/vs15/scrollbar-right.png diff --git a/src/stylesheets/vs15/scrollbar-up-disabled.png b/themes/vs15-themes/vs15/scrollbar-up-disabled.png similarity index 100% rename from src/stylesheets/vs15/scrollbar-up-disabled.png rename to themes/vs15-themes/vs15/scrollbar-up-disabled.png diff --git a/src/stylesheets/vs15/scrollbar-up-hover.png b/themes/vs15-themes/vs15/scrollbar-up-hover.png similarity index 100% rename from src/stylesheets/vs15/scrollbar-up-hover.png rename to themes/vs15-themes/vs15/scrollbar-up-hover.png diff --git a/src/stylesheets/vs15/scrollbar-up.png b/themes/vs15-themes/vs15/scrollbar-up.png similarity index 100% rename from src/stylesheets/vs15/scrollbar-up.png rename to themes/vs15-themes/vs15/scrollbar-up.png diff --git a/src/stylesheets/vs15/sort-asc.png b/themes/vs15-themes/vs15/sort-asc.png similarity index 100% rename from src/stylesheets/vs15/sort-asc.png rename to themes/vs15-themes/vs15/sort-asc.png diff --git a/src/stylesheets/vs15/sort-desc.png b/themes/vs15-themes/vs15/sort-desc.png similarity index 100% rename from src/stylesheets/vs15/sort-desc.png rename to themes/vs15-themes/vs15/sort-desc.png diff --git a/src/stylesheets/vs15/spinner-down.png b/themes/vs15-themes/vs15/spinner-down.png similarity index 100% rename from src/stylesheets/vs15/spinner-down.png rename to themes/vs15-themes/vs15/spinner-down.png diff --git a/src/stylesheets/vs15/spinner-up.png b/themes/vs15-themes/vs15/spinner-up.png similarity index 100% rename from src/stylesheets/vs15/spinner-up.png rename to themes/vs15-themes/vs15/spinner-up.png diff --git a/src/stylesheets/vs15/sub-menu-arrow-hover.png b/themes/vs15-themes/vs15/sub-menu-arrow-hover.png similarity index 100% rename from src/stylesheets/vs15/sub-menu-arrow-hover.png rename to themes/vs15-themes/vs15/sub-menu-arrow-hover.png diff --git a/src/stylesheets/vs15/sub-menu-arrow.png b/themes/vs15-themes/vs15/sub-menu-arrow.png similarity index 100% rename from src/stylesheets/vs15/sub-menu-arrow.png rename to themes/vs15-themes/vs15/sub-menu-arrow.png From c6516af5db2b4fa8a5b2c035da073badfb55880c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sun, 16 Jul 2023 13:33:23 +0200 Subject: [PATCH 14/26] Recurse folders to find Qt plugins. --- src/proxyqt.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/proxyqt.cpp b/src/proxyqt.cpp index b1ec86dac..42e9567ce 100644 --- a/src/proxyqt.cpp +++ b/src/proxyqt.cpp @@ -25,8 +25,9 @@ QList> ProxyQtLoader::load(const MOBase::PluginExtension& extens // TODO - retrieve plugins from extension instead of listing them - QDirIterator iter(QDir(extension.directory(), {}, QDir::NoSort, - QDir::Files | QDir::NoDotAndDotDot)); + QDirIterator iter( + QDir(extension.directory(), {}, QDir::NoSort, QDir::Files | QDir::NoDotAndDotDot), + QDirIterator::Subdirectories); while (iter.hasNext()) { iter.next(); const auto filePath = iter.filePath(); From 28f134cc86316681f4ca758d2e3952f24d8ee285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 22 Jul 2023 11:10:14 +0200 Subject: [PATCH 15/26] Add IExtensionList implementation. --- src/CMakeLists.txt | 1 + src/extensionlistproxy.cpp | 51 ++++++++++++++++++++++++++++++++++++++ src/extensionlistproxy.h | 29 ++++++++++++++++++++++ src/organizerproxy.cpp | 14 +++++++++-- src/organizerproxy.h | 11 +++++--- src/pluginmanager.cpp | 2 +- 6 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 src/extensionlistproxy.cpp create mode 100644 src/extensionlistproxy.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fe647e964..9492d0086 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -279,6 +279,7 @@ mo2_add_filter(NAME src/proxies GROUPS downloadmanagerproxy gamefeaturesproxy modlistproxy + extensionlistproxy organizerproxy pluginlistproxy proxyutils diff --git a/src/extensionlistproxy.cpp b/src/extensionlistproxy.cpp new file mode 100644 index 000000000..948fb7ee3 --- /dev/null +++ b/src/extensionlistproxy.cpp @@ -0,0 +1,51 @@ +#include "extensionlistproxy.h" + +#include + +using namespace MOBase; + +ExtensionListProxy::ExtensionListProxy(OrganizerProxy* oproxy, + const ExtensionManager& manager) + : m_oproxy(oproxy), m_manager(&manager) +{} + +ExtensionListProxy ::~ExtensionListProxy() {} + +bool ExtensionListProxy::installed(const QString& identifier) const +{ + return m_manager->extension(identifier) != nullptr; +} + +bool ExtensionListProxy::enabled(const QString& extension) const +{ + return m_manager->isEnabled(extension); +} + +bool ExtensionListProxy::enabled(const IExtension& extension) const +{ + return m_manager->isEnabled(extension); +} + +const IExtension& ExtensionListProxy::get(QString const& identifier) const +{ + auto* extension = m_manager->extension(identifier); + if (extension) { + return *extension; + } + throw std::out_of_range(std::format("extension '{}' not found", identifier)); +} + +const IExtension& ExtensionListProxy::at(std::size_t const& index) const +{ + return *m_manager->extensions().at(index); +} + +const IExtension& ExtensionListProxy::operator[](std::size_t const& index) const +{ + return *m_manager->extensions().at(index); +} + +std::size_t ExtensionListProxy::size() const +{ + return m_manager->extensions().size(); +} diff --git a/src/extensionlistproxy.h b/src/extensionlistproxy.h new file mode 100644 index 000000000..47a394f69 --- /dev/null +++ b/src/extensionlistproxy.h @@ -0,0 +1,29 @@ +#ifndef EXTENSIONLISTPROXY_H +#define EXTENSIONLISTPROXY_H + +#include + +#include "extensionmanager.h" + +class OrganizerProxy; + +class ExtensionListProxy : public MOBase::IExtensionList +{ +public: + ExtensionListProxy(OrganizerProxy* oproxy, const ExtensionManager& manager); + virtual ~ExtensionListProxy(); + + bool installed(const QString& identifier) const override; + bool enabled(const QString& extension) const override; + bool enabled(const MOBase::IExtension& extension) const override; + const MOBase::IExtension& get(QString const& identifier) const override; + const MOBase::IExtension& at(std::size_t const& index) const override; + const MOBase::IExtension& operator[](std::size_t const& index) const override; + std::size_t size() const override; + +private: + OrganizerProxy* m_oproxy; + const ExtensionManager* m_manager; +}; + +#endif diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 959940773..fb98c875b 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -1,6 +1,8 @@ #include "organizerproxy.h" #include "downloadmanagerproxy.h" +#include "extensionlistproxy.h" +#include "extensionmanager.h" #include "gamefeaturesproxy.h" #include "glob_matching.h" #include "modlistproxy.h" @@ -18,11 +20,14 @@ using namespace MOBase; using namespace MOShared; -OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, PluginManager* pluginManager, - MOBase::IPlugin* plugin) +OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, + const ExtensionManager& extensionManager, + PluginManager* pluginManager, MOBase::IPlugin* plugin) : m_Proxied(organizer), m_PluginManager(pluginManager), m_Plugin(plugin), m_DownloadManagerProxy( std::make_unique(this, organizer->downloadManager())), + m_ExtensionListProxy( + std::make_unique(this, extensionManager)), m_ModListProxy(std::make_unique(this, organizer->modList())), m_PluginListProxy( std::make_unique(this, organizer->pluginList())), @@ -177,6 +182,11 @@ void OrganizerProxy::modDataChanged(IModInterface* mod) m_Proxied->modDataChanged(mod); } +MOBase::IExtensionList& OrganizerProxy::extensionList() const +{ + return *m_ExtensionListProxy; +} + bool OrganizerProxy::isPluginEnabled(QString const& pluginName) const { return m_PluginManager->isEnabled(pluginName); diff --git a/src/organizerproxy.h b/src/organizerproxy.h index ba5efe455..03205b06b 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -12,14 +12,16 @@ class GameFeaturesProxy; class PluginManager; class DownloadManagerProxy; class ModListProxy; +class ExtensionManager; class PluginListProxy; +class ExtensionListProxy; class OrganizerProxy : public MOBase::IOrganizer { public: - OrganizerProxy(OrganizerCore* organizer, PluginManager* pluginManager, - MOBase::IPlugin* plugin); + OrganizerProxy(OrganizerCore* organizer, const ExtensionManager& extensionManager, + PluginManager* pluginManager, MOBase::IPlugin* plugin); ~OrganizerProxy(); public: @@ -92,7 +94,9 @@ class OrganizerProxy : public MOBase::IOrganizer bool onProfileChanged( std::function const& func) override; - // Plugin related: + // Plugin/extension related: + virtual MOBase::IExtensionList& extensionList() const override; + virtual bool isPluginEnabled(QString const& pluginName) const override; virtual bool isPluginEnabled(MOBase::IPlugin* plugin) const override; virtual QVariant pluginSetting(const QString& pluginName, @@ -150,6 +154,7 @@ class OrganizerProxy : public MOBase::IOrganizer std::vector m_Connections; std::unique_ptr m_DownloadManagerProxy; + std::unique_ptr m_ExtensionListProxy; std::unique_ptr m_ModListProxy; std::unique_ptr m_PluginListProxy; std::unique_ptr m_GameFeaturesProxy; diff --git a/src/pluginmanager.cpp b/src/pluginmanager.cpp index 594bd1107..64713d10e 100644 --- a/src/pluginmanager.cpp +++ b/src/pluginmanager.cpp @@ -309,7 +309,7 @@ bool PluginManager::initPlugin(PluginExtension const& extension, IPlugin* plugin OrganizerProxy* proxy = nullptr; if (m_core) { - proxy = new OrganizerProxy(m_core, this, plugin); + proxy = new OrganizerProxy(m_core, m_extensions, this, plugin); proxy->setParent(as_qobject(plugin)); } From 0d699bd02512f39a5ebfb711af505d7f1a527312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 22 Jul 2023 11:22:56 +0200 Subject: [PATCH 16/26] Remove unused fileMapping() overload. --- src/organizercore.cpp | 44 ------------------------------------------- src/organizercore.h | 5 ----- 2 files changed, 49 deletions(-) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 93b7ec5ef..76083a2cb 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -2109,47 +2109,3 @@ std::vector OrganizerCore::fileMapping(const QString& profileName, return result; } - -std::vector OrganizerCore::fileMapping(const QString& dataPath, - const QString& relPath, - const DirectoryEntry* base, - const DirectoryEntry* directoryEntry, - int createDestination) -{ - std::vector result; - - for (FileEntryPtr current : directoryEntry->getFiles()) { - bool isArchive = false; - int origin = current->getOrigin(isArchive); - if (isArchive || (origin == 0)) { - continue; - } - - QString originPath = QString::fromStdWString(base->getOriginByID(origin).getPath()); - QString fileName = QString::fromStdWString(current->getName()); - // QString fileName = ToQString(current->getName()); - QString source = originPath + relPath + fileName; - QString target = dataPath + relPath + fileName; - if (source != target) { - result.push_back({source, target, false, false}); - } - } - - // recurse into subdirectories - for (const auto& d : directoryEntry->getSubDirectories()) { - int origin = d->anyOrigin(); - - QString originPath = QString::fromStdWString(base->getOriginByID(origin).getPath()); - QString dirName = QString::fromStdWString(d->getName()); - QString source = originPath + relPath + dirName; - QString target = dataPath + relPath + dirName; - - bool writeDestination = (base == directoryEntry) && (origin == createDestination); - - result.push_back({source, target, true, writeDestination}); - std::vector subRes = - fileMapping(dataPath, relPath + dirName + "\\", base, d, createDestination); - result.insert(result.end(), subRes.begin(), subRes.end()); - } - return result; -} diff --git a/src/organizercore.h b/src/organizercore.h index 31da3b757..d56ab4446 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -509,11 +509,6 @@ public slots: std::vector fileMapping(const QString& profile, const QString& customOverwrite); - std::vector fileMapping(const QString& dataPath, const QString& relPath, - const MOShared::DirectoryEntry* base, - const MOShared::DirectoryEntry* directoryEntry, - int createDestination); - private slots: void onDirectoryRefreshed(); From af52b779e6c28ef255b382ff882bd11436fb1284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 22 Jul 2023 14:39:16 +0200 Subject: [PATCH 17/26] Update theme extensions. --- src/inibakery.h | 5 +++ themes/CMakeLists.txt | 4 +- themes/dark-theme/metadata.json | 14 ++++--- themes/dracula-theme/metadata.json | 12 +++--- themes/make-metadata.ps1 | 59 ++++++++++++++++++--------- themes/nighteyes-theme/metadata.json | 12 +++--- themes/parchment-theme/metadata.json | 12 +++--- themes/skyrim-theme/metadata.json | 12 +++--- themes/vs15-themes/metadata.json | 60 ++++++++++++++-------------- 9 files changed, 115 insertions(+), 75 deletions(-) diff --git a/src/inibakery.h b/src/inibakery.h index 2707085aa..a32ef55aa 100644 --- a/src/inibakery.h +++ b/src/inibakery.h @@ -7,6 +7,11 @@ class OrganizerCore; +// small classes that deal with preparing profiles before runs for local saves, bsa +// invalidation, etc., and providing mapping for local profile files when needed +// +// this class replaces the old INI Bakery plugin +// class IniBakery { public: diff --git a/themes/CMakeLists.txt b/themes/CMakeLists.txt index 18871c885..58fc857d6 100644 --- a/themes/CMakeLists.txt +++ b/themes/CMakeLists.txt @@ -6,6 +6,8 @@ file(GLOB theme_directories foreach(theme_directory ${theme_directories}) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${theme_directory}") - install(DIRECTORY ${theme_directory} DESTINATION bin/extensions) + file(READ ${theme_directory}/metadata.json JSON_METADATA) + string(JSON theme_identifier GET ${JSON_METADATA} id) + install(DIRECTORY ${theme_directory}/ DESTINATION bin/extensions/${theme_identifier}) endif() endforeach() diff --git a/themes/dark-theme/metadata.json b/themes/dark-theme/metadata.json index 0bd00fa70..9075b2c5f 100644 --- a/themes/dark-theme/metadata.json +++ b/themes/dark-theme/metadata.json @@ -1,13 +1,15 @@ { - "identifier": "dark-theme", - "type": "theme", + "id": "mo2-theme-dark", "name": "Dark Theme", "description": "Dark theme for ModOrganizer2.", "version": "1.0.0", - "themes": { - "dark": { - "name": "Dark", - "path": "dark.qss" + "type": "theme", + "content": { + "themes": { + "dark": { + "name": "Dark", + "path": "dark.qss" + } } } } diff --git a/themes/dracula-theme/metadata.json b/themes/dracula-theme/metadata.json index 4e1cb8543..b811bf853 100644 --- a/themes/dracula-theme/metadata.json +++ b/themes/dracula-theme/metadata.json @@ -1,13 +1,15 @@ { - "identifier": "dracula-theme", + "id": "mo2-theme-dracula", "type": "theme", "name": "Dracula Theme", "description": "Dracula theme for ModOrganizer2.", "version": "1.0.0", - "themes": { - "dracula": { - "name": "Dracula", - "path": "dracula.qss" + "content": { + "themes": { + "dracula": { + "name": "Dracula", + "path": "dracula.qss" + } } } } diff --git a/themes/make-metadata.ps1 b/themes/make-metadata.ps1 index 4143291c8..1625f3377 100644 --- a/themes/make-metadata.ps1 +++ b/themes/make-metadata.ps1 @@ -1,25 +1,46 @@ -$fixNames = @{ - dark = "Dark"; - dracula = "Dracula"; - nighteyes = "Night Eyes"; - parchment = "Parchment"; - skyrim = "Skyrim"; -} - -Get-ChildItem -Directory -Exclude "vs15" | ForEach-Object { - $name = $fixNames[$_.Name]; +Get-ChildItem -Directory | ForEach-Object { + $metadata = Get-Content "$_/metadata.json" | ConvertFrom-Json + $metadata + $name = $metadata.identifier -replace "-themes?" $data = [ordered]@{ - identifier = $_.Name + "-theme"; + id = "mo2-theme-" + $name; type = "theme"; - name = "$name Theme"; - description = "$name theme for ModOrganizer2."; + name = $metadata.name; + description = $metadata.description; version = "1.0.0"; - themes = @{ - $_.Name = [ordered]@{ - name = $name; - path = (Get-ChildItem $_ -Filter "*.qss")[0].Name; - } + content = @{ + themes = $metadata.themes; }; } - ConvertTo-Json -InputObject $data | Set-Content (Join-Path $_ "metadata.json") + $data + # ConvertTo-Json -InputObject $data # | Set-Content (Join-Path $_ "metadata.json") } + +# $fixNames = @{ +# dark = "Dark"; +# dracula = "Dracula"; +# nighteyes = "Night Eyes"; +# parchment = "Parchment"; +# skyrim = "Skyrim"; +# } + +# Get-ChildItem -Directory -Exclude "vs15" | ForEach-Object { +# $name = $fixNames[$_.Name]; +# $data = [ordered]@{ +# identifier = "mo2-theme-" + $_.Name; +# type = "theme"; +# name = "$name Theme"; +# description = "$name theme for ModOrganizer2."; +# version = "1.0.0"; +# content = @{ +# themes = @{ +# $_.Name = [ordered]@{ +# name = $name; +# path = (Get-ChildItem $_ -Filter "*.qss")[0].Name; +# } +# }; +# }; +# ; +# } +# ConvertTo-Json -InputObject $data | Set-Content (Join-Path $_ "metadata.json") +# } diff --git a/themes/nighteyes-theme/metadata.json b/themes/nighteyes-theme/metadata.json index f8d8b3261..cf9d8fc7b 100644 --- a/themes/nighteyes-theme/metadata.json +++ b/themes/nighteyes-theme/metadata.json @@ -1,13 +1,15 @@ { - "identifier": "nighteyes-theme", + "id": "mo2-theme-nighteyes", "type": "theme", "name": "Night Eyes Theme", "description": "Night Eyes theme for ModOrganizer2.", "version": "1.0.0", - "themes": { - "nighteyes": { - "name": "Night Eyes", - "path": "nigheyes.qss" + "content": { + "themes": { + "nighteyes": { + "name": "Night Eyes", + "path": "nigheyes.qss" + } } } } diff --git a/themes/parchment-theme/metadata.json b/themes/parchment-theme/metadata.json index 4e53ba407..09448ec76 100644 --- a/themes/parchment-theme/metadata.json +++ b/themes/parchment-theme/metadata.json @@ -1,13 +1,15 @@ { - "identifier": "parchment-theme", + "id": "mo2-theme-parchment", "type": "theme", "name": "Parchment Theme", "description": "Parchment theme for ModOrganizer2.", "version": "1.0.0", - "themes": { - "parchment": { - "name": "Parchment", - "path": "parchment.qss" + "content": { + "themes": { + "parchment": { + "name": "Parchment", + "path": "parchment.qss" + } } } } diff --git a/themes/skyrim-theme/metadata.json b/themes/skyrim-theme/metadata.json index b28358b1b..f2f78ec09 100644 --- a/themes/skyrim-theme/metadata.json +++ b/themes/skyrim-theme/metadata.json @@ -1,13 +1,15 @@ { - "identifier": "skyrim-theme", + "id": "mo2-theme-skyrim", "type": "theme", "name": "Skyrim Theme", "description": "Skyrim theme for ModOrganizer2.", "version": "1.0.0", - "themes": { - "skyrim": { - "name": "Skyrim", - "path": "skyrim.qss" + "content": { + "themes": { + "skyrim": { + "name": "Skyrim", + "path": "skyrim.qss" + } } } } diff --git a/themes/vs15-themes/metadata.json b/themes/vs15-themes/metadata.json index 0f6ea552d..c6404d462 100644 --- a/themes/vs15-themes/metadata.json +++ b/themes/vs15-themes/metadata.json @@ -1,37 +1,39 @@ { - "identifier": "vs15-dark-themes", + "id": "mo2-theme-vs15-dark", "type": "theme", "name": "VS15 Themes", "description": "Set of dark themes, inspired by Visual Studio, for ModOrganizer2.", "version": "1.0.0", - "themes": { - "vs15-dark-blue": { - "name": "VS15 - Dark Blue", - "path": "vs15 Dark-Blue.qss" - }, - "vs15-dark-green": { - "name": "VS15 - Dark Green", - "path": "vs15 Dark-Green.qss" - }, - "vs15-dark-orange": { - "name": "VS15 - Dark Orange", - "path": "vs15 Dark-Orange.qss" - }, - "vs15-dark-pink": { - "name": "VS15 - Dark Pink", - "path": "vs15 Dark-Pink.qss" - }, - "vs15-dark-purple": { - "name": "VS15 - Dark Purple", - "path": "vs15 Dark-Purple.qss" - }, - "vs15-dark-red": { - "name": "VS15 - Dark Red", - "path": "vs15 Dark-Red.qss" - }, - "vs15-dark-yellow": { - "name": "VS15 - Dark Yellow", - "path": "vs15 Dark-Yellow.qss" + "content": { + "themes": { + "vs15-dark-blue": { + "name": "VS15 - Dark Blue", + "path": "vs15 Dark-Blue.qss" + }, + "vs15-dark-green": { + "name": "VS15 - Dark Green", + "path": "vs15 Dark-Green.qss" + }, + "vs15-dark-orange": { + "name": "VS15 - Dark Orange", + "path": "vs15 Dark-Orange.qss" + }, + "vs15-dark-pink": { + "name": "VS15 - Dark Pink", + "path": "vs15 Dark-Pink.qss" + }, + "vs15-dark-purple": { + "name": "VS15 - Dark Purple", + "path": "vs15 Dark-Purple.qss" + }, + "vs15-dark-red": { + "name": "VS15 - Dark Red", + "path": "vs15 Dark-Red.qss" + }, + "vs15-dark-yellow": { + "name": "VS15 - Dark Yellow", + "path": "vs15 Dark-Yellow.qss" + } } } } From 20711bd3bfab84a7d49c306fcea061e656de95f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 22 Jul 2023 18:41:40 +0200 Subject: [PATCH 18/26] Start working on extensions tab in settings. --- src/CMakeLists.txt | 3 +- src/mainwindow.cpp | 11 +- src/mainwindow.h | 5 +- src/moapplication.cpp | 3 +- src/pluginmanager.cpp | 2 +- src/settingsdialog.cpp | 9 +- src/settingsdialog.h | 5 +- src/settingsdialog.ui | 12 +- src/settingsdialogextensionrow.cpp | 18 ++ src/settingsdialogextensionrow.h | 23 +++ src/settingsdialogextensionrow.ui | 123 ++++++++++++ src/settingsdialogextensions.cpp | 300 +++++++++++++++++++++++++++++ src/settingsdialogextensions.h | 60 ++++++ src/settingsdialogplugins.cpp | 275 -------------------------- src/settingsdialogplugins.h | 56 ------ 15 files changed, 550 insertions(+), 355 deletions(-) create mode 100644 src/settingsdialogextensionrow.cpp create mode 100644 src/settingsdialogextensionrow.h create mode 100644 src/settingsdialogextensionrow.ui create mode 100644 src/settingsdialogextensions.cpp create mode 100644 src/settingsdialogextensions.h delete mode 100644 src/settingsdialogplugins.cpp delete mode 100644 src/settingsdialogplugins.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9492d0086..d24a9c6e7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -306,7 +306,8 @@ mo2_add_filter(NAME src/settingsdialog GROUPS settingsdialoggeneral settingsdialognexus settingsdialogpaths - settingsdialogplugins + settingsdialogextensions + settingsdialogextensionrow settingsdialogworkarounds settingsdialogmodlist settingsdialogtheme diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 199342f57..fc5d161dc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -230,14 +230,15 @@ void setFilterShortcuts(QWidget* widget, QLineEdit* edit) } MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginManager& pluginManager, ThemeManager& themeManager, + ExtensionManager& extensionManager, PluginManager& pluginManager, + ThemeManager& themeManager, TranslationManager& translationManager, QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_WasVisible(false), m_FirstPaint(true), m_linksSeparator(nullptr), m_Tutorial(this, "MainWindow"), m_OldProfileIndex(-1), m_OldExecutableIndex(-1), m_CategoryFactory(CategoryFactory::instance()), m_OrganizerCore(organizerCore), - m_PluginManager(pluginManager), m_ThemeManager(themeManager), - m_TranslationManager(translationManager), + m_ExtensionManager(extensionManager), m_PluginManager(pluginManager), + m_ThemeManager(themeManager), m_TranslationManager(translationManager), m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)), m_LinkToolbar(nullptr), m_LinkDesktop(nullptr), m_LinkStartMenu(nullptr), m_NumberOfProblems(0), m_ProblemsCheckRequired(false) @@ -2719,8 +2720,8 @@ void MainWindow::on_actionSettings_triggered() const bool oldCheckForUpdates = settings.checkForUpdates(); const int oldMaxDumps = settings.diagnostics().maxCoreDumps(); - SettingsDialog dialog(m_PluginManager, m_ThemeManager, m_TranslationManager, settings, - this); + SettingsDialog dialog(m_ExtensionManager, m_PluginManager, m_ThemeManager, + m_TranslationManager, settings, this); dialog.exec(); auto e = dialog.exitNeeded(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 763cf6f32..18435bc78 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -28,6 +28,7 @@ along with Mod Organizer. If not, see . #include #include "delayedfilewriter.h" +#include "extensionmanager.h" #include "iuserinterface.h" #include "modinfo.h" #include "modlistbypriorityproxy.h" @@ -128,7 +129,8 @@ class MainWindow : public QMainWindow, public IUserInterface public: explicit MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginManager& pluginManager, ThemeManager& themeManager, + ExtensionManager& extensionManager, PluginManager& pluginManager, + ThemeManager& themeManager, TranslationManager& translationManager, QWidget* parent = 0); ~MainWindow(); @@ -298,6 +300,7 @@ private slots: QTime m_StartTime; OrganizerCore& m_OrganizerCore; + ExtensionManager& m_ExtensionManager; PluginManager& m_PluginManager; ThemeManager& m_ThemeManager; TranslationManager& m_TranslationManager; diff --git a/src/moapplication.cpp b/src/moapplication.cpp index a0adc9fde..18626ff9a 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -294,7 +294,8 @@ int MOApplication::run(MOMultiProcess& multiProcess) { tt.start("MOApplication::doOneRun() MainWindow setup"); - MainWindow mainWindow(*m_settings, *m_core, *m_plugins, *m_themes, *m_translations); + MainWindow mainWindow(*m_settings, *m_core, *m_extensions, *m_plugins, *m_themes, + *m_translations); // the nexus interface can show dialogs, make sure they're parented to the // main window diff --git a/src/pluginmanager.cpp b/src/pluginmanager.cpp index 64713d10e..4b0e6c649 100644 --- a/src/pluginmanager.cpp +++ b/src/pluginmanager.cpp @@ -73,7 +73,7 @@ PluginDetails::PluginDetails(PluginManager* manager, PluginExtension const& exte void PluginDetails::fetchRequirements() { - m_requirements = m_plugin->requirements(); + // m_requirements = m_plugin->requirements(); } std::vector PluginDetails::problems() const diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 1db59117e..d352489ba 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -19,23 +19,24 @@ along with Mod Organizer. If not, see . #include "settingsdialog.h" #include "settingsdialogdiagnostics.h" +#include "settingsdialogextensions.h" #include "settingsdialoggeneral.h" #include "settingsdialogmodlist.h" #include "settingsdialognexus.h" #include "settingsdialogpaths.h" -#include "settingsdialogplugins.h" #include "settingsdialogtheme.h" #include "settingsdialogworkarounds.h" #include "ui_settingsdialog.h" using namespace MOBase; -SettingsDialog::SettingsDialog(PluginManager& pluginManager, +SettingsDialog::SettingsDialog(ExtensionManager& extensionManager, + PluginManager& pluginManager, ThemeManager const& themeManager, TranslationManager const& translationManager, Settings& settings, QWidget* parent) : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog), - m_settings(settings), m_exit(Exit::None), m_pluginManager(&pluginManager) + m_settings(settings), m_exit(Exit::None) { ui->setupUi(this); @@ -50,7 +51,7 @@ SettingsDialog::SettingsDialog(PluginManager& pluginManager, std::unique_ptr(new DiagnosticsSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new NexusSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr( - new PluginsSettingsTab(settings, *m_pluginManager, *this))); + new ExtensionsSettingsTab(settings, extensionManager, pluginManager, *this))); m_tabs.push_back( std::unique_ptr(new WorkaroundsSettingsTab(settings, *this))); } diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 25a6bf9d9..da3555f3d 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "tutorabledialog.h" class PluginManager; +class ExtensionManager; class Settings; class SettingsDialog; class ThemeManager; @@ -66,7 +67,8 @@ class SettingsDialog : public MOBase::TutorableDialog friend class SettingsTab; public: - explicit SettingsDialog(PluginManager& pluginManager, + explicit SettingsDialog(ExtensionManager& extensionManager, + PluginManager& pluginManager, ThemeManager const& themeManager, TranslationManager const& translationManager, Settings& settings, QWidget* parent = 0); @@ -94,7 +96,6 @@ public slots: Settings& m_settings; std::vector> m_tabs; ExitFlags m_exit; - PluginManager* m_pluginManager; }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 6011b1588..330087c7a 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1476,9 +1476,9 @@ If you disable this feature, MO will only display official DLCs this way. Please - + - Plugins + Extensions @@ -1532,13 +1532,7 @@ If you disable this feature, MO will only display official DLCs this way. Please 0 - - - - 1 - - - + diff --git a/src/settingsdialogextensionrow.cpp b/src/settingsdialogextensionrow.cpp new file mode 100644 index 000000000..7f5b37642 --- /dev/null +++ b/src/settingsdialogextensionrow.cpp @@ -0,0 +1,18 @@ +#include "settingsdialogextensionrow.h" + +#include "ui_settingsdialogextensionrow.h" + +using namespace MOBase; + +ExtensionListItemWidget::ExtensionListItemWidget(const IExtension& extension) + : ui{new Ui::ExtensionListItemWidget()}, m_extension{&extension} +{ + ui->setupUi(this); + + QIcon icon = style()->standardIcon(QStyle::SP_DialogOkButton); + ui->extensionIcon->setPixmap(extension.metadata().icon().pixmap(QSize(48, 48))); + ui->extensionName->setText(extension.metadata().name()); + + ui->extensionDescription->setText(extension.metadata().description()); + ui->extensionAuthor->setText(extension.metadata().author().name()); +} diff --git a/src/settingsdialogextensionrow.h b/src/settingsdialogextensionrow.h new file mode 100644 index 000000000..e2aa52806 --- /dev/null +++ b/src/settingsdialogextensionrow.h @@ -0,0 +1,23 @@ +#ifndef SETTINGSDIALOGEXTENSIONROW_H +#define SETTINGSDIALOGEXTENSIONROW_H + +#include + +#include "extension.h" + +namespace Ui +{ +class ExtensionListItemWidget; +} + +class ExtensionListItemWidget : public QWidget +{ +public: + ExtensionListItemWidget(MOBase::IExtension const& extension); + +private: + Ui::ExtensionListItemWidget* ui; + const MOBase::IExtension* m_extension; +}; + +#endif diff --git a/src/settingsdialogextensionrow.ui b/src/settingsdialogextensionrow.ui new file mode 100644 index 000000000..bb9986a17 --- /dev/null +++ b/src/settingsdialogextensionrow.ui @@ -0,0 +1,123 @@ + + + ExtensionListItemWidget + + + + 0 + 0 + 250 + 60 + + + + Form + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + + 48 + 48 + + + + + 45 + 48 + + + + TextLabel + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + + + + 2 + + + + + + 10 + true + + + + TextLabel + + + + + + + + 8 + true + + + + TextLabel + + + + + + + + + + 8 + + + + TextLabel + + + + + + + + + + + + diff --git a/src/settingsdialogextensions.cpp b/src/settingsdialogextensions.cpp new file mode 100644 index 000000000..300655e88 --- /dev/null +++ b/src/settingsdialogextensions.cpp @@ -0,0 +1,300 @@ +#include "settingsdialogextensions.h" +#include "noeditdelegate.h" +#include "ui_settingsdialog.h" +#include + +#include "organizercore.h" +#include "pluginmanager.h" + +#include "settingsdialogextensionrow.h" + +using namespace MOBase; + +struct PluginExtensionComparator +{ + bool operator()(const PluginExtension* lhs, const PluginExtension* rhs) const + { + return lhs->metadata().name().compare(rhs->metadata().name(), Qt::CaseInsensitive); + } +}; + +ExtensionsSettingsTab::ExtensionsSettingsTab(Settings& s, + ExtensionManager& extensionManager, + PluginManager& pluginManager, + SettingsDialog& d) + : SettingsTab(s, d), m_extensionManager(&extensionManager), + m_pluginManager(&pluginManager) +{ + // TODO: use Qt system to sort extensions instead of sorting beforehand + std::vector extensions; + for (auto& extension : m_extensionManager->extensions()) { + extensions.push_back(extension.get()); + } + std::sort(extensions.begin(), extensions.end(), [](auto* lhs, auto* rhs) { + return lhs->metadata().name().compare(rhs->metadata().name(), Qt::CaseInsensitive) < + 0; + }); + + ui->extensionsList->setSortingEnabled(false); + + for (const auto* extension : extensions) { + auto* item = new QListWidgetItem(); + auto* widget = new ExtensionListItemWidget(*extension); + item->setSizeHint(widget->sizeHint()); + ui->extensionsList->addItem(item); + ui->extensionsList->setItemWidget(item, widget); + } + + // ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); + // ui->pluginsList->setHeaderHidden(true); + + //// display plugin settings + // std::map, PluginExtensionComparator> + // pluginsPerExtension; + + // for (IPlugin* plugin : pluginManager.plugins()) { + // pluginsPerExtension[&pluginManager.details(plugin).extension()].push_back(plugin); + // } + + // for (auto& [extension, plugins] : pluginsPerExtension) { + + // QTreeWidgetItem* extensionItem = new QTreeWidgetItem(); + // extensionItem->setData(0, Qt::DisplayRole, extension->metadata().name()); + // ui->pluginsList->addTopLevelItem(extensionItem); + + // for (auto* plugin : plugins) { + + // // only show master + // if (pluginManager.details(plugin).master() != plugin) { + // continue; + // } + + // QTreeWidgetItem* pluginItem = new QTreeWidgetItem(extensionItem); + // pluginItem->setData(0, Qt::DisplayRole, plugin->localizedName()); + // } + //} + + // ui->pluginsList->sortByColumn(0, Qt::AscendingOrder); + + //// display plugin blacklist + // for (const QString& pluginName : settings().plugins().blacklist()) { + // ui->pluginBlacklist->addItem(pluginName); + // } + + // m_filter.setEdit(ui->pluginFilterEdit); + + // QObject::connect(ui->pluginsList, &QTreeWidget::currentItemChanged, + // [&](auto* current, auto* previous) { + // on_pluginsList_currentItemChanged(current, previous); + // }); + + // QShortcut* delShortcut = + // new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); + // QObject::connect(delShortcut, &QShortcut::activated, &dialog(), [&] { + // deleteBlacklistItem(); + // }); + // QObject::connect(&m_filter, &FilterWidget::changed, [&] { + // filterPluginList(); + // }); + + // updateListItems(); + // filterPluginList(); +} +// +// void PluginsSettingsTab::updateListItems() +//{ +// for (auto i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { +// auto* topLevelItem = ui->pluginsList->topLevelItem(i); +// for (auto j = 0; j < topLevelItem->childCount(); ++j) { +// auto* item = topLevelItem->child(j); +// auto* plugin = this->plugin(item); +// +// bool inactive = !m_pluginManager->implementInterface(plugin) && +// !m_pluginManager->isEnabled(plugin); +// +// auto font = item->font(0); +// font.setItalic(inactive); +// item->setFont(0, font); +// for (auto k = 0; k < item->childCount(); ++k) { +// item->child(k)->setFont(0, font); +// } +// } +// } +//} +// +// void PluginsSettingsTab::filterPluginList() +//{ +// auto selectedItems = ui->pluginsList->selectedItems(); +// QTreeWidgetItem* firstNotHidden = nullptr; +// +// for (auto i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { +// auto* topLevelItem = ui->pluginsList->topLevelItem(i); +// +// bool found = false; +// for (auto j = 0; j < topLevelItem->childCount(); ++j) { +// auto* item = topLevelItem->child(j); +// auto* plugin = this->plugin(item); +// +// // Check the item or the child - If any match (item or child), the whole +// // group is displayed. +// bool match = m_filter.matches([plugin](const QRegularExpression& regex) { +// return regex.match(plugin->localizedName()).hasMatch(); +// }); +// +// if (match) { +// found = true; +// item->setHidden(false); +// +// if (firstNotHidden == nullptr) { +// firstNotHidden = item; +// } +// } else { +// item->setHidden(true); +// } +// } +// +// topLevelItem->setHidden(!found); +// } +// +// // Unselect item if hidden: +// if (firstNotHidden) { +// ui->pluginDescription->setVisible(true); +// ui->pluginSettingsList->setVisible(true); +// ui->noPluginLabel->setVisible(false); +// if (selectedItems.isEmpty()) { +// ui->pluginsList->setCurrentItem(firstNotHidden); +// } else if (selectedItems[0]->isHidden()) { +// ui->pluginsList->setCurrentItem(firstNotHidden); +// } +// } else { +// ui->pluginDescription->setVisible(false); +// ui->pluginSettingsList->setVisible(false); +// ui->noPluginLabel->setVisible(true); +// } +//} +// +// IPlugin* PluginsSettingsTab::plugin(QTreeWidgetItem* pluginItem) const +//{ +// return static_cast(qvariant_cast(pluginItem->data(0, PluginRole))); +//} + +void ExtensionsSettingsTab::update() +{ + // transfer plugin settings to in-memory structure + // for (int i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { + // auto* topLevelItem = ui->pluginsList->topLevelItem(i); + // for (int j = 0; j < topLevelItem->childCount(); ++j) { + // auto* item = topLevelItem->child(j); + // settings().plugins().setSettings(plugin(item)->name(), + // item->data(0, SettingsRole).toMap()); + // } + //} + + // set plugin blacklist + QStringList names; + for (QListWidgetItem* item : ui->pluginBlacklist->findItems("*", Qt::MatchWildcard)) { + names.push_back(item->text()); + } + + settings().plugins().setBlacklist(names); + + settings().plugins().save(); +} + +void ExtensionsSettingsTab::closing() +{ + // storeSettings(ui->pluginsList->currentItem()); +} +// +// void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem* current, +// QTreeWidgetItem* previous) +//{ +// storeSettings(previous); +// +// if (!current->data(0, PluginRole).isValid()) { +// return; +// } +// +// ui->pluginSettingsList->clear(); +// IPlugin* plugin = this->plugin(current); +// // ui->authorLabel->setText(plugin->author()); +// // ui->versionLabel->setText(plugin->version().canonicalString()); +// // ui->descriptionLabel->setText(plugin->description()); +// +// //// Checkbox, do not show for children or game plugins, disable +// //// if the plugin cannot be enabled. +// // ui->enabledCheckbox->setVisible( +// // !m_pluginManager->implementInterface(plugin) && +// // plugin->master().isEmpty()); +// +// bool enabled = m_pluginManager->isEnabled(plugin); +// auto& requirements = m_pluginManager->details(plugin); +// auto problems = requirements.problems(); +// +// // Plugin is enable or can be enabled. +// if (enabled || problems.empty()) { +// ui->enabledCheckbox->setDisabled(false); +// ui->enabledCheckbox->setToolTip(""); +// ui->enabledCheckbox->setChecked(enabled); +// } +// // Plugin is disable and cannot be enabled. +// else { +// ui->enabledCheckbox->setDisabled(true); +// ui->enabledCheckbox->setChecked(false); +// if (problems.size() == 1) { +// ui->enabledCheckbox->setToolTip(problems[0].shortDescription()); +// } else { +// QStringList descriptions; +// for (auto& problem : problems) { +// descriptions.append(problem.shortDescription()); +// } +// ui->enabledCheckbox->setToolTip("
      • " + descriptions.join("
      • ") + +// "
      "); +// } +// } +// +// QVariantMap settings = current->data(0, SettingsRole).toMap(); +// QVariantMap descriptions = current->data(0, DescriptionsRole).toMap(); +// ui->pluginSettingsList->setEnabled(settings.count() != 0); +// for (auto iter = settings.begin(); iter != settings.end(); ++iter) { +// QTreeWidgetItem* newItem = new QTreeWidgetItem(QStringList(iter.key())); +// QVariant value = *iter; +// QString description; +// { +// auto descriptionIter = descriptions.find(iter.key()); +// if (descriptionIter != descriptions.end()) { +// description = descriptionIter->toString(); +// } +// } +// +// ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate()); +// newItem->setData(1, Qt::DisplayRole, value); +// newItem->setData(1, Qt::EditRole, value); +// newItem->setToolTip(1, description); +// +// newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); +// ui->pluginSettingsList->addTopLevelItem(newItem); +// } +// +// ui->pluginSettingsList->resizeColumnToContents(0); +// ui->pluginSettingsList->resizeColumnToContents(1); +//} +// +// void PluginsSettingsTab::deleteBlacklistItem() +//{ +// ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); +//} +// +// void PluginsSettingsTab::storeSettings(QTreeWidgetItem* pluginItem) +//{ +// if (pluginItem != nullptr && pluginItem->data(0, PluginRole).isValid()) { +// QVariantMap settings = pluginItem->data(0, SettingsRole).toMap(); +// +// for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) { +// const QTreeWidgetItem* item = ui->pluginSettingsList->topLevelItem(i); +// settings[item->text(0)] = item->data(1, Qt::DisplayRole); +// } +// +// pluginItem->setData(0, SettingsRole, settings); +// } +//} diff --git a/src/settingsdialogextensions.h b/src/settingsdialogextensions.h new file mode 100644 index 000000000..96eb6ceb3 --- /dev/null +++ b/src/settingsdialogextensions.h @@ -0,0 +1,60 @@ +#ifndef SETTINGSDIALOGEXTENSIONS_H +#define SETTINGSDIALOGEXTENSIONS_H + +#include "filterwidget.h" + +#include "settings.h" +#include "settingsdialog.h" + +#include "extensionmanager.h" +#include "pluginmanager.h" + +class ExtensionsSettingsTab : public SettingsTab +{ +public: + ExtensionsSettingsTab(Settings& settings, ExtensionManager& extensionManager, + PluginManager& pluginManager, SettingsDialog& dialog); + + void update(); + void closing() override; + + // private: + // void on_pluginsList_currentItemChanged(QListWidgetItem* current, + // QListWidgetItem* previous); + // void deleteBlacklistItem(); + // void storeSettings(QListWidgetItem* pluginItem); + +private slots: + + ///** + // * @brief Update the list item to display inactive plugins. + // */ + // void updateListItems(); + + ///** + // * @brief Filter the plugin list according to the filter widget. + // * + // */ + // void filterPluginList(); + + ///** + // * @brief Retrieve the plugin associated to the given item in the list. + // * + // */ + // MOBase::IPlugin* plugin(QListWidgetItem* pluginItem) const; + + enum + { + PluginRole = Qt::UserRole, + SettingsRole = Qt::UserRole + 1, + DescriptionsRole = Qt::UserRole + 2 + }; + +private: + ExtensionManager* m_extensionManager; + PluginManager* m_pluginManager; + + MOBase::FilterWidget m_filter; +}; + +#endif // SETTINGSDIALOGPLUGINS_H diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp deleted file mode 100644 index 1a6d0c90d..000000000 --- a/src/settingsdialogplugins.cpp +++ /dev/null @@ -1,275 +0,0 @@ -#include "settingsdialogplugins.h" -#include "noeditdelegate.h" -#include "ui_settingsdialog.h" -#include - -#include "organizercore.h" -#include "pluginmanager.h" - -using namespace MOBase; - -struct PluginExtensionComparator -{ - bool operator()(const PluginExtension* lhs, const PluginExtension* rhs) const - { - return lhs->metadata().name().compare(rhs->metadata().name(), Qt::CaseInsensitive); - } -}; - -PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginManager& pluginManager, - SettingsDialog& d) - : SettingsTab(s, d), m_pluginManager(&pluginManager) -{ - ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); - ui->pluginsList->setHeaderHidden(true); - - // display plugin settings - std::map, PluginExtensionComparator> - pluginsPerExtension; - - for (IPlugin* plugin : pluginManager.plugins()) { - pluginsPerExtension[&pluginManager.details(plugin).extension()].push_back(plugin); - } - - for (auto& [extension, plugins] : pluginsPerExtension) { - - QTreeWidgetItem* extensionItem = new QTreeWidgetItem(); - extensionItem->setData(0, Qt::DisplayRole, extension->metadata().name()); - ui->pluginsList->addTopLevelItem(extensionItem); - - for (auto* plugin : plugins) { - - // only show master - if (pluginManager.details(plugin).master() != plugin) { - continue; - } - - QTreeWidgetItem* pluginItem = new QTreeWidgetItem(extensionItem); - pluginItem->setData(0, Qt::DisplayRole, plugin->localizedName()); - } - } - - ui->pluginsList->sortByColumn(0, Qt::AscendingOrder); - - // display plugin blacklist - for (const QString& pluginName : settings().plugins().blacklist()) { - ui->pluginBlacklist->addItem(pluginName); - } - - m_filter.setEdit(ui->pluginFilterEdit); - - QObject::connect(ui->pluginsList, &QTreeWidget::currentItemChanged, - [&](auto* current, auto* previous) { - on_pluginsList_currentItemChanged(current, previous); - }); - - QShortcut* delShortcut = - new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); - QObject::connect(delShortcut, &QShortcut::activated, &dialog(), [&] { - deleteBlacklistItem(); - }); - QObject::connect(&m_filter, &FilterWidget::changed, [&] { - filterPluginList(); - }); - - // updateListItems(); - // filterPluginList(); -} - -void PluginsSettingsTab::updateListItems() -{ - for (auto i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { - auto* topLevelItem = ui->pluginsList->topLevelItem(i); - for (auto j = 0; j < topLevelItem->childCount(); ++j) { - auto* item = topLevelItem->child(j); - auto* plugin = this->plugin(item); - - bool inactive = !m_pluginManager->implementInterface(plugin) && - !m_pluginManager->isEnabled(plugin); - - auto font = item->font(0); - font.setItalic(inactive); - item->setFont(0, font); - for (auto k = 0; k < item->childCount(); ++k) { - item->child(k)->setFont(0, font); - } - } - } -} - -void PluginsSettingsTab::filterPluginList() -{ - auto selectedItems = ui->pluginsList->selectedItems(); - QTreeWidgetItem* firstNotHidden = nullptr; - - for (auto i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { - auto* topLevelItem = ui->pluginsList->topLevelItem(i); - - bool found = false; - for (auto j = 0; j < topLevelItem->childCount(); ++j) { - auto* item = topLevelItem->child(j); - auto* plugin = this->plugin(item); - - // Check the item or the child - If any match (item or child), the whole - // group is displayed. - bool match = m_filter.matches([plugin](const QRegularExpression& regex) { - return regex.match(plugin->localizedName()).hasMatch(); - }); - - if (match) { - found = true; - item->setHidden(false); - - if (firstNotHidden == nullptr) { - firstNotHidden = item; - } - } else { - item->setHidden(true); - } - } - - topLevelItem->setHidden(!found); - } - - // Unselect item if hidden: - if (firstNotHidden) { - ui->pluginDescription->setVisible(true); - ui->pluginSettingsList->setVisible(true); - ui->noPluginLabel->setVisible(false); - if (selectedItems.isEmpty()) { - ui->pluginsList->setCurrentItem(firstNotHidden); - } else if (selectedItems[0]->isHidden()) { - ui->pluginsList->setCurrentItem(firstNotHidden); - } - } else { - ui->pluginDescription->setVisible(false); - ui->pluginSettingsList->setVisible(false); - ui->noPluginLabel->setVisible(true); - } -} - -IPlugin* PluginsSettingsTab::plugin(QTreeWidgetItem* pluginItem) const -{ - return static_cast(qvariant_cast(pluginItem->data(0, PluginRole))); -} - -void PluginsSettingsTab::update() -{ - // transfer plugin settings to in-memory structure - // for (int i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { - // auto* topLevelItem = ui->pluginsList->topLevelItem(i); - // for (int j = 0; j < topLevelItem->childCount(); ++j) { - // auto* item = topLevelItem->child(j); - // settings().plugins().setSettings(plugin(item)->name(), - // item->data(0, SettingsRole).toMap()); - // } - //} - - // set plugin blacklist - QStringList names; - for (QListWidgetItem* item : ui->pluginBlacklist->findItems("*", Qt::MatchWildcard)) { - names.push_back(item->text()); - } - - settings().plugins().setBlacklist(names); - - settings().plugins().save(); -} - -void PluginsSettingsTab::closing() -{ - storeSettings(ui->pluginsList->currentItem()); -} - -void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem* current, - QTreeWidgetItem* previous) -{ - storeSettings(previous); - - if (!current->data(0, PluginRole).isValid()) { - return; - } - - ui->pluginSettingsList->clear(); - IPlugin* plugin = this->plugin(current); - ui->authorLabel->setText(plugin->author()); - ui->versionLabel->setText(plugin->version().canonicalString()); - ui->descriptionLabel->setText(plugin->description()); - - // Checkbox, do not show for children or game plugins, disable - // if the plugin cannot be enabled. - ui->enabledCheckbox->setVisible( - !m_pluginManager->implementInterface(plugin) && - plugin->master().isEmpty()); - - bool enabled = m_pluginManager->isEnabled(plugin); - auto& requirements = m_pluginManager->details(plugin); - auto problems = requirements.problems(); - - // Plugin is enable or can be enabled. - if (enabled || problems.empty()) { - ui->enabledCheckbox->setDisabled(false); - ui->enabledCheckbox->setToolTip(""); - ui->enabledCheckbox->setChecked(enabled); - } - // Plugin is disable and cannot be enabled. - else { - ui->enabledCheckbox->setDisabled(true); - ui->enabledCheckbox->setChecked(false); - if (problems.size() == 1) { - ui->enabledCheckbox->setToolTip(problems[0].shortDescription()); - } else { - QStringList descriptions; - for (auto& problem : problems) { - descriptions.append(problem.shortDescription()); - } - ui->enabledCheckbox->setToolTip("
      • " + descriptions.join("
      • ") + - "
      "); - } - } - - QVariantMap settings = current->data(0, SettingsRole).toMap(); - QVariantMap descriptions = current->data(0, DescriptionsRole).toMap(); - ui->pluginSettingsList->setEnabled(settings.count() != 0); - for (auto iter = settings.begin(); iter != settings.end(); ++iter) { - QTreeWidgetItem* newItem = new QTreeWidgetItem(QStringList(iter.key())); - QVariant value = *iter; - QString description; - { - auto descriptionIter = descriptions.find(iter.key()); - if (descriptionIter != descriptions.end()) { - description = descriptionIter->toString(); - } - } - - ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate()); - newItem->setData(1, Qt::DisplayRole, value); - newItem->setData(1, Qt::EditRole, value); - newItem->setToolTip(1, description); - - newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); - ui->pluginSettingsList->addTopLevelItem(newItem); - } - - ui->pluginSettingsList->resizeColumnToContents(0); - ui->pluginSettingsList->resizeColumnToContents(1); -} - -void PluginsSettingsTab::deleteBlacklistItem() -{ - ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); -} - -void PluginsSettingsTab::storeSettings(QTreeWidgetItem* pluginItem) -{ - if (pluginItem != nullptr && pluginItem->data(0, PluginRole).isValid()) { - QVariantMap settings = pluginItem->data(0, SettingsRole).toMap(); - - for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) { - const QTreeWidgetItem* item = ui->pluginSettingsList->topLevelItem(i); - settings[item->text(0)] = item->data(1, Qt::DisplayRole); - } - - pluginItem->setData(0, SettingsRole, settings); - } -} diff --git a/src/settingsdialogplugins.h b/src/settingsdialogplugins.h deleted file mode 100644 index 218bd7469..000000000 --- a/src/settingsdialogplugins.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef SETTINGSDIALOGPLUGINS_H -#define SETTINGSDIALOGPLUGINS_H - -#include "filterwidget.h" - -#include "settings.h" -#include "settingsdialog.h" - -class PluginsSettingsTab : public SettingsTab -{ -public: - PluginsSettingsTab(Settings& settings, PluginManager& pluginManager, - SettingsDialog& dialog); - - void update(); - void closing() override; - -private: - void on_pluginsList_currentItemChanged(QTreeWidgetItem* current, - QTreeWidgetItem* previous); - void deleteBlacklistItem(); - void storeSettings(QTreeWidgetItem* pluginItem); - -private slots: - - /** - * @brief Update the list item to display inactive plugins. - */ - void updateListItems(); - - /** - * @brief Filter the plugin list according to the filter widget. - * - */ - void filterPluginList(); - - /** - * @brief Retrieve the plugin associated to the given item in the list. - * - */ - MOBase::IPlugin* plugin(QTreeWidgetItem* pluginItem) const; - - enum - { - PluginRole = Qt::UserRole, - SettingsRole = Qt::UserRole + 1, - DescriptionsRole = Qt::UserRole + 2 - }; - -private: - PluginManager* m_pluginManager; - - MOBase::FilterWidget m_filter; -}; - -#endif // SETTINGSDIALOGPLUGINS_H From c9f592049ec49448707d924ad20496d656cb153b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 5 Aug 2024 21:51:12 +0200 Subject: [PATCH 19/26] Fix after rebase. --- src/extensionlistproxy.h | 2 +- src/extensionmanager.h | 2 +- src/extensionwatcher.h | 2 +- src/organizer_en.ts | 809 +++++++++++++------------------ src/organizercore.cpp | 2 +- src/pluginmanager.h | 20 +- src/previewgenerator.cpp | 4 +- src/proxyqt.h | 2 +- src/settingsdialogextensionrow.h | 2 +- src/thememanager.cpp | 122 ++++- src/thememanager.h | 8 +- src/translationmanager.h | 2 +- 12 files changed, 470 insertions(+), 507 deletions(-) diff --git a/src/extensionlistproxy.h b/src/extensionlistproxy.h index 47a394f69..84245cdc2 100644 --- a/src/extensionlistproxy.h +++ b/src/extensionlistproxy.h @@ -1,7 +1,7 @@ #ifndef EXTENSIONLISTPROXY_H #define EXTENSIONLISTPROXY_H -#include +#include #include "extensionmanager.h" diff --git a/src/extensionmanager.h b/src/extensionmanager.h index 5ecc05595..3cef8d689 100644 --- a/src/extensionmanager.h +++ b/src/extensionmanager.h @@ -7,7 +7,7 @@ #include #include -#include +#include #include "extensionwatcher.h" diff --git a/src/extensionwatcher.h b/src/extensionwatcher.h index 5dd47259b..2d476e1b2 100644 --- a/src/extensionwatcher.h +++ b/src/extensionwatcher.h @@ -1,7 +1,7 @@ #ifndef EXTENSIONWATCHER_H #define EXTENSIONWATCHER_H -#include +#include // an extension watcher is a class that watches extensions get loaded/unloaded, // typically to extract information from theme that are needed by MO2 diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 5715889bd..b25ece0a5 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -836,44 +836,6 @@ p, li { white-space: pre-wrap; } - - DisableProxyPluginDialog - - - Really disable plugin? - - - - - Disabling the '%1' plugin will prevent the following plugins from working: - - - - - Plugin - - - - - Description - - - - - Do you want to continue? You will need to restart Mod Organizer for the change to take effect. - - - - - Yes - - - - - No - - - DownloadList @@ -1796,197 +1758,213 @@ Right now the only case I know of where this needs to be overwritten is for the + + ExtensionListItemWidget + + + Form + + + + + + + + TextLabel + + + FileTree - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + File '%1' does not exist, you may need to refresh. - + (only has %1 file(s)) - + %1 file(s) selected - + &Add as Executable - + Add this file to the executables list - + This file is not executable - + Reveal in E&xplorer - + Opens the file in Explorer - - - - - - - + + + + + + + This file is in an archive - + Open &Mod Info - + Opens the Mod Info Window - + This file is not in a managed mod - + &Un-Hide - + Un-hides the file - + &Hide - + Hides the file - + &Execute - + Launches this program - + Execute with &VFS - + Launches this program hooked to the VFS - + &Open - + Opens this file with its default handler - + Open with &VFS - + Opens this file with its default handler hooked to the VFS - + &Preview - + Previews this file within Mod Organizer - + This file is in an archive or has no preview handler associated with it - + &Save Tree to Text File... - + Writes the list of files to a text file - + &Refresh - + Refreshes the list - + Ex&pand All - + &Collapse All @@ -2501,96 +2479,96 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Switching instances - + Mod Organizer must restart to manage the instance '%1'. - + This confirmation can be disabled in the settings. - + Restart Mod Organizer - - + + Cancel - - + + Rename instance - + The active instance cannot be renamed. - + Instance name - + Error - + Failed to rename "%1" to "%2": %3 - - - + + + Deleting instance - + The active instance cannot be deleted. - + These files and folders will be deleted - + All checked items will be deleted. - + Move to the recycle bin - + Delete permanently - + Nothing to delete. - + A portable instance already exists. @@ -2731,37 +2709,37 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MOApplication - + Failed to create log folder. - + Failed to set up data paths. - + Download started - + This shortcut or command line is for instance '%1', but the current instance is '%2'. - + This shortcut or command line is for profile '%1', but the current profile is '%2'. - + an error occurred: %1 - + an error occurred @@ -3003,7 +2981,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Sort the plugins using LOOT. @@ -3137,7 +3115,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Name @@ -3395,7 +3373,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Endorse Mod Organizer @@ -3479,169 +3457,169 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Toolbar and Menu - + Desktop - + Start Menu - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - + Endorse - + Won't Endorse - + First Steps Translation strings for tutorial names - + Conflict Resolution - + Overview - + Help on UI - + Documentation - - + + Game Support Wiki - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help" menu. - + Never ask to show tutorials - + Do you know how to mod this game? Do you need to learn? There's a game support wiki available! Click OK to open the wiki. In the future, you can access this link from the "Help" menu. - + Category Setup - + Please choose how to handle the default category setup. If you've already connected to Nexus, you can automatically import Nexus categories for this game (if applicable). Otherwise, use the old Mod Organizer default category structure, or leave the categories blank (for manual setup). - - + + &Import Nexus Categories - + Use &Old Category Defaults - + Do &Nothing - + This is your first time running version 2.5 or higher with an old MO2 instance. The category system now relies on an updated system to map Nexus categories. In order to assign Nexus categories automatically, you will need to import the Nexus categories for the currently managed game and map them to your preferred category structure. @@ -3652,321 +3630,321 @@ As a final option, you can disable Nexus category mapping altogether, which can - + &Open Categories Dialog - + &Disable Nexus Mappings - + &Close - + &Don't show this again - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + Update available - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + None of your %1 mods appear to have had recent file updates. - + All of your mods have been checked recently. We restrict update checks to help preserve your available API requests. - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Error %1: Request to Nexus failed: %2 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + Remove '%1' from the toolbar - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file @@ -4435,12 +4413,12 @@ p, li { white-space: pre-wrap; } ModInfoRegular - + %1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory - + Categories: <br> @@ -5626,208 +5604,208 @@ Please enter a name: OrganizerCore - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + Failed to write settings - + An error occurred trying to write back MO settings to %1: %2 - + Download started - + Download failed - + The selected profile '%1' does not exist. The profile '%2' will be used instead - + Installation cancelled - + Another installation is currently in progress. - + Installation successful - + Configure Mod - + This mod contains ini tweaks. Do you want to configure them now? - + mod not found: %1 - + Extraction cancelled - + The installation was cancelled while extracting files. If this was prior to a FOMOD setup, this warning may be ignored. However, if this was during installation, the mod will likely be missing files. - + file not found: %1 - - + + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + File '%1' not found. - + Failed to generate preview for %1 - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + You need to be logged in with Nexus - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + Error - + The designated write target "%1" is not enabled. @@ -5950,55 +5928,6 @@ Continue? - - PluginContainer - - - Plugin error - - - - - Mod Organizer failed to load the plugin '%1' last time it was started. - - - - - The plugin can be skipped for this session, blacklisted, or loaded normally, in which case it might fail again. Blacklisted plugins can be re-enabled later in the settings. - - - - - Skip this plugin - - - - - Blacklist this plugin - - - - - Load this plugin - - - - - Some plugins could not be loaded - - - - - - Description missing - - - - - The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - - - PluginList @@ -6302,44 +6231,44 @@ Continue? - PluginTypeName + PluginManager + + + Plugin + + - + Diagnose - + Game - + Installer - + Mod Page - + Preview - + Tool - - Proxy - - - - + File Mapper @@ -6869,12 +6798,12 @@ p, li { white-space: pre-wrap; } - + Download URL must start with https:// - + Download started @@ -6890,7 +6819,7 @@ p, li { white-space: pre-wrap; } - + Portable @@ -7137,14 +7066,6 @@ p, li { white-space: pre-wrap; } empty field name - - - Disabling the '%1' plugin will prevent the following %2 plugin(s) from working: - - - - - No menu available @@ -7193,7 +7114,7 @@ Destination: - + Disabled because @@ -7203,22 +7124,22 @@ Destination: - + Cannot open instance '%1', failed to read INI file %2. - + Cannot open instance '%1', the managed game was not found in the INI file %2. Select the game managed by this instance. - + Cannot open instance '%1', the game plugin '%2' doesn't exist. It may have been deleted by an antivirus. Select another instance. - + Cannot open instance '%1', the game directory '%2' doesn't exist or the game plugin '%3' doesn't recognize it. Select the game managed by this instance. @@ -7235,7 +7156,7 @@ Destination: - + @@ -7245,7 +7166,7 @@ Destination: - + Failed to create "%1". Your user account probably lacks permission. @@ -7352,33 +7273,33 @@ Destination: - + Please use "Help" from the toolbar to get usage instructions to all elements - + Visit %1 on Nexus - - + + <Manage...> - + failed to parse profile %1: %2 - + Instance at '%1' not found. Select another instance. - + Instance at '%1' not found. You must create a new instance @@ -7506,15 +7427,10 @@ Destination: - + One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is likely to be incompatible with MO2's virtual filesystem. - - - failed to initialize plugin %1: %2 - - failed to access %1 @@ -7623,12 +7539,12 @@ This program is known to cause issues with Mod Organizer, such as freezing or bl - + Confirm? - + This will reset all the choices you made to dialogs and make them all visible again. Continue? @@ -7715,31 +7631,6 @@ This program is known to cause issues with Mod Organizer, such as freezing or bl The given path was not recognized as a valid game installation. The current game plugin requires the executable to be in a "%1" subfolder of the game directory. - - - Cannot disable plugin - - - - - The '%1' plugin is used by the current game plugin and cannot disabled. - - - - - <p>Disabling the '%1' plugin will also disable the following plugins:</p><ul>%1</ul><p>Do you want to continue?</p> - - - - - Really disable plugin? - - - - - This plugin is required for Mod Organizer to work properly and cannot be disabled. - - Executables Blacklist @@ -8629,7 +8520,7 @@ If you disable this feature, MO will only display official DLCs this way. Please - + ... @@ -8766,7 +8657,7 @@ If you disable this feature, MO will only display official DLCs this way. Please - + Options @@ -8827,124 +8718,119 @@ If you disable this feature, MO will only display official DLCs this way. Please - - Plugins - - - - + Author: - + Version: - + Description: - + Enabled - + Key - + Value - + No plugin found. - + Blacklisted Plugins (use <del> to remove): - + Workarounds - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - + Enable parsing of Archives. This is an Experimental Feature. Has negative effects on performance and known incorrectness. - + <html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html> - + Enable archives parsing (experimental) - - + + Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. - + Lock GUI when running executable - + Steam - + Password - + Username - + Steam App ID - + The Steam AppID for your game - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -8960,69 +8846,69 @@ p, li { white-space: pre-wrap; } - + Network - + Disable automatic internet features - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + Offline Mode - + Use a proxy for network connections. - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + Use System HTTP Proxy + + - - + + - - Use "%1" as a placeholder for the URL. - + Custom browser - - + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - + Reset Window Geometries + - For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! @@ -9030,7 +8916,7 @@ p, li { white-space: pre-wrap; } - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended @@ -9039,69 +8925,74 @@ programs you are intentionally running. - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running. - + Executables Blacklist - + Back-date BSAs - - + + Extensions + + + + + Files to skip or ignore from the virtual file system. - + Skip File Suffixes - - + + Directories to skip or ignore from the virtual file system. - + Skip Directories - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + Diagnostics - + Logs and Crashes - + Log Level - + Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log". "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. @@ -9109,17 +9000,17 @@ programs you are intentionally running. - + Crash Dumps - + Decides which type of crash dumps are collected when injected processes crash. - + Decides which type of crash dumps are collected when injected processes crash. "None" Disables the generation of crash dumps by MO. @@ -9130,17 +9021,17 @@ programs you are intentionally running. - + Max Dumps To Keep - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. Set "Crash Dumps" above to None to disable crash dump collection. @@ -9148,22 +9039,22 @@ programs you are intentionally running. - + Integrated LOOT - + LOOT Log Level - + Click a link to open the location - + Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. @@ -9173,12 +9064,12 @@ programs you are intentionally running. - + Confirm - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? @@ -9224,14 +9115,6 @@ programs you are intentionally running. - - T - - - Plugin - - - TransferSavesDialog diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 76083a2cb..0cbbf1199 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1081,7 +1081,7 @@ bool OrganizerCore::previewFileWithAlternatives(QWidget* parent, QString fileNam libbsarch::memory_blob fileData = archiveLoader.extract_to_memory(fileName.toStdWString()); QByteArray convertedFileData((char*)(fileData.data), fileData.size); - QWidget* wid = m_PluginContainer->previewGenerator().genArchivePreview( + QWidget* wid = m_PluginManager->previewGenerator().genArchivePreview( convertedFileData, filePath); if (wid == nullptr) { reportError(tr("failed to generate preview for %1").arg(filePath)); diff --git a/src/pluginmanager.h b/src/pluginmanager.h index 9ffa6c2e8..11c56f170 100644 --- a/src/pluginmanager.h +++ b/src/pluginmanager.h @@ -11,16 +11,16 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "game_features.h" #include "previewgenerator.h" diff --git a/src/previewgenerator.cpp b/src/previewgenerator.cpp index ed2eb7c8a..cd5376cfe 100644 --- a/src/previewgenerator.cpp +++ b/src/previewgenerator.cpp @@ -67,9 +67,9 @@ QWidget* PreviewGenerator::genArchivePreview(const QByteArray& fileData, const QString& fileName) const { const QString ext = QFileInfo(fileName).suffix().toLower(); - auto& previews = m_PluginContainer.plugins(); + auto& previews = m_PluginManager.plugins(); for (auto* preview : previews) { - if (m_PluginContainer.isEnabled(preview) && + if (m_PluginManager.isEnabled(preview) && preview->supportedExtensions().contains(ext) && preview->supportsArchives()) { return preview->genDataPreview(fileData, fileName, m_MaxSize); } diff --git a/src/proxyqt.h b/src/proxyqt.h index 80c7fb95b..a234fab1e 100644 --- a/src/proxyqt.h +++ b/src/proxyqt.h @@ -3,7 +3,7 @@ #include -#include +#include class ProxyQtLoader : public MOBase::IPluginLoader { diff --git a/src/settingsdialogextensionrow.h b/src/settingsdialogextensionrow.h index e2aa52806..80a489f75 100644 --- a/src/settingsdialogextensionrow.h +++ b/src/settingsdialogextensionrow.h @@ -3,7 +3,7 @@ #include -#include "extension.h" +#include namespace Ui { diff --git a/src/thememanager.cpp b/src/thememanager.cpp index cc9dfa008..765f736ae 100644 --- a/src/thememanager.cpp +++ b/src/thememanager.cpp @@ -3,9 +3,10 @@ #include #include #include +#include -#include -#include +#include +#include #include "shared/appconfig.h" @@ -60,6 +61,87 @@ class ProxyStyle : public QProxyStyle } }; +namespace +{ +QString readWholeFile(std::filesystem::path const& path) +{ + QFile file(path); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + return {}; + } + + return QTextStream(&file).readAll(); +} + +QStringList extractTopStyleSheetComments(QString const& stylesheet) +{ + QTextStream stream(stylesheet.toUtf8()); + QStringList topComments; + + while (true) { + const auto byteLine = stream.readLine(); + if (byteLine.isNull()) { + break; + } + + const auto line = QString(byteLine).trimmed(); + + // skip empty lines + if (line.isEmpty()) { + continue; + } + + // only handle single line comments + if (!line.startsWith("/*")) { + break; + } + + topComments.push_back(line.mid(2, line.size() - 4).trimmed()); + } + + return topComments; +} + +QString extractBaseStyleFromStyleSheet(std::filesystem::path const& path, + QString const& stylesheet, + const QString& defaultStyle) +{ + // read the first line of the files that are either empty or comments + // + const auto topLines = extractTopStyleSheetComments(stylesheet); + + const auto factoryStyles = QStyleFactory::keys(); + + QString style = defaultStyle; + + for (const auto& line : topLines) { + if (!line.startsWith("mo2-base-style")) { + continue; + } + + const auto parts = line.split(":"); + if (parts.size() != 2) { + log::warn("found invalid top-comment for mo2 in {}: {}", path, line); + continue; + } + + const auto tmpStyle = parts[1].trimmed(); + const auto index = factoryStyles.indexOf(tmpStyle, 0, Qt::CaseInsensitive); + if (index == -1) { + log::warn("base style '{}' from style '{}' not found", tmpStyle, path, line); + continue; + } + + style = factoryStyles[index]; + log::info("found base style '{}' for style '{}'", style, path); + break; + } + + return style; +} + +} // namespace + ThemeManager::ThemeManager(QApplication* application) : m_app{application} { // add built-in themes @@ -67,8 +149,7 @@ ThemeManager::ThemeManager(QApplication* application) : m_app{application} // find the default theme - this might be a built-in Qt theme, or null, in which case // we just create a default theme - if (auto it = - m_baseThemesByIdentifier.find(m_app->style()->objectName().toStdString()); + if (auto it = m_baseThemesByIdentifier.find("windowsvista"); it != m_baseThemesByIdentifier.end()) { m_defaultTheme = it->second; } else { @@ -148,12 +229,14 @@ void ThemeManager::loadQtTheme(std::string_view themeIdentifier) void ThemeManager::loadExtensionTheme(std::shared_ptr const& theme) { + auto baseTheme = ToQString(m_defaultTheme->identifier()); + const auto stylesheet = buildStyleSheet(theme, baseTheme); + // load the default theme - m_app->setStyle( - new ProxyStyle(QStyleFactory::create(ToQString(m_defaultTheme->identifier())))); + m_app->setStyle(new ProxyStyle(QStyleFactory::create(baseTheme))); // build the stylesheet and set it - m_app->setStyleSheet(buildStyleSheet(theme)); + m_app->setStyleSheet(stylesheet); } void ThemeManager::unload() @@ -198,24 +281,19 @@ void ThemeManager::addQtThemes() } } -namespace +QString ThemeManager::buildStyleSheet(std::shared_ptr const& theme, + QString& baseTheme) const { -QString readWholeFile(std::filesystem::path const& path) -{ - QFile file(path); - if (!file.open(QFile::ReadOnly | QFile::Text)) { - return {}; - } + // read the file + const auto stylesheetContent = readWholeFile(theme->stylesheet()); - return QTextStream(&file).readAll(); -} -} // namespace + // check for base theme override + baseTheme = + extractBaseStyleFromStyleSheet(theme->stylesheet(), stylesheetContent, baseTheme); -QString ThemeManager::buildStyleSheet(std::shared_ptr const& theme) const -{ - // create the base stylesheet - QString stylesheet = patchStyleSheet(readWholeFile(theme->stylesheet()), - theme->stylesheet().parent_path()); + // patch the file + QString stylesheet = + patchStyleSheet(stylesheetContent, theme->stylesheet().parent_path()); for (auto&& themeAddition : m_additions) { if (themeAddition->isAdditionFor(*theme)) { diff --git a/src/thememanager.h b/src/thememanager.h index a5211ba2c..099755cc2 100644 --- a/src/thememanager.h +++ b/src/thememanager.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include "extensionwatcher.h" @@ -70,9 +70,11 @@ class ThemeManager : public ExtensionWatcher, // void loadExtensionTheme(std::shared_ptr const& theme); - // build a stylesheet for a theme + // build a stylesheet for a theme, extracting the base theme if needed (if no base + // theme is found, the baseTheme variable is kept untouched) // - QString buildStyleSheet(std::shared_ptr const& theme) const; + QString buildStyleSheet(std::shared_ptr const& theme, + QString& baseTheme) const; // patch the given stylesheet by replacing url() to be relative to the given folder // diff --git a/src/translationmanager.h b/src/translationmanager.h index 0afab7a82..691a2af0f 100644 --- a/src/translationmanager.h +++ b/src/translationmanager.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include "extensionwatcher.h" From 5061ff5e59640fc4c6bf2a41586038221a5997ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 9 Aug 2024 15:12:49 +0200 Subject: [PATCH 20/26] Default icon for extension. --- src/settingsdialogextensionrow.cpp | 35 +++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/settingsdialogextensionrow.cpp b/src/settingsdialogextensionrow.cpp index 7f5b37642..e39b0fccb 100644 --- a/src/settingsdialogextensionrow.cpp +++ b/src/settingsdialogextensionrow.cpp @@ -4,13 +4,46 @@ using namespace MOBase; +namespace +{ + +const auto& defaultIcon() +{ + static QIcon icon; + + if (icon.isNull()) { + const QImage baseIcon(":/MO/gui/app_icon"); + QImage grayIcon = baseIcon.convertToFormat(QImage::Format_ARGB32); + { + for (int y = 0; y < grayIcon.height(); ++y) { + QRgb* scanLine = (QRgb*)grayIcon.scanLine(y); + for (int x = 0; x < grayIcon.width(); ++x) { + QRgb pixel = *scanLine; + uint ci = uint(qGray(pixel)); + *scanLine = qRgba(ci, ci, ci, qAlpha(pixel) / 3); + ++scanLine; + } + } + } + icon = QIcon(QPixmap::fromImage(grayIcon)); + } + + return icon; +} + +} // namespace + ExtensionListItemWidget::ExtensionListItemWidget(const IExtension& extension) : ui{new Ui::ExtensionListItemWidget()}, m_extension{&extension} { ui->setupUi(this); + const auto& metadata = extension.metadata(); + + const auto& icon = metadata.icon().isNull() ? defaultIcon() : metadata.icon(); + QIcon icon = style()->standardIcon(QStyle::SP_DialogOkButton); - ui->extensionIcon->setPixmap(extension.metadata().icon().pixmap(QSize(48, 48))); + ui->extensionIcon->setPixmap(icon.pixmap(QSize(48, 48))); ui->extensionName->setText(extension.metadata().name()); ui->extensionDescription->setText(extension.metadata().description()); From 5c264af63e69368dfc5e9de65957d9af9d5b928e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 9 Aug 2024 17:47:38 +0200 Subject: [PATCH 21/26] Minor fixes. --- src/settingsdialogextensionrow.cpp | 4 +--- vcpkg-configuration.json | 21 --------------------- vcpkg.json | 21 +++++++++++++++++++++ 3 files changed, 22 insertions(+), 24 deletions(-) delete mode 100644 vcpkg-configuration.json diff --git a/src/settingsdialogextensionrow.cpp b/src/settingsdialogextensionrow.cpp index e39b0fccb..5a5b4e8e4 100644 --- a/src/settingsdialogextensionrow.cpp +++ b/src/settingsdialogextensionrow.cpp @@ -39,10 +39,8 @@ ExtensionListItemWidget::ExtensionListItemWidget(const IExtension& extension) ui->setupUi(this); const auto& metadata = extension.metadata(); + const auto& icon = metadata.icon().isNull() ? defaultIcon() : metadata.icon(); - const auto& icon = metadata.icon().isNull() ? defaultIcon() : metadata.icon(); - - QIcon icon = style()->standardIcon(QStyle::SP_DialogOkButton); ui->extensionIcon->setPixmap(icon.pixmap(QSize(48, 48))); ui->extensionName->setText(extension.metadata().name()); diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json deleted file mode 100644 index 6a56decd9..000000000 --- a/vcpkg-configuration.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "default-registry": { - "kind": "git", - "repository": "https://github.com/Microsoft/vcpkg", - "baseline": "f61a294e765b257926ae9e9d85f96468a0af74e7" - }, - "registries": [ - { - "kind": "git", - "repository": "https://github.com/Microsoft/vcpkg", - "baseline": "f61a294e765b257926ae9e9d85f96468a0af74e7", - "packages": ["boost*", "boost-*"] - }, - { - "kind": "git", - "repository": "https://github.com/ModOrganizer2/vcpkg-registry", - "baseline": "a1cd2ddbf1afb836419a5f1a9f70d6378fc41df2", - "packages": ["mo2-*", "7zip", "usvfs"] - } - ] -} diff --git a/vcpkg.json b/vcpkg.json index 013a13873..4cbcfa353 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -28,5 +28,26 @@ "usvfs" ] } + }, + "vcpkg-configuration": { + "default-registry": { + "kind": "git", + "repository": "https://github.com/Microsoft/vcpkg", + "baseline": "8ae59b5b1329a51875abc71d528da93d9c3e8972" + }, + "registries": [ + { + "kind": "git", + "repository": "https://github.com/Microsoft/vcpkg", + "baseline": "8ae59b5b1329a51875abc71d528da93d9c3e8972", + "packages": ["boost*", "boost-*"] + }, + { + "kind": "git", + "repository": "https://github.com/ModOrganizer2/vcpkg-registry", + "baseline": "84ff92223433d101738a3c6cef96fa6ae6a6f302", + "packages": ["mo2-*", "7zip", "usvfs"] + } + ] } } From 17a2e5ef31eec976f46920eaf74c47402f6a9336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 10 Aug 2024 12:25:42 +0200 Subject: [PATCH 22/26] Start implementing extension info side. --- CMakeLists.txt | 2 + src/CMakeLists.txt | 3 +- src/extensionmanager.cpp | 5 +- src/organizer_en.ts | 211 +++++++++++++++------------- src/settings.cpp | 24 ++-- src/settingsdialog.ui | 156 +++----------------- src/settingsdialogextensioninfo.cpp | 48 +++++++ src/settingsdialogextensioninfo.h | 29 ++++ src/settingsdialogextensioninfo.ui | 145 +++++++++++++++++++ src/settingsdialogextensionrow.h | 4 + src/settingsdialogextensions.cpp | 32 +++-- src/settingsdialogextensions.h | 5 + 12 files changed, 397 insertions(+), 267 deletions(-) create mode 100644 src/settingsdialogextensioninfo.cpp create mode 100644 src/settingsdialogextensioninfo.h create mode 100644 src/settingsdialogextensioninfo.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index bb8591a49..92f7b9066 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,8 @@ set(MO2_CMAKE_DEPRECATED_UIBASE_INCLUDE ON) project(organizer) +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) + # if MO2_INSTALL_IS_BIN is set, this means that we should install directly into the # installation prefix, without the bin/ subfolder, typically for a standalone build # to update an existing install diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d24a9c6e7..53ba50cfd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -121,7 +121,7 @@ mo2_add_filter(NAME src/categories GROUPS mo2_add_filter(NAME src/core GROUPS archivefiletree - githubpp + github inibakery installationmanager nexusinterface @@ -308,6 +308,7 @@ mo2_add_filter(NAME src/settingsdialog GROUPS settingsdialogpaths settingsdialogextensions settingsdialogextensionrow + settingsdialogextensioninfo settingsdialogworkarounds settingsdialogmodlist settingsdialogtheme diff --git a/src/extensionmanager.cpp b/src/extensionmanager.cpp index e894abe98..3df56f7dd 100644 --- a/src/extensionmanager.cpp +++ b/src/extensionmanager.cpp @@ -24,9 +24,8 @@ void ExtensionManager::loadExtensions(fs::path const& directory) continue; } - log::debug("extension correctly loaded from '{}': {}, {}", - entry.path().native(), extension->metadata().identifier(), - extension->metadata().type()); + log::debug("extension data loaded from '{}': {}, {}", entry.path().native(), + extension->metadata().identifier(), extension->metadata().type()); triggerWatchers(*extension); m_extensions.push_back(std::move(extension)); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index b25ece0a5..35d281d57 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1758,6 +1758,54 @@ Right now the only case I know of where this needs to be overwritten is for the + + ExtensionListInfoWidget + + + Form + + + + + Author: + + + + + Version: + + + + + Description: + + + + + Enabled + + + + + Key + + + + + Value + + + + + No plugin found. + + + + + Translation and theme extensions cannot be disabled. + + + ExtensionListItemWidget @@ -8520,7 +8568,7 @@ If you disable this feature, MO will only display official DLCs this way. Please - + ... @@ -8657,7 +8705,7 @@ If you disable this feature, MO will only display official DLCs this way. Please - + Options @@ -8718,119 +8766,84 @@ If you disable this feature, MO will only display official DLCs this way. Please - - Author: - - - - - Version: - - - - - Description: - - - - - Enabled - - - - - Key - - - - - Value - - - - - No plugin found. - - - - + Blacklisted Plugins (use <del> to remove): - + Workarounds - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - + Enable parsing of Archives. This is an Experimental Feature. Has negative effects on performance and known incorrectness. - + <html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html> - + Enable archives parsing (experimental) - - + + Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. - + Lock GUI when running executable - + Steam - + Password - + Username - + Steam App ID - + The Steam AppID for your game - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -8846,69 +8859,69 @@ p, li { white-space: pre-wrap; } - + Network - + Disable automatic internet features - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + Offline Mode - + Use a proxy for network connections. - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + Use System HTTP Proxy - - - - - - + + + + + + Use "%1" as a placeholder for the URL. - + Custom browser - - + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - + Reset Window Geometries - - + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! @@ -8916,7 +8929,7 @@ p, li { white-space: pre-wrap; } - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended @@ -8925,17 +8938,17 @@ programs you are intentionally running. - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running. - + Executables Blacklist - + Back-date BSAs @@ -8945,54 +8958,54 @@ programs you are intentionally running. - - + + Files to skip or ignore from the virtual file system. - + Skip File Suffixes - - + + Directories to skip or ignore from the virtual file system. - + Skip Directories - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + Diagnostics - + Logs and Crashes - + Log Level - + Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log". "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. @@ -9000,17 +9013,17 @@ programs you are intentionally running. - + Crash Dumps - + Decides which type of crash dumps are collected when injected processes crash. - + Decides which type of crash dumps are collected when injected processes crash. "None" Disables the generation of crash dumps by MO. @@ -9021,17 +9034,17 @@ programs you are intentionally running. - + Max Dumps To Keep - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. Set "Crash Dumps" above to None to disable crash dump collection. @@ -9039,22 +9052,22 @@ programs you are intentionally running. - + Integrated LOOT - + LOOT Log Level - + Click a link to open the location - + Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. diff --git a/src/settings.cpp b/src/settings.cpp index 5ece014b1..a1bb97d81 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1355,32 +1355,32 @@ void PluginSettings::registerPlugin(IPlugin* plugin) m_PluginSettings.insert(plugin->name(), QVariantMap()); m_PluginDescriptions.insert(plugin->name(), QVariantMap()); - for (const PluginSetting& setting : plugin->settings()) { - const QString settingName = plugin->name() + "/" + setting.key; + for (const auto& setting : plugin->settings()) { + const auto settingPath = plugin->name() + "/" + setting.name(); - QVariant temp = get(m_Settings, "Plugins", settingName, QVariant()); + QVariant temp = get(m_Settings, "Plugins", settingPath, QVariant()); // No previous enabled? Skip. - if (setting.key == "enabled" && (!temp.isValid() || !temp.canConvert())) { + if (setting.name() == "enabled" && (!temp.isValid() || !temp.canConvert())) { continue; } if (!temp.isValid()) { - temp = setting.defaultValue; - } else if (!temp.convert(setting.defaultValue.type())) { + temp = setting.defaultValue(); + } else if (!temp.convert(setting.defaultValue().metaType())) { log::warn("failed to interpret \"{}\" as correct type for \"{}\" in plugin " "\"{}\", using default", - temp.toString(), setting.key, plugin->name()); + temp.toString(), setting.name(), plugin->name()); - temp = setting.defaultValue; + temp = setting.defaultValue(); } - m_PluginSettings[plugin->name()][setting.key] = temp; + m_PluginSettings[plugin->name()][setting.name()] = temp; - m_PluginDescriptions[plugin->name()][setting.key] = + m_PluginDescriptions[plugin->name()][setting.name()] = QString("%1 (default: %2)") - .arg(setting.description) - .arg(setting.defaultValue.toString()); + .arg(setting.description()) + .arg(setting.defaultValue().toString()); } // Handle previous "enabled" settings: diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 330087c7a..80c4fffee 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -7,7 +7,7 @@ 0 0 820 - 592 + 607 @@ -17,7 +17,7 @@ - 0 + 5 @@ -44,8 +44,8 @@ 0 0 - 761 - 550 + 766 + 611 @@ -1052,8 +1052,8 @@ If you disable this feature, MO will only display official DLCs this way. Please 0 0 - 761 - 515 + 766 + 548 @@ -1537,7 +1537,7 @@ If you disable this feature, MO will only display official DLCs this way. Please - + @@ -1547,8 +1547,8 @@ If you disable this feature, MO will only display official DLCs this way. Please
      - - + + 0 @@ -1561,130 +1561,6 @@ If you disable this feature, MO will only display official DLCs this way. Please 0 - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - 6 - - - - - Author: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - Version: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - Description: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - - - - - Enabled - - - - - - - - - - 0 - - - false - - - false - - - false - - - false - - - 170 - - - - Key - - - - - Value - - - - - - - - No plugin found. - - - Qt::AlignCenter - - - @@ -1736,8 +1612,8 @@ If you disable this feature, MO will only display official DLCs this way. Please 0 0 - 778 - 475 + 780 + 489 @@ -2035,9 +1911,6 @@ p, li { white-space: pre-wrap; } - - Qt::Orientation::Horizontal - 40 @@ -2330,6 +2203,12 @@ programs you are intentionally running. QTableWidget
      colortable.h
      + + ExtensionListInfoWidget + QWidget +
      settingsdialogextensioninfo.h
      + 1 +
      languageBox @@ -2348,7 +2227,6 @@ programs you are intentionally running. browseOverwriteDirBtn managedGameDirEdit browseGameDirBtn - pluginSettingsList lockGUIBox diff --git a/src/settingsdialogextensioninfo.cpp b/src/settingsdialogextensioninfo.cpp new file mode 100644 index 000000000..9a98745e4 --- /dev/null +++ b/src/settingsdialogextensioninfo.cpp @@ -0,0 +1,48 @@ +#include "settingsdialogextensioninfo.h" + +#include "ui_settingsdialogextensioninfo.h" + +#include + +#include + +using namespace MOBase; + +ExtensionListInfoWidget::ExtensionListInfoWidget(QWidget* parent) + : QWidget(parent), ui{new Ui::ExtensionListInfoWidget()} +{ + ui->setupUi(this); + + ui->authorLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + ui->authorLabel->setOpenExternalLinks(true); +} + +void ExtensionListInfoWidget::setExtension(const IExtension& extension) +{ + m_extension = &extension; + + const auto& metadata = m_extension->metadata(); + const auto& author = metadata.author(); + + if (author.homepage().isEmpty()) { + ui->authorLabel->setText(metadata.author().name()); + } else { + ui->authorLabel->setText(QString::fromStdString( + std::format("{}", author.homepage(), author.name()))); + } + ui->descriptionLabel->setText(metadata.description()); + ui->versionLabel->setText(metadata.version().string(Version::FormatCondensed)); + + if (metadata.type() == ExtensionType::THEME || + metadata.type() == ExtensionType::TRANSLATION) { + ui->enabledCheckbox->setChecked(true); + ui->enabledCheckbox->setEnabled(false); + ui->enabledCheckbox->setToolTip( + tr("Translation and theme extensions cannot be disabled.")); + } else { + // TODO: + // ui->enabledCheckbox->setChecked(); + ui->enabledCheckbox->setEnabled(true); + ui->enabledCheckbox->setToolTip(QString()); + } +} diff --git a/src/settingsdialogextensioninfo.h b/src/settingsdialogextensioninfo.h new file mode 100644 index 000000000..3c013a9f3 --- /dev/null +++ b/src/settingsdialogextensioninfo.h @@ -0,0 +1,29 @@ +#ifndef SETTINGSDIALOGEXTENSIONINFO_H +#define SETTINGSDIALOGEXTENSIONINFO_H + +#include + +#include + +namespace Ui +{ +class ExtensionListInfoWidget; +} + +class ExtensionListInfoWidget : public QWidget +{ +public: + ExtensionListInfoWidget(QWidget* parent = nullptr); + + // set the extension to display + // + void setExtension(const MOBase::IExtension& extension); + +private: + Ui::ExtensionListInfoWidget* ui; + + // currently displayed extension (default to nullptr) + const MOBase::IExtension* m_extension{nullptr}; +}; + +#endif diff --git a/src/settingsdialogextensioninfo.ui b/src/settingsdialogextensioninfo.ui new file mode 100644 index 000000000..3951e3037 --- /dev/null +++ b/src/settingsdialogextensioninfo.ui @@ -0,0 +1,145 @@ + + + ExtensionListInfoWidget + + + + 0 + 0 + 400 + 410 + + + + Form + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + 6 + + + + + Author: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Version: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Description: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + Enabled + + + + + + + + + + 0 + + + false + + + false + + + false + + + false + + + 170 + + + + Key + + + + + Value + + + + + + + + No plugin found. + + + Qt::AlignCenter + + + + + + + + diff --git a/src/settingsdialogextensionrow.h b/src/settingsdialogextensionrow.h index 80a489f75..ce65278d1 100644 --- a/src/settingsdialogextensionrow.h +++ b/src/settingsdialogextensionrow.h @@ -15,6 +15,10 @@ class ExtensionListItemWidget : public QWidget public: ExtensionListItemWidget(MOBase::IExtension const& extension); + // retrieve the extension associated with this widget + // + const auto& extension() const { return *m_extension; } + private: Ui::ExtensionListItemWidget* ui; const MOBase::IExtension* m_extension; diff --git a/src/settingsdialogextensions.cpp b/src/settingsdialogextensions.cpp index 300655e88..9a86e76f9 100644 --- a/src/settingsdialogextensions.cpp +++ b/src/settingsdialogextensions.cpp @@ -10,14 +10,6 @@ using namespace MOBase; -struct PluginExtensionComparator -{ - bool operator()(const PluginExtension* lhs, const PluginExtension* rhs) const - { - return lhs->metadata().name().compare(rhs->metadata().name(), Qt::CaseInsensitive); - } -}; - ExtensionsSettingsTab::ExtensionsSettingsTab(Settings& s, ExtensionManager& extensionManager, PluginManager& pluginManager, @@ -45,6 +37,14 @@ ExtensionsSettingsTab::ExtensionsSettingsTab(Settings& s, ui->extensionsList->setItemWidget(item, widget); } + QObject::connect(ui->extensionsList, &QListWidget::currentItemChanged, + [this](QListWidgetItem* current, QListWidgetItem*) { + if (auto* widget = dynamic_cast( + ui->extensionsList->itemWidget(current))) { + extensionSelected(widget->extension()); + } + }); + // ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); // ui->pluginsList->setHeaderHidden(true); @@ -83,11 +83,6 @@ ExtensionsSettingsTab::ExtensionsSettingsTab(Settings& s, // m_filter.setEdit(ui->pluginFilterEdit); - // QObject::connect(ui->pluginsList, &QTreeWidget::currentItemChanged, - // [&](auto* current, auto* previous) { - // on_pluginsList_currentItemChanged(current, previous); - // }); - // QShortcut* delShortcut = // new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); // QObject::connect(delShortcut, &QShortcut::activated, &dialog(), [&] { @@ -205,6 +200,17 @@ void ExtensionsSettingsTab::closing() { // storeSettings(ui->pluginsList->currentItem()); } + +void ExtensionsSettingsTab::extensionSelected(IExtension const& extension) +{ + // TODO: store current settings in-memory for save later OR save live when modifying? + if (m_currentExtension) { + } + + m_currentExtension = &extension; + ui->infoWidget->setExtension(extension); +} + // // void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem* current, // QTreeWidgetItem* previous) diff --git a/src/settingsdialogextensions.h b/src/settingsdialogextensions.h index 96eb6ceb3..70760d0b2 100644 --- a/src/settingsdialogextensions.h +++ b/src/settingsdialogextensions.h @@ -43,6 +43,8 @@ private slots: // */ // MOBase::IPlugin* plugin(QListWidgetItem* pluginItem) const; + void extensionSelected(MOBase::IExtension const& extension); + enum { PluginRole = Qt::UserRole, @@ -54,6 +56,9 @@ private slots: ExtensionManager* m_extensionManager; PluginManager* m_pluginManager; + // the currently selected extension + const MOBase::IExtension* m_currentExtension{nullptr}; + MOBase::FilterWidget m_filter; }; From ee6624f0910815ac907d79f178876072859a605c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 10 Aug 2024 14:29:11 +0200 Subject: [PATCH 23/26] Use MO2_INSTALL_BIN from mo2-cmake instead of custom CMake variable. --- CMakeLists.txt | 13 +++---------- src/CMakeLists.txt | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92f7b9066..fc5b72f77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,20 +5,13 @@ set(MO2_CMAKE_DEPRECATED_UIBASE_INCLUDE ON) project(organizer) -set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +find_package(mo2-cmake CONFIG REQUIRED) -# if MO2_INSTALL_IS_BIN is set, this means that we should install directly into the -# installation prefix, without the bin/ subfolder, typically for a standalone build -# to update an existing install -if (MO2_INSTALL_IS_BIN) - set(_bin ".") -else() - set(_bin bin) -endif() +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) add_subdirectory(src) add_subdirectory(themes) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT organizer) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/dump_running_process.bat DESTINATION ${_bin}) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/dump_running_process.bat DESTINATION ${MO2_INSTALL_BIN}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 53ba50cfd..6a6deb0b3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,11 +30,11 @@ mo2_configure_target(organizer WARNINGS 4 TRANSLATIONS OFF) # we add translations "manually" to handle MO2_INSTALL_IS_BIN mo2_add_translations(organizer INSTALL_RELEASE - INSTALL_DIRECTORY "${_bin}/translations" + INSTALL_DIRECTORY "${MO2_INSTALL_BIN}/translations" SOURCES ${CMAKE_CURRENT_SOURCE_DIR}) mo2_set_project_to_run_from_install( - organizer EXECUTABLE ${CMAKE_INSTALL_PREFIX}/${_bin}/ModOrganizer.exe) + organizer EXECUTABLE ${CMAKE_INSTALL_PREFIX}/${MO2_INSTALL_BIN}/ModOrganizer.exe) target_link_libraries(organizer PRIVATE Shlwapi Bcrypt @@ -44,30 +44,30 @@ target_link_libraries(organizer PRIVATE Qt6::WebEngineWidgets Qt6::WebSockets Version Dbghelp) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.qt6" - DESTINATION ${_bin}/dlls + DESTINATION ${MO2_INSTALL_BIN}/dlls CONFIGURATIONS Release RelWithDebInfo RENAME dlls.manifest) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.debug.qt6" - DESTINATION ${_bin}/dlls + DESTINATION ${MO2_INSTALL_BIN}/dlls CONFIGURATIONS Debug RENAME dlls.manifest) if (NOT MO2_SKIP_TUTORIALS_INSTALL) install( DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tutorials" - DESTINATION ${_bin}) + DESTINATION ${MO2_INSTALL_BIN}) endif() install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/resources/markdown.html" - DESTINATION ${_bin}/resources) + DESTINATION ${MO2_INSTALL_BIN}/resources) # install ModOrganizer.exe itself -install(FILES $ DESTINATION ${_bin}) +install(FILES $ DESTINATION ${MO2_INSTALL_BIN}) # install dependencies DLLs -install(FILES $ DESTINATION ${_bin}/dlls) -install(FILES $ DESTINATION ${_bin}/dlls) -install(FILES $ DESTINATION ${_bin}/dlls) +install(FILES $ DESTINATION ${MO2_INSTALL_BIN}/dlls) +install(FILES $ DESTINATION ${MO2_INSTALL_BIN}/dlls) +install(FILES $ DESTINATION ${MO2_INSTALL_BIN}/dlls) # this may copy over the ones from uibase/usvfs # - when building with mob, this should not matter as the files should be identical @@ -83,7 +83,7 @@ install(FILES $ $ $ -DESTINATION ${_bin}) +DESTINATION ${MO2_INSTALL_BIN}) # do not install PDB if CMAKE_INSTALL_PREFIX is "bin" if (NOT MO2_INSTALL_IS_BIN) @@ -91,7 +91,7 @@ if (NOT MO2_INSTALL_IS_BIN) endif() mo2_deploy_qt( - DIRECTORY ${_bin} + DIRECTORY ${MO2_INSTALL_BIN} BINARIES ModOrganizer.exe $) # set source groups for VS From 263eb6a55275dc8e8de961628dbd7ecd7e3257ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 10 Aug 2024 14:29:21 +0200 Subject: [PATCH 24/26] Minor updates of themes. --- themes/CMakeLists.txt | 2 +- themes/nighteyes-theme/metadata.json | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/themes/CMakeLists.txt b/themes/CMakeLists.txt index 58fc857d6..edc48ad52 100644 --- a/themes/CMakeLists.txt +++ b/themes/CMakeLists.txt @@ -8,6 +8,6 @@ foreach(theme_directory ${theme_directories}) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${theme_directory}") file(READ ${theme_directory}/metadata.json JSON_METADATA) string(JSON theme_identifier GET ${JSON_METADATA} id) - install(DIRECTORY ${theme_directory}/ DESTINATION bin/extensions/${theme_identifier}) + install(DIRECTORY ${theme_directory}/ DESTINATION ${MO2_INSTALL_BIN}/extensions/${theme_identifier}) endif() endforeach() diff --git a/themes/nighteyes-theme/metadata.json b/themes/nighteyes-theme/metadata.json index cf9d8fc7b..d8d7b6a3f 100644 --- a/themes/nighteyes-theme/metadata.json +++ b/themes/nighteyes-theme/metadata.json @@ -2,8 +2,12 @@ "id": "mo2-theme-nighteyes", "type": "theme", "name": "Night Eyes Theme", + "author": { + "name": "ciathyza", + "homepage": "https://github.com/ciathyza" + }, "description": "Night Eyes theme for ModOrganizer2.", - "version": "1.0.0", + "version": "1.2.0", "content": { "themes": { "nighteyes": { From 890bee54442f78e6643b617846648eed97cbcda7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 10 Aug 2024 14:30:08 +0200 Subject: [PATCH 25/26] Refactor and clean plugin settings stuff. --- .gitignore | 1 + src/CMakeLists.txt | 1 + src/extensionsettings.cpp | 147 +++++++++++++++++++++++ src/extensionsettings.h | 86 +++++++++++++ src/organizer_en.ts | 32 ++--- src/pluginmanager.cpp | 15 +-- src/settings.cpp | 245 -------------------------------------- src/settings.h | 105 +--------------- src/settingsutilities.h | 43 ++----- 9 files changed, 262 insertions(+), 413 deletions(-) create mode 100644 src/extensionsettings.cpp create mode 100644 src/extensionsettings.h diff --git a/.gitignore b/.gitignore index 935c42609..39ca36c12 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,6 @@ edit /msbuild.log /*std*.log /*build +.vscode /src/version.aps diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6a6deb0b3..3e57081a8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -296,6 +296,7 @@ mo2_add_filter(NAME src/register GROUPS ) mo2_add_filter(NAME src/settings GROUPS + extensionsettings settings settingsutilities ) diff --git a/src/extensionsettings.cpp b/src/extensionsettings.cpp new file mode 100644 index 000000000..7e1a9dc8a --- /dev/null +++ b/src/extensionsettings.cpp @@ -0,0 +1,147 @@ +#include "extensionsettings.h" + +#include "settingsutilities.h" + +using namespace MOBase; + +static const QString PLUGINS_GROUP = "Plugins"; +static const QString PLUGINS_PERSISTENT_GROUP = "PluginPersistance"; + +PluginSettings::PluginSettings(QSettings& settings) : m_Settings(settings) {} + +QString PluginSettings::path(const QString& pluginName, const QString& key) +{ + return pluginName + "/" + key; +} + +void PluginSettings::checkPluginSettings(const IPlugin* plugin) const +{ + for (const auto& setting : plugin->settings()) { + const auto settingPath = path(plugin->name(), setting.name()); + + QVariant temp = get(m_Settings, PLUGINS_GROUP, settingPath, QVariant()); + + // No previous enabled? Skip. + if (setting.name() == "enabled" && (!temp.isValid() || !temp.canConvert())) { + continue; + } + + if (!temp.isValid()) { + temp = setting.defaultValue(); + } else if (!temp.convert(setting.defaultValue().metaType())) { + log::warn("failed to interpret \"{}\" as correct type for \"{}\" in plugin " + "\"{}\", using default", + temp.toString(), setting.name(), plugin->name()); + + temp = setting.defaultValue(); + } + } +} + +void PluginSettings::fixPluginEnabledSetting(const IPlugin* plugin) +{ + // handle previous "enabled" settings + // TODO: keep this? + const auto previousEnabledPath = plugin->name() + "/enabled"; + const QVariant previousEnabled = + get(m_Settings, PLUGINS_GROUP, previousEnabledPath, QVariant()); + if (previousEnabled.isValid()) { + setPersistent(plugin->name(), "enabled", previousEnabled.toBool(), true); + + // We need to drop it manually in Settings since it is not possible to remove + // plugin settings: + remove(m_Settings, PLUGINS_GROUP, previousEnabledPath); + } +} + +QVariant PluginSettings::setting(const QString& pluginName, const QString& key, + const QVariant& defaultValue) const +{ + return get(m_Settings, "Settings", path(pluginName, key), defaultValue); +} + +void PluginSettings::setSetting(const QString& pluginName, const QString& key, + const QVariant& value) +{ + const auto settingPath = path(pluginName, key); + const auto oldValue = + get(m_Settings, PLUGINS_GROUP, settingPath, QVariant()); + set(m_Settings, PLUGINS_GROUP, settingPath, value); + emit pluginSettingChanged(pluginName, key, oldValue, value); +} + +QVariant PluginSettings::persistent(const QString& pluginName, const QString& key, + const QVariant& def) const +{ + return get(m_Settings, "PluginPersistance", pluginName + "/" + key, def); +} + +void PluginSettings::setPersistent(const QString& pluginName, const QString& key, + const QVariant& value, bool sync) +{ + set(m_Settings, PLUGINS_PERSISTENT_GROUP, pluginName + "/" + key, value); + + if (sync) { + m_Settings.sync(); + } +} + +void PluginSettings::addBlacklist(const QString& fileName) +{ + m_PluginBlacklist.insert(fileName); + writeBlacklist(); +} + +bool PluginSettings::blacklisted(const QString& fileName) const +{ + return m_PluginBlacklist.contains(fileName); +} + +void PluginSettings::setBlacklist(const QStringList& pluginNames) +{ + m_PluginBlacklist.clear(); + + for (const auto& name : pluginNames) { + m_PluginBlacklist.insert(name); + } +} + +const QSet& PluginSettings::blacklist() const +{ + return m_PluginBlacklist; +} + +void PluginSettings::save() +{ + m_Settings.sync(); + writeBlacklist(); +} + +void PluginSettings::writeBlacklist() +{ + const auto current = readBlacklist(); + + if (current.size() > m_PluginBlacklist.size()) { + // Qt can't remove array elements, the section must be cleared + removeSection(m_Settings, "pluginBlacklist"); + } + + ScopedWriteArray swa(m_Settings, "pluginBlacklist", m_PluginBlacklist.size()); + + for (const QString& plugin : m_PluginBlacklist) { + swa.next(); + swa.set("name", plugin); + } +} + +QSet PluginSettings::readBlacklist() const +{ + QSet set; + + ScopedReadArray sra(m_Settings, "pluginBlacklist"); + sra.for_each([&] { + set.insert(sra.get("name")); + }); + + return set; +} diff --git a/src/extensionsettings.h b/src/extensionsettings.h new file mode 100644 index 000000000..80caa9b91 --- /dev/null +++ b/src/extensionsettings.h @@ -0,0 +1,86 @@ +#ifndef EXTENSIONSETTINGS_H +#define EXTENSIONSETTINGS_H + +#include +#include + +#include + +// settings about plugins +// +class PluginSettings : public QObject +{ + Q_OBJECT + +public: + PluginSettings(QSettings& settings); + + // fix enabled settings from previous MO2 installation + // + void fixPluginEnabledSetting(const MOBase::IPlugin* plugin); + + // check that the settings stored for the given plugin are of the appropriate type, + // warning user if not + // + void checkPluginSettings(const MOBase::IPlugin* plugin) const; + + // returns the plugin setting for the given key + // + QVariant setting(const QString& pluginName, const QString& key, + const QVariant& defaultValue = {}) const; + + // sets the plugin setting for the given key + // + void setSetting(const QString& pluginName, const QString& key, const QVariant& value); + + // get/set persistent settings + QVariant persistent(const QString& pluginName, const QString& key, + const QVariant& def) const; + void setPersistent(const QString& pluginName, const QString& key, + const QVariant& value, bool sync); + + // adds the given plugin to the blacklist + // + void addBlacklist(const QString& fileName); + + // returns whether the given plugin is blacklisted + // + bool blacklisted(const QString& fileName) const; + + // overwrites the whole blacklist + // + void setBlacklist(const QStringList& pluginNames); + + // returns the blacklist + // + const QSet& blacklist() const; + + // commits all the settings to the ini + // + void save(); + +Q_SIGNALS: + + // emitted when a plugin setting changes + // + void pluginSettingChanged(QString const& pluginName, const QString& key, + const QVariant& oldValue, const QVariant& newValue); + +private: + QSettings& m_Settings; + QSet m_PluginBlacklist; + + // retrieve the path to the given setting + // + static QString path(const QString& pluginName, const QString& key); + + // commits the blacklist to the ini + // + void writeBlacklist(); + + // reads the blacklist from the ini + // + QSet readBlacklist() const; +}; + +#endif diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 35d281d57..1c3ac2859 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -6247,7 +6247,6 @@ Continue? <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESHs:</td><td align=right>%11 </td><td align=right>%12</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr><tr><td>Overlay:</td><td align=right>%11 </td><td align=right>%12</td></tr></table> @@ -7538,19 +7537,12 @@ This program is known to cause issues with Mod Organizer, such as freezing or bl - - - - attempt to store setting for unknown plugin "%1" - - - - + Failed - + Failed to start the helper application: %1 @@ -8765,6 +8757,11 @@ If you disable this feature, MO will only display official DLCs this way. Please Preferred Servers (Drag & Drop) + + + Extensions + + Blacklisted Plugins (use <del> to remove): @@ -8928,6 +8925,11 @@ p, li { white-space: pre-wrap; } + + + Back-date BSAs + + Add executables to the blacklist to prevent them from @@ -8947,16 +8949,6 @@ programs you are intentionally running. Executables Blacklist - - - Back-date BSAs - - - - - Extensions - - diff --git a/src/pluginmanager.cpp b/src/pluginmanager.cpp index 4b0e6c649..882c84a86 100644 --- a/src/pluginmanager.cpp +++ b/src/pluginmanager.cpp @@ -383,7 +383,8 @@ IPlugin* PluginManager::registerPlugin(const PluginExtension& extension, plugin->setParent(this); if (m_core) { - m_core->settings().plugins().registerPlugin(pluginObj); + m_core->settings().plugins().fixPluginEnabledSetting(pluginObj); + m_core->settings().plugins().checkPluginSettings(pluginObj); } { // diagnosis plugin @@ -564,8 +565,6 @@ void PluginManager::unloadPlugin(MOBase::IPlugin* plugin, QObject* object) mapNames.erase(plugin->name()); } - m_core->settings().plugins().unregisterPlugin(plugin); - // force disconnection of the signals from the proxies // // this is a safety operations since those signals should be disconnected when the @@ -611,16 +610,6 @@ bool PluginManager::unloadPlugins(const MOBase::PluginExtension& extension) void PluginManager::unloadPlugins() { - if (m_core) { - // this will clear several structures that can hold on to pointers to - // plugins, as well as read the plugin blacklist from the ini file, which - // is used in loadPlugins() below to skip plugins - // - // note that the first thing loadPlugins() does is call unloadPlugins(), - // so this makes sure the blacklist is always available - m_core->settings().plugins().clearPlugins(); - } - bf::for_each(m_plugins, [](auto& t) { t.second.clear(); }); diff --git a/src/settings.cpp b/src/settings.cpp index a1bb97d81..84e32fea5 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1338,251 +1338,6 @@ QColor ColorSettings::idealTextColor(const QColor& rBackgroundColor) return QColor(iLuminance >= 128 ? Qt::black : Qt::white); } -PluginSettings::PluginSettings(QSettings& settings) : m_Settings(settings) {} - -void PluginSettings::clearPlugins() -{ - m_Plugins.clear(); - m_PluginSettings.clear(); - m_PluginBlacklist.clear(); - - m_PluginBlacklist = readBlacklist(); -} - -void PluginSettings::registerPlugin(IPlugin* plugin) -{ - m_Plugins.push_back(plugin); - m_PluginSettings.insert(plugin->name(), QVariantMap()); - m_PluginDescriptions.insert(plugin->name(), QVariantMap()); - - for (const auto& setting : plugin->settings()) { - const auto settingPath = plugin->name() + "/" + setting.name(); - - QVariant temp = get(m_Settings, "Plugins", settingPath, QVariant()); - - // No previous enabled? Skip. - if (setting.name() == "enabled" && (!temp.isValid() || !temp.canConvert())) { - continue; - } - - if (!temp.isValid()) { - temp = setting.defaultValue(); - } else if (!temp.convert(setting.defaultValue().metaType())) { - log::warn("failed to interpret \"{}\" as correct type for \"{}\" in plugin " - "\"{}\", using default", - temp.toString(), setting.name(), plugin->name()); - - temp = setting.defaultValue(); - } - - m_PluginSettings[plugin->name()][setting.name()] = temp; - - m_PluginDescriptions[plugin->name()][setting.name()] = - QString("%1 (default: %2)") - .arg(setting.description()) - .arg(setting.defaultValue().toString()); - } - - // Handle previous "enabled" settings: - if (m_PluginSettings[plugin->name()].contains("enabled")) { - setPersistent(plugin->name(), "enabled", - m_PluginSettings[plugin->name()]["enabled"].toBool(), true); - m_PluginSettings[plugin->name()].remove("enabled"); - m_PluginDescriptions[plugin->name()].remove("enabled"); - - // We need to drop it manually in Settings since it is not possible to remove plugin - // settings: - remove(m_Settings, "Plugins", plugin->name() + "/enabled"); - } -} - -void PluginSettings::unregisterPlugin(IPlugin* plugin) -{ - auto it = std::find(m_Plugins.begin(), m_Plugins.end(), plugin); - if (it != m_Plugins.end()) { - m_Plugins.erase(it); - } - m_PluginSettings.remove(plugin->name()); - m_PluginDescriptions.remove(plugin->name()); -} - -std::vector PluginSettings::plugins() const -{ - return m_Plugins; -} - -QVariant PluginSettings::setting(const QString& pluginName, const QString& key) const -{ - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - return QVariant(); - } - - auto iterSetting = iterPlugin->find(key); - if (iterSetting == iterPlugin->end()) { - return QVariant(); - } - - return *iterSetting; -} - -void PluginSettings::setSetting(const QString& pluginName, const QString& key, - const QVariant& value) -{ - auto iterPlugin = m_PluginSettings.find(pluginName); - - if (iterPlugin == m_PluginSettings.end()) { - throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"") - .arg(pluginName)); - } - - QVariant oldValue = m_PluginSettings[pluginName][key]; - - // store the new setting both in memory and in the ini - m_PluginSettings[pluginName][key] = value; - set(m_Settings, "Plugins", pluginName + "/" + key, value); - - // emit signal: - emit pluginSettingChanged(pluginName, key, oldValue, value); -} - -QVariantMap PluginSettings::settings(const QString& pluginName) const -{ - return m_PluginSettings[pluginName]; -} - -void PluginSettings::setSettings(const QString& pluginName, const QVariantMap& map) -{ - auto iterPlugin = m_PluginSettings.find(pluginName); - - if (iterPlugin == m_PluginSettings.end()) { - throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"") - .arg(pluginName)); - } - - QVariantMap oldSettings = m_PluginSettings[pluginName]; - m_PluginSettings[pluginName] = map; - - // Emit signals for settings that have been changed or added: - for (auto& k : map.keys()) { - // .value() return a default-constructed QVariant if k is not in oldSettings: - QVariant oldValue = oldSettings.value(k); - if (oldValue != map[k]) { - emit pluginSettingChanged(pluginName, k, oldSettings.value(k), map[k]); - } - } - - // Emit signals for settings that have been removed: - for (auto& k : oldSettings.keys()) { - if (!map.contains(k)) { - emit pluginSettingChanged(pluginName, k, oldSettings[k], QVariant()); - } - } -} - -QVariantMap PluginSettings::descriptions(const QString& pluginName) const -{ - return m_PluginDescriptions[pluginName]; -} - -void PluginSettings::setDescriptions(const QString& pluginName, const QVariantMap& map) -{ - m_PluginDescriptions[pluginName] = map; -} - -QVariant PluginSettings::persistent(const QString& pluginName, const QString& key, - const QVariant& def) const -{ - if (!m_PluginSettings.contains(pluginName)) { - return def; - } - - return get(m_Settings, "PluginPersistance", pluginName + "/" + key, def); -} - -void PluginSettings::setPersistent(const QString& pluginName, const QString& key, - const QVariant& value, bool sync) -{ - if (!m_PluginSettings.contains(pluginName)) { - throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"") - .arg(pluginName)); - } - - set(m_Settings, "PluginPersistance", pluginName + "/" + key, value); - - if (sync) { - m_Settings.sync(); - } -} - -void PluginSettings::addBlacklist(const QString& fileName) -{ - m_PluginBlacklist.insert(fileName); - writeBlacklist(); -} - -bool PluginSettings::blacklisted(const QString& fileName) const -{ - return m_PluginBlacklist.contains(fileName); -} - -void PluginSettings::setBlacklist(const QStringList& pluginNames) -{ - m_PluginBlacklist.clear(); - - for (const auto& name : pluginNames) { - m_PluginBlacklist.insert(name); - } -} - -const QSet& PluginSettings::blacklist() const -{ - return m_PluginBlacklist; -} - -void PluginSettings::save() -{ - for (auto iterPlugins = m_PluginSettings.begin(); - iterPlugins != m_PluginSettings.end(); ++iterPlugins) { - for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); - ++iterSettings) { - const auto key = iterPlugins.key() + "/" + iterSettings.key(); - set(m_Settings, "Plugins", key, iterSettings.value()); - } - } - - writeBlacklist(); -} - -void PluginSettings::writeBlacklist() -{ - const auto current = readBlacklist(); - - if (current.size() > m_PluginBlacklist.size()) { - // Qt can't remove array elements, the section must be cleared - removeSection(m_Settings, "pluginBlacklist"); - } - - ScopedWriteArray swa(m_Settings, "pluginBlacklist", m_PluginBlacklist.size()); - - for (const QString& plugin : m_PluginBlacklist) { - swa.next(); - swa.set("name", plugin); - } -} - -QSet PluginSettings::readBlacklist() const -{ - QSet set; - - ScopedReadArray sra(m_Settings, "pluginBlacklist"); - sra.for_each([&] { - set.insert(sra.get("name")); - }); - - return set; -} - const QString PathSettings::BaseDirVariable = "%BASE_DIR%"; PathSettings::PathSettings(QSettings& settings) : m_Settings(settings) {} diff --git a/src/settings.h b/src/settings.h index f12fbb1b1..68a281428 100644 --- a/src/settings.h +++ b/src/settings.h @@ -20,13 +20,16 @@ along with Mod Organizer. If not, see . #ifndef SETTINGS_H #define SETTINGS_H -#include "envdump.h" -#include -#include #include #include +#include #include +#include + +#include "envdump.h" +#include "extensionsettings.h" + #ifdef interface #undef interface #endif @@ -278,102 +281,6 @@ class ColorSettings QSettings& m_Settings; }; -// settings about plugins -// -class PluginSettings : public QObject -{ - Q_OBJECT - -public: - PluginSettings(QSettings& settings); - - // forgets all the plugins - // - void clearPlugins(); - - // adds/removes the given plugin to the list and loads all of its settings - // - void registerPlugin(MOBase::IPlugin* plugin); - void unregisterPlugin(MOBase::IPlugin* plugin); - - // returns all the registered plugins - // - std::vector plugins() const; - - // returns the plugin setting for the given key - // - QVariant setting(const QString& pluginName, const QString& key) const; - - // sets the plugin setting for the given key - // - void setSetting(const QString& pluginName, const QString& key, const QVariant& value); - - // returns all settings - // - QVariantMap settings(const QString& pluginName) const; - - // overwrites all settings - // - void setSettings(const QString& pluginName, const QVariantMap& map); - - // returns all descriptions - // - QVariantMap descriptions(const QString& pluginName) const; - - // overwrites all descriptions - // - void setDescriptions(const QString& pluginName, const QVariantMap& map); - - // ? - QVariant persistent(const QString& pluginName, const QString& key, - const QVariant& def) const; - void setPersistent(const QString& pluginName, const QString& key, - const QVariant& value, bool sync); - - // adds the given plugin to the blacklist - // - void addBlacklist(const QString& fileName); - - // returns whether the given plugin is blacklisted - // - bool blacklisted(const QString& fileName) const; - - // overwrites the whole blacklist - // - void setBlacklist(const QStringList& pluginNames); - - // returns the blacklist - // - const QSet& blacklist() const; - - // commits all the settings to the ini - // - void save(); - -Q_SIGNALS: - - /** - * Emitted when a plugin setting changes. - */ - void pluginSettingChanged(QString const& pluginName, const QString& key, - const QVariant& oldValue, const QVariant& newValue); - -private: - QSettings& m_Settings; - std::vector m_Plugins; - QMap m_PluginSettings; - QMap m_PluginDescriptions; - QSet m_PluginBlacklist; - - // commits the blacklist to the ini - // - void writeBlacklist(); - - // reads the blacklist from the ini - // - QSet readBlacklist() const; -}; - // paths for the game and various components // // if the 'resolve' parameter is true, %BASE_DIR% is expanded; it's set to diff --git a/src/settingsutilities.h b/src/settingsutilities.h index cd55464e5..a5bd9c068 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -1,43 +1,16 @@ #ifndef SETTINGSUTILITIES_H #define SETTINGSUTILITIES_H -#include +#include + +#include +#include namespace MOBase { class ExpanderWidget; } -template -struct ValueConverter -{ - static const T& convert(const T& t) { return t; } -}; - -template -struct ValueConverter>> -{ - static QString convert(const T& t) - { - return QString("%1").arg(static_cast>(t)); - } -}; - -template <> -struct ValueConverter -{ - static QString convert(const QVariantList& t) - { - return QString("%1").arg(QVariant(t).toStringList().join(",")); - } -}; - -template <> -struct ValueConverter -{ - static QString convert(const QStringList& t) { return t.join(", "); } -}; - bool shouldLogSetting(const QString& displayName); template @@ -47,13 +20,11 @@ void logChange(const QString& displayName, std::optional oldValue, const T& n return; } - using VC = ValueConverter; - if (oldValue) { - MOBase::log::debug("setting '{}' changed from '{}' to '{}'", displayName, - VC::convert(*oldValue), VC::convert(newValue)); + MOBase::log::debug("setting '{}' changed from '{}' to '{}'", displayName, *oldValue, + newValue); } else { - MOBase::log::debug("setting '{}' set to '{}'", displayName, VC::convert(newValue)); + MOBase::log::debug("setting '{}' set to '{}'", displayName, newValue); } } From 8006988b2048586b125ecab2c4c1209bf36e9d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 12 Aug 2024 10:01:43 +0200 Subject: [PATCH 26/26] Start updating the settings tab for extension. --- CMakePresets.json | 2 +- src/CMakeLists.txt | 2 +- src/extensionmanager.cpp | 20 +++- src/extensionmanager.h | 7 ++ src/extensionsettings.cpp | 48 ++++++++- src/extensionsettings.h | 40 ++++++++ src/instancemanager.cpp | 2 +- src/moapplication.cpp | 2 +- src/nxmaccessmanager.cpp | 8 +- src/organizer_en.ts | 76 ++++++++------- src/pluginmanager.cpp | 32 ++---- src/pluginmanager.h | 5 - src/settings.cpp | 17 +++- src/settings.h | 4 + src/settingsdialogextensioninfo.cpp | 145 +++++++++++++++++++++++++++- src/settingsdialogextensioninfo.h | 24 +++++ src/settingsdialogextensioninfo.ui | 29 +----- src/settingsdialogextensions.cpp | 2 + src/texteditor.cpp | 2 +- src/translationmanager.cpp | 2 +- 20 files changed, 361 insertions(+), 108 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index eeeaaacd0..bda8b9afd 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -29,7 +29,7 @@ "value": "x64" }, "cacheVariables": { - "CMAKE_CXX_FLAGS": "/EHsc /MP /W4", + "CMAKE_CXX_FLAGS": "/EHsc /MP /W3", "VCPKG_TARGET_TRIPLET": { "type": "STRING", "value": "x64-windows-static-md" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3e57081a8..e4f0cc1cb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,7 +25,7 @@ set_target_properties(organizer PROPERTIES # disable translations because we want to be able to install somewhere else if # required -mo2_configure_target(organizer WARNINGS 4 TRANSLATIONS OFF) +mo2_configure_target(organizer WARNINGS 3 TRANSLATIONS OFF) # we add translations "manually" to handle MO2_INSTALL_IS_BIN mo2_add_translations(organizer diff --git a/src/extensionmanager.cpp b/src/extensionmanager.cpp index 3df56f7dd..422bd0e6f 100644 --- a/src/extensionmanager.cpp +++ b/src/extensionmanager.cpp @@ -1,10 +1,14 @@ #include "extensionmanager.h" -#include +#include + +#include "organizercore.h" using namespace MOBase; namespace fs = std::filesystem; +ExtensionManager::ExtensionManager(OrganizerCore* core) : m_core{core} {} + void ExtensionManager::loadExtensions(fs::path const& directory) { for (const auto& entry : fs::directory_iterator{directory}) { @@ -60,8 +64,18 @@ const IExtension* ExtensionManager::extension(QString const& identifier) const bool ExtensionManager::isEnabled(MOBase::IExtension const& extension) const { - // TODO - return true; + if (!m_core) { + return true; + } + + for (auto& requirement : extension.metadata().requirements()) { + // TODO: needs an organizerproxy... + // if (!requirement.check(m_core)) { + // return false; + //} + } + + return m_core->settings().extensions().isEnabled(extension, true); } bool ExtensionManager::isEnabled(QString const& identifier) const diff --git a/src/extensionmanager.h b/src/extensionmanager.h index 3cef8d689..a3f7f0711 100644 --- a/src/extensionmanager.h +++ b/src/extensionmanager.h @@ -10,10 +10,15 @@ #include #include "extensionwatcher.h" +#include "organizerproxy.h" + +class OrganizerCore; class ExtensionManager { public: + ExtensionManager(OrganizerCore* core); + // retrieve the list of currently loaded extensions // const auto& extensions() const { return m_extensions; } @@ -58,6 +63,8 @@ class ExtensionManager void triggerWatchers(const MOBase::IExtension& extension) const; private: + OrganizerCore* m_core; + std::unique_ptr m_proxy; std::vector> m_extensions; using WatcherMap = boost::fusion::map< diff --git a/src/extensionsettings.cpp b/src/extensionsettings.cpp index 7e1a9dc8a..669a3d5a8 100644 --- a/src/extensionsettings.cpp +++ b/src/extensionsettings.cpp @@ -4,9 +4,55 @@ using namespace MOBase; +static const QString EXTENSIONS_GROUP = "Extensions"; +static const QString EXTENSIONS_ENABLED_GROUP = "ExtensionsEnabled"; static const QString PLUGINS_GROUP = "Plugins"; static const QString PLUGINS_PERSISTENT_GROUP = "PluginPersistance"; +ExtensionSettings::ExtensionSettings(QSettings& settings) : m_Settings(settings) {} + +QString ExtensionSettings::path(const IExtension& extension, const Setting& setting) +{ + QString path = extension.metadata().identifier(); + if (!setting.group().isEmpty()) { + path += "/" + setting.group(); + } + return path + "/" + setting.name(); +} + +bool ExtensionSettings::isEnabled(const MOBase::IExtension& extension, + bool defaultValue) const +{ + return get(m_Settings, EXTENSIONS_ENABLED_GROUP, + extension.metadata().identifier(), defaultValue); +} + +void ExtensionSettings::setEnabled(const MOBase::IExtension& extension, + bool enabled) const +{ + set(m_Settings, EXTENSIONS_ENABLED_GROUP, extension.metadata().identifier(), enabled); +} + +QVariant ExtensionSettings::setting(const IExtension& extension, + const Setting& setting) const +{ + return get(m_Settings, EXTENSIONS_GROUP, path(extension, setting), + setting.defaultValue()); +} + +void ExtensionSettings::setSetting(const IExtension& extension, const Setting& setting, + const QVariant& value) +{ + set(m_Settings, EXTENSIONS_GROUP, path(extension, setting), value); +} + +// commits all the settings to the ini +// +void ExtensionSettings::save() +{ + m_Settings.sync(); +} + PluginSettings::PluginSettings(QSettings& settings) : m_Settings(settings) {} QString PluginSettings::path(const QString& pluginName, const QString& key) @@ -57,7 +103,7 @@ void PluginSettings::fixPluginEnabledSetting(const IPlugin* plugin) QVariant PluginSettings::setting(const QString& pluginName, const QString& key, const QVariant& defaultValue) const { - return get(m_Settings, "Settings", path(pluginName, key), defaultValue); + return get(m_Settings, PLUGINS_GROUP, path(pluginName, key), defaultValue); } void PluginSettings::setSetting(const QString& pluginName, const QString& key, diff --git a/src/extensionsettings.h b/src/extensionsettings.h index 80caa9b91..ca91423b9 100644 --- a/src/extensionsettings.h +++ b/src/extensionsettings.h @@ -4,8 +4,48 @@ #include #include +#include #include +// settings about extensions +class ExtensionSettings : public QObject +{ + Q_OBJECT + +public: + ExtensionSettings(QSettings& settings); + + // check if the specified extension is enabled in the settings + // + bool isEnabled(const MOBase::IExtension& extension, bool defaultValue = true) const; + + // set the extension as enabled or disabled in the settings + // + void setEnabled(const MOBase::IExtension& extension, bool enabled) const; + + // returns the plugin setting for the given key + // + QVariant setting(const MOBase::IExtension& extension, + const MOBase::Setting& setting) const; + + // sets the plugin setting for the given key + // + void setSetting(const MOBase::IExtension& extension, const MOBase::Setting& setting, + const QVariant& value); + + // commits all the settings to the ini + // + void save(); + +private: + QSettings& m_Settings; + + // retrieve the path to the given setting + // + static QString path(const MOBase::IExtension& extension, + const MOBase::Setting& setting); +}; + // settings about plugins // class PluginSettings : public QObject diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 29640776a..ebdf939d7 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -704,7 +704,7 @@ std::unique_ptr selectInstance() // since there is no instance currently active, load plugins with a null // OrganizerCore; see PluginManager::initPlugin() NexusInterface ni(nullptr); - ExtensionManager ec; + ExtensionManager ec(nullptr); ec.loadExtensions(QDir(QCoreApplication::applicationDirPath() + "/extensions") .filesystemAbsolutePath()); diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 18626ff9a..79994938d 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -215,7 +215,7 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) m_themes = std::make_unique(this); m_translations = std::make_unique(this); - m_extensions = std::make_unique(); + m_extensions = std::make_unique(m_core.get()); m_extensions->registerWatcher(*m_themes); m_extensions->registerWatcher(*m_translations); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 3efdcda01..ade36e3ab 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -162,11 +162,9 @@ NexusSSOLogin::NexusSSOLogin() : m_keyReceived(false), m_active(false) onConnected(); }); - QObject::connect(&m_socket, - qOverload(&QWebSocket::error), - [&](auto&& e) { - onError(e); - }); + QObject::connect(&m_socket, &QWebSocket::errorOccurred, [&](auto&& e) { + onError(e); + }); QObject::connect(&m_socket, &QWebSocket::sslErrors, [&](auto&& errors) { onSslErrors(errors); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 1c3ac2859..410b22d52 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1786,22 +1786,12 @@ Right now the only case I know of where this needs to be overwritten is for the - + Key - - Value - - - - - No plugin found. - - - - + Translation and theme extensions cannot be disabled. @@ -1822,6 +1812,24 @@ Right now the only case I know of where this needs to be overwritten is for the + + ExtensionSettingWidget + + + False + + + + + True + + + + + Edit + + + FileTree @@ -7382,94 +7390,94 @@ Destination: - + Connecting to Nexus... - + Waiting for Nexus... - + Opened Nexus in browser. - + Switch to your browser and accept the request. - + Finished. - + No answer from Nexus. - - + + A firewall might be blocking Mod Organizer. - + Nexus closed the connection. - + Cancelled. - + Failed to request %1 - - + + Cancelled - + Internal error - + HTTP code %1 - + Invalid JSON - + Bad response - + API key is empty - + SSL error - + Timed out @@ -7537,12 +7545,12 @@ This program is known to cause issues with Mod Organizer, such as freezing or bl - + Failed - + Failed to start the helper application: %1 diff --git a/src/pluginmanager.cpp b/src/pluginmanager.cpp index 882c84a86..48a6e09fe 100644 --- a/src/pluginmanager.cpp +++ b/src/pluginmanager.cpp @@ -73,7 +73,7 @@ PluginDetails::PluginDetails(PluginManager* manager, PluginExtension const& exte void PluginDetails::fetchRequirements() { - // m_requirements = m_plugin->requirements(); + m_requirements = m_plugin->requirements(); } std::vector PluginDetails::problems() const @@ -133,6 +133,11 @@ PluginManager::PluginManager(ExtensionManager const& manager, OrganizerCore* cor m_gameFeatures(std::make_unique(core, this)) { m_loaders = makeLoaders(); + + if (m_core) { + bf::at_key(m_plugins).push_back(m_core); + m_core->connectPlugins(this); + } } QStringList PluginManager::implementedInterfaces(IPlugin* plugin) const @@ -249,12 +254,7 @@ bool PluginManager::isEnabled(MOBase::IPlugin* plugin) const return plugin == m_core->managedGame(); } - // check the master of the group - const auto& d = details(plugin); - if (d.master() && d.master() != plugin) { - return isEnabled(d.master()); - } - + // TODO: allow disabling/enabling plugins alone? return m_extensions.isEnabled(details(plugin).extension()); } @@ -499,28 +499,12 @@ bool PluginManager::loadPlugins(const MOBase::PluginExtension& extension) continue; } - // find the best interface - auto it = std::min_element(std::begin(objectGroup), std::end(objectGroup), - [&](auto const& lhs, auto const& rhs) { - return isBetterInterface(lhs, rhs); - }); - IPlugin* master = qobject_cast(*it); - // register plugins in the group for (auto* object : objectGroup) { - IPlugin* plugin = registerPlugin(extension, object, objectGroup); - - if (plugin) { - m_details.at(plugin).m_master = master; - } + registerPlugin(extension, object, objectGroup); } } - // TODO: move this elsewhere, e.g., in core - if (m_core) { - bf::at_key(m_plugins).push_back(m_core); - m_core->connectPlugins(this); - } return true; } diff --git a/src/pluginmanager.h b/src/pluginmanager.h index 11c56f170..b51b6d229 100644 --- a/src/pluginmanager.h +++ b/src/pluginmanager.h @@ -48,10 +48,6 @@ class PluginDetails // const auto* proxy() const { return m_organizer; } - // the "master" of the group this plugin belongs to - // - MOBase::IPlugin* master() const { return m_master; } - // the extension containing this plugin // const MOBase::PluginExtension& extension() const { return *m_extension; } @@ -79,7 +75,6 @@ class PluginDetails PluginManager* m_manager; MOBase::IPlugin* m_plugin; const MOBase::PluginExtension* m_extension; - MOBase::IPlugin* m_master; std::vector> m_requirements; OrganizerProxy* m_organizer; diff --git a/src/settings.cpp b/src/settings.cpp index 84e32fea5..53b5bc96c 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -64,9 +64,10 @@ Settings* Settings::s_Instance = nullptr; Settings::Settings(const QString& path, bool globalInstance) : m_Settings(path, QSettings::IniFormat), m_Game(m_Settings), m_Geometry(m_Settings), m_Widgets(m_Settings, globalInstance), - m_Colors(m_Settings), m_Plugins(m_Settings), m_Paths(m_Settings), - m_Network(m_Settings, globalInstance), m_Nexus(*this, m_Settings), - m_Steam(*this, m_Settings), m_Interface(m_Settings), m_Diagnostics(m_Settings) + m_Colors(m_Settings), m_Extensions(m_Settings), m_Plugins(m_Settings), + m_Paths(m_Settings), m_Network(m_Settings, globalInstance), + m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings), m_Interface(m_Settings), + m_Diagnostics(m_Settings) { if (globalInstance) { if (s_Instance != nullptr) { @@ -457,6 +458,16 @@ const ColorSettings& Settings::colors() const return m_Colors; } +ExtensionSettings& Settings::extensions() +{ + return m_Extensions; +} + +const ExtensionSettings& Settings::extensions() const +{ + return m_Extensions; +} + PluginSettings& Settings::plugins() { return m_Plugins; diff --git a/src/settings.h b/src/settings.h index 68a281428..b0a01db1f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -768,6 +768,9 @@ class Settings : public QObject ColorSettings& colors(); const ColorSettings& colors() const; + ExtensionSettings& extensions(); + const ExtensionSettings& extensions() const; + PluginSettings& plugins(); const PluginSettings& plugins() const; @@ -818,6 +821,7 @@ public slots: GeometrySettings m_Geometry; WidgetSettings m_Widgets; ColorSettings m_Colors; + ExtensionSettings m_Extensions; PluginSettings m_Plugins; PathSettings m_Paths; NetworkSettings m_Network; diff --git a/src/settingsdialogextensioninfo.cpp b/src/settingsdialogextensioninfo.cpp index 9a98745e4..96dd33d4a 100644 --- a/src/settingsdialogextensioninfo.cpp +++ b/src/settingsdialogextensioninfo.cpp @@ -4,10 +4,99 @@ #include +#include + #include +#include "extensionmanager.h" +#include "pluginmanager.h" +#include "settings.h" + using namespace MOBase; +ExtensionSettingWidget::ExtensionSettingWidget(Setting const& setting, + QVariant const& value, QWidget* parent) + : QWidget(parent), m_value(value) +{ + setLayout(new QVBoxLayout()); + + { + auto* titleLabel = new QLabel(setting.title()); + auto font = titleLabel->font(); + font.setBold(true); + titleLabel->setFont(font); + layout()->addWidget(titleLabel); + } + + if (!setting.description().isEmpty()) { + auto* descriptionLabel = new QLabel(setting.description()); + auto font = descriptionLabel->font(); + font.setItalic(true); + font.setPointSize(static_cast(font.pointSize() * 0.85)); + descriptionLabel->setFont(font); + layout()->addWidget(descriptionLabel); + } + + { + QWidget* valueWidget = nullptr; + switch (setting.defaultValue().typeId()) { + case QMetaType::Bool: { + auto* comboBox = new QComboBox(); + comboBox->addItems({tr("False"), tr("True")}); + comboBox->setCurrentIndex(value.toBool()); + valueWidget = comboBox; + } break; + case QMetaType::QString: { + auto* lineEdit = new QLineEdit(value.toString()); + valueWidget = lineEdit; + } break; + case QMetaType::Float: + case QMetaType::Double: { + auto* lineEdit = new QLineEdit(QString::number(value.toInt())); + lineEdit->setValidator(new QDoubleValidator(lineEdit)); + valueWidget = lineEdit; + } break; + case QMetaType::Int: + case QMetaType::LongLong: + case QMetaType::UInt: + case QMetaType::ULongLong: { + auto* lineEdit = new QLineEdit(QString::number(value.toInt())); + lineEdit->setValidator(new QIntValidator(lineEdit)); + valueWidget = lineEdit; + } break; + case QMetaType::QColor: { + valueWidget = new QWidget(this); + auto* layout = new QHBoxLayout(); + valueWidget->setLayout(layout); + + const auto color = m_value.value(); + auto* textWidget = new QLabel(color.name()); + auto* colorWidget = new QLabel(""); + colorWidget->setStyleSheet( + QString("QLabel { background-color: %1; }").arg(color.name())); + auto* button = new QPushButton(tr("Edit")); + connect(button, &QPushButton::clicked, [textWidget, colorWidget, this]() { + const auto newColor = QColorDialog::getColor(m_value.value()); + if (newColor.isValid()) { + m_value = newColor; + textWidget->setText(newColor.name()); + colorWidget->setStyleSheet( + QString("QLabel { background-color: %1; }").arg(newColor.name())); + } + }); + + layout->addWidget(textWidget); + layout->addWidget(colorWidget, 1); + layout->addWidget(button); + } break; + } + + if (valueWidget) { + layout()->addWidget(valueWidget); + } + } +} + ExtensionListInfoWidget::ExtensionListInfoWidget(QWidget* parent) : QWidget(parent), ui{new Ui::ExtensionListInfoWidget()} { @@ -15,12 +104,30 @@ ExtensionListInfoWidget::ExtensionListInfoWidget(QWidget* parent) ui->authorLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); ui->authorLabel->setOpenExternalLinks(true); + + ui->pluginSettingsList->setRootIsDecorated(true); + ui->pluginSettingsList->setSelectionMode(QAbstractItemView::NoSelection); + ui->pluginSettingsList->setItemsExpandable(true); + ui->pluginSettingsList->setColumnCount(1); + ui->pluginSettingsList->header()->setSectionResizeMode( + 0, QHeaderView::ResizeMode::Stretch); +} + +void ExtensionListInfoWidget::setup(Settings& settings, + ExtensionManager& extensionManager, + PluginManager& pluginManager) +{ + m_settings = &settings; + m_extensionManager = &extensionManager; + m_pluginManager = &pluginManager; } void ExtensionListInfoWidget::setExtension(const IExtension& extension) { m_extension = &extension; + // update the header for the extension + const auto& metadata = m_extension->metadata(); const auto& author = metadata.author(); @@ -40,9 +147,43 @@ void ExtensionListInfoWidget::setExtension(const IExtension& extension) ui->enabledCheckbox->setToolTip( tr("Translation and theme extensions cannot be disabled.")); } else { - // TODO: - // ui->enabledCheckbox->setChecked(); + ui->enabledCheckbox->setChecked(m_extensionManager->isEnabled(extension)); ui->enabledCheckbox->setEnabled(true); ui->enabledCheckbox->setToolTip(QString()); } + + ui->pluginSettingsList->clear(); + + // update the list of settings + if (const auto* pluginExtension = dynamic_cast(m_extension)) { + + // TODO: refactor code somewhere to have direct access of the plugins for a given + // extension + for (auto& plugin : m_pluginManager->plugins()) { + if (&m_pluginManager->details(plugin).extension() != m_extension) { + continue; + } + + const auto settings = plugin->settings(); + if (settings.isEmpty()) { + continue; + } + + QTreeWidgetItem* pluginItem = new QTreeWidgetItem({plugin->localizedName()}); + ui->pluginSettingsList->addTopLevelItem(pluginItem); + + for (auto& setting : settings) { + auto* settingItem = new QTreeWidgetItem(); + auto* settingWidget = new ExtensionSettingWidget( + setting, m_settings->plugins().setting(plugin->name(), setting.name(), + setting.defaultValue())); + pluginItem->addChild(settingItem); + ui->pluginSettingsList->setItemWidget(settingItem, 0, settingWidget); + settingItem->setSizeHint(0, settingWidget->sizeHint()); + } + + pluginItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); + pluginItem->setExpanded(true); + } + } } diff --git a/src/settingsdialogextensioninfo.h b/src/settingsdialogextensioninfo.h index 3c013a9f3..0a134a4fb 100644 --- a/src/settingsdialogextensioninfo.h +++ b/src/settingsdialogextensioninfo.h @@ -10,11 +10,31 @@ namespace Ui class ExtensionListInfoWidget; } +class ExtensionManager; +class PluginManager; +class Settings; + +class ExtensionSettingWidget : public QWidget +{ + Q_OBJECT +public: + ExtensionSettingWidget(MOBase::Setting const& setting, QVariant const& value, + QWidget* parent = nullptr); + +private: + QVariant m_value; +}; + class ExtensionListInfoWidget : public QWidget { public: ExtensionListInfoWidget(QWidget* parent = nullptr); + // setup the widget, should be called before any other functions + // + void setup(Settings& settings, ExtensionManager& extensionManager, + PluginManager& pluginManager); + // set the extension to display // void setExtension(const MOBase::IExtension& extension); @@ -22,6 +42,10 @@ class ExtensionListInfoWidget : public QWidget private: Ui::ExtensionListInfoWidget* ui; + Settings* m_settings; + ExtensionManager* m_extensionManager; + PluginManager* m_pluginManager; + // currently displayed extension (default to nullptr) const MOBase::IExtension* m_extension{nullptr}; }; diff --git a/src/settingsdialogextensioninfo.ui b/src/settingsdialogextensioninfo.ui index 3951e3037..ab479aa7e 100644 --- a/src/settingsdialogextensioninfo.ui +++ b/src/settingsdialogextensioninfo.ui @@ -101,41 +101,20 @@ 0 - - false - - - false + + true - - false + + 1 false - - 170 - Key - - - Value - - -
      - - - - - No plugin found. - - - Qt::AlignCenter - diff --git a/src/settingsdialogextensions.cpp b/src/settingsdialogextensions.cpp index 9a86e76f9..813f39917 100644 --- a/src/settingsdialogextensions.cpp +++ b/src/settingsdialogextensions.cpp @@ -17,6 +17,8 @@ ExtensionsSettingsTab::ExtensionsSettingsTab(Settings& s, : SettingsTab(s, d), m_extensionManager(&extensionManager), m_pluginManager(&pluginManager) { + ui->infoWidget->setup(s, extensionManager, pluginManager); + // TODO: use Qt system to sort extensions instead of sorting beforehand std::vector extensions; for (auto& extension : m_extensionManager->extensions()) { diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 281a545d7..b618d8c26 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -464,7 +464,7 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) m_save = new QAction(QIcon(":/MO/gui/save"), QObject::tr("&Save"), &editor); m_save->setShortcutContext(Qt::WidgetWithChildrenShortcut); - m_save->setShortcut(Qt::CTRL + Qt::Key_S); + m_save->setShortcut(Qt::CTRL | Qt::Key_S); m_editor.addAction(m_save); m_wordWrap = diff --git a/src/translationmanager.cpp b/src/translationmanager.cpp index 339209417..1eeafe167 100644 --- a/src/translationmanager.cpp +++ b/src/translationmanager.cpp @@ -47,7 +47,7 @@ void TranslationManager::addOldFormatTranslations() QString languageString = QString("%1 (%2)") .arg(locale.nativeLanguageName()) - .arg(locale.nativeCountryName()); + .arg(locale.nativeTerritoryName()); if (locale.language() == QLocale::Chinese) { if (languageCode == "zh_TW") {