mirror of
https://github.com/UltimMC/Launcher.git
synced 2025-12-13 12:12:14 +00:00
Compare commits
1 Commits
feature/me
...
feature/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8369dbdcc0 |
5
.arcconfig
Normal file
5
.arcconfig
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"project_id": "MultiMC5",
|
||||
"conduit_uri": "http://ph.multimc.org"
|
||||
}
|
||||
|
||||
25
.clang-format
Normal file
25
.clang-format
Normal file
@@ -0,0 +1,25 @@
|
||||
UseTab: false
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
AccessModifierOffset: -4
|
||||
IndentCaseLabels: false
|
||||
IndentFunctionDeclarationAfterType: false
|
||||
NamespaceIndentation: None
|
||||
|
||||
BreakBeforeBraces: Allman
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
ColumnLimit: 160
|
||||
MaxEmptyLinesToKeep: 1
|
||||
|
||||
Standard: Cpp11
|
||||
Cpp11BracedListStyle: true
|
||||
|
||||
SpacesInParentheses: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpaceAfterControlStatementKeyword: true
|
||||
|
||||
AlignTrailingComments: true
|
||||
SpacesBeforeTrailingComments: 1
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
Thumbs.db
|
||||
*.kdev4
|
||||
.kdev4
|
||||
.user
|
||||
.directory
|
||||
resources/CMakeFiles
|
||||
@@ -9,7 +9,8 @@ resources/MultiMCLauncher.jar
|
||||
html/
|
||||
|
||||
# Project Files
|
||||
*.pro.user
|
||||
MultiMC5.kdev4
|
||||
MultiMC.pro.user
|
||||
CMakeLists.txt.user
|
||||
CMakeLists.txt.user.*
|
||||
/.project
|
||||
|
||||
38
.travis.yml
Normal file
38
.travis.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
# General set up
|
||||
language: cpp
|
||||
cache: apt
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: precise
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
env: TRAVIS_DIST=precise QT_VERSION=5.4.2
|
||||
- os: linux
|
||||
dist: precise
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
env: TRAVIS_DIST=precise QT_VERSION=5.6.2
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
env: TRAVIS_DIST=trusty QT_VERSION=5.4.2
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
env: TRAVIS_DIST=trusty QT_VERSION=5.6.2
|
||||
|
||||
# Install dependencies
|
||||
install:
|
||||
- source travis/prepare.sh # installs qt and cmake. need to source because some env vars are set from there
|
||||
|
||||
# Actual work
|
||||
before_script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH ..
|
||||
script:
|
||||
- make -j4 && make test ARGS="-V"
|
||||
@@ -32,7 +32,7 @@ set(CMAKE_C_STANDARD_REQUIRED true)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
include(GenerateExportHeader)
|
||||
set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
|
||||
if(UNIX AND APPLE)
|
||||
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
|
||||
endif()
|
||||
@@ -46,7 +46,7 @@ set(MultiMC_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetc
|
||||
######## Set version numbers ########
|
||||
set(MultiMC_VERSION_MAJOR 0)
|
||||
set(MultiMC_VERSION_MINOR 6)
|
||||
set(MultiMC_VERSION_HOTFIX 8)
|
||||
set(MultiMC_VERSION_HOTFIX 5)
|
||||
|
||||
# Build number
|
||||
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
|
||||
@@ -77,7 +77,6 @@ set(MultiMC_RELEASE_VERSION_NAME "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MIN
|
||||
|
||||
#### Custom target to just print the version.
|
||||
add_custom_target(version echo "Version: ${MultiMC_RELEASE_VERSION_NAME}")
|
||||
add_custom_target(tcversion echo "\\#\\#teamcity[setParameter name=\\'env.MULTIMC_VERSION\\' value=\\'${MultiMC_RELEASE_VERSION_NAME}\\']")
|
||||
|
||||
################################ 3rd Party Libs ################################
|
||||
|
||||
@@ -88,7 +87,6 @@ find_package(Qt5Concurrent REQUIRED)
|
||||
find_package(Qt5Network REQUIRED)
|
||||
find_package(Qt5Test REQUIRED)
|
||||
find_package(Qt5Xml REQUIRED)
|
||||
find_package(Qt5Multimedia REQUIRED)
|
||||
|
||||
# The Qt5 cmake files don't provide its install paths, so ask qmake.
|
||||
include(QMakeQuery)
|
||||
|
||||
48
README.md
48
README.md
@@ -13,22 +13,21 @@ The project uses C++ and Qt5 as the language and base framework. This might seem
|
||||
|
||||
We can do more, with less, on worse hardware and leave more resources for the game while keeping the launcher running and providing extra features.
|
||||
|
||||
If you want to contribute, either talk to us on [Discord](https://discord.gg/0k2zsXGNHs0fE4Wm), [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC) or pick up some item from the github issues [workflowy](https://github.com/MultiMC/MultiMC5/issues) - there is always plenty of ideas around.
|
||||
If you want to contribute, either talk to us on [Discord](https://discord.gg/0k2zsXGNHs0fE4Wm), [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC) or pick up some item from [workflowy](https://workflowy.com/s/2EyDMcp7CU) - there are many.
|
||||
|
||||
### Building
|
||||
If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions.
|
||||
|
||||
### Code formatting
|
||||
Just follow the existing formatting.
|
||||
The ci server is running at [ci.multimc.org](http://ci.multimc.org), where you can watch the builds happen in (or very close to) real time. It can also serve as a nice reference on how to set up the build environment on your end.
|
||||
|
||||
In general:
|
||||
* Indent with 4 space unless it's in a submodule
|
||||
* Keep lists (of arguments, parameters, initializators...) as lists, not paragraphs.
|
||||
* Prefer readability over dogma.
|
||||
According to travis.ci, the builds are currently [](https://travis-ci.org/MultiMC/MultiMC5)
|
||||
|
||||
### Code formatting
|
||||
We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the project. We highly recommend setting it up so the project stays well formatted.
|
||||
|
||||
|
||||
## Translations
|
||||
Translations can be done [on crowdin](https://translate.multimc.org).
|
||||
Translations can be done either directly in the [translations repository](https://github.com/MultiMC/MultiMC5). For more details, see: [Translating-MultiMC](https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC).
|
||||
|
||||
## Forking/Redistributing
|
||||
We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.
|
||||
@@ -44,36 +43,3 @@ Copyright © 2013-2019 MultiMC Contributors
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this program except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
## Build status
|
||||
### Linux (Intel32)
|
||||
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_Linux32_Build&guest=1">
|
||||
Build: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_Linux32_Build)/statusIcon"/>
|
||||
</a>
|
||||
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_Linux32_Deploy&guest=1">
|
||||
Deploy: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_Linux32_Deploy)/statusIcon"/>
|
||||
</a>
|
||||
|
||||
### Linux (AMD64)
|
||||
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_Linux64_Build&guest=1">
|
||||
Build: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_Linux64_Build)/statusIcon"/>
|
||||
</a>
|
||||
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_Linux64_Deploy&guest=1">
|
||||
Deploy: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_Linux64_Deploy)/statusIcon"/>
|
||||
</a>
|
||||
|
||||
### macOS (AMD64)
|
||||
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_MacOS_Build&guest=1">
|
||||
Build: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_MacOS_Build)/statusIcon"/>
|
||||
</a>
|
||||
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_MacOS_Deploy&guest=1">
|
||||
Deploy: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_MacOS_Deploy)/statusIcon"/>
|
||||
</a>
|
||||
|
||||
### Windows (Intel32)
|
||||
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_Windows_Build&guest=1">
|
||||
Build: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_Windows_Build)/statusIcon"/>
|
||||
</a>
|
||||
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_Windows_Deploy&guest=1">
|
||||
Deploy: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_Windows_Deploy)/statusIcon"/>
|
||||
</a>
|
||||
|
||||
@@ -254,17 +254,7 @@ void IconList::installIcons(const QStringList &iconFiles)
|
||||
{
|
||||
for (QString file : iconFiles)
|
||||
{
|
||||
QFileInfo fileinfo(file);
|
||||
if (!fileinfo.isReadable() || !fileinfo.isFile())
|
||||
continue;
|
||||
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
|
||||
|
||||
QString suffix = fileinfo.suffix();
|
||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
|
||||
continue;
|
||||
|
||||
if (!QFile::copy(file, target))
|
||||
continue;
|
||||
installIcon(file, QFileInfo(file).fileName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,11 +262,24 @@ void IconList::installIcon(const QString &file, const QString &name)
|
||||
{
|
||||
QFileInfo fileinfo(file);
|
||||
if(!fileinfo.isReadable() || !fileinfo.isFile())
|
||||
{
|
||||
qWarning() << "Failed to install icon" << fileinfo.absoluteFilePath();
|
||||
return;
|
||||
}
|
||||
|
||||
QString target = FS::PathCombine(m_dir.dirName(), name);
|
||||
|
||||
QFile::copy(file, target);
|
||||
QPixmap icon(fileinfo.absoluteFilePath());
|
||||
if(icon.isNull())
|
||||
{
|
||||
qWarning() << "Icon " << fileinfo.absoluteFilePath() << "is null";
|
||||
return;
|
||||
}
|
||||
|
||||
auto currentSize = icon.size();
|
||||
auto targetedWidth = qMin(currentSize.width(), 512);
|
||||
auto targetedHeight = qMin(currentSize.height(), 512);
|
||||
icon.scaled(targetedWidth, targetedHeight).save(target);
|
||||
}
|
||||
|
||||
bool IconList::iconFileExists(const QString &key) const
|
||||
|
||||
@@ -213,10 +213,8 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/auth/flows/RefreshTask.cpp
|
||||
minecraft/auth/flows/ValidateTask.h
|
||||
minecraft/auth/flows/ValidateTask.cpp
|
||||
|
||||
minecraft/gameoptions/GameOptions.h
|
||||
minecraft/gameoptions/GameOptions.cpp
|
||||
|
||||
minecraft/update/AssetUpdateTask.h
|
||||
minecraft/update/AssetUpdateTask.cpp
|
||||
minecraft/update/FMLLibrariesTask.cpp
|
||||
@@ -225,7 +223,6 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/update/FoldersTask.h
|
||||
minecraft/update/LibrariesTask.cpp
|
||||
minecraft/update/LibrariesTask.h
|
||||
|
||||
minecraft/launch/ClaimAccount.cpp
|
||||
minecraft/launch/ClaimAccount.h
|
||||
minecraft/launch/CreateServerResourcePacksFolder.cpp
|
||||
@@ -242,16 +239,12 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/launch/PrintInstanceInfo.h
|
||||
minecraft/launch/ReconstructAssets.cpp
|
||||
minecraft/launch/ReconstructAssets.h
|
||||
minecraft/launch/ScanModFolders.cpp
|
||||
minecraft/launch/ScanModFolders.h
|
||||
|
||||
minecraft/legacy/LegacyModList.h
|
||||
minecraft/legacy/LegacyModList.cpp
|
||||
minecraft/legacy/LegacyInstance.h
|
||||
minecraft/legacy/LegacyInstance.cpp
|
||||
minecraft/legacy/LegacyUpgradeTask.h
|
||||
minecraft/legacy/LegacyUpgradeTask.cpp
|
||||
|
||||
minecraft/GradleSpecifier.h
|
||||
minecraft/MinecraftInstance.cpp
|
||||
minecraft/MinecraftInstance.h
|
||||
@@ -286,21 +279,15 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/VersionFile.h
|
||||
minecraft/VersionFilterData.h
|
||||
minecraft/VersionFilterData.cpp
|
||||
minecraft/Mod.h
|
||||
minecraft/Mod.cpp
|
||||
minecraft/SimpleModList.h
|
||||
minecraft/SimpleModList.cpp
|
||||
minecraft/World.h
|
||||
minecraft/World.cpp
|
||||
minecraft/WorldList.h
|
||||
minecraft/WorldList.cpp
|
||||
|
||||
minecraft/mod/Mod.h
|
||||
minecraft/mod/Mod.cpp
|
||||
minecraft/mod/ModDetails.h
|
||||
minecraft/mod/ModFolderModel.h
|
||||
minecraft/mod/ModFolderModel.cpp
|
||||
minecraft/mod/ModFolderLoadTask.h
|
||||
minecraft/mod/ModFolderLoadTask.cpp
|
||||
minecraft/mod/LocalModParseTask.h
|
||||
minecraft/mod/LocalModParseTask.cpp
|
||||
|
||||
# Assets
|
||||
minecraft/AssetsUtils.h
|
||||
minecraft/AssetsUtils.cpp
|
||||
@@ -331,8 +318,8 @@ add_unit_test(Library
|
||||
)
|
||||
|
||||
# FIXME: shares data with FileSystem test
|
||||
add_unit_test(ModFolderModel
|
||||
SOURCES minecraft/mod/ModFolderModel_test.cpp
|
||||
add_unit_test(SimpleModList
|
||||
SOURCES minecraft/SimpleModList_test.cpp
|
||||
DATA testdata
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
@@ -439,14 +426,15 @@ set(META_SOURCES
|
||||
)
|
||||
|
||||
set(FTB_SOURCES
|
||||
modplatform/legacy_ftb/PackFetchTask.h
|
||||
modplatform/legacy_ftb/PackFetchTask.cpp
|
||||
modplatform/legacy_ftb/PackInstallTask.h
|
||||
modplatform/legacy_ftb/PackInstallTask.cpp
|
||||
modplatform/legacy_ftb/PrivatePackManager.h
|
||||
modplatform/legacy_ftb/PrivatePackManager.cpp
|
||||
modplatform/ftb/FtbPackFetchTask.h
|
||||
modplatform/ftb/FtbPackFetchTask.cpp
|
||||
modplatform/ftb/FtbPackInstallTask.h
|
||||
modplatform/ftb/FtbPackInstallTask.cpp
|
||||
|
||||
modplatform/legacy_ftb/PackHelpers.h
|
||||
modplatform/ftb/FtbPrivatePackManager.h
|
||||
modplatform/ftb/FtbPrivatePackManager.cpp
|
||||
|
||||
modplatform/ftb/PackHelpers.h
|
||||
)
|
||||
|
||||
set(FLAME_SOURCES
|
||||
@@ -489,6 +477,8 @@ set(LOGIC_SOURCES
|
||||
${FLAME_SOURCES}
|
||||
)
|
||||
|
||||
message(STATUS "FOO! ${LOGIC_SOURCES}")
|
||||
|
||||
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
|
||||
set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
|
||||
|
||||
|
||||
@@ -69,8 +69,8 @@ namespace ArgumentStyle
|
||||
{
|
||||
enum Enum
|
||||
{
|
||||
Space, /**< --option value */
|
||||
Equals, /**< --option=value */
|
||||
Space, /**< --option=value */
|
||||
Equals, /**< --option value */
|
||||
SpaceAndEquals, /**< --option[= ]value */
|
||||
#ifdef Q_OS_WIN32
|
||||
Default = Equals
|
||||
|
||||
@@ -174,11 +174,6 @@ bool copy::operator()(const QString &offset)
|
||||
bool deletePath(QString path)
|
||||
{
|
||||
bool OK = true;
|
||||
QFileInfo finfo(path);
|
||||
if(finfo.isFile()) {
|
||||
return QFile::remove(path);
|
||||
}
|
||||
|
||||
QDir dir(path);
|
||||
|
||||
if (!dir.exists())
|
||||
@@ -299,7 +294,7 @@ QString NormalizePath(QString path)
|
||||
}
|
||||
}
|
||||
|
||||
QString badFilenameChars = "\"\\/?<>:;*|!+\r\n";
|
||||
QString badFilenameChars = "\"\\/?<>:*|!+\r\n";
|
||||
|
||||
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
||||
{
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
#include "pathmatcher/RegexpMatcher.h"
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime)
|
||||
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves)
|
||||
{
|
||||
m_origInstance = origInstance;
|
||||
m_keepPlaytime = keepPlaytime;
|
||||
|
||||
if(!copySaves)
|
||||
{
|
||||
@@ -47,9 +46,6 @@ void InstanceCopyTask::copyFinished()
|
||||
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
|
||||
inst->setName(m_instName);
|
||||
inst->setIconKey(m_instIcon);
|
||||
if(!m_keepPlaytime) {
|
||||
inst->resetTimePlayed();
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime);
|
||||
explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves);
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
@@ -28,5 +28,4 @@ private: /* data */
|
||||
QFuture<bool> m_copyFuture;
|
||||
QFutureWatcher<bool> m_copyFutureWatcher;
|
||||
std::unique_ptr<IPathMatcher> m_matcher;
|
||||
bool m_keepPlaytime;
|
||||
};
|
||||
|
||||
@@ -100,10 +100,6 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
return pdata->name();
|
||||
}
|
||||
case Qt::AccessibleTextRole:
|
||||
{
|
||||
return tr("%1 Instance").arg(pdata->name());
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
{
|
||||
return pdata->instanceRoot();
|
||||
@@ -160,8 +156,8 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
||||
{
|
||||
return GroupId();
|
||||
}
|
||||
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||
if(iter != m_instanceGroupIndex.end())
|
||||
auto iter = m_groupMap.find(inst->id());
|
||||
if(iter != m_groupMap.end())
|
||||
{
|
||||
return *iter;
|
||||
}
|
||||
@@ -178,8 +174,8 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||
if(iter != m_instanceGroupIndex.end())
|
||||
auto iter = m_groupMap.find(inst->id());
|
||||
if(iter != m_groupMap.end())
|
||||
{
|
||||
if(*iter != name)
|
||||
{
|
||||
@@ -190,12 +186,12 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
else
|
||||
{
|
||||
changed = true;
|
||||
m_instanceGroupIndex[id] = name;
|
||||
m_groupMap[id] = name;
|
||||
}
|
||||
|
||||
if(changed)
|
||||
{
|
||||
m_groupNameCache.insert(name);
|
||||
m_groups.insert(name);
|
||||
auto idx = getInstIndex(inst.get());
|
||||
emit dataChanged(index(idx), index(idx), {GroupRole});
|
||||
saveGroupList();
|
||||
@@ -204,7 +200,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
|
||||
QStringList InstanceList::getGroups()
|
||||
{
|
||||
return m_groupNameCache.toList();
|
||||
return m_groups.toList();
|
||||
}
|
||||
|
||||
void InstanceList::deleteGroup(const QString& name)
|
||||
@@ -217,7 +213,7 @@ void InstanceList::deleteGroup(const QString& name)
|
||||
auto instGroupName = getInstanceGroup(instID);
|
||||
if(instGroupName == name)
|
||||
{
|
||||
m_instanceGroupIndex.remove(instID);
|
||||
m_groupMap.remove(instID);
|
||||
qDebug() << "Remove" << instID << "from group" << name;
|
||||
removed = true;
|
||||
auto idx = getInstIndex(instance.get());
|
||||
@@ -233,21 +229,16 @@ void InstanceList::deleteGroup(const QString& name)
|
||||
}
|
||||
}
|
||||
|
||||
bool InstanceList::isGroupCollapsed(const QString& group)
|
||||
{
|
||||
return m_collapsedGroups.contains(group);
|
||||
}
|
||||
|
||||
void InstanceList::deleteInstance(const InstanceId& id)
|
||||
{
|
||||
auto inst = getInstanceById(id);
|
||||
if(!inst)
|
||||
{
|
||||
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
|
||||
qDebug() << "Cannot delete instance" << id << " No such instance is present.";
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_instanceGroupIndex.remove(id))
|
||||
if(m_groupMap.remove(id))
|
||||
{
|
||||
saveGroupList();
|
||||
}
|
||||
@@ -520,7 +511,7 @@ void InstanceList::saveGroupList()
|
||||
WatchLock foo(m_watcher, m_instDir);
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
QMap<QString, QSet<QString>> reverseGroupMap;
|
||||
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++)
|
||||
for (auto iter = m_groupMap.begin(); iter != m_groupMap.end(); iter++)
|
||||
{
|
||||
QString id = iter.key();
|
||||
QString group = iter.value();
|
||||
@@ -553,7 +544,7 @@ void InstanceList::saveGroupList()
|
||||
auto name = iter.key();
|
||||
QJsonObject groupObj;
|
||||
QJsonArray instanceArr;
|
||||
groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name)));
|
||||
groupObj.insert("hidden", QJsonValue(QString("false")));
|
||||
for (auto item : list)
|
||||
{
|
||||
instanceArr.append(QJsonValue(item));
|
||||
@@ -577,6 +568,7 @@ void InstanceList::saveGroupList()
|
||||
void InstanceList::loadGroupList()
|
||||
{
|
||||
qDebug() << "Will load group list now.";
|
||||
QSet<QString> groupSet;
|
||||
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
|
||||
@@ -627,8 +619,7 @@ void InstanceList::loadGroupList()
|
||||
return;
|
||||
}
|
||||
|
||||
QSet<QString> groupSet;
|
||||
m_instanceGroupIndex.clear();
|
||||
m_groupMap.clear();
|
||||
|
||||
// Iterate through all the groups.
|
||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||
@@ -639,35 +630,37 @@ void InstanceList::loadGroupList()
|
||||
// If not an object, complain and skip to the next one.
|
||||
if (!iter.value().isObject())
|
||||
{
|
||||
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
|
||||
qWarning() << QString("Group '%1' in the group list should "
|
||||
"be an object.")
|
||||
.arg(groupName)
|
||||
.toUtf8();
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject groupObj = iter.value().toObject();
|
||||
if (!groupObj.value("instances").isArray())
|
||||
{
|
||||
qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.").arg(groupName).toUtf8();
|
||||
qWarning() << QString("Group '%1' in the group list is invalid. "
|
||||
"It should contain an array "
|
||||
"called 'instances'.")
|
||||
.arg(groupName)
|
||||
.toUtf8();
|
||||
continue;
|
||||
}
|
||||
|
||||
// keep a list/set of groups for choosing
|
||||
groupSet.insert(groupName);
|
||||
|
||||
auto hidden = groupObj.value("hidden").toBool(false);
|
||||
if(hidden) {
|
||||
m_collapsedGroups.insert(groupName);
|
||||
}
|
||||
|
||||
// Iterate through the list of instances in the group.
|
||||
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
||||
|
||||
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++)
|
||||
{
|
||||
m_instanceGroupIndex[(*iter2).toString()] = groupName;
|
||||
m_groupMap[(*iter2).toString()] = groupName;
|
||||
}
|
||||
}
|
||||
m_groupsLoaded = true;
|
||||
m_groupNameCache.unite(groupSet);
|
||||
m_groups.unite(groupSet);
|
||||
qDebug() << "Group list loaded.";
|
||||
}
|
||||
|
||||
@@ -692,17 +685,6 @@ void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value)
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed)
|
||||
{
|
||||
qDebug() << "Group" << group << (collapsed ? "collapsed" : "expanded");
|
||||
if(collapsed) {
|
||||
m_collapsedGroups.insert(group);
|
||||
} else {
|
||||
m_collapsedGroups.remove(group);
|
||||
}
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
class InstanceStaging : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -833,11 +815,10 @@ bool InstanceList::commitStagedInstance(const QString& path, const QString& inst
|
||||
qWarning() << "Failed to move" << path << "to" << destination;
|
||||
return false;
|
||||
}
|
||||
m_instanceGroupIndex[instID] = groupName;
|
||||
m_groupMap[instID] = groupName;
|
||||
instanceSet.insert(instID);
|
||||
m_groupNameCache.insert(groupName);
|
||||
m_groups.insert(groupName);
|
||||
emit instancesChanged();
|
||||
emit instanceSelectRequest(instID);
|
||||
}
|
||||
saveGroupList();
|
||||
return true;
|
||||
|
||||
@@ -99,8 +99,6 @@ public:
|
||||
InstancePtr getInstanceById(QString id) const;
|
||||
QModelIndex getInstanceIndexById(const QString &id) const;
|
||||
QStringList getGroups();
|
||||
bool isGroupCollapsed(const QString &groupName);
|
||||
|
||||
GroupId getInstanceGroup(const InstanceId & id) const;
|
||||
void setInstanceGroup(const InstanceId & id, const GroupId& name);
|
||||
|
||||
@@ -131,12 +129,10 @@ public:
|
||||
signals:
|
||||
void dataIsInvalid();
|
||||
void instancesChanged();
|
||||
void instanceSelectRequest(QString instanceId);
|
||||
void groupsChanged(QSet<QString> groups);
|
||||
|
||||
public slots:
|
||||
void on_InstFolderChanged(const Setting &setting, QVariant value);
|
||||
void on_GroupStateChanged(const QString &group, bool collapsed);
|
||||
|
||||
private slots:
|
||||
void propertiesChanged(BaseInstance *inst);
|
||||
@@ -157,14 +153,12 @@ private:
|
||||
int m_watchLevel = 0;
|
||||
bool m_dirty = false;
|
||||
QList<InstancePtr> m_instances;
|
||||
QSet<QString> m_groupNameCache;
|
||||
QSet<QString> m_groups;
|
||||
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
QString m_instDir;
|
||||
QFileSystemWatcher * m_watcher;
|
||||
// FIXME: this is so inefficient that looking at it is almost painful.
|
||||
QSet<QString> m_collapsedGroups;
|
||||
QMap<InstanceId, GroupId> m_instanceGroupIndex;
|
||||
QMap<InstanceId, GroupId> m_groupMap;
|
||||
QSet<InstanceId> instanceSet;
|
||||
bool m_groupsLoaded = false;
|
||||
bool m_instancesProbed = false;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include <QString>
|
||||
#include <QFileInfo>
|
||||
#include <QSet>
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include "minecraft/Mod.h"
|
||||
#include <functional>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
@@ -43,8 +43,6 @@ ComponentList::ComponentList(MinecraftInstance * instance)
|
||||
d->m_instance = instance;
|
||||
d->m_saveTimer.setSingleShot(true);
|
||||
d->m_saveTimer.setInterval(5000);
|
||||
d->interactionDisabled = instance->isRunning();
|
||||
connect(d->m_instance, &BaseInstance::runningStatusChanged, this, &ComponentList::disableInteraction);
|
||||
connect(&d->m_saveTimer, &QTimer::timeout, this, &ComponentList::save_internal);
|
||||
}
|
||||
|
||||
@@ -637,9 +635,6 @@ void ComponentList::componentDataChanged()
|
||||
qWarning() << "ComponentList got dataChenged signal from a non-Component!";
|
||||
return;
|
||||
}
|
||||
if(objPtr->getID() == "net.minecraft") {
|
||||
emit minecraftChanged();
|
||||
}
|
||||
// figure out which one is it... in a seriously dumb way.
|
||||
int index = 0;
|
||||
for (auto component: d->components)
|
||||
@@ -767,9 +762,8 @@ QVariant ComponentList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
case NameColumn: {
|
||||
return patch->isEnabled() ? Qt::Checked : Qt::Unchecked;
|
||||
}
|
||||
case NameColumn:
|
||||
return d->components.at(row)->isEnabled() ? Qt::Checked : Qt::Unchecked;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@@ -779,7 +773,7 @@ QVariant ComponentList::data(const QModelIndex &index, int role) const
|
||||
switch (column)
|
||||
{
|
||||
case NameColumn:
|
||||
return patch->getName();
|
||||
return d->components.at(row)->getName();
|
||||
case VersionColumn:
|
||||
{
|
||||
if(patch->isCustom())
|
||||
@@ -859,25 +853,21 @@ QVariant ComponentList::headerData(int section, Qt::Orientation orientation, int
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
// FIXME: zero precision mess
|
||||
Qt::ItemFlags ComponentList::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
if (!index.isValid())
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
Qt::ItemFlags outFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
||||
|
||||
int row = index.row();
|
||||
|
||||
if (row < 0 || row >= d->components.size()) {
|
||||
if (row < 0 || row >= d->components.size())
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
auto patch = d->components.at(row);
|
||||
// TODO: this will need fine-tuning later...
|
||||
if(patch->canBeDisabled() && !d->interactionDisabled)
|
||||
if(patch->canBeDisabled())
|
||||
{
|
||||
outFlags |= Qt::ItemIsUserCheckable;
|
||||
}
|
||||
@@ -1212,14 +1202,3 @@ QString ComponentList::getComponentVersion(const QString& uid) const
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
void ComponentList::disableInteraction(bool disable)
|
||||
{
|
||||
if(d->interactionDisabled != disable) {
|
||||
d->interactionDisabled = disable;
|
||||
auto size = d->components.size();
|
||||
if(size) {
|
||||
emit dataChanged(index(0), index(size - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,9 +104,6 @@ public:
|
||||
/// if there is a save scheduled, do it now.
|
||||
void saveNow();
|
||||
|
||||
signals:
|
||||
void minecraftChanged();
|
||||
|
||||
public:
|
||||
/// get the profile component by id
|
||||
Component * getComponent(const QString &id);
|
||||
@@ -134,7 +131,6 @@ private slots:
|
||||
void updateSucceeded();
|
||||
void updateFailed(const QString & error);
|
||||
void componentDataChanged();
|
||||
void disableInteraction(bool disable);
|
||||
|
||||
private:
|
||||
bool load();
|
||||
|
||||
@@ -38,6 +38,5 @@ struct ComponentListData
|
||||
QTimer m_saveTimer;
|
||||
shared_qobject_ptr<Task> m_updateTask;
|
||||
bool loaded = false;
|
||||
bool interactionDisabled = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -451,17 +451,13 @@ static bool getTrivialComponentChanges(const ComponentIndex & index, const Requi
|
||||
auto & comp = (*compIter);
|
||||
if(comp->getVersion() != req.equalsVersion)
|
||||
{
|
||||
if(comp->isCustom()) {
|
||||
if(comp->m_dependencyOnly)
|
||||
{
|
||||
decision = Decision::VersionNotSame;
|
||||
}
|
||||
else
|
||||
{
|
||||
decision = Decision::LockedVersionNotSame;
|
||||
} else {
|
||||
if(comp->m_dependencyOnly)
|
||||
{
|
||||
decision = Decision::VersionNotSame;
|
||||
}
|
||||
else
|
||||
{
|
||||
decision = Decision::LockedVersionNotSame;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -21,13 +21,12 @@
|
||||
#include "minecraft/launch/ModMinecraftJar.h"
|
||||
#include "minecraft/launch/ClaimAccount.h"
|
||||
#include "minecraft/launch/ReconstructAssets.h"
|
||||
#include "minecraft/launch/ScanModFolders.h"
|
||||
#include "java/launch/CheckJava.h"
|
||||
#include "java/JavaUtils.h"
|
||||
#include "meta/Index.h"
|
||||
#include "meta/VersionList.h"
|
||||
|
||||
#include "mod/ModFolderModel.h"
|
||||
#include "SimpleModList.h"
|
||||
#include "WorldList.h"
|
||||
|
||||
#include "icons/IIconList.h"
|
||||
@@ -551,38 +550,37 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session)
|
||||
out << "";
|
||||
}
|
||||
|
||||
auto printModList = [&](const QString & label, ModFolderModel & model) {
|
||||
if(model.size())
|
||||
if(loaderModList()->size())
|
||||
{
|
||||
out << "Mods:";
|
||||
for(auto & mod: loaderModList()->allMods())
|
||||
{
|
||||
out << QString("%1:").arg(label);
|
||||
auto modList = model.allMods();
|
||||
std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) {
|
||||
auto aName = a.filename().completeBaseName();
|
||||
auto bName = b.filename().completeBaseName();
|
||||
return aName.localeAwareCompare(bName) < 0;
|
||||
});
|
||||
for(auto & mod: modList)
|
||||
{
|
||||
if(mod.type() == Mod::MOD_FOLDER)
|
||||
{
|
||||
out << u8" [📁] " + mod.filename().completeBaseName() + " (folder)";
|
||||
continue;
|
||||
}
|
||||
if(!mod.enabled())
|
||||
continue;
|
||||
if(mod.type() == Mod::MOD_FOLDER)
|
||||
continue;
|
||||
// TODO: proper implementation would need to descend into folders.
|
||||
|
||||
if(mod.enabled()) {
|
||||
out << u8" [✔️] " + mod.filename().completeBaseName();
|
||||
}
|
||||
else {
|
||||
out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)";
|
||||
}
|
||||
|
||||
}
|
||||
out << "";
|
||||
out << " " + mod.filename().completeBaseName();
|
||||
}
|
||||
};
|
||||
out << "";
|
||||
}
|
||||
|
||||
printModList("Mods", *(loaderModList().get()));
|
||||
printModList("Core Mods", *(coreModList().get()));
|
||||
if(coreModList()->size())
|
||||
{
|
||||
out << "Core Mods:";
|
||||
for(auto & coremod: coreModList()->allMods())
|
||||
{
|
||||
if(!coremod.enabled())
|
||||
continue;
|
||||
if(coremod.type() == Mod::MOD_FOLDER)
|
||||
continue;
|
||||
// TODO: proper implementation would need to descend into folders.
|
||||
|
||||
out << " " + coremod.filename().completeBaseName();
|
||||
}
|
||||
out << "";
|
||||
}
|
||||
|
||||
auto & jarMods = profile->getJarMods();
|
||||
if(jarMods.size())
|
||||
@@ -650,7 +648,8 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
|
||||
auto i = sessionRef.u.properties.begin();
|
||||
while (i != sessionRef.u.properties.end())
|
||||
{
|
||||
if(i.value().length() <= 3) {
|
||||
if(i.key() == "preferredLanguage")
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
@@ -829,11 +828,6 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
process->appendStep(new ModMinecraftJar(pptr));
|
||||
}
|
||||
|
||||
// if there are any jar mods
|
||||
{
|
||||
process->appendStep(new ScanModFolders(pptr));
|
||||
}
|
||||
|
||||
// print some instance info here...
|
||||
{
|
||||
process->appendStep(new PrintInstanceInfo(pptr, session));
|
||||
@@ -899,47 +893,43 @@ JavaVersion MinecraftInstance::getJavaVersion() const
|
||||
return JavaVersion(settings()->get("JavaVersion").toString());
|
||||
}
|
||||
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const
|
||||
std::shared_ptr<SimpleModList> MinecraftInstance::loaderModList() const
|
||||
{
|
||||
if (!m_loader_mod_list)
|
||||
{
|
||||
m_loader_mod_list.reset(new ModFolderModel(loaderModsDir()));
|
||||
m_loader_mod_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
|
||||
m_loader_mod_list.reset(new SimpleModList(loaderModsDir()));
|
||||
}
|
||||
m_loader_mod_list->update();
|
||||
return m_loader_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const
|
||||
std::shared_ptr<SimpleModList> MinecraftInstance::coreModList() const
|
||||
{
|
||||
if (!m_core_mod_list)
|
||||
{
|
||||
m_core_mod_list.reset(new ModFolderModel(coreModsDir()));
|
||||
m_core_mod_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
|
||||
m_core_mod_list.reset(new SimpleModList(coreModsDir()));
|
||||
}
|
||||
m_core_mod_list->update();
|
||||
return m_core_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const
|
||||
std::shared_ptr<SimpleModList> MinecraftInstance::resourcePackList() const
|
||||
{
|
||||
if (!m_resource_pack_list)
|
||||
{
|
||||
m_resource_pack_list.reset(new ModFolderModel(resourcePacksDir()));
|
||||
m_resource_pack_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction);
|
||||
m_resource_pack_list.reset(new SimpleModList(resourcePacksDir()));
|
||||
}
|
||||
m_resource_pack_list->update();
|
||||
return m_resource_pack_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const
|
||||
std::shared_ptr<SimpleModList> MinecraftInstance::texturePackList() const
|
||||
{
|
||||
if (!m_texture_pack_list)
|
||||
{
|
||||
m_texture_pack_list.reset(new ModFolderModel(texturePacksDir()));
|
||||
m_texture_pack_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction);
|
||||
m_texture_pack_list.reset(new SimpleModList(texturePacksDir()));
|
||||
}
|
||||
m_texture_pack_list->update();
|
||||
return m_texture_pack_list;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#pragma once
|
||||
#include "BaseInstance.h"
|
||||
#include <java/JavaVersion.h>
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include "minecraft/Mod.h"
|
||||
#include <QProcess>
|
||||
#include <QDir>
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class ModFolderModel;
|
||||
class ModsModel;
|
||||
class SimpleModList;
|
||||
class WorldList;
|
||||
class GameOptions;
|
||||
class LaunchStep;
|
||||
@@ -67,10 +68,11 @@ public:
|
||||
std::shared_ptr<ComponentList> getComponentList() const;
|
||||
|
||||
////// Mod Lists //////
|
||||
std::shared_ptr<ModFolderModel> loaderModList() const;
|
||||
std::shared_ptr<ModFolderModel> coreModList() const;
|
||||
std::shared_ptr<ModFolderModel> resourcePackList() const;
|
||||
std::shared_ptr<ModFolderModel> texturePackList() const;
|
||||
std::shared_ptr<ModsModel> modsModel() const;
|
||||
std::shared_ptr<SimpleModList> loaderModList() const;
|
||||
std::shared_ptr<SimpleModList> coreModList() const;
|
||||
std::shared_ptr<SimpleModList> resourcePackList() const;
|
||||
std::shared_ptr<SimpleModList> texturePackList() const;
|
||||
std::shared_ptr<WorldList> worldList() const;
|
||||
std::shared_ptr<GameOptions> gameOptionsModel() const;
|
||||
|
||||
@@ -111,6 +113,9 @@ public:
|
||||
|
||||
virtual JavaVersion getJavaVersion() const;
|
||||
|
||||
signals:
|
||||
void versionReloaded();
|
||||
|
||||
protected:
|
||||
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
|
||||
QStringList validLaunchMethods();
|
||||
@@ -121,10 +126,11 @@ private:
|
||||
|
||||
protected: // data
|
||||
std::shared_ptr<ComponentList> m_components;
|
||||
mutable std::shared_ptr<ModFolderModel> m_loader_mod_list;
|
||||
mutable std::shared_ptr<ModFolderModel> m_core_mod_list;
|
||||
mutable std::shared_ptr<ModFolderModel> m_resource_pack_list;
|
||||
mutable std::shared_ptr<ModFolderModel> m_texture_pack_list;
|
||||
mutable std::shared_ptr<ModsModel> m_mods_model;
|
||||
mutable std::shared_ptr<SimpleModList> m_loader_mod_list;
|
||||
mutable std::shared_ptr<SimpleModList> m_core_mod_list;
|
||||
mutable std::shared_ptr<SimpleModList> m_resource_pack_list;
|
||||
mutable std::shared_ptr<SimpleModList> m_texture_pack_list;
|
||||
mutable std::shared_ptr<WorldList> m_world_list;
|
||||
mutable std::shared_ptr<GameOptions> m_game_options;
|
||||
};
|
||||
|
||||
433
api/logic/minecraft/Mod.cpp
Normal file
433
api/logic/minecraft/Mod.cpp
Normal file
@@ -0,0 +1,433 @@
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
#include <quazip.h>
|
||||
#include <quazipfile.h>
|
||||
|
||||
#include "Mod.h"
|
||||
#include "settings/INIFile.h"
|
||||
#include <FileSystem.h>
|
||||
#include <QDebug>
|
||||
|
||||
Mod::Mod(const QFileInfo &file)
|
||||
{
|
||||
repath(file);
|
||||
m_changedDateTime = file.lastModified();
|
||||
}
|
||||
|
||||
void Mod::repath(const QFileInfo &file)
|
||||
{
|
||||
m_file = file;
|
||||
QString name_base = file.fileName();
|
||||
|
||||
m_type = Mod::MOD_UNKNOWN;
|
||||
|
||||
if (m_file.isDir())
|
||||
{
|
||||
m_type = MOD_FOLDER;
|
||||
m_name = name_base;
|
||||
m_mmc_id = name_base;
|
||||
}
|
||||
else if (m_file.isFile())
|
||||
{
|
||||
if (name_base.endsWith(".disabled"))
|
||||
{
|
||||
m_enabled = false;
|
||||
name_base.chop(9);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_enabled = true;
|
||||
}
|
||||
m_mmc_id = name_base;
|
||||
if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
|
||||
{
|
||||
m_type = MOD_ZIPFILE;
|
||||
name_base.chop(4);
|
||||
}
|
||||
else if (name_base.endsWith(".litemod"))
|
||||
{
|
||||
m_type = MOD_LITEMOD;
|
||||
name_base.chop(8);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_type = MOD_SINGLEFILE;
|
||||
}
|
||||
m_name = name_base;
|
||||
}
|
||||
|
||||
if (m_type == MOD_ZIPFILE)
|
||||
{
|
||||
QuaZip zip(m_file.filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return;
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
if (zip.setCurrentFile("mcmod.info"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
ReadMCModInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
else if (zip.setCurrentFile("fabric.mod.json"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
ReadFabricModInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
else if (zip.setCurrentFile("forgeversion.properties"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
ReadForgeInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
zip.close();
|
||||
}
|
||||
else if (m_type == MOD_FOLDER)
|
||||
{
|
||||
QFileInfo mcmod_info(FS::PathCombine(m_file.filePath(), "mcmod.info"));
|
||||
if (mcmod_info.isFile())
|
||||
{
|
||||
QFile mcmod(mcmod_info.filePath());
|
||||
if (!mcmod.open(QIODevice::ReadOnly))
|
||||
return;
|
||||
auto data = mcmod.readAll();
|
||||
if (data.isEmpty() || data.isNull())
|
||||
return;
|
||||
ReadMCModInfo(data);
|
||||
}
|
||||
}
|
||||
else if (m_type == MOD_LITEMOD)
|
||||
{
|
||||
QuaZip zip(m_file.filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return;
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
if (zip.setCurrentFile("litemod.json"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
ReadLiteModInfo(file.readAll());
|
||||
file.close();
|
||||
}
|
||||
zip.close();
|
||||
}
|
||||
}
|
||||
|
||||
// NEW format
|
||||
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
|
||||
|
||||
// OLD format:
|
||||
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
|
||||
void Mod::ReadMCModInfo(QByteArray contents)
|
||||
{
|
||||
auto getInfoFromArray = [&](QJsonArray arr)->void
|
||||
{
|
||||
if (!arr.at(0).isObject())
|
||||
return;
|
||||
auto firstObj = arr.at(0).toObject();
|
||||
m_mod_id = firstObj.value("modid").toString();
|
||||
m_name = firstObj.value("name").toString();
|
||||
m_version = firstObj.value("version").toString();
|
||||
m_homeurl = firstObj.value("url").toString();
|
||||
m_updateurl = firstObj.value("updateUrl").toString();
|
||||
m_homeurl = m_homeurl.trimmed();
|
||||
if(!m_homeurl.isEmpty())
|
||||
{
|
||||
// fix up url.
|
||||
if (!m_homeurl.startsWith("http://") && !m_homeurl.startsWith("https://") &&
|
||||
!m_homeurl.startsWith("ftp://"))
|
||||
{
|
||||
m_homeurl.prepend("http://");
|
||||
}
|
||||
}
|
||||
m_description = firstObj.value("description").toString();
|
||||
QJsonArray authors = firstObj.value("authorList").toArray();
|
||||
if (authors.size() == 0)
|
||||
authors = firstObj.value("authors").toArray();
|
||||
|
||||
if (authors.size() == 0)
|
||||
m_authors = "";
|
||||
else if (authors.size() >= 1)
|
||||
{
|
||||
m_authors = authors.at(0).toString();
|
||||
for (int i = 1; i < authors.size(); i++)
|
||||
{
|
||||
m_authors += ", " + authors.at(i).toString();
|
||||
}
|
||||
}
|
||||
m_credits = firstObj.value("credits").toString();
|
||||
return;
|
||||
}
|
||||
;
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
|
||||
// this is the very old format that had just the array
|
||||
if (jsonDoc.isArray())
|
||||
{
|
||||
getInfoFromArray(jsonDoc.array());
|
||||
}
|
||||
else if (jsonDoc.isObject())
|
||||
{
|
||||
auto val = jsonDoc.object().value("modinfoversion");
|
||||
if(val.isUndefined())
|
||||
val = jsonDoc.object().value("modListVersion");
|
||||
int version = val.toDouble();
|
||||
if (version != 2)
|
||||
{
|
||||
qCritical() << "BAD stuff happened to mod json:";
|
||||
qCritical() << contents;
|
||||
return;
|
||||
}
|
||||
auto arrVal = jsonDoc.object().value("modlist");
|
||||
if(arrVal.isUndefined())
|
||||
arrVal = jsonDoc.object().value("modList");
|
||||
if (arrVal.isArray())
|
||||
{
|
||||
getInfoFromArray(arrVal.toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://fabricmc.net/wiki/documentation:fabric_mod_json
|
||||
void Mod::ReadFabricModInfo(QByteArray contents)
|
||||
{
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
|
||||
auto object = jsonDoc.object();
|
||||
auto schemaVersion = object.contains("schemaVersion") ? object.value("schemaVersion").toInt(0) : 0;
|
||||
|
||||
m_mod_id = object.value("id").toString();
|
||||
m_version = object.value("version").toString();
|
||||
|
||||
m_name = object.contains("name") ? object.value("name").toString() : m_mod_id;
|
||||
m_description = object.value("description").toString();
|
||||
|
||||
if (schemaVersion >= 1)
|
||||
{
|
||||
QJsonArray authors = object.value("authors").toArray();
|
||||
m_authors = "";
|
||||
|
||||
for (int i = 0; i < authors.size(); i++)
|
||||
{
|
||||
QString author_name = authors.at(i).isObject()
|
||||
? authors.at(i).toObject().value("name").toString()
|
||||
: authors.at(i).toString();
|
||||
|
||||
if (i > 0)
|
||||
m_authors += ", " + author_name;
|
||||
else {
|
||||
m_authors += author_name;
|
||||
}
|
||||
}
|
||||
|
||||
if (object.contains("contact"))
|
||||
{
|
||||
QJsonObject contact = object.value("contact").toObject();
|
||||
|
||||
if (contact.contains("homepage"))
|
||||
m_homeurl = contact.value("homepage").toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mod::ReadForgeInfo(QByteArray contents)
|
||||
{
|
||||
// Read the data
|
||||
m_name = "Minecraft Forge";
|
||||
m_mod_id = "Forge";
|
||||
m_homeurl = "http://www.minecraftforge.net/forum/";
|
||||
INIFile ini;
|
||||
if (!ini.loadFile(contents))
|
||||
return;
|
||||
|
||||
QString major = ini.get("forge.major.number", "0").toString();
|
||||
QString minor = ini.get("forge.minor.number", "0").toString();
|
||||
QString revision = ini.get("forge.revision.number", "0").toString();
|
||||
QString build = ini.get("forge.build.number", "0").toString();
|
||||
|
||||
m_version = major + "." + minor + "." + revision + "." + build;
|
||||
}
|
||||
|
||||
void Mod::ReadLiteModInfo(QByteArray contents)
|
||||
{
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
|
||||
auto object = jsonDoc.object();
|
||||
if (object.contains("name"))
|
||||
{
|
||||
m_mod_id = m_name = object.value("name").toString();
|
||||
}
|
||||
if (object.contains("version"))
|
||||
{
|
||||
m_version = object.value("version").toString("");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_version = object.value("revision").toString("");
|
||||
}
|
||||
m_mcversion = object.value("mcversion").toString();
|
||||
m_authors = object.value("author").toString();
|
||||
m_description = object.value("description").toString();
|
||||
m_homeurl = object.value("url").toString();
|
||||
}
|
||||
|
||||
bool Mod::replace(Mod &with)
|
||||
{
|
||||
if (!destroy())
|
||||
return false;
|
||||
bool success = false;
|
||||
auto t = with.type();
|
||||
|
||||
if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE || t == MOD_LITEMOD)
|
||||
{
|
||||
qDebug() << "Copy: " << with.m_file.filePath() << " to " << m_file.filePath();
|
||||
success = QFile::copy(with.m_file.filePath(), m_file.filePath());
|
||||
}
|
||||
if (t == MOD_FOLDER)
|
||||
{
|
||||
success = FS::copy(with.m_file.filePath(), m_file.path())();
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
m_name = with.m_name;
|
||||
m_mmc_id = with.m_mmc_id;
|
||||
m_mod_id = with.m_mod_id;
|
||||
m_version = with.m_version;
|
||||
m_mcversion = with.m_mcversion;
|
||||
m_description = with.m_description;
|
||||
m_authors = with.m_authors;
|
||||
m_credits = with.m_credits;
|
||||
m_homeurl = with.m_homeurl;
|
||||
m_type = with.m_type;
|
||||
m_file.refresh();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool Mod::destroy()
|
||||
{
|
||||
if (m_type == MOD_FOLDER)
|
||||
{
|
||||
QDir d(m_file.filePath());
|
||||
if (d.removeRecursively())
|
||||
{
|
||||
m_type = MOD_UNKNOWN;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE || m_type == MOD_LITEMOD)
|
||||
{
|
||||
QFile f(m_file.filePath());
|
||||
if (f.remove())
|
||||
{
|
||||
m_type = MOD_UNKNOWN;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Mod::version() const
|
||||
{
|
||||
switch (type())
|
||||
{
|
||||
case MOD_ZIPFILE:
|
||||
case MOD_LITEMOD:
|
||||
return m_version;
|
||||
case MOD_FOLDER:
|
||||
return "Folder";
|
||||
case MOD_SINGLEFILE:
|
||||
return "File";
|
||||
default:
|
||||
return "VOID";
|
||||
}
|
||||
}
|
||||
|
||||
bool Mod::enable(bool value)
|
||||
{
|
||||
if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
|
||||
return false;
|
||||
|
||||
if (m_enabled == value)
|
||||
return false;
|
||||
|
||||
QString path = m_file.absoluteFilePath();
|
||||
if (value)
|
||||
{
|
||||
QFile foo(path);
|
||||
if (!path.endsWith(".disabled"))
|
||||
return false;
|
||||
path.chop(9);
|
||||
if (!foo.rename(path))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
QFile foo(path);
|
||||
path += ".disabled";
|
||||
if (!foo.rename(path))
|
||||
return false;
|
||||
}
|
||||
m_file = QFileInfo(path);
|
||||
m_enabled = value;
|
||||
return true;
|
||||
}
|
||||
bool Mod::operator==(const Mod &other) const
|
||||
{
|
||||
return mmc_id() == other.mmc_id();
|
||||
}
|
||||
bool Mod::strongCompare(const Mod &other) const
|
||||
{
|
||||
return mmc_id() == other.mmc_id() && version() == other.version() && type() == other.type();
|
||||
}
|
||||
@@ -16,16 +16,8 @@
|
||||
#pragma once
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
#include <QList>
|
||||
#include <memory>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
#include "ModDetails.h"
|
||||
|
||||
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Mod
|
||||
class Mod
|
||||
{
|
||||
public:
|
||||
enum ModType
|
||||
@@ -37,7 +29,6 @@ public:
|
||||
MOD_LITEMOD, //!< The mod is a litemod
|
||||
};
|
||||
|
||||
Mod() = default;
|
||||
Mod(const QFileInfo &file);
|
||||
|
||||
QFileInfo filename() const
|
||||
@@ -48,14 +39,54 @@ public:
|
||||
{
|
||||
return m_mmc_id;
|
||||
}
|
||||
QString mod_id() const
|
||||
{
|
||||
return m_mod_id;
|
||||
}
|
||||
ModType type() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
QString mcversion() const
|
||||
{
|
||||
return m_mcversion;
|
||||
}
|
||||
;
|
||||
bool valid()
|
||||
{
|
||||
return m_type != MOD_UNKNOWN;
|
||||
}
|
||||
QString name() const
|
||||
{
|
||||
QString name = m_name.trimmed();
|
||||
if(name.isEmpty() || name == "Example Mod")
|
||||
{
|
||||
return m_mmc_id;
|
||||
}
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QString version() const;
|
||||
|
||||
QString homeurl() const
|
||||
{
|
||||
return m_homeurl;
|
||||
}
|
||||
|
||||
QString description() const
|
||||
{
|
||||
return m_description;
|
||||
}
|
||||
|
||||
QString authors() const
|
||||
{
|
||||
return m_authors;
|
||||
}
|
||||
|
||||
QString credits() const
|
||||
{
|
||||
return m_credits;
|
||||
}
|
||||
|
||||
QDateTime dateTimeChanged() const
|
||||
{
|
||||
@@ -67,51 +98,46 @@ public:
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
const ModDetails &details() const;
|
||||
|
||||
QString name() const;
|
||||
QString version() const;
|
||||
QString homeurl() const;
|
||||
QString description() const;
|
||||
QStringList authors() const;
|
||||
|
||||
bool enable(bool value);
|
||||
|
||||
// delete all the files of this mod
|
||||
bool destroy();
|
||||
|
||||
// replace this mod with a copy of the other
|
||||
bool replace(Mod &with);
|
||||
// change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
|
||||
void repath(const QFileInfo &file);
|
||||
|
||||
bool shouldResolve() {
|
||||
return !m_resolving && !m_resolved;
|
||||
}
|
||||
bool isResolving() {
|
||||
return m_resolving;
|
||||
}
|
||||
int resolutionTicket()
|
||||
{
|
||||
return m_resolutionTicket;
|
||||
}
|
||||
void setResolving(bool resolving, int resolutionTicket) {
|
||||
m_resolving = resolving;
|
||||
m_resolutionTicket = resolutionTicket;
|
||||
}
|
||||
void finishResolvingWithDetails(std::shared_ptr<ModDetails> details){
|
||||
m_resolving = false;
|
||||
m_resolved = true;
|
||||
m_localDetails = details;
|
||||
}
|
||||
// WEAK compare operator - used for replacing mods
|
||||
bool operator==(const Mod &other) const;
|
||||
bool strongCompare(const Mod &other) const;
|
||||
|
||||
private:
|
||||
void ReadMCModInfo(QByteArray contents);
|
||||
void ReadFabricModInfo(QByteArray contents);
|
||||
void ReadForgeInfo(QByteArray contents);
|
||||
void ReadLiteModInfo(QByteArray contents);
|
||||
|
||||
protected:
|
||||
|
||||
// FIXME: what do do with those? HMM...
|
||||
/*
|
||||
void ReadModInfoData(QString info);
|
||||
void ReadForgeInfoData(QString infoFileData);
|
||||
*/
|
||||
|
||||
QFileInfo m_file;
|
||||
QDateTime m_changedDateTime;
|
||||
QString m_mmc_id;
|
||||
QString m_name;
|
||||
QString m_mod_id;
|
||||
bool m_enabled = true;
|
||||
bool m_resolving = false;
|
||||
bool m_resolved = false;
|
||||
int m_resolutionTicket = 0;
|
||||
ModType m_type = MOD_UNKNOWN;
|
||||
std::shared_ptr<ModDetails> m_localDetails;
|
||||
QString m_name;
|
||||
QString m_version;
|
||||
QString m_mcversion;
|
||||
QString m_homeurl;
|
||||
QString m_updateurl;
|
||||
QString m_description;
|
||||
QString m_authors;
|
||||
QString m_credits;
|
||||
|
||||
ModType m_type;
|
||||
};
|
||||
@@ -33,7 +33,7 @@ slots:
|
||||
|
||||
auto time_parsed = timeFromS3Time(timestamp);
|
||||
auto time_serialized = timeToS3Time(time_parsed);
|
||||
|
||||
|
||||
QCOMPARE(time_serialized, timestamp);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ModFolderModel.h"
|
||||
#include "SimpleModList.h"
|
||||
#include <FileSystem.h>
|
||||
#include <QMimeData>
|
||||
#include <QUrl>
|
||||
@@ -21,21 +21,18 @@
|
||||
#include <QString>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QDebug>
|
||||
#include "ModFolderLoadTask.h"
|
||||
#include <QThreadPool>
|
||||
#include <algorithm>
|
||||
#include "LocalModParseTask.h"
|
||||
|
||||
ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir)
|
||||
SimpleModList::SimpleModList(const QString &dir) : QAbstractListModel(), m_dir(dir)
|
||||
{
|
||||
FS::ensureFolderPathExists(m_dir.absolutePath());
|
||||
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks);
|
||||
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
|
||||
QDir::NoSymLinks);
|
||||
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
||||
m_watcher = new QFileSystemWatcher(this);
|
||||
connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
|
||||
}
|
||||
|
||||
void ModFolderModel::startWatching()
|
||||
void SimpleModList::startWatching()
|
||||
{
|
||||
if(is_watching)
|
||||
return;
|
||||
@@ -53,7 +50,7 @@ void ModFolderModel::startWatching()
|
||||
}
|
||||
}
|
||||
|
||||
void ModFolderModel::stopWatching()
|
||||
void SimpleModList::stopWatching()
|
||||
{
|
||||
if(!is_watching)
|
||||
return;
|
||||
@@ -69,164 +66,39 @@ void ModFolderModel::stopWatching()
|
||||
}
|
||||
}
|
||||
|
||||
bool ModFolderModel::update()
|
||||
bool SimpleModList::update()
|
||||
{
|
||||
if (!isValid()) {
|
||||
if (!isValid())
|
||||
return false;
|
||||
}
|
||||
if(m_update) {
|
||||
scheduled_update = true;
|
||||
return true;
|
||||
|
||||
QList<Mod> newMods;
|
||||
m_dir.refresh();
|
||||
for (auto entry : m_dir.entryInfoList())
|
||||
{
|
||||
newMods.append(Mod(entry));
|
||||
}
|
||||
|
||||
auto task = new ModFolderLoadTask(m_dir);
|
||||
m_update = task->result();
|
||||
QThreadPool *threadPool = QThreadPool::globalInstance();
|
||||
connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate);
|
||||
threadPool->start(task);
|
||||
beginResetModel();
|
||||
mods.swap(newMods);
|
||||
endResetModel();
|
||||
|
||||
emit changed();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModFolderModel::finishUpdate()
|
||||
{
|
||||
QSet<QString> currentSet = modsIndex.keys().toSet();
|
||||
auto & newMods = m_update->mods;
|
||||
QSet<QString> newSet = newMods.keys().toSet();
|
||||
|
||||
// see if the kept mods changed in some way
|
||||
{
|
||||
QSet<QString> kept = currentSet;
|
||||
kept.intersect(newSet);
|
||||
for(auto & keptMod: kept) {
|
||||
auto & newMod = newMods[keptMod];
|
||||
auto row = modsIndex[keptMod];
|
||||
auto & currentMod = mods[row];
|
||||
if(newMod.dateTimeChanged() == currentMod.dateTimeChanged()) {
|
||||
// no significant change, ignore...
|
||||
continue;
|
||||
}
|
||||
auto & oldMod = mods[row];
|
||||
if(oldMod.isResolving()) {
|
||||
activeTickets.remove(oldMod.resolutionTicket());
|
||||
}
|
||||
oldMod = newMod;
|
||||
resolveMod(mods[row]);
|
||||
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
||||
}
|
||||
}
|
||||
|
||||
// remove mods no longer present
|
||||
{
|
||||
QSet<QString> removed = currentSet;
|
||||
QList<int> removedRows;
|
||||
removed.subtract(newSet);
|
||||
for(auto & removedMod: removed) {
|
||||
removedRows.append(modsIndex[removedMod]);
|
||||
}
|
||||
std::sort(removedRows.begin(), removedRows.end(), std::greater<int>());
|
||||
for(auto iter = removedRows.begin(); iter != removedRows.end(); iter++) {
|
||||
int removedIndex = *iter;
|
||||
beginRemoveRows(QModelIndex(), removedIndex, removedIndex);
|
||||
auto removedIter = mods.begin() + removedIndex;
|
||||
if(removedIter->isResolving()) {
|
||||
activeTickets.remove(removedIter->resolutionTicket());
|
||||
}
|
||||
mods.erase(removedIter);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
// add new mods to the end
|
||||
{
|
||||
QSet<QString> added = newSet;
|
||||
added.subtract(currentSet);
|
||||
beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1);
|
||||
for(auto & addedMod: added) {
|
||||
mods.append(newMods[addedMod]);
|
||||
resolveMod(mods.last());
|
||||
}
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
// update index
|
||||
{
|
||||
modsIndex.clear();
|
||||
int idx = 0;
|
||||
for(auto & mod: mods) {
|
||||
modsIndex[mod.mmc_id()] = idx;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
m_update.reset();
|
||||
|
||||
emit updateFinished();
|
||||
|
||||
if(scheduled_update) {
|
||||
scheduled_update = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void ModFolderModel::resolveMod(Mod& m)
|
||||
{
|
||||
if(!m.shouldResolve()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename());
|
||||
auto result = task->result();
|
||||
result->id = m.mmc_id();
|
||||
activeTickets.insert(nextResolutionTicket, result);
|
||||
m.setResolving(true, nextResolutionTicket);
|
||||
nextResolutionTicket++;
|
||||
QThreadPool *threadPool = QThreadPool::globalInstance();
|
||||
connect(task, &LocalModParseTask::finished, this, &ModFolderModel::finishModParse);
|
||||
threadPool->start(task);
|
||||
}
|
||||
|
||||
void ModFolderModel::finishModParse(int token)
|
||||
{
|
||||
auto iter = activeTickets.find(token);
|
||||
if(iter == activeTickets.end()) {
|
||||
return;
|
||||
}
|
||||
auto result = *iter;
|
||||
activeTickets.remove(token);
|
||||
int row = modsIndex[result->id];
|
||||
auto & mod = mods[row];
|
||||
mod.finishResolvingWithDetails(result->details);
|
||||
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
||||
}
|
||||
|
||||
void ModFolderModel::disableInteraction(bool disabled)
|
||||
{
|
||||
if (interaction_disabled == disabled) {
|
||||
return;
|
||||
}
|
||||
interaction_disabled = disabled;
|
||||
if(size()) {
|
||||
emit dataChanged(index(0), index(size() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
void ModFolderModel::directoryChanged(QString path)
|
||||
void SimpleModList::directoryChanged(QString path)
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
bool ModFolderModel::isValid()
|
||||
bool SimpleModList::isValid()
|
||||
{
|
||||
return m_dir.exists() && m_dir.isReadable();
|
||||
}
|
||||
|
||||
// FIXME: this does not take disabled mod (with extra .disable extension) into account...
|
||||
bool ModFolderModel::installMod(const QString &filename)
|
||||
bool SimpleModList::installMod(const QString &filename)
|
||||
{
|
||||
if(interaction_disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
|
||||
auto originalPath = FS::NormalizePath(filename);
|
||||
QFileInfo fileinfo(originalPath);
|
||||
@@ -303,31 +175,23 @@ bool ModFolderModel::installMod(const QString &filename)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable)
|
||||
bool SimpleModList::enableMods(const QModelIndexList& indexes, bool enable)
|
||||
{
|
||||
if(interaction_disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
for (auto index: indexes)
|
||||
for (auto i: indexes)
|
||||
{
|
||||
if(index.column() != 0) {
|
||||
continue;
|
||||
}
|
||||
setModStatus(index.row(), enable);
|
||||
Mod &m = mods[i.row()];
|
||||
m.enable(enable);
|
||||
emit dataChanged(i, i);
|
||||
}
|
||||
emit changed();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
||||
bool SimpleModList::deleteMods(const QModelIndexList& indexes)
|
||||
{
|
||||
if(interaction_disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
@@ -336,15 +200,16 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
||||
Mod &m = mods[i.row()];
|
||||
m.destroy();
|
||||
}
|
||||
emit changed();
|
||||
return true;
|
||||
}
|
||||
|
||||
int ModFolderModel::columnCount(const QModelIndex &parent) const
|
||||
int SimpleModList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
}
|
||||
|
||||
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
||||
QVariant SimpleModList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
@@ -362,17 +227,8 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
case NameColumn:
|
||||
return mods[row].name();
|
||||
case VersionColumn: {
|
||||
switch(mods[row].type()) {
|
||||
case Mod::MOD_FOLDER:
|
||||
return tr("Folder");
|
||||
case Mod::MOD_SINGLEFILE:
|
||||
return tr("File");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case VersionColumn:
|
||||
return mods[row].version();
|
||||
}
|
||||
case DateColumn:
|
||||
return mods[row].dateTimeChanged();
|
||||
|
||||
@@ -396,7 +252,7 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
}
|
||||
|
||||
bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
bool SimpleModList::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
|
||||
{
|
||||
@@ -405,53 +261,17 @@ bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, in
|
||||
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
return setModStatus(index.row(), Toggle);
|
||||
auto &mod = mods[index.row()];
|
||||
if (mod.enable(!mod.enabled()))
|
||||
{
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action)
|
||||
{
|
||||
if(row < 0 || row >= mods.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &mod = mods[row];
|
||||
bool desiredStatus;
|
||||
switch(action) {
|
||||
case Enable:
|
||||
desiredStatus = true;
|
||||
break;
|
||||
case Disable:
|
||||
desiredStatus = false;
|
||||
break;
|
||||
case Toggle:
|
||||
default:
|
||||
desiredStatus = !mod.enabled();
|
||||
break;
|
||||
}
|
||||
|
||||
if(desiredStatus == mod.enabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// preserve the row, but change its ID
|
||||
auto oldId = mod.mmc_id();
|
||||
if(!mod.enable(!mod.enabled())) {
|
||||
return false;
|
||||
}
|
||||
auto newId = mod.mmc_id();
|
||||
if(modsIndex.contains(newId)) {
|
||||
// NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled
|
||||
// But is it necessary?
|
||||
}
|
||||
modsIndex.remove(oldId);
|
||||
modsIndex[newId] = row;
|
||||
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
QVariant SimpleModList::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
@@ -490,37 +310,30 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const
|
||||
Qt::ItemFlags SimpleModList::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||
auto flags = defaultFlags;
|
||||
if(interaction_disabled) {
|
||||
flags &= ~Qt::ItemIsDropEnabled;
|
||||
}
|
||||
if (index.isValid())
|
||||
return Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled |
|
||||
defaultFlags;
|
||||
else
|
||||
{
|
||||
flags |= Qt::ItemIsDropEnabled;
|
||||
if(index.isValid()) {
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
}
|
||||
|
||||
Qt::DropActions ModFolderModel::supportedDropActions() const
|
||||
Qt::DropActions SimpleModList::supportedDropActions() const
|
||||
{
|
||||
// copy from outside, move from within and other mod lists
|
||||
return Qt::CopyAction | Qt::MoveAction;
|
||||
}
|
||||
|
||||
QStringList ModFolderModel::mimeTypes() const
|
||||
QStringList SimpleModList::mimeTypes() const
|
||||
{
|
||||
QStringList types;
|
||||
types << "text/uri-list";
|
||||
return types;
|
||||
}
|
||||
|
||||
bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
|
||||
bool SimpleModList::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
{
|
||||
@@ -16,17 +16,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "Mod.h"
|
||||
#include "minecraft/Mod.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
#include "ModFolderLoadTask.h"
|
||||
#include "LocalModParseTask.h"
|
||||
|
||||
class LegacyInstance;
|
||||
class BaseInstance;
|
||||
@@ -36,7 +32,7 @@ class QFileSystemWatcher;
|
||||
* A legacy mod list.
|
||||
* Backed by a folder.
|
||||
*/
|
||||
class MULTIMC_LOGIC_EXPORT ModFolderModel : public QAbstractListModel
|
||||
class MULTIMC_LOGIC_EXPORT SimpleModList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -44,16 +40,11 @@ public:
|
||||
{
|
||||
ActiveColumn = 0,
|
||||
NameColumn,
|
||||
VersionColumn,
|
||||
DateColumn,
|
||||
VersionColumn,
|
||||
NUM_COLUMNS
|
||||
};
|
||||
enum ModStatusAction {
|
||||
Disable,
|
||||
Enable,
|
||||
Toggle
|
||||
};
|
||||
ModFolderModel(const QString &dir);
|
||||
SimpleModList(const QString &dir);
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
||||
@@ -85,13 +76,9 @@ public:
|
||||
{
|
||||
return mods[index];
|
||||
}
|
||||
const Mod &at(size_t index) const
|
||||
{
|
||||
return mods.at(index);
|
||||
}
|
||||
|
||||
/// Reloads the mod list and returns true if the list changed.
|
||||
bool update();
|
||||
virtual bool update();
|
||||
|
||||
/**
|
||||
* Adds the given mod to the list at the given index - if the list supports custom ordering
|
||||
@@ -99,15 +86,15 @@ public:
|
||||
bool installMod(const QString& filename);
|
||||
|
||||
/// Deletes all the selected mods
|
||||
bool deleteMods(const QModelIndexList &indexes);
|
||||
virtual bool deleteMods(const QModelIndexList &indexes);
|
||||
|
||||
/// Enable or disable listed mods
|
||||
bool setModStatus(const QModelIndexList &indexes, ModStatusAction action);
|
||||
virtual bool enableMods(const QModelIndexList &indexes, bool enable = true);
|
||||
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
|
||||
bool isValid();
|
||||
virtual bool isValid();
|
||||
|
||||
QDir dir()
|
||||
{
|
||||
@@ -119,31 +106,16 @@ public:
|
||||
return mods;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void disableInteraction(bool disabled);
|
||||
|
||||
private
|
||||
slots:
|
||||
void directoryChanged(QString path);
|
||||
void finishUpdate();
|
||||
void finishModParse(int token);
|
||||
|
||||
signals:
|
||||
void updateFinished();
|
||||
|
||||
private:
|
||||
void resolveMod(Mod& m);
|
||||
bool setModStatus(int index, ModStatusAction action);
|
||||
void changed();
|
||||
|
||||
protected:
|
||||
QFileSystemWatcher *m_watcher;
|
||||
bool is_watching = false;
|
||||
ModFolderLoadTask::ResultPtr m_update;
|
||||
bool scheduled_update = false;
|
||||
bool interaction_disabled = false;
|
||||
QDir m_dir;
|
||||
QMap<QString, int> modsIndex;
|
||||
QMap<int, LocalModParseTask::ResultPtr> activeTickets;
|
||||
int nextResolutionTicket = 0;
|
||||
QList<Mod> mods;
|
||||
};
|
||||
@@ -4,9 +4,9 @@
|
||||
#include "TestUtil.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "minecraft/SimpleModList.h"
|
||||
|
||||
class ModFolderModelTest : public QObject
|
||||
class SimpleModListTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -32,7 +32,7 @@ slots:
|
||||
{
|
||||
QString folder = source;
|
||||
QTemporaryDir tempDir;
|
||||
ModFolderModel m(tempDir.path());
|
||||
SimpleModList m(tempDir.path());
|
||||
m.installMod(folder);
|
||||
verify(tempDir.path());
|
||||
}
|
||||
@@ -41,13 +41,13 @@ slots:
|
||||
{
|
||||
QString folder = source + '/';
|
||||
QTemporaryDir tempDir;
|
||||
ModFolderModel m(tempDir.path());
|
||||
SimpleModList m(tempDir.path());
|
||||
m.installMod(folder);
|
||||
verify(tempDir.path());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(ModFolderModelTest)
|
||||
QTEST_GUILESS_MAIN(SimpleModListTest)
|
||||
|
||||
#include "ModFolderModel_test.moc"
|
||||
#include "SimpleModList_test.moc"
|
||||
@@ -429,3 +429,7 @@ bool World::operator==(const World &other) const
|
||||
{
|
||||
return is_valid == other.is_valid && folderName() == other.folderName();
|
||||
}
|
||||
bool World::strongCompare(const World &other) const
|
||||
{
|
||||
return is_valid == other.is_valid && folderName() == other.folderName();
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ public:
|
||||
|
||||
// WEAK compare operator - used for replacing worlds
|
||||
bool operator==(const World &other) const;
|
||||
bool strongCompare(const World &other) const;
|
||||
|
||||
private:
|
||||
void readFromZip(const QFileInfo &file);
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ScanModFolders.h"
|
||||
#include "launch/LaunchTask.h"
|
||||
#include "MMCZip.h"
|
||||
#include "minecraft/OpSys.h"
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
|
||||
void ScanModFolders::executeTask()
|
||||
{
|
||||
auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
|
||||
|
||||
auto loaders = m_inst->loaderModList();
|
||||
connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone);
|
||||
loaders->update();
|
||||
|
||||
auto cores = m_inst->coreModList();
|
||||
connect(cores.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone);
|
||||
cores->update();
|
||||
}
|
||||
|
||||
void ScanModFolders::modsDone()
|
||||
{
|
||||
m_modsDone = true;
|
||||
checkDone();
|
||||
}
|
||||
|
||||
void ScanModFolders::coreModsDone()
|
||||
{
|
||||
m_coreModsDone = true;
|
||||
checkDone();
|
||||
}
|
||||
|
||||
void ScanModFolders::checkDone()
|
||||
{
|
||||
if(m_modsDone && m_coreModsDone) {
|
||||
emitSucceeded();
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <launch/LaunchStep.h>
|
||||
#include <memory>
|
||||
|
||||
class ScanModFolders: public LaunchStep
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ScanModFolders(LaunchTask *parent) : LaunchStep(parent) {};
|
||||
virtual ~ScanModFolders(){};
|
||||
|
||||
virtual void executeTask() override;
|
||||
virtual bool canAbort() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
private slots:
|
||||
void coreModsDone();
|
||||
void modsDone();
|
||||
private:
|
||||
void checkDone();
|
||||
|
||||
private: // DATA
|
||||
bool m_modsDone = false;
|
||||
bool m_coreModsDone = false;
|
||||
};
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "LegacyInstance.h"
|
||||
|
||||
#include "minecraft/legacy/LegacyModList.h"
|
||||
#include "minecraft/SimpleModList.h"
|
||||
#include "minecraft/WorldList.h"
|
||||
#include <MMCZip.h>
|
||||
#include <FileSystem.h>
|
||||
@@ -106,6 +107,11 @@ std::shared_ptr<LegacyModList> LegacyInstance::jarModList() const
|
||||
return jar_mod_list;
|
||||
}
|
||||
|
||||
QList<Mod> LegacyInstance::getJarMods() const
|
||||
{
|
||||
return jarModList()->allMods();
|
||||
}
|
||||
|
||||
QString LegacyInstance::gameRoot() const
|
||||
{
|
||||
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseInstance.h"
|
||||
#include "minecraft/Mod.h"
|
||||
#include "launch/LaunchTask.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class ModFolderModel;
|
||||
class SimpleModList;
|
||||
class LegacyModList;
|
||||
class WorldList;
|
||||
class Task;
|
||||
@@ -77,6 +78,7 @@ public:
|
||||
QString customBaseJar() const;
|
||||
|
||||
std::shared_ptr<LegacyModList> jarModList() const;
|
||||
QList<Mod> getJarMods() const;
|
||||
std::shared_ptr<WorldList> worldList() const;
|
||||
|
||||
/*!
|
||||
|
||||
@@ -22,7 +22,8 @@ LegacyModList::LegacyModList(const QString &dir, const QString &list_file)
|
||||
: m_dir(dir), m_list_file(list_file)
|
||||
{
|
||||
FS::ensureFolderPathExists(m_dir.absolutePath());
|
||||
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks);
|
||||
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
|
||||
QDir::NoSymLinks);
|
||||
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
||||
}
|
||||
|
||||
@@ -33,11 +34,15 @@ LegacyModList::LegacyModList(const QString &dir, const QString &list_file)
|
||||
};
|
||||
typedef QList<OrderItem> OrderList;
|
||||
|
||||
static void internalSort(QList<LegacyModList::Mod> &what)
|
||||
static void internalSort(QList<Mod> &what)
|
||||
{
|
||||
auto predicate = [](const LegacyModList::Mod &left, const LegacyModList::Mod &right)
|
||||
auto predicate = [](const Mod &left, const Mod &right)
|
||||
{
|
||||
return left.fileName().localeAwareCompare(right.fileName()) < 0;
|
||||
if (left.name() == right.name())
|
||||
{
|
||||
return left.mmc_id().localeAwareCompare(right.mmc_id()) < 0;
|
||||
}
|
||||
return left.name().localeAwareCompare(right.name()) < 0;
|
||||
};
|
||||
std::sort(what.begin(), what.end(), predicate);
|
||||
}
|
||||
@@ -85,6 +90,7 @@ bool LegacyModList::update()
|
||||
QList<Mod> newMods;
|
||||
m_dir.refresh();
|
||||
auto folderContents = m_dir.entryInfoList();
|
||||
bool orderOrStateChanged = false;
|
||||
|
||||
// first, process the ordered items (if any)
|
||||
OrderList listOrder = readListFile(m_list_file);
|
||||
@@ -118,19 +124,48 @@ bool LegacyModList::update()
|
||||
// remove from the actual folder contents list
|
||||
folderContents.takeAt(idx);
|
||||
// append the new mod
|
||||
orderedMods.append(info);
|
||||
orderedMods.append(Mod(info));
|
||||
if (isEnabled != item.enabled)
|
||||
orderOrStateChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
orderOrStateChanged = true;
|
||||
}
|
||||
}
|
||||
// if there are any untracked files... append them sorted at the end
|
||||
// if there are any untracked files...
|
||||
if (folderContents.size())
|
||||
{
|
||||
// the order surely changed!
|
||||
for (auto entry : folderContents)
|
||||
{
|
||||
newMods.append(entry);
|
||||
newMods.append(Mod(entry));
|
||||
}
|
||||
internalSort(newMods);
|
||||
orderedMods.append(newMods);
|
||||
orderOrStateChanged = true;
|
||||
}
|
||||
// otherwise, if we were already tracking some mods
|
||||
else if (mods.size())
|
||||
{
|
||||
// if the number doesn't match, order changed.
|
||||
if (mods.size() != orderedMods.size())
|
||||
orderOrStateChanged = true;
|
||||
// if it does match, compare the mods themselves
|
||||
else
|
||||
for (int i = 0; i < mods.size(); i++)
|
||||
{
|
||||
if (!mods[i].strongCompare(orderedMods[i]))
|
||||
{
|
||||
orderOrStateChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mods.swap(orderedMods);
|
||||
if (orderOrStateChanged && !m_list_file.isEmpty())
|
||||
{
|
||||
qDebug() << "Mod list " << m_list_file << " changed!";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -19,14 +19,21 @@
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
|
||||
#include "minecraft/Mod.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class LegacyInstance;
|
||||
class BaseInstance;
|
||||
|
||||
/**
|
||||
* A legacy mod list.
|
||||
* Backed by a folder.
|
||||
*/
|
||||
class MULTIMC_LOGIC_EXPORT LegacyModList
|
||||
{
|
||||
public:
|
||||
|
||||
using Mod = QFileInfo;
|
||||
|
||||
LegacyModList(const QString &dir, const QString &list_file = QString());
|
||||
|
||||
/// Reloads the mod list and returns true if the list changed.
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "LegacyInstance.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
#include "LegacyModList.h"
|
||||
#include "classparser.h"
|
||||
|
||||
LegacyUpgradeTask::LegacyUpgradeTask(InstancePtr origInstance)
|
||||
@@ -97,10 +96,10 @@ void LegacyUpgradeTask::copyFinished()
|
||||
components->installCustomJar(jarPath);
|
||||
}
|
||||
|
||||
auto jarMods = legacyInst->jarModList()->allMods();
|
||||
auto jarMods = legacyInst->getJarMods();
|
||||
for(auto & jarMod: jarMods)
|
||||
{
|
||||
QString modPath = jarMod.absoluteFilePath();
|
||||
QString modPath = jarMod.filename().absoluteFilePath();
|
||||
qDebug() << "jarMod: " << modPath;
|
||||
components->installJarMods({modPath});
|
||||
}
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
#include "LocalModParseTask.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
#include <quazip.h>
|
||||
#include <quazipfile.h>
|
||||
|
||||
#include "settings/INIFile.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// NEW format
|
||||
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
|
||||
|
||||
// OLD format:
|
||||
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
|
||||
std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
|
||||
{
|
||||
auto getInfoFromArray = [&](QJsonArray arr)->std::shared_ptr<ModDetails>
|
||||
{
|
||||
if (!arr.at(0).isObject()) {
|
||||
return nullptr;
|
||||
}
|
||||
std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
|
||||
auto firstObj = arr.at(0).toObject();
|
||||
details->mod_id = firstObj.value("modid").toString();
|
||||
auto name = firstObj.value("name").toString();
|
||||
// NOTE: ignore stupid example mods copies where the author didn't even bother to change the name
|
||||
if(name != "Example Mod") {
|
||||
details->name = name;
|
||||
}
|
||||
details->version = firstObj.value("version").toString();
|
||||
details->updateurl = firstObj.value("updateUrl").toString();
|
||||
auto homeurl = firstObj.value("url").toString().trimmed();
|
||||
if(!homeurl.isEmpty())
|
||||
{
|
||||
// fix up url.
|
||||
if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://"))
|
||||
{
|
||||
homeurl.prepend("http://");
|
||||
}
|
||||
}
|
||||
details->homeurl = homeurl;
|
||||
details->description = firstObj.value("description").toString();
|
||||
QJsonArray authors = firstObj.value("authorList").toArray();
|
||||
if (authors.size() == 0) {
|
||||
// FIXME: what is the format of this? is there any?
|
||||
authors = firstObj.value("authors").toArray();
|
||||
}
|
||||
|
||||
for (auto author: authors)
|
||||
{
|
||||
details->authors.append(author.toString());
|
||||
}
|
||||
details->credits = firstObj.value("credits").toString();
|
||||
return details;
|
||||
};
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
|
||||
// this is the very old format that had just the array
|
||||
if (jsonDoc.isArray())
|
||||
{
|
||||
return getInfoFromArray(jsonDoc.array());
|
||||
}
|
||||
else if (jsonDoc.isObject())
|
||||
{
|
||||
auto val = jsonDoc.object().value("modinfoversion");
|
||||
if(val.isUndefined()) {
|
||||
val = jsonDoc.object().value("modListVersion");
|
||||
}
|
||||
int version = val.toDouble();
|
||||
if (version != 2)
|
||||
{
|
||||
qCritical() << "BAD stuff happened to mod json:";
|
||||
qCritical() << contents;
|
||||
return nullptr;
|
||||
}
|
||||
auto arrVal = jsonDoc.object().value("modlist");
|
||||
if(arrVal.isUndefined()) {
|
||||
arrVal = jsonDoc.object().value("modList");
|
||||
}
|
||||
if (arrVal.isArray())
|
||||
{
|
||||
return getInfoFromArray(arrVal.toArray());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// https://fabricmc.net/wiki/documentation:fabric_mod_json
|
||||
std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents)
|
||||
{
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
|
||||
auto object = jsonDoc.object();
|
||||
auto schemaVersion = object.contains("schemaVersion") ? object.value("schemaVersion").toInt(0) : 0;
|
||||
|
||||
std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
|
||||
|
||||
details->mod_id = object.value("id").toString();
|
||||
details->version = object.value("version").toString();
|
||||
|
||||
details->name = object.contains("name") ? object.value("name").toString() : details->mod_id;
|
||||
details->description = object.value("description").toString();
|
||||
|
||||
if (schemaVersion >= 1)
|
||||
{
|
||||
QJsonArray authors = object.value("authors").toArray();
|
||||
for (auto author: authors)
|
||||
{
|
||||
if(author.isObject()) {
|
||||
details->authors.append(author.toObject().value("name").toString());
|
||||
}
|
||||
else {
|
||||
details->authors.append(author.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (object.contains("contact"))
|
||||
{
|
||||
QJsonObject contact = object.value("contact").toObject();
|
||||
|
||||
if (contact.contains("homepage"))
|
||||
{
|
||||
details->homeurl = contact.value("homepage").toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModDetails> ReadForgeInfo(QByteArray contents)
|
||||
{
|
||||
std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
|
||||
// Read the data
|
||||
details->name = "Minecraft Forge";
|
||||
details->mod_id = "Forge";
|
||||
details->homeurl = "http://www.minecraftforge.net/forum/";
|
||||
INIFile ini;
|
||||
if (!ini.loadFile(contents))
|
||||
return details;
|
||||
|
||||
QString major = ini.get("forge.major.number", "0").toString();
|
||||
QString minor = ini.get("forge.minor.number", "0").toString();
|
||||
QString revision = ini.get("forge.revision.number", "0").toString();
|
||||
QString build = ini.get("forge.build.number", "0").toString();
|
||||
|
||||
details->version = major + "." + minor + "." + revision + "." + build;
|
||||
return details;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModDetails> ReadLiteModInfo(QByteArray contents)
|
||||
{
|
||||
std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
|
||||
auto object = jsonDoc.object();
|
||||
if (object.contains("name"))
|
||||
{
|
||||
details->mod_id = details->name = object.value("name").toString();
|
||||
}
|
||||
if (object.contains("version"))
|
||||
{
|
||||
details->version = object.value("version").toString("");
|
||||
}
|
||||
else
|
||||
{
|
||||
details->version = object.value("revision").toString("");
|
||||
}
|
||||
details->mcversion = object.value("mcversion").toString();
|
||||
auto author = object.value("author").toString();
|
||||
if(!author.isEmpty()) {
|
||||
details->authors.append(author);
|
||||
}
|
||||
details->description = object.value("description").toString();
|
||||
details->homeurl = object.value("url").toString();
|
||||
return details;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LocalModParseTask::LocalModParseTask(int token, Mod::ModType type, const QFileInfo& modFile):
|
||||
m_token(token),
|
||||
m_type(type),
|
||||
m_modFile(modFile),
|
||||
m_result(new Result())
|
||||
{
|
||||
}
|
||||
|
||||
void LocalModParseTask::processAsZip()
|
||||
{
|
||||
QuaZip zip(m_modFile.filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return;
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
if (zip.setCurrentFile("mcmod.info"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
m_result->details = ReadMCModInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
else if (zip.setCurrentFile("fabric.mod.json"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
m_result->details = ReadFabricModInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
else if (zip.setCurrentFile("forgeversion.properties"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
m_result->details = ReadForgeInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
zip.close();
|
||||
}
|
||||
|
||||
void LocalModParseTask::processAsFolder()
|
||||
{
|
||||
QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info"));
|
||||
if (mcmod_info.isFile())
|
||||
{
|
||||
QFile mcmod(mcmod_info.filePath());
|
||||
if (!mcmod.open(QIODevice::ReadOnly))
|
||||
return;
|
||||
auto data = mcmod.readAll();
|
||||
if (data.isEmpty() || data.isNull())
|
||||
return;
|
||||
m_result->details = ReadMCModInfo(data);
|
||||
}
|
||||
}
|
||||
|
||||
void LocalModParseTask::processAsLitemod()
|
||||
{
|
||||
QuaZip zip(m_modFile.filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return;
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
if (zip.setCurrentFile("litemod.json"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
m_result->details = ReadLiteModInfo(file.readAll());
|
||||
file.close();
|
||||
}
|
||||
zip.close();
|
||||
}
|
||||
|
||||
void LocalModParseTask::run()
|
||||
{
|
||||
switch(m_type)
|
||||
{
|
||||
case Mod::MOD_ZIPFILE:
|
||||
processAsZip();
|
||||
break;
|
||||
case Mod::MOD_FOLDER:
|
||||
processAsFolder();
|
||||
break;
|
||||
case Mod::MOD_LITEMOD:
|
||||
processAsLitemod();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
emit finished(m_token);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
#include <QRunnable>
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
#include "Mod.h"
|
||||
#include "ModDetails.h"
|
||||
|
||||
class LocalModParseTask : public QObject, public QRunnable
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct Result {
|
||||
QString id;
|
||||
std::shared_ptr<ModDetails> details;
|
||||
};
|
||||
using ResultPtr = std::shared_ptr<Result>;
|
||||
ResultPtr result() const {
|
||||
return m_result;
|
||||
}
|
||||
|
||||
LocalModParseTask(int token, Mod::ModType type, const QFileInfo & modFile);
|
||||
void run();
|
||||
|
||||
signals:
|
||||
void finished(int token);
|
||||
|
||||
private:
|
||||
void processAsZip();
|
||||
void processAsFolder();
|
||||
void processAsLitemod();
|
||||
|
||||
private:
|
||||
int m_token;
|
||||
Mod::ModType m_type;
|
||||
QFileInfo m_modFile;
|
||||
ResultPtr m_result;
|
||||
};
|
||||
@@ -1,151 +0,0 @@
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
|
||||
#include "Mod.h"
|
||||
#include <QDebug>
|
||||
#include <FileSystem.h>
|
||||
|
||||
namespace {
|
||||
|
||||
ModDetails invalidDetails;
|
||||
|
||||
}
|
||||
|
||||
|
||||
Mod::Mod(const QFileInfo &file)
|
||||
{
|
||||
repath(file);
|
||||
m_changedDateTime = file.lastModified();
|
||||
}
|
||||
|
||||
void Mod::repath(const QFileInfo &file)
|
||||
{
|
||||
m_file = file;
|
||||
QString name_base = file.fileName();
|
||||
|
||||
m_type = Mod::MOD_UNKNOWN;
|
||||
|
||||
m_mmc_id = name_base;
|
||||
|
||||
if (m_file.isDir())
|
||||
{
|
||||
m_type = MOD_FOLDER;
|
||||
m_name = name_base;
|
||||
}
|
||||
else if (m_file.isFile())
|
||||
{
|
||||
if (name_base.endsWith(".disabled"))
|
||||
{
|
||||
m_enabled = false;
|
||||
name_base.chop(9);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_enabled = true;
|
||||
}
|
||||
if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
|
||||
{
|
||||
m_type = MOD_ZIPFILE;
|
||||
name_base.chop(4);
|
||||
}
|
||||
else if (name_base.endsWith(".litemod"))
|
||||
{
|
||||
m_type = MOD_LITEMOD;
|
||||
name_base.chop(8);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_type = MOD_SINGLEFILE;
|
||||
}
|
||||
m_name = name_base;
|
||||
}
|
||||
}
|
||||
|
||||
bool Mod::enable(bool value)
|
||||
{
|
||||
if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
|
||||
return false;
|
||||
|
||||
if (m_enabled == value)
|
||||
return false;
|
||||
|
||||
QString path = m_file.absoluteFilePath();
|
||||
if (value)
|
||||
{
|
||||
QFile foo(path);
|
||||
if (!path.endsWith(".disabled"))
|
||||
return false;
|
||||
path.chop(9);
|
||||
if (!foo.rename(path))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
QFile foo(path);
|
||||
path += ".disabled";
|
||||
if (!foo.rename(path))
|
||||
return false;
|
||||
}
|
||||
repath(QFileInfo(path));
|
||||
m_enabled = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mod::destroy()
|
||||
{
|
||||
m_type = MOD_UNKNOWN;
|
||||
return FS::deletePath(m_file.filePath());
|
||||
}
|
||||
|
||||
|
||||
const ModDetails & Mod::details() const
|
||||
{
|
||||
if(!m_localDetails)
|
||||
return invalidDetails;
|
||||
return *m_localDetails;
|
||||
}
|
||||
|
||||
|
||||
QString Mod::version() const
|
||||
{
|
||||
return details().version;
|
||||
}
|
||||
|
||||
QString Mod::name() const
|
||||
{
|
||||
auto & d = details();
|
||||
if(!d.name.isEmpty()) {
|
||||
return d.name;
|
||||
}
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QString Mod::homeurl() const
|
||||
{
|
||||
return details().homeurl;
|
||||
}
|
||||
|
||||
QString Mod::description() const
|
||||
{
|
||||
return details().description;
|
||||
}
|
||||
|
||||
QStringList Mod::authors() const
|
||||
{
|
||||
return details().authors;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
struct ModDetails
|
||||
{
|
||||
QString mod_id;
|
||||
QString name;
|
||||
QString version;
|
||||
QString mcversion;
|
||||
QString homeurl;
|
||||
QString updateurl;
|
||||
QString description;
|
||||
QStringList authors;
|
||||
QString credits;
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
#include "ModFolderLoadTask.h"
|
||||
#include <QDebug>
|
||||
|
||||
ModFolderLoadTask::ModFolderLoadTask(QDir dir) :
|
||||
m_dir(dir), m_result(new Result())
|
||||
{
|
||||
}
|
||||
|
||||
void ModFolderLoadTask::run()
|
||||
{
|
||||
m_dir.refresh();
|
||||
for (auto entry : m_dir.entryInfoList())
|
||||
{
|
||||
Mod m(entry);
|
||||
m_result->mods[m.mmc_id()] = m;
|
||||
}
|
||||
emit succeeded();
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
#include <QRunnable>
|
||||
#include <QObject>
|
||||
#include <QDir>
|
||||
#include <QMap>
|
||||
#include "Mod.h"
|
||||
#include <memory>
|
||||
|
||||
class ModFolderLoadTask : public QObject, public QRunnable
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct Result {
|
||||
QMap<QString, Mod> mods;
|
||||
};
|
||||
using ResultPtr = std::shared_ptr<Result>;
|
||||
ResultPtr result() const {
|
||||
return m_result;
|
||||
}
|
||||
|
||||
public:
|
||||
ModFolderLoadTask(QDir dir);
|
||||
void run();
|
||||
signals:
|
||||
void succeeded();
|
||||
private:
|
||||
QDir m_dir;
|
||||
ResultPtr m_result;
|
||||
};
|
||||
@@ -1,9 +1,7 @@
|
||||
#include "FileResolvingTask.h"
|
||||
#include "Json.h"
|
||||
|
||||
namespace {
|
||||
const char * metabase = "https://cursemeta.dries007.net";
|
||||
}
|
||||
const char * metabase = "https://cursemeta.dries007.net";
|
||||
|
||||
Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess)
|
||||
: m_toProcess(toProcess)
|
||||
@@ -36,14 +34,70 @@ void Flame::FileResolvingTask::netJobFinished()
|
||||
int index = 0;
|
||||
for(auto & bytes: results)
|
||||
{
|
||||
auto & out = m_toProcess.files[index];
|
||||
try
|
||||
{
|
||||
failed &= (!out.parseFromBytes(bytes));
|
||||
auto doc = Json::requireDocument(bytes);
|
||||
auto obj = Json::requireObject(doc);
|
||||
auto & out = m_toProcess.files[index];
|
||||
// result code signifies true failure.
|
||||
if(obj.contains("code"))
|
||||
{
|
||||
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a negative result:";
|
||||
qCritical() << bytes;
|
||||
failed = true;
|
||||
continue;
|
||||
}
|
||||
out.fileName = Json::requireString(obj, "FileNameOnDisk");
|
||||
QString rawUrl = Json::requireString(obj, "DownloadURL");
|
||||
out.url = QUrl(rawUrl, QUrl::TolerantMode);
|
||||
if(!out.url.isValid())
|
||||
{
|
||||
throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
|
||||
}
|
||||
// This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
|
||||
// It is also optional
|
||||
QJsonObject projObj = Json::ensureObject(obj, "_Project", {});
|
||||
if(!projObj.isEmpty())
|
||||
{
|
||||
QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower();
|
||||
if(strType == "singlefile")
|
||||
{
|
||||
out.type = File::Type::SingleFile;
|
||||
}
|
||||
else if(strType == "ctoc")
|
||||
{
|
||||
out.type = File::Type::Ctoc;
|
||||
}
|
||||
else if(strType == "cmod2")
|
||||
{
|
||||
out.type = File::Type::Cmod2;
|
||||
}
|
||||
else if(strType == "mod")
|
||||
{
|
||||
out.type = File::Type::Mod;
|
||||
}
|
||||
else if(strType == "folder")
|
||||
{
|
||||
out.type = File::Type::Folder;
|
||||
}
|
||||
else if(strType == "modpack")
|
||||
{
|
||||
out.type = File::Type::Modpack;
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of unknown file type:" << strType;
|
||||
out.type = File::Type::Unknown;
|
||||
failed = true;
|
||||
continue;
|
||||
}
|
||||
out.targetFolder = Json::ensureString(projObj, "Path", "mods");
|
||||
}
|
||||
out.resolved = true;
|
||||
}
|
||||
catch (const JSONValidationError &e)
|
||||
{
|
||||
|
||||
auto & out = m_toProcess.files[index];
|
||||
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:";
|
||||
qCritical() << e.cause();
|
||||
qCritical() << "JSON:";
|
||||
|
||||
@@ -64,63 +64,3 @@ void Flame::loadManifest(Flame::Manifest & m, const QString &filepath)
|
||||
}
|
||||
loadManifestV1(m, obj);
|
||||
}
|
||||
|
||||
bool Flame::File::parseFromBytes(const QByteArray& bytes)
|
||||
{
|
||||
auto doc = Json::requireDocument(bytes);
|
||||
auto obj = Json::requireObject(doc);
|
||||
// result code signifies true failure.
|
||||
if(obj.contains("code"))
|
||||
{
|
||||
qCritical() << "Resolving of" << projectId << fileId << "failed because of a negative result:";
|
||||
qCritical() << bytes;
|
||||
return false;
|
||||
}
|
||||
fileName = Json::requireString(obj, "FileNameOnDisk");
|
||||
QString rawUrl = Json::requireString(obj, "DownloadURL");
|
||||
url = QUrl(rawUrl, QUrl::TolerantMode);
|
||||
if(!url.isValid())
|
||||
{
|
||||
throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
|
||||
}
|
||||
// This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
|
||||
// It is also optional
|
||||
QJsonObject projObj = Json::ensureObject(obj, "_Project", {});
|
||||
if(!projObj.isEmpty())
|
||||
{
|
||||
QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower();
|
||||
if(strType == "singlefile")
|
||||
{
|
||||
type = File::Type::SingleFile;
|
||||
}
|
||||
else if(strType == "ctoc")
|
||||
{
|
||||
type = File::Type::Ctoc;
|
||||
}
|
||||
else if(strType == "cmod2")
|
||||
{
|
||||
type = File::Type::Cmod2;
|
||||
}
|
||||
else if(strType == "mod")
|
||||
{
|
||||
type = File::Type::Mod;
|
||||
}
|
||||
else if(strType == "folder")
|
||||
{
|
||||
type = File::Type::Folder;
|
||||
}
|
||||
else if(strType == "modpack")
|
||||
{
|
||||
type = File::Type::Modpack;
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Resolving of" << projectId << fileId << "failed because of unknown file type:" << strType;
|
||||
type = File::Type::Unknown;
|
||||
return false;
|
||||
}
|
||||
targetFolder = Json::ensureString(projObj, "Path", "mods");
|
||||
}
|
||||
resolved = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,6 @@ namespace Flame
|
||||
{
|
||||
struct File
|
||||
{
|
||||
// NOTE: throws JSONValidationError
|
||||
bool parseFromBytes(const QByteArray &bytes);
|
||||
|
||||
int projectId = 0;
|
||||
int fileId = 0;
|
||||
// NOTE: the opposite to 'optional'. This is at the time of writing unused.
|
||||
|
||||
@@ -1,36 +1,34 @@
|
||||
#include "PackFetchTask.h"
|
||||
#include "PrivatePackManager.h"
|
||||
|
||||
#include "FtbPackFetchTask.h"
|
||||
#include <QDomDocument>
|
||||
#include "FtbPrivatePackManager.h"
|
||||
|
||||
#include "net/URLConstants.h"
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
void PackFetchTask::fetch()
|
||||
void FtbPackFetchTask::fetch()
|
||||
{
|
||||
publicPacks.clear();
|
||||
thirdPartyPacks.clear();
|
||||
|
||||
NetJob *netJob = new NetJob("LegacyFTB::ModpackFetch");
|
||||
NetJob *netJob = new NetJob("FtbModpackFetch");
|
||||
|
||||
QUrl publicPacksUrl = QUrl(URLConstants::LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");
|
||||
QUrl publicPacksUrl = QUrl(URLConstants::FTB_CDN_BASE_URL + "static/modpacks.xml");
|
||||
qDebug() << "Downloading public version info from" << publicPacksUrl.toString();
|
||||
netJob->addNetAction(Net::Download::makeByteArray(publicPacksUrl, &publicModpacksXmlFileData));
|
||||
|
||||
QUrl thirdPartyUrl = QUrl(URLConstants::LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml");
|
||||
QUrl thirdPartyUrl = QUrl(URLConstants::FTB_CDN_BASE_URL + "static/thirdparty.xml");
|
||||
qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString();
|
||||
netJob->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, &thirdPartyModpacksXmlFileData));
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, &PackFetchTask::fileDownloadFinished);
|
||||
QObject::connect(netJob, &NetJob::failed, this, &PackFetchTask::fileDownloadFailed);
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, &FtbPackFetchTask::fileDownloadFinished);
|
||||
QObject::connect(netJob, &NetJob::failed, this, &FtbPackFetchTask::fileDownloadFailed);
|
||||
|
||||
jobPtr.reset(netJob);
|
||||
netJob->start();
|
||||
}
|
||||
|
||||
void PackFetchTask::fetchPrivate(const QStringList & toFetch)
|
||||
void FtbPackFetchTask::fetchPrivate(const QStringList & toFetch)
|
||||
{
|
||||
QString privatePackBaseUrl = URLConstants::LEGACY_FTB_CDN_BASE_URL + "static/%1.xml";
|
||||
QString privatePackBaseUrl = URLConstants::FTB_CDN_BASE_URL + "static/%1.xml";
|
||||
|
||||
for (auto &packCode: toFetch)
|
||||
{
|
||||
@@ -40,9 +38,9 @@ void PackFetchTask::fetchPrivate(const QStringList & toFetch)
|
||||
|
||||
QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode]
|
||||
{
|
||||
ModpackList packs;
|
||||
parseAndAddPacks(*data, PackType::Private, packs);
|
||||
foreach(Modpack currentPack, packs)
|
||||
FtbModpackList packs;
|
||||
parseAndAddPacks(*data, FtbPackType::Private, packs);
|
||||
foreach(FtbModpack currentPack, packs)
|
||||
{
|
||||
currentPack.packCode = packCode;
|
||||
emit privateFileDownloadFinished(currentPack);
|
||||
@@ -67,18 +65,18 @@ void PackFetchTask::fetchPrivate(const QStringList & toFetch)
|
||||
}
|
||||
}
|
||||
|
||||
void PackFetchTask::fileDownloadFinished()
|
||||
void FtbPackFetchTask::fileDownloadFinished()
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
||||
QStringList failedLists;
|
||||
|
||||
if(!parseAndAddPacks(publicModpacksXmlFileData, PackType::Public, publicPacks))
|
||||
if(!parseAndAddPacks(publicModpacksXmlFileData, FtbPackType::Public, publicPacks))
|
||||
{
|
||||
failedLists.append(tr("Public Packs"));
|
||||
}
|
||||
|
||||
if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, PackType::ThirdParty, thirdPartyPacks))
|
||||
if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, FtbPackType::ThirdParty, thirdPartyPacks))
|
||||
{
|
||||
failedLists.append(tr("Third Party Packs"));
|
||||
}
|
||||
@@ -93,7 +91,7 @@ void PackFetchTask::fileDownloadFinished()
|
||||
}
|
||||
}
|
||||
|
||||
bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list)
|
||||
bool FtbPackFetchTask::parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list)
|
||||
{
|
||||
QDomDocument doc;
|
||||
|
||||
@@ -114,7 +112,7 @@ bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, Modpac
|
||||
{
|
||||
QDomElement element = nodes.at(i).toElement();
|
||||
|
||||
Modpack modpack;
|
||||
FtbModpack modpack;
|
||||
modpack.name = element.attribute("name");
|
||||
modpack.currentVersion = element.attribute("version");
|
||||
modpack.mcVersion = element.attribute("mcVersion");
|
||||
@@ -163,10 +161,8 @@ bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, Modpac
|
||||
return true;
|
||||
}
|
||||
|
||||
void PackFetchTask::fileDownloadFailed(QString reason)
|
||||
void FtbPackFetchTask::fileDownloadFailed(QString reason)
|
||||
{
|
||||
qWarning() << "Fetching FTBPacks failed:" << reason;
|
||||
qWarning() << "Fetching FtbPacks failed:" << reason;
|
||||
emit failed(reason);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,15 +6,13 @@
|
||||
#include <QObject>
|
||||
#include "PackHelpers.h"
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT PackFetchTask : public QObject {
|
||||
class MULTIMC_LOGIC_EXPORT FtbPackFetchTask : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PackFetchTask() = default;
|
||||
virtual ~PackFetchTask() = default;
|
||||
FtbPackFetchTask() = default;
|
||||
virtual ~FtbPackFetchTask() = default;
|
||||
|
||||
void fetch();
|
||||
void fetchPrivate(const QStringList &toFetch);
|
||||
@@ -25,20 +23,18 @@ private:
|
||||
QByteArray publicModpacksXmlFileData;
|
||||
QByteArray thirdPartyModpacksXmlFileData;
|
||||
|
||||
bool parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list);
|
||||
ModpackList publicPacks;
|
||||
ModpackList thirdPartyPacks;
|
||||
bool parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list);
|
||||
FtbModpackList publicPacks;
|
||||
FtbModpackList thirdPartyPacks;
|
||||
|
||||
protected slots:
|
||||
void fileDownloadFinished();
|
||||
void fileDownloadFailed(QString reason);
|
||||
|
||||
signals:
|
||||
void finished(ModpackList publicPacks, ModpackList thirdPartyPacks);
|
||||
void finished(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks);
|
||||
void failed(QString reason);
|
||||
|
||||
void privateFileDownloadFinished(Modpack modpack);
|
||||
void privateFileDownloadFinished(FtbModpack modpack);
|
||||
void privateFileDownloadFailed(QString reason, QString packCode);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,32 +1,28 @@
|
||||
#include "PackInstallTask.h"
|
||||
|
||||
#include "FtbPackInstallTask.h"
|
||||
#include "Env.h"
|
||||
#include "MMCZip.h"
|
||||
|
||||
#include "QtConcurrent"
|
||||
#include "BaseInstance.h"
|
||||
#include "FileSystem.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
#include "minecraft/GradleSpecifier.h"
|
||||
|
||||
#include "net/URLConstants.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
PackInstallTask::PackInstallTask(Modpack pack, QString version)
|
||||
FtbPackInstallTask::FtbPackInstallTask(FtbModpack pack, QString version)
|
||||
{
|
||||
m_pack = pack;
|
||||
m_version = version;
|
||||
}
|
||||
|
||||
void PackInstallTask::executeTask()
|
||||
void FtbPackInstallTask::executeTask()
|
||||
{
|
||||
downloadPack();
|
||||
}
|
||||
|
||||
void PackInstallTask::downloadPack()
|
||||
void FtbPackInstallTask::downloadPack()
|
||||
{
|
||||
setStatus(tr("Downloading zip for %1").arg(m_pack.name));
|
||||
|
||||
@@ -36,46 +32,46 @@ void PackInstallTask::downloadPack()
|
||||
|
||||
entry->setStale(true);
|
||||
QString url;
|
||||
if(m_pack.type == PackType::Private)
|
||||
if(m_pack.type == FtbPackType::Private)
|
||||
{
|
||||
url = QString(URLConstants::LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(packoffset);
|
||||
url = QString(URLConstants::FTB_CDN_BASE_URL + "privatepacks/%1").arg(packoffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
url = QString(URLConstants::LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(packoffset);
|
||||
url = QString(URLConstants::FTB_CDN_BASE_URL + "modpacks/%1").arg(packoffset);
|
||||
}
|
||||
job->addNetAction(Net::Download::makeCached(url, entry));
|
||||
archivePath = entry->getFullPath();
|
||||
|
||||
netJobContainer.reset(job);
|
||||
connect(job, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
||||
connect(job, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
||||
connect(job, &NetJob::progress, this, &PackInstallTask::onDownloadProgress);
|
||||
connect(job, &NetJob::succeeded, this, &FtbPackInstallTask::onDownloadSucceeded);
|
||||
connect(job, &NetJob::failed, this, &FtbPackInstallTask::onDownloadFailed);
|
||||
connect(job, &NetJob::progress, this, &FtbPackInstallTask::onDownloadProgress);
|
||||
job->start();
|
||||
|
||||
progress(1, 4);
|
||||
}
|
||||
|
||||
void PackInstallTask::onDownloadSucceeded()
|
||||
void FtbPackInstallTask::onDownloadSucceeded()
|
||||
{
|
||||
abortable = false;
|
||||
unzip();
|
||||
}
|
||||
|
||||
void PackInstallTask::onDownloadFailed(QString reason)
|
||||
void FtbPackInstallTask::onDownloadFailed(QString reason)
|
||||
{
|
||||
abortable = false;
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
void PackInstallTask::onDownloadProgress(qint64 current, qint64 total)
|
||||
void FtbPackInstallTask::onDownloadProgress(qint64 current, qint64 total)
|
||||
{
|
||||
abortable = true;
|
||||
progress(current, total * 4);
|
||||
setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10));
|
||||
}
|
||||
|
||||
void PackInstallTask::unzip()
|
||||
void FtbPackInstallTask::unzip()
|
||||
{
|
||||
progress(2, 4);
|
||||
setStatus(tr("Extracting modpack"));
|
||||
@@ -89,22 +85,22 @@ void PackInstallTask::unzip()
|
||||
}
|
||||
|
||||
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &FtbPackInstallTask::onUnzipFinished);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &FtbPackInstallTask::onUnzipCanceled);
|
||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||
}
|
||||
|
||||
void PackInstallTask::onUnzipFinished()
|
||||
void FtbPackInstallTask::onUnzipFinished()
|
||||
{
|
||||
install();
|
||||
}
|
||||
|
||||
void PackInstallTask::onUnzipCanceled()
|
||||
void FtbPackInstallTask::onUnzipCanceled()
|
||||
{
|
||||
emitAborted();
|
||||
}
|
||||
|
||||
void PackInstallTask::install()
|
||||
void FtbPackInstallTask::install()
|
||||
{
|
||||
progress(3, 4);
|
||||
setStatus(tr("Installing modpack"));
|
||||
@@ -201,7 +197,7 @@ void PackInstallTask::install()
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
bool PackInstallTask::abort()
|
||||
bool FtbPackInstallTask::abort()
|
||||
{
|
||||
if(abortable)
|
||||
{
|
||||
@@ -209,5 +205,3 @@ bool PackInstallTask::abort()
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,17 +6,15 @@
|
||||
#include "meta/Index.h"
|
||||
#include "meta/Version.h"
|
||||
#include "meta/VersionList.h"
|
||||
#include "PackHelpers.h"
|
||||
#include "modplatform/ftb/PackHelpers.h"
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT PackInstallTask : public InstanceTask
|
||||
class MULTIMC_LOGIC_EXPORT FtbPackInstallTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PackInstallTask(Modpack pack, QString version);
|
||||
virtual ~PackInstallTask(){}
|
||||
explicit FtbPackInstallTask(FtbModpack pack, QString version);
|
||||
virtual ~FtbPackInstallTask(){}
|
||||
|
||||
bool abort() override;
|
||||
|
||||
@@ -45,8 +43,6 @@ private: /* data */
|
||||
NetJobPtr netJobContainer;
|
||||
QString archivePath;
|
||||
|
||||
Modpack m_pack;
|
||||
FtbModpack m_pack;
|
||||
QString m_version;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
#include "PrivatePackManager.h"
|
||||
#include "FtbPrivatePackManager.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
void PrivatePackManager::load()
|
||||
void FtbPrivatePackManager::load()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -20,7 +18,7 @@ void PrivatePackManager::load()
|
||||
}
|
||||
}
|
||||
|
||||
void PrivatePackManager::save() const
|
||||
void FtbPrivatePackManager::save() const
|
||||
{
|
||||
if(!dirty)
|
||||
{
|
||||
@@ -37,5 +35,3 @@ void PrivatePackManager::save() const
|
||||
qWarning() << "Failed to write third party FTB pack codes to" << m_filename;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,12 +5,10 @@
|
||||
#include <QFile>
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT PrivatePackManager
|
||||
class MULTIMC_LOGIC_EXPORT FtbPrivatePackManager
|
||||
{
|
||||
public:
|
||||
~PrivatePackManager()
|
||||
~FtbPrivatePackManager()
|
||||
{
|
||||
save();
|
||||
}
|
||||
@@ -40,5 +38,3 @@ private:
|
||||
QString m_filename = "private_packs.txt";
|
||||
mutable bool dirty = false;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -5,17 +5,15 @@
|
||||
#include <QStringList>
|
||||
#include <QMetaType>
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
//Header for structs etc...
|
||||
enum class PackType
|
||||
enum class FtbPackType
|
||||
{
|
||||
Public,
|
||||
ThirdParty,
|
||||
Private
|
||||
};
|
||||
|
||||
struct Modpack
|
||||
struct FtbModpack
|
||||
{
|
||||
QString name;
|
||||
QString description;
|
||||
@@ -33,13 +31,11 @@ struct Modpack
|
||||
bool bugged = false;
|
||||
bool broken = false;
|
||||
|
||||
PackType type;
|
||||
FtbPackType type;
|
||||
QString packCode;
|
||||
};
|
||||
|
||||
typedef QList<Modpack> ModpackList;
|
||||
|
||||
}
|
||||
|
||||
//We need it for the proxy model
|
||||
Q_DECLARE_METATYPE(LegacyFTB::Modpack)
|
||||
Q_DECLARE_METATYPE(FtbModpack)
|
||||
|
||||
typedef QList<FtbModpack> FtbModpackList;
|
||||
@@ -29,8 +29,7 @@ const QString IMGUR_BASE_URL("https://api.imgur.com/3/");
|
||||
const QString FMLLIBS_OUR_BASE_URL("https://files.multimc.org/fmllibs/");
|
||||
const QString FMLLIBS_FORGE_BASE_URL("https://files.minecraftforge.net/fmllibs/");
|
||||
const QString TRANSLATIONS_BASE_URL("https://files.multimc.org/translations/");
|
||||
|
||||
const QString LEGACY_FTB_CDN_BASE_URL("https://dist.creeper.host/FTB2/");
|
||||
const QString FTB_CDN_BASE_URL("https://ftb.forgecdn.net/FTB2/");
|
||||
|
||||
QString getJarPath(QString version);
|
||||
QString getLegacyJarUrl(QString version);
|
||||
|
||||
@@ -36,10 +36,8 @@ QString INIFile::unescape(QString orig)
|
||||
{
|
||||
if(c == 'n')
|
||||
out += '\n';
|
||||
else if(c == 't')
|
||||
else if (c == 't')
|
||||
out += '\t';
|
||||
else if(c == '#')
|
||||
out += '#';
|
||||
else
|
||||
out += c;
|
||||
prev = 0;
|
||||
@@ -69,8 +67,6 @@ QString INIFile::escape(QString orig)
|
||||
out += "\\t";
|
||||
else if(c == '\\')
|
||||
out += "\\\\";
|
||||
else if(c == '#')
|
||||
out += "\\#";
|
||||
else
|
||||
out += c;
|
||||
}
|
||||
@@ -124,15 +120,7 @@ bool INIFile::loadFile(QByteArray file)
|
||||
{
|
||||
QString &lineRaw = lines[i];
|
||||
// Ignore comments.
|
||||
int commentIndex = 0;
|
||||
QString line = lineRaw;
|
||||
// Search for comments until no more escaped # are available
|
||||
while((commentIndex = line.indexOf('#', commentIndex + 1)) != -1) {
|
||||
if(commentIndex > 0 && line.at(commentIndex - 1) == '\\') {
|
||||
continue;
|
||||
}
|
||||
line = line.left(lineRaw.indexOf('#')).trimmed();
|
||||
}
|
||||
QString line = lineRaw.left(lineRaw.indexOf('#')).trimmed();
|
||||
|
||||
int eqPos = line.indexOf('=');
|
||||
if (eqPos == -1)
|
||||
|
||||
@@ -26,7 +26,6 @@ slots:
|
||||
QTest::newRow("Plain text") << "Lorem ipsum dolor sit amet.";
|
||||
QTest::newRow("Escape sequences") << "Lorem\n\t\n\\n\\tAAZ\nipsum dolor\n\nsit amet.";
|
||||
QTest::newRow("Escape sequences 2") << "\"\n\n\"";
|
||||
QTest::newRow("Hashtags") << "some data#something";
|
||||
}
|
||||
void test_Escape()
|
||||
{
|
||||
@@ -41,7 +40,7 @@ slots:
|
||||
void test_SaveLoad()
|
||||
{
|
||||
QString a = "a";
|
||||
QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\#thisIsNotAComment";
|
||||
QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\";
|
||||
QString filename = "test_SaveLoad.ini";
|
||||
|
||||
// save
|
||||
|
||||
@@ -33,12 +33,7 @@ struct Language
|
||||
Language(const QString & _key)
|
||||
{
|
||||
key = _key;
|
||||
if(key == "pt") {
|
||||
locale = QLocale("pt_PT");
|
||||
}
|
||||
else {
|
||||
locale = QLocale(key);
|
||||
}
|
||||
locale = QLocale(key);
|
||||
updated = (key == defaultLangCode);
|
||||
}
|
||||
|
||||
|
||||
@@ -131,14 +131,7 @@ void DownloadTask::processDownloadedVersionInfo()
|
||||
QObject::connect(netJob.get(), &NetJob::progress, this, &DownloadTask::fileDownloadProgressChanged);
|
||||
QObject::connect(netJob.get(), &NetJob::failed, this, &DownloadTask::fileDownloadFailed);
|
||||
|
||||
if(netJob->size() == 1) // Translation issues... see https://github.com/MultiMC/MultiMC5/issues/1701
|
||||
{
|
||||
setStatus(tr("Downloading one update file."));
|
||||
}
|
||||
else
|
||||
{
|
||||
setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
|
||||
}
|
||||
setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
|
||||
qDebug() << "Begin downloading update files to" << m_updateFilesDir.path();
|
||||
m_filesNetJob = netJob;
|
||||
m_filesNetJob->start();
|
||||
|
||||
@@ -185,6 +185,25 @@ slots:
|
||||
qDebug() << expectedOperations;
|
||||
QCOMPARE(operations, expectedOperations);
|
||||
}
|
||||
|
||||
void test_OSXPathFixup()
|
||||
{
|
||||
QString path, pathOrig;
|
||||
bool result;
|
||||
// Proper OSX path
|
||||
pathOrig = path = "MultiMC.app/Foo/Bar/Baz";
|
||||
qDebug() << "Proper OSX path: " << path;
|
||||
result = fixPathForOSX(path);
|
||||
QCOMPARE(path, QString("Foo/Bar/Baz"));
|
||||
QCOMPARE(result, true);
|
||||
|
||||
// Bad OSX path
|
||||
pathOrig = path = "translations/klingon.lol";
|
||||
qDebug() << "Bad OSX path: " << path;
|
||||
result = fixPathForOSX(path);
|
||||
QCOMPARE(path, pathOrig);
|
||||
QCOMPARE(result, false);
|
||||
}
|
||||
};
|
||||
|
||||
extern "C"
|
||||
|
||||
@@ -33,7 +33,13 @@ bool parseVersionInfo(const QByteArray &data, VersionFileList &list, QString &er
|
||||
QJsonObject fileObj = fileValue.toObject();
|
||||
|
||||
QString file_path = fileObj.value("Path").toString();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// On OSX, the paths for the updater need to be fixed.
|
||||
// basically, anything that isn't in the .app folder is ignored.
|
||||
// everything else is changed so the code that processes the files actually finds
|
||||
// them and puts the replacements in the right spots.
|
||||
fixPathForOSX(file_path);
|
||||
#endif
|
||||
VersionFileEntry file{file_path, fileObj.value("Perms").toVariant().toInt(),
|
||||
FileSourceList(), fileObj.value("MD5").toString(), };
|
||||
qDebug() << "File" << file.path << "with perms" << file.mode;
|
||||
@@ -195,4 +201,19 @@ bool processFileLists
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fixPathForOSX(QString &path)
|
||||
{
|
||||
if (path.startsWith("MultiMC.app/"))
|
||||
{
|
||||
// remove the prefix and add a new, more appropriate one.
|
||||
path.remove(0, 12);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Update path not within .app: " << path;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,5 +123,14 @@ bool MULTIMC_LOGIC_EXPORT processFileLists
|
||||
OperationList &ops
|
||||
);
|
||||
|
||||
/*!
|
||||
* This fixes destination paths for OSX - removes 'MultiMC.app' prefix
|
||||
* The updater runs in MultiMC.app/Contents/MacOs by default
|
||||
* The destination paths are such as this: MultiMC.app/blah/blah
|
||||
*
|
||||
* @return false if the path couldn't be fixed (is invalid)
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT fixPathForOSX(QString &path);
|
||||
|
||||
}
|
||||
Q_DECLARE_METATYPE(GoUpdate::Status)
|
||||
|
||||
@@ -125,14 +125,20 @@ SET(MULTIMC_SOURCES
|
||||
pages/global/ProxyPage.h
|
||||
pages/global/PasteEEPage.cpp
|
||||
pages/global/PasteEEPage.h
|
||||
pages/global/PackagesPage.cpp
|
||||
pages/global/PackagesPage.h
|
||||
|
||||
# GUI - platform pages
|
||||
pages/modplatform/VanillaPage.cpp
|
||||
pages/modplatform/VanillaPage.h
|
||||
pages/modplatform/legacy_ftb/Page.cpp
|
||||
pages/modplatform/legacy_ftb/Page.h
|
||||
pages/modplatform/legacy_ftb/ListModel.h
|
||||
pages/modplatform/legacy_ftb/ListModel.cpp
|
||||
pages/modplatform/FTBPage.cpp
|
||||
pages/modplatform/FTBPage.h
|
||||
pages/modplatform/FtbListModel.h
|
||||
pages/modplatform/FtbListModel.cpp
|
||||
pages/modplatform/TwitchPage.cpp
|
||||
pages/modplatform/TwitchPage.h
|
||||
pages/modplatform/TechnicPage.cpp
|
||||
pages/modplatform/TechnicPage.h
|
||||
pages/modplatform/ImportPage.cpp
|
||||
pages/modplatform/ImportPage.h
|
||||
|
||||
@@ -153,6 +159,8 @@ SET(MULTIMC_SOURCES
|
||||
dialogs/IconPickerDialog.h
|
||||
dialogs/LoginDialog.cpp
|
||||
dialogs/LoginDialog.h
|
||||
dialogs/ModEditDialogCommon.cpp
|
||||
dialogs/ModEditDialogCommon.h
|
||||
dialogs/NewComponentDialog.cpp
|
||||
dialogs/NewComponentDialog.h
|
||||
dialogs/NewInstanceDialog.cpp
|
||||
@@ -176,8 +184,6 @@ SET(MULTIMC_SOURCES
|
||||
widgets/Common.h
|
||||
widgets/CustomCommands.cpp
|
||||
widgets/CustomCommands.h
|
||||
widgets/DropLabel.cpp
|
||||
widgets/DropLabel.h
|
||||
widgets/FocusLineEdit.cpp
|
||||
widgets/FocusLineEdit.h
|
||||
widgets/IconLabel.cpp
|
||||
@@ -207,15 +213,10 @@ SET(MULTIMC_SOURCES
|
||||
widgets/VersionSelectWidget.h
|
||||
widgets/ProgressWidget.h
|
||||
widgets/ProgressWidget.cpp
|
||||
widgets/WideBar.h
|
||||
widgets/WideBar.cpp
|
||||
|
||||
# GUI - instance group view
|
||||
groupview/GroupedProxyModel.cpp
|
||||
groupview/GroupedProxyModel.h
|
||||
groupview/AccessibleGroupView.cpp
|
||||
groupview/AccessibleGroupView.h
|
||||
groupview/AccessibleGroupView_p.h
|
||||
groupview/GroupView.cpp
|
||||
groupview/GroupView.h
|
||||
groupview/InstanceDelegate.cpp
|
||||
@@ -247,10 +248,13 @@ SET(MULTIMC_UIS
|
||||
pages/global/MultiMCPage.ui
|
||||
pages/global/ProxyPage.ui
|
||||
pages/global/PasteEEPage.ui
|
||||
pages/global/PackagesPage.ui
|
||||
|
||||
# Platform pages
|
||||
pages/modplatform/VanillaPage.ui
|
||||
pages/modplatform/legacy_ftb/Page.ui
|
||||
pages/modplatform/FTBPage.ui
|
||||
pages/modplatform/TwitchPage.ui
|
||||
pages/modplatform/TechnicPage.ui
|
||||
pages/modplatform/ImportPage.ui
|
||||
|
||||
# Dialogs
|
||||
@@ -274,6 +278,7 @@ SET(MULTIMC_UIS
|
||||
)
|
||||
|
||||
set(MULTIMC_QRCS
|
||||
resources/assets/assets.qrc
|
||||
resources/backgrounds/backgrounds.qrc
|
||||
resources/multimc/multimc.qrc
|
||||
resources/pe_dark/pe_dark.qrc
|
||||
@@ -297,7 +302,7 @@ qt5_add_resources(MULTIMC_RESOURCES ${MULTIMC_QRCS})
|
||||
|
||||
# Add executable
|
||||
add_executable(MultiMC MACOSX_BUNDLE WIN32 ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MULTIMC_RESOURCES} ${MULTIMC_RCS})
|
||||
target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown MultiMC_rainbow LocalPeer ganalytics Qt5::Multimedia)
|
||||
target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown MultiMC_rainbow LocalPeer ganalytics)
|
||||
if(DEFINED MultiMC_APP_BINARY_NAME)
|
||||
set_target_properties(MultiMC PROPERTIES OUTPUT_NAME "${MultiMC_APP_BINARY_NAME}")
|
||||
endif()
|
||||
|
||||
@@ -45,7 +45,7 @@ public:
|
||||
values.append(new ResourcePackPage(onesix.get()));
|
||||
values.append(new TexturePackPage(onesix.get()));
|
||||
values.append(new NotesPage(onesix.get()));
|
||||
values.append(new WorldListPage(onesix.get(), onesix->worldList()));
|
||||
values.append(new WorldListPage(onesix.get(), onesix->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds"));
|
||||
values.append(new ServersPage(onesix.get()));
|
||||
// values.append(new GameOptionsPage(onesix.get()));
|
||||
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
{
|
||||
values.append(new LegacyUpgradePage(legacy));
|
||||
values.append(new NotesPage(legacy.get()));
|
||||
values.append(new WorldListPage(legacy.get(), legacy->worldList()));
|
||||
values.append(new WorldListPage(legacy.get(), legacy->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds"));
|
||||
values.append(new ScreenshotsPage(FS::PathCombine(legacy->gameRoot(), "screenshots")));
|
||||
}
|
||||
auto logMatcher = inst->getLogFileMatcher();
|
||||
|
||||
@@ -50,7 +50,6 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent)
|
||||
m_container = new PageContainer(provider.get(), "console", this);
|
||||
m_container->setParentContainer(this);
|
||||
setCentralWidget(m_container);
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Add custom buttons to the page container layout.
|
||||
@@ -150,13 +149,10 @@ void InstanceWindow::on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask
|
||||
m_proc = proc;
|
||||
}
|
||||
|
||||
void InstanceWindow::on_RunningState_changed(bool running)
|
||||
void InstanceWindow::on_RunningState_changed(bool)
|
||||
{
|
||||
updateLaunchButtons();
|
||||
m_container->refreshContainer();
|
||||
if(running) {
|
||||
selectPage("log");
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceWindow::on_closeButton_clicked()
|
||||
|
||||
@@ -24,7 +24,7 @@ void LaunchController::executeTask()
|
||||
{
|
||||
if (!m_instance)
|
||||
{
|
||||
emitFailed(tr("No instance specified!"));
|
||||
emitFailed(tr("No instance specified"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ void LaunchController::login()
|
||||
// if no account is selected, we bail
|
||||
if (!account.get())
|
||||
{
|
||||
emitFailed(tr("No account selected for launch."));
|
||||
emitFailed(tr("No account selected for launch"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,8 @@ void LaunchController::login()
|
||||
// we loop until the user succeeds in logging in or gives up
|
||||
bool tryagain = true;
|
||||
// the failure. the default failure.
|
||||
const QString needLoginAgain = tr("Your account is currently not logged in. Please enter your password to log in again. <br /> <br /> This could be caused by a password change.");
|
||||
const QString needLoginAgain = tr("Your account is currently not logged in. Please enter "
|
||||
"your password to log in again.");
|
||||
QString failReason = needLoginAgain;
|
||||
|
||||
while (tryagain)
|
||||
@@ -192,7 +193,7 @@ void LaunchController::launchInstance()
|
||||
|
||||
if(!m_instance->reloadSettings())
|
||||
{
|
||||
QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't load the instance profile."));
|
||||
QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't load the instance profile."));
|
||||
emitFailed(tr("Couldn't load the instance profile."));
|
||||
return;
|
||||
}
|
||||
@@ -232,8 +233,8 @@ void LaunchController::readyForLaunch()
|
||||
if (!m_profiler->check(&error))
|
||||
{
|
||||
m_launcher->abort();
|
||||
QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error));
|
||||
emitFailed("Profiler startup failed!");
|
||||
QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't start profiler: %1").arg(error));
|
||||
emitFailed("Profiler startup failed");
|
||||
return;
|
||||
}
|
||||
BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
|
||||
@@ -244,7 +245,7 @@ void LaunchController::readyForLaunch()
|
||||
msg.setText(tr("The game launch is delayed until you press the "
|
||||
"button. This is the right time to setup the profiler, as the "
|
||||
"profiler server is running now.\n\n%1").arg(message));
|
||||
msg.setWindowTitle(tr("Waiting."));
|
||||
msg.setWindowTitle(tr("Waiting"));
|
||||
msg.setIcon(QMessageBox::Information);
|
||||
msg.addButton(tr("Launch"), QMessageBox::AcceptRole);
|
||||
msg.setModal(true);
|
||||
@@ -261,7 +262,7 @@ void LaunchController::readyForLaunch()
|
||||
msg.setModal(true);
|
||||
msg.exec();
|
||||
m_launcher->abort();
|
||||
emitFailed("Profiler startup failed!");
|
||||
emitFailed("Profiler startup failed");
|
||||
});
|
||||
profilerInstance->beginProfiling(m_launcher);
|
||||
}
|
||||
|
||||
@@ -288,7 +288,6 @@ public:
|
||||
foldersMenuButton->setPopupMode(QToolButton::InstantPopup);
|
||||
foldersMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
foldersMenuButton->setIcon(MMC->getThemedIcon("viewfolder"));
|
||||
foldersMenuButton->setFocusPolicy(Qt::NoFocus);
|
||||
all_toolbuttons.append(&foldersMenuButton);
|
||||
QWidgetAction* foldersButtonAction = new QWidgetAction(MainWindow);
|
||||
foldersButtonAction->setDefaultWidget(foldersMenuButton);
|
||||
@@ -346,7 +345,6 @@ public:
|
||||
helpMenuButton->setPopupMode(QToolButton::InstantPopup);
|
||||
helpMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
helpMenuButton->setIcon(MMC->getThemedIcon("help"));
|
||||
helpMenuButton->setFocusPolicy(Qt::NoFocus);
|
||||
all_toolbuttons.append(&helpMenuButton);
|
||||
QWidgetAction* helpButtonAction = new QWidgetAction(MainWindow);
|
||||
helpButtonAction->setDefaultWidget(helpMenuButton);
|
||||
@@ -575,10 +573,9 @@ public:
|
||||
{
|
||||
MainWindow->setObjectName(QStringLiteral("MainWindow"));
|
||||
}
|
||||
MainWindow->resize(800, 600);
|
||||
MainWindow->resize(694, 563);
|
||||
MainWindow->setWindowIcon(MMC->getThemedIcon("logo"));
|
||||
MainWindow->setWindowTitle("MultiMC 5");
|
||||
MainWindow->setAccessibleName("MultiMC");
|
||||
|
||||
createMainToolbar(MainWindow);
|
||||
|
||||
@@ -586,6 +583,7 @@ public:
|
||||
centralWidget->setObjectName(QStringLiteral("centralWidget"));
|
||||
horizontalLayout = new QHBoxLayout(centralWidget);
|
||||
horizontalLayout->setSpacing(0);
|
||||
horizontalLayout->setContentsMargins(11, 11, 11, 11);
|
||||
horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
|
||||
horizontalLayout->setSizeConstraint(QLayout::SetDefaultConstraint);
|
||||
horizontalLayout->setContentsMargins(0, 0, 0, 0);
|
||||
@@ -654,7 +652,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
|
||||
newsLabel->setIcon(MMC->getThemedIcon("news"));
|
||||
newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
newsLabel->setFocusPolicy(Qt::NoFocus);
|
||||
ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel);
|
||||
QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked);
|
||||
QObject::connect(m_newsChecker.get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel);
|
||||
@@ -683,10 +680,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
|
||||
connect(proxymodel, &InstanceProxyModel::dataChanged, this, &MainWindow::instanceDataChanged);
|
||||
|
||||
view->setModel(proxymodel);
|
||||
view->setSourceOfGroupCollapseStatus([](const QString & groupName)->bool {
|
||||
return MMC->instances()->isGroupCollapsed(groupName);
|
||||
});
|
||||
connect(view, &GroupView::groupStateChanged, MMC->instances().get(), &InstanceList::on_GroupStateChanged);
|
||||
ui->horizontalLayout->addWidget(view);
|
||||
}
|
||||
// The cat background
|
||||
@@ -709,9 +702,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
|
||||
// model reset -> selection is invalid. All the instance pointers are wrong.
|
||||
connect(MMC->instances().get(), &InstanceList::dataIsInvalid, this, &MainWindow::selectionBad);
|
||||
|
||||
// handle newly added instances
|
||||
connect(MMC->instances().get(), &InstanceList::instanceSelectRequest, this, &MainWindow::instanceSelectRequest);
|
||||
|
||||
// When the global settings page closes, we want to know about it and update our state
|
||||
connect(MMC, &MultiMC::globalSettingsClosed, this, &MainWindow::globalSettingsClosed);
|
||||
|
||||
@@ -835,13 +825,6 @@ MainWindow::~MainWindow()
|
||||
{
|
||||
}
|
||||
|
||||
QMenu * MainWindow::createPopupMenu()
|
||||
{
|
||||
QMenu* filteredMenu = QMainWindow::createPopupMenu();
|
||||
filteredMenu->removeAction( ui->mainToolBar->toggleViewAction() );
|
||||
return filteredMenu;
|
||||
}
|
||||
|
||||
void MainWindow::konamiTriggered()
|
||||
{
|
||||
// ENV.enableFeature("NewModsPage");
|
||||
@@ -1285,7 +1268,7 @@ void MainWindow::downloadUpdates(GoUpdate::Status status)
|
||||
|
||||
void MainWindow::onCatToggled(bool state)
|
||||
{
|
||||
setCatBackground(state, false);
|
||||
setCatBackground(state);
|
||||
MMC->settings()->set("TheCat", state);
|
||||
}
|
||||
|
||||
@@ -1299,9 +1282,8 @@ T non_stupid_abs(T in)
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setCatBackground(bool enabled, bool initial)
|
||||
void MainWindow::setCatBackground(bool enabled)
|
||||
{
|
||||
static int currentMeow = 0;
|
||||
if (enabled)
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
@@ -1318,28 +1300,9 @@ GroupView
|
||||
background-repeat: none;
|
||||
background-color:palette(base);
|
||||
})").arg(cat));
|
||||
if(!initial) {
|
||||
const char * sounds[] = {
|
||||
"/home/peterix/meows/Fiona52fixed.wav",
|
||||
"/home/peterix/meows/Fiona72.wav",
|
||||
"/home/peterix/meows/Fiona76.wav",
|
||||
"/home/peterix/meows/Fiona81.wav",
|
||||
"/home/peterix/meows/Fiona85.wav"
|
||||
};
|
||||
qDebug() << "PLAY MEOW" << currentMeow;
|
||||
meow.setSource(QUrl::fromLocalFile(sounds[currentMeow]));
|
||||
meow.setLoopCount(1);
|
||||
meow.play();
|
||||
int newMeow = qrand() % 4;
|
||||
if(newMeow == currentMeow) {
|
||||
newMeow++;
|
||||
}
|
||||
currentMeow = newMeow;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
meow.stop();
|
||||
view->setStyleSheet(QString());
|
||||
}
|
||||
}
|
||||
@@ -1378,7 +1341,7 @@ void MainWindow::on_actionCopyInstance_triggered()
|
||||
if (!copyInstDlg.exec())
|
||||
return;
|
||||
|
||||
auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves(), copyInstDlg.shouldKeepPlaytime());
|
||||
auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves());
|
||||
copyTask->setName(copyInstDlg.instName());
|
||||
copyTask->setGroup(copyInstDlg.instGroup());
|
||||
copyTask->setIcon(copyInstDlg.iconKey());
|
||||
@@ -1688,13 +1651,19 @@ void MainWindow::on_actionAbout_triggered()
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void MainWindow::on_mainToolBar_visibilityChanged(bool)
|
||||
{
|
||||
// Don't allow hiding the main toolbar.
|
||||
// This is the only way I could find to prevent it... :/
|
||||
ui->mainToolBar->setVisible(true);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionDeleteInstance_triggered()
|
||||
{
|
||||
if (!m_selectedInstance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto id = m_selectedInstance->id();
|
||||
auto response = CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("CAREFUL!"),
|
||||
@@ -1705,7 +1674,7 @@ void MainWindow::on_actionDeleteInstance_triggered()
|
||||
)->exec();
|
||||
if (response == QMessageBox::Yes)
|
||||
{
|
||||
MMC->instances()->deleteInstance(id);
|
||||
MMC->instances()->deleteInstance(m_selectedInstance->id());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1867,11 +1836,6 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::instanceSelectRequest(QString id)
|
||||
{
|
||||
setSelectedInstanceById(id);
|
||||
}
|
||||
|
||||
void MainWindow::instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
|
||||
{
|
||||
auto current = view->selectionModel()->currentIndex();
|
||||
@@ -1921,7 +1885,7 @@ void MainWindow::checkInstancePathForProblems()
|
||||
warning.setDefaultButton(QMessageBox::Ok);
|
||||
warning.exec();
|
||||
}
|
||||
else if (pathfoldername.startsWith(QDir::tempPath()) || pathfoldername.contains("/TempState/"))
|
||||
else if (pathfoldername.contains(QDir::tempPath()))
|
||||
{
|
||||
QMessageBox warning(this);
|
||||
warning.setText(tr("Your instance folder is in a temporary folder: \'%1\'!").arg(QDir::tempPath()));
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include <QMainWindow>
|
||||
#include <QProcess>
|
||||
#include <QTimer>
|
||||
#include <QtMultimedia/QSoundEffect>
|
||||
|
||||
#include "BaseInstance.h"
|
||||
#include "minecraft/auth/MojangAccount.h"
|
||||
@@ -61,9 +60,6 @@ public:
|
||||
signals:
|
||||
void isClosing();
|
||||
|
||||
protected:
|
||||
QMenu * createPopupMenu() override;
|
||||
|
||||
private slots:
|
||||
void onCatToggled(bool);
|
||||
|
||||
@@ -113,6 +109,8 @@ private slots:
|
||||
|
||||
void newsButtonClicked();
|
||||
|
||||
void on_mainToolBar_visibilityChanged(bool);
|
||||
|
||||
void on_actionLaunchInstance_triggered();
|
||||
|
||||
void on_actionLaunchInstanceOffline_triggered();
|
||||
@@ -154,8 +152,6 @@ private slots:
|
||||
|
||||
void instanceChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
|
||||
void instanceSelectRequest(QString id);
|
||||
|
||||
void instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
|
||||
|
||||
void selectionBad();
|
||||
@@ -190,7 +186,7 @@ private slots:
|
||||
private:
|
||||
void addInstance(QString url = QString());
|
||||
void activateInstance(InstancePtr instance);
|
||||
void setCatBackground(bool enabled, bool initial = true);
|
||||
void setCatBackground(bool enabled);
|
||||
void updateInstanceToolIcon(QString new_icon);
|
||||
void setSelectedInstanceById(const QString &id);
|
||||
|
||||
@@ -201,8 +197,6 @@ private:
|
||||
private:
|
||||
std::unique_ptr<Ui> ui;
|
||||
|
||||
QSoundEffect meow;
|
||||
|
||||
// these are managed by Qt's memory management model!
|
||||
GroupView *view = nullptr;
|
||||
InstanceProxyModel *proxymodel = nullptr;
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
#include "BuildConfig.h"
|
||||
#include "MainWindow.h"
|
||||
#include "InstanceWindow.h"
|
||||
|
||||
#include "groupview/AccessibleGroupView.h"
|
||||
#include <QAccessible>
|
||||
|
||||
#include "pages/BasePageProvider.h"
|
||||
#include "pages/global/MultiMCPage.h"
|
||||
#include "pages/global/MinecraftPage.h"
|
||||
@@ -15,6 +11,7 @@
|
||||
#include "pages/global/ExternalToolsPage.h"
|
||||
#include "pages/global/AccountListPage.h"
|
||||
#include "pages/global/PasteEEPage.h"
|
||||
#include "pages/global/PackagesPage.h"
|
||||
#include "pages/global/CustomCommandsPage.h"
|
||||
|
||||
#include "themes/ITheme.h"
|
||||
@@ -156,23 +153,23 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
|
||||
// --help
|
||||
parser.addSwitch("help");
|
||||
parser.addShortOpt("help", 'h');
|
||||
parser.addDocumentation("help", "Display this help and exit.");
|
||||
parser.addDocumentation("help", "display this help and exit.");
|
||||
// --version
|
||||
parser.addSwitch("version");
|
||||
parser.addShortOpt("version", 'V');
|
||||
parser.addDocumentation("version", "Display program version and exit.");
|
||||
parser.addDocumentation("version", "display program version and exit.");
|
||||
// --dir
|
||||
parser.addOption("dir");
|
||||
parser.addShortOpt("dir", 'd');
|
||||
parser.addDocumentation("dir", "Use the supplied folder as MultiMC root instead of "
|
||||
parser.addDocumentation("dir", "use the supplied folder as MultiMC root instead of "
|
||||
"the binary location (use '.' for current)");
|
||||
// --launch
|
||||
parser.addOption("launch");
|
||||
parser.addShortOpt("launch", 'l');
|
||||
parser.addDocumentation("launch", "Launch the specified instance (by instance ID)");
|
||||
parser.addDocumentation("launch", "launch the specified instance (by instance ID)");
|
||||
// --alive
|
||||
parser.addSwitch("alive");
|
||||
parser.addDocumentation("alive", "Write a small '" + liveCheckFile + "' file after MultiMC starts");
|
||||
parser.addDocumentation("alive", "write a small '" + liveCheckFile + "' file after MultiMC starts");
|
||||
|
||||
// parse the arguments
|
||||
try
|
||||
@@ -381,7 +378,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
|
||||
auto payload = appID.toString().toUtf8();
|
||||
if(check.write(payload) != payload.size())
|
||||
{
|
||||
qWarning() << "Could not write into" << liveCheckFile << "!";
|
||||
qWarning() << "Could not write into" << liveCheckFile;
|
||||
check.remove();
|
||||
break;
|
||||
}
|
||||
@@ -526,6 +523,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
|
||||
m_globalSettingsProvider->addPage<LanguagePage>();
|
||||
m_globalSettingsProvider->addPage<CustomCommandsPage>();
|
||||
m_globalSettingsProvider->addPage<ProxyPage>();
|
||||
// m_globalSettingsProvider->addPage<PackagesPage>();
|
||||
m_globalSettingsProvider->addPage<ExternalToolsPage>();
|
||||
m_globalSettingsProvider->addPage<AccountListPage>();
|
||||
m_globalSettingsProvider->addPage<PasteEEPage>();
|
||||
@@ -533,10 +531,6 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
|
||||
qDebug() << "<> Settings loaded.";
|
||||
}
|
||||
|
||||
#ifndef QT_NO_ACCESSIBILITY
|
||||
QAccessible::installFactory(groupViewAccessibleFactory);
|
||||
#endif /* !QT_NO_ACCESSIBILITY */
|
||||
|
||||
// load translations
|
||||
{
|
||||
m_translations.reset(new TranslationsModel("translations"));
|
||||
@@ -600,12 +594,12 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
|
||||
{
|
||||
auto InstDirSetting = m_settings->getSetting("InstanceDir");
|
||||
// instance path: check for problems with '!' in instance path and warn the user in the log
|
||||
// and remember that we have to show him a dialog when the gui starts (if it does so)
|
||||
// and rememer that we have to show him a dialog when the gui starts (if it does so)
|
||||
QString instDir = InstDirSetting->get().toString();
|
||||
qDebug() << "Instance path : " << instDir;
|
||||
if (FS::checkProblemticPathJava(QDir(instDir)))
|
||||
{
|
||||
qWarning() << "Your instance path contains \'!\' and this is known to cause java problems!";
|
||||
qWarning() << "Your instance path contains \'!\' and this is known to cause java problems";
|
||||
}
|
||||
m_instances.reset(new InstanceList(m_settings, instDir, this));
|
||||
connect(InstDirSetting.get(), &Setting::SettingChanged, m_instances.get(), &InstanceList::on_InstFolderChanged);
|
||||
@@ -947,7 +941,7 @@ bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *pro
|
||||
{
|
||||
if(m_updateRunning)
|
||||
{
|
||||
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
|
||||
qDebug() << "Cannot launch instances while an update is running.";
|
||||
}
|
||||
else if(instance->canLaunch())
|
||||
{
|
||||
@@ -996,7 +990,7 @@ bool MultiMC::kill(InstancePtr instance)
|
||||
{
|
||||
if (!instance->isRunning())
|
||||
{
|
||||
qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running.";
|
||||
qWarning() << "Attempted to kill instance" << instance->id() << "which isn't running.";
|
||||
return false;
|
||||
}
|
||||
auto & extras = m_instanceExtras[instance->id()];
|
||||
|
||||
@@ -23,43 +23,56 @@
|
||||
|
||||
#include "HoeDown.h"
|
||||
|
||||
namespace {
|
||||
// Credits
|
||||
// This is a hack, but I can't think of a better way to do this easily without screwing with QTextDocument...
|
||||
QString getCreditsHtml(QStringList patrons)
|
||||
static QString getCreditsHtml(QStringList patrons)
|
||||
{
|
||||
QString patronsHeading = QObject::tr("Patrons", "About Credits");
|
||||
QString output;
|
||||
QTextStream stream(&output);
|
||||
stream << "<center>\n";
|
||||
// TODO: possibly retrieve from git history at build time?
|
||||
stream << "<h3>" << QObject::tr("MultiMC Developers", "About Credits") << "</h3>\n";
|
||||
stream << "<p>Andrew Okin <<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>></p>\n";
|
||||
stream << "<p>Petr Mrázek <<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>></p>\n";
|
||||
stream << "<p>Sky Welch <<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>></p>\n";
|
||||
stream << "<p>Jan (02JanDal) <<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>></p>\n";
|
||||
stream << "<p>RoboSky <<a href='https://twitter.com/RoboSky_'>@RoboSky_</a>></p>\n";
|
||||
stream << "<br />\n";
|
||||
|
||||
stream << "<h3>" << QObject::tr("With thanks to", "About Credits") << "</h3>\n";
|
||||
stream << "<p>Orochimarufan <<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>></p>\n";
|
||||
stream << "<p>TakSuyu <<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>></p>\n";
|
||||
stream << "<p>Kilobyte <<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>></p>\n";
|
||||
stream << "<p>Rootbear75 <<a href='https://twitter.com/rootbear75'>@rootbear75</a>></p>\n";
|
||||
stream << "<br />\n";
|
||||
|
||||
if(!patrons.isEmpty()) {
|
||||
stream << "<h3>" << QObject::tr("Patrons", "About Credits") << "</h3>\n";
|
||||
QString creditsHtml = QObject::tr(
|
||||
"<!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 { white-space: pre-wrap; margin-top:2px; margin-bottom:2px; }"
|
||||
"</style>"
|
||||
"</head>"
|
||||
""
|
||||
"<body style=' font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;'>"
|
||||
""
|
||||
"<h3>MultiMC Developers</h3>"
|
||||
"<p>Andrew Okin <<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>></p>"
|
||||
"<p>Petr Mrázek <<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>></p>"
|
||||
"<p>Sky Welch <<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>></p>"
|
||||
"<p>Jan (02JanDal) <<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>></p>"
|
||||
"<p>RoboSky <<a href='https://twitter.com/RoboSky_'>@RoboSky_</a>></p>"
|
||||
""
|
||||
"<h3>With thanks to</h3>"
|
||||
"<p>Orochimarufan <<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>></p>"
|
||||
"<p>TakSuyu <<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>></p>"
|
||||
"<p>Kilobyte <<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>></p>"
|
||||
"<p>Rootbear75 <<a href='https://twitter.com/rootbear75'>@rootbear75</a>></p>"
|
||||
""
|
||||
"<h3>Patrons</h3>"
|
||||
"%1"
|
||||
""
|
||||
"</body>"
|
||||
"</html>");
|
||||
if (patrons.isEmpty())
|
||||
return creditsHtml.arg(QObject::tr("<p>Loading...</p>"));
|
||||
else
|
||||
{
|
||||
QString patronsStr;
|
||||
for (QString patron : patrons)
|
||||
{
|
||||
stream << "<p>" << patron << "</p>\n";
|
||||
patronsStr.append(QString("<p>%1</p>").arg(patron));
|
||||
}
|
||||
|
||||
return creditsHtml.arg(patronsStr);
|
||||
}
|
||||
stream << "</center>\n";
|
||||
return output;
|
||||
}
|
||||
|
||||
QString getLicenseHtml()
|
||||
static QString getLicenseHtml()
|
||||
{
|
||||
HoeDown hoedown;
|
||||
QFile dataFile(":/documents/COPYING.md");
|
||||
@@ -68,8 +81,6 @@ QString getLicenseHtml()
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
@@ -98,15 +109,6 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia
|
||||
else
|
||||
ui->channelLabel->setVisible(false);
|
||||
|
||||
ui->redistributionText->setHtml(tr(
|
||||
"<p>We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.</p>\n"
|
||||
"<p>Part of the reason for using the Apache license is we don't want people using the "MultiMC" name when redistributing the project. "
|
||||
"This means people must take the time to go through the source code and remove all references to "MultiMC", including but not limited to the project "
|
||||
"icon and the title of windows, (no <b>MultiMC-fork</b> in the title).</p>\n"
|
||||
"<p>The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. "
|
||||
"However, it should be abundantly clear that the project is a fork <b>without</b> implying that you have our blessing.</p>"
|
||||
));
|
||||
|
||||
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));
|
||||
|
||||
connect(ui->aboutQt, &QPushButton::clicked, &QApplication::aboutQt);
|
||||
|
||||
@@ -212,11 +212,28 @@
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="html">
|
||||
<string><!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; }
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html></string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="translationInfo">
|
||||
<property name="text">
|
||||
<string extracomment="Hey, Translator, feel free to put credit to you here">No Language file loaded.</string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="licenseTab">
|
||||
@@ -240,6 +257,13 @@
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="html">
|
||||
<string notr="true"><!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; }
|
||||
</style></head><body style=" font-family:'DejaVu Sans Mono'; font-size:12pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
@@ -253,7 +277,18 @@
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="redistributionText">
|
||||
<widget class="QTextEdit" name="textEdit">
|
||||
<property name="html">
|
||||
<string><!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; }
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Bitstream Vera Sans'; font-size:11pt;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">Part of the reason for using the Apache license is we don't want people using the &quot;MultiMC&quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &quot;MultiMC&quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Bitstream Vera Sans'; font-size:11pt;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork </span><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:600;">without</span><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;"> implying that you have our blessing.</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
@@ -302,11 +337,14 @@
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>creditsText</tabstop>
|
||||
<tabstop>translationInfo</tabstop>
|
||||
<tabstop>licenseText</tabstop>
|
||||
<tabstop>redistributionText</tabstop>
|
||||
<tabstop>textEdit</tabstop>
|
||||
<tabstop>aboutQt</tabstop>
|
||||
<tabstop>closeButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<resources>
|
||||
<include location="../../resources/multimc/multimc.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -53,7 +53,6 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
|
||||
ui->groupBox->setCurrentIndex(index);
|
||||
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
|
||||
ui->copySavesCheckbox->setChecked(m_copySaves);
|
||||
ui->keepPlaytimeCheckbox->setChecked(m_keepPlaytime);
|
||||
}
|
||||
|
||||
CopyInstanceDialog::~CopyInstanceDialog()
|
||||
@@ -124,21 +123,3 @@ void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state)
|
||||
m_copySaves = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool CopyInstanceDialog::shouldKeepPlaytime() const
|
||||
{
|
||||
return m_keepPlaytime;
|
||||
}
|
||||
|
||||
|
||||
void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state)
|
||||
{
|
||||
if(state == Qt::Unchecked)
|
||||
{
|
||||
m_keepPlaytime = false;
|
||||
}
|
||||
else if(state == Qt::Checked)
|
||||
{
|
||||
m_keepPlaytime = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,19 +40,16 @@ public:
|
||||
QString instGroup() const;
|
||||
QString iconKey() const;
|
||||
bool shouldCopySaves() const;
|
||||
bool shouldKeepPlaytime() const;
|
||||
|
||||
private
|
||||
slots:
|
||||
void on_iconButton_clicked();
|
||||
void on_instNameTextBox_textChanged(const QString &arg1);
|
||||
void on_copySavesCheckbox_stateChanged(int state);
|
||||
void on_keepPlaytimeCheckbox_stateChanged(int state);
|
||||
|
||||
private:
|
||||
Ui::CopyInstanceDialog *ui;
|
||||
QString InstIconKey;
|
||||
InstancePtr m_original;
|
||||
bool m_copySaves = true;
|
||||
bool m_keepPlaytime = true;
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>345</width>
|
||||
<height>323</height>
|
||||
<height>240</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -116,13 +116,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="keepPlaytimeCheckbox">
|
||||
<property name="text">
|
||||
<string>Keep play time</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
@@ -135,13 +128,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>iconButton</tabstop>
|
||||
<tabstop>instNameTextBox</tabstop>
|
||||
<tabstop>groupBox</tabstop>
|
||||
<tabstop>copySavesCheckbox</tabstop>
|
||||
<tabstop>keepPlaytimeCheckbox</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../graphics.qrc"/>
|
||||
</resources>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<item>
|
||||
<widget class="QLineEdit" name="userTextBox">
|
||||
<property name="placeholderText">
|
||||
<string>Email</string>
|
||||
<string>Email / Username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<item>
|
||||
<widget class="QLineEdit" name="userTextBox">
|
||||
<property name="placeholderText">
|
||||
<string>Email</string>
|
||||
<string>Email / Username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
40
application/dialogs/ModEditDialogCommon.cpp
Normal file
40
application/dialogs/ModEditDialogCommon.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "ModEditDialogCommon.h"
|
||||
#include "CustomMessageBox.h"
|
||||
#include <QUrl>
|
||||
|
||||
bool lastfirst(QModelIndexList &list, int &first, int &last)
|
||||
{
|
||||
if (list.isEmpty())
|
||||
return false;
|
||||
first = last = list[0].row();
|
||||
for (auto item : list)
|
||||
{
|
||||
int row = item.row();
|
||||
if (row < first)
|
||||
first = row;
|
||||
if (row > last)
|
||||
last = row;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void showWebsiteForMod(QWidget *parentDlg, Mod &m)
|
||||
{
|
||||
QString url = m.homeurl();
|
||||
if (url.size())
|
||||
{
|
||||
// catch the cases where the protocol is missing
|
||||
if (!url.startsWith("http"))
|
||||
{
|
||||
url = "http://" + url;
|
||||
}
|
||||
DesktopServices::openUrl(url);
|
||||
}
|
||||
else
|
||||
{
|
||||
CustomMessageBox::selectable(
|
||||
parentDlg, QObject::tr("How sad!"),
|
||||
QObject::tr("The mod author didn't provide a website link for this mod."),
|
||||
QMessageBox::Warning);
|
||||
}
|
||||
}
|
||||
9
application/dialogs/ModEditDialogCommon.h
Normal file
9
application/dialogs/ModEditDialogCommon.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <QModelIndex>
|
||||
#include <DesktopServices.h>
|
||||
#include <QWidget>
|
||||
#include <minecraft/Mod.h>
|
||||
|
||||
bool lastfirst(QModelIndexList &list, int &first, int &last);
|
||||
|
||||
void showWebsiteForMod(QWidget *parentDlg, Mod &m);
|
||||
@@ -34,9 +34,10 @@
|
||||
|
||||
#include "widgets/PageContainer.h"
|
||||
#include <pages/modplatform/VanillaPage.h>
|
||||
#include <pages/modplatform/legacy_ftb/Page.h>
|
||||
#include <pages/modplatform/FTBPage.h>
|
||||
#include <pages/modplatform/TwitchPage.h>
|
||||
#include <pages/modplatform/ImportPage.h>
|
||||
|
||||
#include <pages/modplatform/TechnicPage.h>
|
||||
|
||||
NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent)
|
||||
: QDialog(parent), ui(new Ui::NewInstanceDialog)
|
||||
@@ -93,7 +94,6 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
|
||||
|
||||
if(!url.isEmpty())
|
||||
{
|
||||
QUrl actualUrl(url);
|
||||
m_container->selectPage("import");
|
||||
importPage->setUrl(url);
|
||||
}
|
||||
@@ -122,8 +122,10 @@ QList<BasePage *> NewInstanceDialog::getPages()
|
||||
return
|
||||
{
|
||||
new VanillaPage(this),
|
||||
new FTBPage(this),
|
||||
importPage,
|
||||
new LegacyFTB::Page(this),
|
||||
new TwitchPage(this),
|
||||
new TechnicPage(this)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ UpdateDialog::UpdateDialog(bool hasUpdate, QWidget *parent) : QDialog(parent), u
|
||||
ui->btnUpdateNow->setHidden(true);
|
||||
ui->btnUpdateLater->setText(tr("Close"));
|
||||
}
|
||||
ui->changelogBrowser->setHtml(tr("<center><h1>Loading changelog...</h1></center>"));
|
||||
loadChangelog();
|
||||
restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("UpdateDialogGeometry").toByteArray()));
|
||||
}
|
||||
|
||||
@@ -42,6 +42,13 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="changelogBrowser">
|
||||
<property name="html">
|
||||
<string><!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; }
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;">
|
||||
<p align="center" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:22pt;">Loading changelog...</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
||||
@@ -1,778 +0,0 @@
|
||||
#include "GroupView.h"
|
||||
#include "AccessibleGroupView.h"
|
||||
#include "AccessibleGroupView_p.h"
|
||||
|
||||
#include <qvariant.h>
|
||||
#include <qaccessible.h>
|
||||
#include <qheaderview.h>
|
||||
|
||||
#ifndef QT_NO_ACCESSIBILITY
|
||||
|
||||
QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object)
|
||||
{
|
||||
QAccessibleInterface *iface = 0;
|
||||
if (!object || !object->isWidgetType())
|
||||
return iface;
|
||||
|
||||
QWidget *widget = static_cast<QWidget*>(object);
|
||||
|
||||
if (classname == QLatin1String("GroupView")) {
|
||||
iface = new AccessibleGroupView((GroupView *)widget);
|
||||
}
|
||||
return iface;
|
||||
}
|
||||
|
||||
|
||||
QAbstractItemView *AccessibleGroupView::view() const
|
||||
{
|
||||
return qobject_cast<QAbstractItemView*>(object());
|
||||
}
|
||||
|
||||
int AccessibleGroupView::logicalIndex(const QModelIndex &index) const
|
||||
{
|
||||
if (!view()->model() || !index.isValid())
|
||||
return -1;
|
||||
return index.row() * (index.model()->columnCount()) + index.column();
|
||||
}
|
||||
|
||||
AccessibleGroupView::AccessibleGroupView(QWidget *w)
|
||||
: QAccessibleObject(w)
|
||||
{
|
||||
Q_ASSERT(view());
|
||||
}
|
||||
|
||||
bool AccessibleGroupView::isValid() const
|
||||
{
|
||||
return view();
|
||||
}
|
||||
|
||||
AccessibleGroupView::~AccessibleGroupView()
|
||||
{
|
||||
for (QAccessible::Id id : childToId) {
|
||||
QAccessible::deleteAccessibleInterface(id);
|
||||
}
|
||||
}
|
||||
|
||||
QAccessibleInterface *AccessibleGroupView::cellAt(int row, int column) const
|
||||
{
|
||||
if (!view()->model()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
QModelIndex index = view()->model()->index(row, column, view()->rootIndex());
|
||||
if (Q_UNLIKELY(!index.isValid())) {
|
||||
qWarning() << "AccessibleGroupView::cellAt: invalid index: " << index << " for " << view();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return child(logicalIndex(index));
|
||||
}
|
||||
|
||||
QAccessibleInterface *AccessibleGroupView::caption() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString AccessibleGroupView::columnDescription(int column) const
|
||||
{
|
||||
if (!view()->model())
|
||||
return QString();
|
||||
|
||||
return view()->model()->headerData(column, Qt::Horizontal).toString();
|
||||
}
|
||||
|
||||
int AccessibleGroupView::columnCount() const
|
||||
{
|
||||
if (!view()->model())
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AccessibleGroupView::rowCount() const
|
||||
{
|
||||
if (!view()->model())
|
||||
return 0;
|
||||
return view()->model()->rowCount();
|
||||
}
|
||||
|
||||
int AccessibleGroupView::selectedCellCount() const
|
||||
{
|
||||
if (!view()->selectionModel())
|
||||
return 0;
|
||||
return view()->selectionModel()->selectedIndexes().count();
|
||||
}
|
||||
|
||||
int AccessibleGroupView::selectedColumnCount() const
|
||||
{
|
||||
if (!view()->selectionModel())
|
||||
return 0;
|
||||
return view()->selectionModel()->selectedColumns().count();
|
||||
}
|
||||
|
||||
int AccessibleGroupView::selectedRowCount() const
|
||||
{
|
||||
if (!view()->selectionModel())
|
||||
return 0;
|
||||
return view()->selectionModel()->selectedRows().count();
|
||||
}
|
||||
|
||||
QString AccessibleGroupView::rowDescription(int row) const
|
||||
{
|
||||
if (!view()->model())
|
||||
return QString();
|
||||
return view()->model()->headerData(row, Qt::Vertical).toString();
|
||||
}
|
||||
|
||||
QList<QAccessibleInterface *> AccessibleGroupView::selectedCells() const
|
||||
{
|
||||
QList<QAccessibleInterface*> cells;
|
||||
if (!view()->selectionModel())
|
||||
return cells;
|
||||
const QModelIndexList selectedIndexes = view()->selectionModel()->selectedIndexes();
|
||||
cells.reserve(selectedIndexes.size());
|
||||
for (const QModelIndex &index : selectedIndexes)
|
||||
cells.append(child(logicalIndex(index)));
|
||||
return cells;
|
||||
}
|
||||
|
||||
QList<int> AccessibleGroupView::selectedColumns() const
|
||||
{
|
||||
if (!view()->selectionModel()) {
|
||||
return QList<int>();
|
||||
}
|
||||
|
||||
const QModelIndexList selectedColumns = view()->selectionModel()->selectedColumns();
|
||||
|
||||
QList<int> columns;
|
||||
columns.reserve(selectedColumns.size());
|
||||
for (const QModelIndex &index : selectedColumns) {
|
||||
columns.append(index.column());
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
QList<int> AccessibleGroupView::selectedRows() const
|
||||
{
|
||||
if (!view()->selectionModel()) {
|
||||
return QList<int>();
|
||||
}
|
||||
|
||||
QList<int> rows;
|
||||
|
||||
const QModelIndexList selectedRows = view()->selectionModel()->selectedRows();
|
||||
|
||||
rows.reserve(selectedRows.size());
|
||||
for (const QModelIndex &index : selectedRows) {
|
||||
rows.append(index.row());
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
QAccessibleInterface *AccessibleGroupView::summary() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AccessibleGroupView::isColumnSelected(int column) const
|
||||
{
|
||||
if (!view()->selectionModel()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return view()->selectionModel()->isColumnSelected(column, QModelIndex());
|
||||
}
|
||||
|
||||
bool AccessibleGroupView::isRowSelected(int row) const
|
||||
{
|
||||
if (!view()->selectionModel()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return view()->selectionModel()->isRowSelected(row, QModelIndex());
|
||||
}
|
||||
|
||||
bool AccessibleGroupView::selectRow(int row)
|
||||
{
|
||||
if (!view()->model() || !view()->selectionModel()) {
|
||||
return false;
|
||||
}
|
||||
QModelIndex index = view()->model()->index(row, 0, view()->rootIndex());
|
||||
|
||||
if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectColumns) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (view()->selectionMode()) {
|
||||
case QAbstractItemView::NoSelection: {
|
||||
return false;
|
||||
}
|
||||
case QAbstractItemView::SingleSelection: {
|
||||
if (view()->selectionBehavior() != QAbstractItemView::SelectRows && columnCount() > 1 )
|
||||
return false;
|
||||
view()->clearSelection();
|
||||
break;
|
||||
}
|
||||
case QAbstractItemView::ContiguousSelection: {
|
||||
if ((!row || !view()->selectionModel()->isRowSelected(row - 1, view()->rootIndex())) && !view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex())) {
|
||||
view()->clearSelection();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AccessibleGroupView::selectColumn(int column)
|
||||
{
|
||||
if (!view()->model() || !view()->selectionModel()) {
|
||||
return false;
|
||||
}
|
||||
QModelIndex index = view()->model()->index(0, column, view()->rootIndex());
|
||||
|
||||
if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectRows) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (view()->selectionMode()) {
|
||||
case QAbstractItemView::NoSelection: {
|
||||
return false;
|
||||
}
|
||||
case QAbstractItemView::SingleSelection: {
|
||||
if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1) {
|
||||
return false;
|
||||
}
|
||||
// fallthrough intentional
|
||||
}
|
||||
case QAbstractItemView::ContiguousSelection: {
|
||||
if ((!column || !view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) && !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) {
|
||||
view()->clearSelection();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Columns);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AccessibleGroupView::unselectRow(int row)
|
||||
{
|
||||
if (!view()->model() || !view()->selectionModel()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QModelIndex index = view()->model()->index(row, 0, view()->rootIndex());
|
||||
if (!index.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QItemSelection selection(index, index);
|
||||
auto selectionModel = view()->selectionModel();
|
||||
|
||||
switch (view()->selectionMode()) {
|
||||
case QAbstractItemView::SingleSelection:
|
||||
// no unselect
|
||||
if (selectedRowCount() == 1) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case QAbstractItemView::ContiguousSelection: {
|
||||
// no unselect
|
||||
if (selectedRowCount() == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if ((!row || selectionModel->isRowSelected(row - 1, view()->rootIndex())) && selectionModel->isRowSelected(row + 1, view()->rootIndex())) {
|
||||
//If there are rows selected both up the current row and down the current rown,
|
||||
//the ones which are down the current row will be deselected
|
||||
selection = QItemSelection(index, view()->model()->index(rowCount() - 1, 0, view()->rootIndex()));
|
||||
}
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
selectionModel->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Rows);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AccessibleGroupView::unselectColumn(int column)
|
||||
{
|
||||
auto model = view()->model();
|
||||
if (!model || !view()->selectionModel()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QModelIndex index = model->index(0, column, view()->rootIndex());
|
||||
if (!index.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QItemSelection selection(index, index);
|
||||
|
||||
switch (view()->selectionMode()) {
|
||||
case QAbstractItemView::SingleSelection: {
|
||||
//In SingleSelection and ContiguousSelection once an item
|
||||
//is selected, there's no way for the user to unselect all items
|
||||
if (selectedColumnCount() == 1) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QAbstractItemView::ContiguousSelection:
|
||||
if (selectedColumnCount() == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((!column || view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex()))
|
||||
&& view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) {
|
||||
//If there are columns selected both at the left of the current row and at the right
|
||||
//of the current row, the ones which are at the right will be deselected
|
||||
selection = QItemSelection(index, model->index(0, columnCount() - 1, view()->rootIndex()));
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
view()->selectionModel()->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Columns);
|
||||
return true;
|
||||
}
|
||||
|
||||
QAccessible::Role AccessibleGroupView::role() const
|
||||
{
|
||||
return QAccessible::List;
|
||||
}
|
||||
|
||||
QAccessible::State AccessibleGroupView::state() const
|
||||
{
|
||||
return QAccessible::State();
|
||||
}
|
||||
|
||||
QAccessibleInterface *AccessibleGroupView::childAt(int x, int y) const
|
||||
{
|
||||
QPoint viewportOffset = view()->viewport()->mapTo(view(), QPoint(0,0));
|
||||
QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset);
|
||||
// FIXME: if indexPosition < 0 in one coordinate, return header
|
||||
|
||||
QModelIndex index = view()->indexAt(indexPosition);
|
||||
if (index.isValid()) {
|
||||
return child(logicalIndex(index));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AccessibleGroupView::childCount() const
|
||||
{
|
||||
if (!view()->model()) {
|
||||
return 0;
|
||||
}
|
||||
return (view()->model()->rowCount()) * (view()->model()->columnCount());
|
||||
}
|
||||
|
||||
int AccessibleGroupView::indexOfChild(const QAccessibleInterface *iface) const
|
||||
{
|
||||
if (!view()->model())
|
||||
return -1;
|
||||
QAccessibleInterface *parent = iface->parent();
|
||||
if (parent->object() != view())
|
||||
return -1;
|
||||
|
||||
Q_ASSERT(iface->role() != QAccessible::TreeItem); // should be handled by tree class
|
||||
if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) {
|
||||
const AccessibleGroupViewItem* cell = static_cast<const AccessibleGroupViewItem*>(iface);
|
||||
return logicalIndex(cell->m_index);
|
||||
} else if (iface->role() == QAccessible::Pane) {
|
||||
return 0; // corner button
|
||||
} else {
|
||||
qWarning() << "AccessibleGroupView::indexOfChild has a child with unknown role..." << iface->role() << iface->text(QAccessible::Name);
|
||||
}
|
||||
// FIXME: we are in denial of our children. this should stop.
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString AccessibleGroupView::text(QAccessible::Text t) const
|
||||
{
|
||||
if (t == QAccessible::Description)
|
||||
return view()->accessibleDescription();
|
||||
return view()->accessibleName();
|
||||
}
|
||||
|
||||
QRect AccessibleGroupView::rect() const
|
||||
{
|
||||
if (!view()->isVisible())
|
||||
return QRect();
|
||||
QPoint pos = view()->mapToGlobal(QPoint(0, 0));
|
||||
return QRect(pos.x(), pos.y(), view()->width(), view()->height());
|
||||
}
|
||||
|
||||
QAccessibleInterface *AccessibleGroupView::parent() const
|
||||
{
|
||||
if (view() && view()->parent()) {
|
||||
if (qstrcmp("QComboBoxPrivateContainer", view()->parent()->metaObject()->className()) == 0) {
|
||||
return QAccessible::queryAccessibleInterface(view()->parent()->parent());
|
||||
}
|
||||
return QAccessible::queryAccessibleInterface(view()->parent());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
QAccessibleInterface *AccessibleGroupView::child(int logicalIndex) const
|
||||
{
|
||||
if (!view()->model())
|
||||
return 0;
|
||||
|
||||
auto id = childToId.constFind(logicalIndex);
|
||||
if (id != childToId.constEnd())
|
||||
return QAccessible::accessibleInterface(id.value());
|
||||
|
||||
int columns = view()->model()->columnCount();
|
||||
|
||||
int row = logicalIndex / columns;
|
||||
int column = logicalIndex % columns;
|
||||
|
||||
QAccessibleInterface *iface = 0;
|
||||
|
||||
QModelIndex index = view()->model()->index(row, column, view()->rootIndex());
|
||||
if (Q_UNLIKELY(!index.isValid())) {
|
||||
qWarning("AccessibleGroupView::child: Invalid index at: %d %d", row, column);
|
||||
return 0;
|
||||
}
|
||||
iface = new AccessibleGroupViewItem(view(), index);
|
||||
|
||||
QAccessible::registerAccessibleInterface(iface);
|
||||
childToId.insert(logicalIndex, QAccessible::uniqueId(iface));
|
||||
return iface;
|
||||
}
|
||||
|
||||
void *AccessibleGroupView::interface_cast(QAccessible::InterfaceType t)
|
||||
{
|
||||
if (t == QAccessible::TableInterface)
|
||||
return static_cast<QAccessibleTableInterface*>(this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AccessibleGroupView::modelChange(QAccessibleTableModelChangeEvent *event)
|
||||
{
|
||||
// if there is no cache yet, we don't update anything
|
||||
if (childToId.isEmpty())
|
||||
return;
|
||||
|
||||
switch (event->modelChangeType()) {
|
||||
case QAccessibleTableModelChangeEvent::ModelReset:
|
||||
for (QAccessible::Id id : childToId)
|
||||
QAccessible::deleteAccessibleInterface(id);
|
||||
childToId.clear();
|
||||
break;
|
||||
|
||||
// rows are inserted: move every row after that
|
||||
case QAccessibleTableModelChangeEvent::RowsInserted:
|
||||
case QAccessibleTableModelChangeEvent::ColumnsInserted: {
|
||||
|
||||
ChildCache newCache;
|
||||
ChildCache::ConstIterator iter = childToId.constBegin();
|
||||
|
||||
while (iter != childToId.constEnd()) {
|
||||
QAccessible::Id id = iter.value();
|
||||
QAccessibleInterface *iface = QAccessible::accessibleInterface(id);
|
||||
Q_ASSERT(iface);
|
||||
if (indexOfChild(iface) >= 0) {
|
||||
newCache.insert(indexOfChild(iface), id);
|
||||
} else {
|
||||
// ### This should really not happen,
|
||||
// but it might if the view has a root index set.
|
||||
// This needs to be fixed.
|
||||
QAccessible::deleteAccessibleInterface(id);
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
childToId = newCache;
|
||||
break;
|
||||
}
|
||||
|
||||
case QAccessibleTableModelChangeEvent::ColumnsRemoved:
|
||||
case QAccessibleTableModelChangeEvent::RowsRemoved: {
|
||||
ChildCache newCache;
|
||||
ChildCache::ConstIterator iter = childToId.constBegin();
|
||||
while (iter != childToId.constEnd()) {
|
||||
QAccessible::Id id = iter.value();
|
||||
QAccessibleInterface *iface = QAccessible::accessibleInterface(id);
|
||||
Q_ASSERT(iface);
|
||||
if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) {
|
||||
Q_ASSERT(iface->tableCellInterface());
|
||||
AccessibleGroupViewItem *cell = static_cast<AccessibleGroupViewItem*>(iface->tableCellInterface());
|
||||
// Since it is a QPersistentModelIndex, we only need to check if it is valid
|
||||
if (cell->m_index.isValid())
|
||||
newCache.insert(indexOfChild(cell), id);
|
||||
else
|
||||
QAccessible::deleteAccessibleInterface(id);
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
childToId = newCache;
|
||||
break;
|
||||
}
|
||||
|
||||
case QAccessibleTableModelChangeEvent::DataChanged:
|
||||
// nothing to do in this case
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TABLE CELL
|
||||
|
||||
AccessibleGroupViewItem::AccessibleGroupViewItem(QAbstractItemView *view_, const QModelIndex &index_)
|
||||
: view(view_), m_index(index_)
|
||||
{
|
||||
if (Q_UNLIKELY(!index_.isValid()))
|
||||
qWarning() << "AccessibleGroupViewItem::AccessibleGroupViewItem with invalid index: " << index_;
|
||||
}
|
||||
|
||||
void *AccessibleGroupViewItem::interface_cast(QAccessible::InterfaceType t)
|
||||
{
|
||||
if (t == QAccessible::TableCellInterface)
|
||||
return static_cast<QAccessibleTableCellInterface*>(this);
|
||||
if (t == QAccessible::ActionInterface)
|
||||
return static_cast<QAccessibleActionInterface*>(this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AccessibleGroupViewItem::columnExtent() const { return 1; }
|
||||
int AccessibleGroupViewItem::rowExtent() const { return 1; }
|
||||
|
||||
QList<QAccessibleInterface*> AccessibleGroupViewItem::rowHeaderCells() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<QAccessibleInterface*> AccessibleGroupViewItem::columnHeaderCells() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
int AccessibleGroupViewItem::columnIndex() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return m_index.column();
|
||||
}
|
||||
|
||||
int AccessibleGroupViewItem::rowIndex() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return m_index.row();
|
||||
}
|
||||
|
||||
bool AccessibleGroupViewItem::isSelected() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return view->selectionModel()->isSelected(m_index);
|
||||
}
|
||||
|
||||
QStringList AccessibleGroupViewItem::actionNames() const
|
||||
{
|
||||
QStringList names;
|
||||
names << toggleAction();
|
||||
return names;
|
||||
}
|
||||
|
||||
void AccessibleGroupViewItem::doAction(const QString& actionName)
|
||||
{
|
||||
if (actionName == toggleAction()) {
|
||||
if (isSelected()) {
|
||||
unselectCell();
|
||||
}
|
||||
else {
|
||||
selectCell();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList AccessibleGroupViewItem::keyBindingsForAction(const QString &) const
|
||||
{
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
|
||||
void AccessibleGroupViewItem::selectCell()
|
||||
{
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
QAbstractItemView::SelectionMode selectionMode = view->selectionMode();
|
||||
if (selectionMode == QAbstractItemView::NoSelection) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(table());
|
||||
QAccessibleTableInterface *cellTable = table()->tableInterface();
|
||||
|
||||
switch (view->selectionBehavior()) {
|
||||
case QAbstractItemView::SelectItems:
|
||||
break;
|
||||
case QAbstractItemView::SelectColumns:
|
||||
if (cellTable)
|
||||
cellTable->selectColumn(m_index.column());
|
||||
return;
|
||||
case QAbstractItemView::SelectRows:
|
||||
if (cellTable)
|
||||
cellTable->selectRow(m_index.row());
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectionMode == QAbstractItemView::SingleSelection) {
|
||||
view->clearSelection();
|
||||
}
|
||||
|
||||
view->selectionModel()->select(m_index, QItemSelectionModel::Select);
|
||||
}
|
||||
|
||||
void AccessibleGroupViewItem::unselectCell()
|
||||
{
|
||||
if (!isValid())
|
||||
return;
|
||||
QAbstractItemView::SelectionMode selectionMode = view->selectionMode();
|
||||
if (selectionMode == QAbstractItemView::NoSelection)
|
||||
return;
|
||||
|
||||
QAccessibleTableInterface *cellTable = table()->tableInterface();
|
||||
|
||||
switch (view->selectionBehavior()) {
|
||||
case QAbstractItemView::SelectItems:
|
||||
break;
|
||||
case QAbstractItemView::SelectColumns:
|
||||
if (cellTable)
|
||||
cellTable->unselectColumn(m_index.column());
|
||||
return;
|
||||
case QAbstractItemView::SelectRows:
|
||||
if (cellTable)
|
||||
cellTable->unselectRow(m_index.row());
|
||||
return;
|
||||
}
|
||||
|
||||
//If the mode is not MultiSelection or ExtendedSelection and only
|
||||
//one cell is selected it cannot be unselected by the user
|
||||
if ((selectionMode != QAbstractItemView::MultiSelection) && (selectionMode != QAbstractItemView::ExtendedSelection) && (view->selectionModel()->selectedIndexes().count() <= 1))
|
||||
return;
|
||||
|
||||
view->selectionModel()->select(m_index, QItemSelectionModel::Deselect);
|
||||
}
|
||||
|
||||
QAccessibleInterface *AccessibleGroupViewItem::table() const
|
||||
{
|
||||
return QAccessible::queryAccessibleInterface(view);
|
||||
}
|
||||
|
||||
QAccessible::Role AccessibleGroupViewItem::role() const
|
||||
{
|
||||
return QAccessible::ListItem;
|
||||
}
|
||||
|
||||
QAccessible::State AccessibleGroupViewItem::state() const
|
||||
{
|
||||
QAccessible::State st;
|
||||
if (!isValid())
|
||||
return st;
|
||||
|
||||
QRect globalRect = view->rect();
|
||||
globalRect.translate(view->mapToGlobal(QPoint(0,0)));
|
||||
if (!globalRect.intersects(rect()))
|
||||
st.invisible = true;
|
||||
|
||||
if (view->selectionModel()->isSelected(m_index))
|
||||
st.selected = true;
|
||||
if (view->selectionModel()->currentIndex() == m_index)
|
||||
st.focused = true;
|
||||
if (m_index.model()->data(m_index, Qt::CheckStateRole).toInt() == Qt::Checked)
|
||||
st.checked = true;
|
||||
|
||||
Qt::ItemFlags flags = m_index.flags();
|
||||
if (flags & Qt::ItemIsSelectable) {
|
||||
st.selectable = true;
|
||||
st.focusable = true;
|
||||
if (view->selectionMode() == QAbstractItemView::MultiSelection)
|
||||
st.multiSelectable = true;
|
||||
if (view->selectionMode() == QAbstractItemView::ExtendedSelection)
|
||||
st.extSelectable = true;
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
|
||||
QRect AccessibleGroupViewItem::rect() const
|
||||
{
|
||||
QRect r;
|
||||
if (!isValid())
|
||||
return r;
|
||||
r = view->visualRect(m_index);
|
||||
|
||||
if (!r.isNull()) {
|
||||
r.translate(view->viewport()->mapTo(view, QPoint(0,0)));
|
||||
r.translate(view->mapToGlobal(QPoint(0, 0)));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
QString AccessibleGroupViewItem::text(QAccessible::Text t) const
|
||||
{
|
||||
QString value;
|
||||
if (!isValid())
|
||||
return value;
|
||||
QAbstractItemModel *model = view->model();
|
||||
switch (t) {
|
||||
case QAccessible::Name:
|
||||
value = model->data(m_index, Qt::AccessibleTextRole).toString();
|
||||
if (value.isEmpty())
|
||||
value = model->data(m_index, Qt::DisplayRole).toString();
|
||||
break;
|
||||
case QAccessible::Description:
|
||||
value = model->data(m_index, Qt::AccessibleDescriptionRole).toString();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void AccessibleGroupViewItem::setText(QAccessible::Text /*t*/, const QString &text)
|
||||
{
|
||||
if (!isValid() || !(m_index.flags() & Qt::ItemIsEditable))
|
||||
return;
|
||||
view->model()->setData(m_index, text);
|
||||
}
|
||||
|
||||
bool AccessibleGroupViewItem::isValid() const
|
||||
{
|
||||
return view && view->model() && m_index.isValid();
|
||||
}
|
||||
|
||||
QAccessibleInterface *AccessibleGroupViewItem::parent() const
|
||||
{
|
||||
return QAccessible::queryAccessibleInterface(view);
|
||||
}
|
||||
|
||||
QAccessibleInterface *AccessibleGroupViewItem::child(int) const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* !QT_NO_ACCESSIBILITY */
|
||||
@@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
class QAccessibleInterface;
|
||||
|
||||
QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object);
|
||||
@@ -1,118 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "QtCore/qpointer.h"
|
||||
#include <QtGui/qaccessible.h>
|
||||
#include <QAccessibleWidget>
|
||||
#include <QAbstractItemView>
|
||||
#ifndef QT_NO_ACCESSIBILITY
|
||||
#include "GroupView.h"
|
||||
// #include <QHeaderView>
|
||||
|
||||
class QAccessibleTableCell;
|
||||
class QAccessibleTableHeaderCell;
|
||||
|
||||
class AccessibleGroupView :public QAccessibleTableInterface, public QAccessibleObject
|
||||
{
|
||||
public:
|
||||
explicit AccessibleGroupView(QWidget *w);
|
||||
bool isValid() const override;
|
||||
|
||||
QAccessible::Role role() const override;
|
||||
QAccessible::State state() const override;
|
||||
QString text(QAccessible::Text t) const override;
|
||||
QRect rect() const override;
|
||||
|
||||
QAccessibleInterface *childAt(int x, int y) const override;
|
||||
int childCount() const override;
|
||||
int indexOfChild(const QAccessibleInterface *) const override;
|
||||
|
||||
QAccessibleInterface *parent() const override;
|
||||
QAccessibleInterface *child(int index) const override;
|
||||
|
||||
void *interface_cast(QAccessible::InterfaceType t) override;
|
||||
|
||||
// table interface
|
||||
QAccessibleInterface *cellAt(int row, int column) const override;
|
||||
QAccessibleInterface *caption() const override;
|
||||
QAccessibleInterface *summary() const override;
|
||||
QString columnDescription(int column) const override;
|
||||
QString rowDescription(int row) const override;
|
||||
int columnCount() const override;
|
||||
int rowCount() const override;
|
||||
|
||||
// selection
|
||||
int selectedCellCount() const override;
|
||||
int selectedColumnCount() const override;
|
||||
int selectedRowCount() const override;
|
||||
QList<QAccessibleInterface*> selectedCells() const override;
|
||||
QList<int> selectedColumns() const override;
|
||||
QList<int> selectedRows() const override;
|
||||
bool isColumnSelected(int column) const override;
|
||||
bool isRowSelected(int row) const override;
|
||||
bool selectRow(int row) override;
|
||||
bool selectColumn(int column) override;
|
||||
bool unselectRow(int row) override;
|
||||
bool unselectColumn(int column) override;
|
||||
|
||||
QAbstractItemView *view() const;
|
||||
|
||||
void modelChange(QAccessibleTableModelChangeEvent *event) override;
|
||||
|
||||
protected:
|
||||
// maybe vector
|
||||
typedef QHash<int, QAccessible::Id> ChildCache;
|
||||
mutable ChildCache childToId;
|
||||
|
||||
virtual ~AccessibleGroupView();
|
||||
|
||||
private:
|
||||
inline int logicalIndex(const QModelIndex &index) const;
|
||||
};
|
||||
|
||||
class AccessibleGroupViewItem: public QAccessibleInterface, public QAccessibleTableCellInterface, public QAccessibleActionInterface
|
||||
{
|
||||
public:
|
||||
AccessibleGroupViewItem(QAbstractItemView *view, const QModelIndex &m_index);
|
||||
|
||||
void *interface_cast(QAccessible::InterfaceType t) override;
|
||||
QObject *object() const override { return nullptr; }
|
||||
QAccessible::Role role() const override;
|
||||
QAccessible::State state() const override;
|
||||
QRect rect() const override;
|
||||
bool isValid() const override;
|
||||
|
||||
QAccessibleInterface *childAt(int, int) const override { return nullptr; }
|
||||
int childCount() const override { return 0; }
|
||||
int indexOfChild(const QAccessibleInterface *) const override { return -1; }
|
||||
|
||||
QString text(QAccessible::Text t) const override;
|
||||
void setText(QAccessible::Text t, const QString &text) override;
|
||||
|
||||
QAccessibleInterface *parent() const override;
|
||||
QAccessibleInterface *child(int) const override;
|
||||
|
||||
// cell interface
|
||||
int columnExtent() const override;
|
||||
QList<QAccessibleInterface*> columnHeaderCells() const override;
|
||||
int columnIndex() const override;
|
||||
int rowExtent() const override;
|
||||
QList<QAccessibleInterface*> rowHeaderCells() const override;
|
||||
int rowIndex() const override;
|
||||
bool isSelected() const override;
|
||||
QAccessibleInterface* table() const override;
|
||||
|
||||
//action interface
|
||||
QStringList actionNames() const override;
|
||||
void doAction(const QString &actionName) override;
|
||||
QStringList keyBindingsForAction(const QString &actionName) const override;
|
||||
|
||||
private:
|
||||
QPointer<QAbstractItemView > view;
|
||||
QPersistentModelIndex m_index;
|
||||
|
||||
void selectCell();
|
||||
void unselectCell();
|
||||
|
||||
friend class AccessibleGroupView;
|
||||
};
|
||||
#endif /* !QT_NO_ACCESSIBILITY */
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <QMimeData>
|
||||
#include <QCache>
|
||||
#include <QScrollBar>
|
||||
#include <QAccessible>
|
||||
|
||||
#include "VisualGroup.h"
|
||||
#include <QDebug>
|
||||
@@ -89,20 +88,6 @@ void GroupView::rowsRemoved()
|
||||
scheduleDelayedItemsLayout();
|
||||
}
|
||||
|
||||
void GroupView::currentChanged(const QModelIndex& current, const QModelIndex& previous)
|
||||
{
|
||||
QAbstractItemView::currentChanged(current, previous);
|
||||
// TODO: for accessibility support, implement+register a factory, steal QAccessibleTable from Qt and return an instance of it for GroupView.
|
||||
#ifndef QT_NO_ACCESSIBILITY
|
||||
if (QAccessible::isActive() && current.isValid()) {
|
||||
QAccessibleEvent event(this, QAccessible::Focus);
|
||||
event.setChild(current.row());
|
||||
QAccessible::updateAccessibility(&event);
|
||||
}
|
||||
#endif /* !QT_NO_ACCESSIBILITY */
|
||||
}
|
||||
|
||||
|
||||
class LocaleString : public QString
|
||||
{
|
||||
public:
|
||||
@@ -177,9 +162,6 @@ void GroupView::updateGeometries()
|
||||
else
|
||||
{
|
||||
auto cat = new VisualGroup(groupName, this);
|
||||
if(fVisibility) {
|
||||
cat->collapsed = fVisibility(groupName);
|
||||
}
|
||||
cats.insert(groupName, cat);
|
||||
cat->update();
|
||||
}
|
||||
@@ -238,8 +220,6 @@ VisualGroup *GroupView::categoryAt(const QPoint &pos, VisualGroup::HitResults &
|
||||
|
||||
QString GroupView::groupNameAt(const QPoint &point)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
VisualGroup::HitResults hitresult;
|
||||
auto group = categoryAt(point + offset(), hitresult);
|
||||
if(group && (hitresult & (VisualGroup::HeaderHit | VisualGroup::BodyHit)))
|
||||
@@ -266,7 +246,7 @@ int GroupView::itemWidth() const
|
||||
|
||||
void GroupView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
// endCategoryEditor();
|
||||
|
||||
QPoint visualPos = event->pos();
|
||||
QPoint geometryPos = event->pos() + offset();
|
||||
@@ -315,8 +295,6 @@ void GroupView::mousePressEvent(QMouseEvent *event)
|
||||
|
||||
void GroupView::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QPoint topLeft;
|
||||
QPoint visualPos = event->pos();
|
||||
QPoint geometryPos = event->pos() + offset();
|
||||
@@ -373,8 +351,6 @@ void GroupView::mouseMoveEvent(QMouseEvent *event)
|
||||
|
||||
void GroupView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QPoint visualPos = event->pos();
|
||||
QPoint geometryPos = event->pos() + offset();
|
||||
QPersistentModelIndex index = indexAt(visualPos);
|
||||
@@ -389,25 +365,17 @@ void GroupView::mouseReleaseEvent(QMouseEvent *event)
|
||||
if (state() == ExpandingState)
|
||||
{
|
||||
m_pressedCategory->collapsed = false;
|
||||
emit groupStateChanged(m_pressedCategory->text, false);
|
||||
|
||||
updateGeometries();
|
||||
viewport()->update();
|
||||
event->accept();
|
||||
m_pressedCategory = nullptr;
|
||||
setState(NoState);
|
||||
return;
|
||||
}
|
||||
else if (state() == CollapsingState)
|
||||
{
|
||||
m_pressedCategory->collapsed = true;
|
||||
emit groupStateChanged(m_pressedCategory->text, true);
|
||||
|
||||
updateGeometries();
|
||||
viewport()->update();
|
||||
event->accept();
|
||||
m_pressedCategory = nullptr;
|
||||
setState(NoState);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -437,8 +405,6 @@ void GroupView::mouseReleaseEvent(QMouseEvent *event)
|
||||
|
||||
void GroupView::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QModelIndex index = indexAt(event->pos());
|
||||
if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index))
|
||||
{
|
||||
@@ -562,8 +528,6 @@ void GroupView::resizeEvent(QResizeEvent *event)
|
||||
|
||||
void GroupView::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
if (!isDragEventAccepted(event))
|
||||
{
|
||||
return;
|
||||
@@ -575,8 +539,6 @@ void GroupView::dragEnterEvent(QDragEnterEvent *event)
|
||||
|
||||
void GroupView::dragMoveEvent(QDragMoveEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
if (!isDragEventAccepted(event))
|
||||
{
|
||||
return;
|
||||
@@ -588,16 +550,12 @@ void GroupView::dragMoveEvent(QDragMoveEvent *event)
|
||||
|
||||
void GroupView::dragLeaveEvent(QDragLeaveEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
m_lastDragPosition = QPoint();
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void GroupView::dropEvent(QDropEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
m_lastDragPosition = QPoint();
|
||||
|
||||
stopAutoScroll();
|
||||
@@ -620,7 +578,8 @@ void GroupView::dropEvent(QDropEvent *event)
|
||||
const QString categoryText = category->text;
|
||||
if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex()))
|
||||
{
|
||||
model()->setData(model()->index(row, 0), categoryText, GroupViewRoles::GroupRole);
|
||||
model()->setData(model()->index(row, 0), categoryText,
|
||||
GroupViewRoles::GroupRole);
|
||||
event->setDropAction(Qt::MoveAction);
|
||||
event->accept();
|
||||
}
|
||||
@@ -647,8 +606,6 @@ void GroupView::dropEvent(QDropEvent *event)
|
||||
|
||||
void GroupView::startDrag(Qt::DropActions supportedActions)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QModelIndexList indexes = selectionModel()->selectedIndexes();
|
||||
if(indexes.count() == 0)
|
||||
return;
|
||||
@@ -694,15 +651,11 @@ void GroupView::startDrag(Qt::DropActions supportedActions)
|
||||
|
||||
QRect GroupView::visualRect(const QModelIndex &index) const
|
||||
{
|
||||
const_cast<GroupView*>(this)->executeDelayedItemsLayout();
|
||||
|
||||
return geometryRect(index).translated(-offset());
|
||||
}
|
||||
|
||||
QRect GroupView::geometryRect(const QModelIndex &index) const
|
||||
{
|
||||
const_cast<GroupView*>(this)->executeDelayedItemsLayout();
|
||||
|
||||
if (!index.isValid() || isIndexHidden(index) || index.column() > 0)
|
||||
{
|
||||
return QRect();
|
||||
@@ -742,10 +695,9 @@ QModelIndex GroupView::indexAt(const QPoint &point) const
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
void GroupView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands)
|
||||
void GroupView::setSelection(const QRect &rect,
|
||||
const QItemSelectionModel::SelectionFlags commands)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
for (int i = 0; i < model()->rowCount(); ++i)
|
||||
{
|
||||
QModelIndex index = model()->index(i, 0);
|
||||
@@ -780,7 +732,8 @@ QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) cons
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const
|
||||
QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices,
|
||||
QRect *r) const
|
||||
{
|
||||
Q_ASSERT(r);
|
||||
QRect &rect = *r;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include <QScrollBar>
|
||||
#include <QCache>
|
||||
#include "VisualGroup.h"
|
||||
#include <functional>
|
||||
|
||||
struct GroupViewRoles
|
||||
{
|
||||
@@ -42,11 +41,6 @@ public:
|
||||
|
||||
void setModel(QAbstractItemModel *model) override;
|
||||
|
||||
using visibilityFunction = std::function<bool(const QString &)>;
|
||||
void setSourceOfGroupCollapseStatus(visibilityFunction f) {
|
||||
fVisibility = f;
|
||||
}
|
||||
|
||||
/// return geometry rectangle occupied by the specified model item
|
||||
QRect geometryRect(const QModelIndex &index) const;
|
||||
/// return visual rectangle occupied by the specified model item
|
||||
@@ -54,7 +48,8 @@ public:
|
||||
/// get the model index at the specified visual point
|
||||
virtual QModelIndex indexAt(const QPoint &point) const override;
|
||||
QString groupNameAt(const QPoint &point);
|
||||
void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override;
|
||||
void setSelection(const QRect &rect,
|
||||
const QItemSelectionModel::SelectionFlags commands) override;
|
||||
|
||||
virtual int horizontalOffset() const override;
|
||||
virtual int verticalOffset() const override;
|
||||
@@ -81,11 +76,9 @@ protected slots:
|
||||
virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override;
|
||||
void modelReset();
|
||||
void rowsRemoved();
|
||||
void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override;
|
||||
|
||||
signals:
|
||||
void droppedURLs(QList<QUrl> urls);
|
||||
void groupStateChanged(QString group, bool collapsed);
|
||||
|
||||
protected:
|
||||
virtual bool isIndexHidden(const QModelIndex &index) const override;
|
||||
@@ -109,8 +102,6 @@ private:
|
||||
friend struct VisualGroup;
|
||||
QList<VisualGroup *> m_groups;
|
||||
|
||||
visibilityFunction fVisibility;
|
||||
|
||||
// geometry
|
||||
int m_leftMargin = 5;
|
||||
int m_rightMargin = 5;
|
||||
|
||||
@@ -205,8 +205,6 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
|
||||
{
|
||||
// FIXME: unused
|
||||
// QSize textSize = viewItemTextSize ( &opt );
|
||||
drawSelectionRect(painter, opt, textHighlightRect);
|
||||
/*
|
||||
QPalette::ColorGroup cg;
|
||||
QStyleOptionViewItem opt2(opt);
|
||||
|
||||
@@ -221,13 +219,10 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
|
||||
{
|
||||
cg = QPalette::Disabled;
|
||||
}
|
||||
*/
|
||||
/*
|
||||
opt2.palette.setCurrentColorGroup(cg);
|
||||
|
||||
// fill in background, if any
|
||||
|
||||
|
||||
if (opt.backgroundBrush.style() != Qt::NoBrush)
|
||||
{
|
||||
QPointF oldBO = painter->brushOrigin();
|
||||
@@ -237,7 +232,6 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
|
||||
}
|
||||
|
||||
drawSelectionRect(painter, opt2, textHighlightRect);
|
||||
*/
|
||||
|
||||
/*
|
||||
if (opt.showDecorationSelected)
|
||||
|
||||
@@ -29,8 +29,7 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
#endif
|
||||
|
||||
// initialize Qt
|
||||
@@ -43,6 +42,7 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
Q_INIT_RESOURCE(multimc);
|
||||
Q_INIT_RESOURCE(backgrounds);
|
||||
Q_INIT_RESOURCE(assets);
|
||||
|
||||
Q_INIT_RESOURCE(pe_dark);
|
||||
Q_INIT_RESOURCE(pe_light);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Package: multimc
|
||||
Version: 1.4-1
|
||||
Version: 1.2-1
|
||||
Architecture: all
|
||||
Maintainer: Petr Mrázek <peterix@gmail.com>
|
||||
Section: games
|
||||
Priority: optional
|
||||
Installed-Size: 75
|
||||
Depends: zenity, desktop-file-utils, qt5-default, wget
|
||||
Depends: zenity, desktop-file-utils
|
||||
Recommends: openjdk-8-jre
|
||||
Homepage: http://multimc.org
|
||||
Description: A local install wrapper for MultiMC
|
||||
|
||||
@@ -4,9 +4,9 @@ A simple ubuntu package for MultiMC that wraps the contains a script that downlo
|
||||
It contains a `.desktop` file, an icon, and a simple script that does the heavy lifting.
|
||||
|
||||
# How to build this?
|
||||
You need dpkg utils. Rename the `multimc` folder to `multimc_1.3-1` and then run:
|
||||
You need dpkg utils. Rename the `multimc` folder to `multimc_1.2-1` and then run:
|
||||
```
|
||||
fakeroot dpkg-deb --build multimc_1.3-1
|
||||
fakeroot dpkg-deb --build multimc_1.2-1
|
||||
```
|
||||
|
||||
Replace the version with whatever is appropriate.
|
||||
|
||||
@@ -39,7 +39,6 @@ PageDialog::PageDialog(BasePageProvider *pageProvider, QString defaultId, QWidge
|
||||
|
||||
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close);
|
||||
buttons->button(QDialogButtonBox::Close)->setDefault(true);
|
||||
buttons->setContentsMargins(6, 0, 6, 0);
|
||||
m_container->addButtons(buttons);
|
||||
|
||||
connect(buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SLOT(close()));
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "ui_AccountListPage.h"
|
||||
|
||||
#include <QItemSelectionModel>
|
||||
#include <QMenu>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
@@ -35,31 +34,24 @@
|
||||
#include "MultiMC.h"
|
||||
|
||||
AccountListPage::AccountListPage(QWidget *parent)
|
||||
: QMainWindow(parent), ui(new Ui::AccountListPage)
|
||||
: QWidget(parent), ui(new Ui::AccountListPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->listView->setEmptyString(tr(
|
||||
"Welcome!\n"
|
||||
"If you're new here, you can click the \"Add\" button to add your Mojang or Minecraft account."
|
||||
));
|
||||
ui->listView->setEmptyMode(VersionListView::String);
|
||||
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
|
||||
m_accounts = MMC->accounts();
|
||||
|
||||
ui->listView->setModel(m_accounts.get());
|
||||
ui->listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
ui->listView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
|
||||
// Expand the account column
|
||||
ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||
|
||||
QItemSelectionModel *selectionModel = ui->listView->selectionModel();
|
||||
|
||||
connect(selectionModel, &QItemSelectionModel::selectionChanged, [this](const QItemSelection &sel, const QItemSelection &dsel) {
|
||||
updateButtonStates();
|
||||
});
|
||||
connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu);
|
||||
connect(selectionModel, &QItemSelectionModel::selectionChanged,
|
||||
[this](const QItemSelection &sel, const QItemSelection &dsel)
|
||||
{ updateButtonStates(); });
|
||||
|
||||
connect(m_accounts.get(), SIGNAL(listChanged()), SLOT(listChanged()));
|
||||
connect(m_accounts.get(), SIGNAL(activeAccountChanged()), SLOT(listChanged()));
|
||||
@@ -72,41 +64,18 @@ AccountListPage::~AccountListPage()
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void AccountListPage::ShowContextMenu(const QPoint& pos)
|
||||
{
|
||||
auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
|
||||
menu->exec(ui->listView->mapToGlobal(pos));
|
||||
delete menu;
|
||||
}
|
||||
|
||||
void AccountListPage::changeEvent(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::LanguageChange)
|
||||
{
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
QMainWindow::changeEvent(event);
|
||||
}
|
||||
|
||||
QMenu * AccountListPage::createPopupMenu()
|
||||
{
|
||||
QMenu* filteredMenu = QMainWindow::createPopupMenu();
|
||||
filteredMenu->removeAction(ui->toolBar->toggleViewAction() );
|
||||
return filteredMenu;
|
||||
}
|
||||
|
||||
|
||||
void AccountListPage::listChanged()
|
||||
{
|
||||
updateButtonStates();
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionAdd_triggered()
|
||||
void AccountListPage::on_addAccountBtn_clicked()
|
||||
{
|
||||
addAccount(tr("Please enter your Minecraft account email and password to add your account."));
|
||||
addAccount(tr("Please enter your Mojang or Minecraft account username and password to add "
|
||||
"your account."));
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionRemove_triggered()
|
||||
void AccountListPage::on_rmAccountBtn_clicked()
|
||||
{
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() > 0)
|
||||
@@ -116,7 +85,7 @@ void AccountListPage::on_actionRemove_triggered()
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionSetDefault_triggered()
|
||||
void AccountListPage::on_setDefaultBtn_clicked()
|
||||
{
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() > 0)
|
||||
@@ -128,7 +97,7 @@ void AccountListPage::on_actionSetDefault_triggered()
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionNoDefault_triggered()
|
||||
void AccountListPage::on_noDefaultBtn_clicked()
|
||||
{
|
||||
m_accounts->setActiveAccount("");
|
||||
}
|
||||
@@ -138,19 +107,11 @@ void AccountListPage::updateButtonStates()
|
||||
// If there is no selection, disable buttons that require something selected.
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
|
||||
ui->actionRemove->setEnabled(selection.size() > 0);
|
||||
ui->actionSetDefault->setEnabled(selection.size() > 0);
|
||||
ui->actionUploadSkin->setEnabled(selection.size() > 0);
|
||||
|
||||
if(m_accounts->activeAccount().get() == nullptr) {
|
||||
ui->actionNoDefault->setEnabled(false);
|
||||
ui->actionNoDefault->setChecked(true);
|
||||
}
|
||||
else {
|
||||
ui->actionNoDefault->setEnabled(true);
|
||||
ui->actionNoDefault->setChecked(false);
|
||||
}
|
||||
ui->rmAccountBtn->setEnabled(selection.size() > 0);
|
||||
ui->setDefaultBtn->setEnabled(selection.size() > 0);
|
||||
ui->uploadSkinBtn->setEnabled(selection.size() > 0);
|
||||
|
||||
ui->noDefaultBtn->setDown(m_accounts->activeAccount().get() == nullptr);
|
||||
}
|
||||
|
||||
void AccountListPage::addAccount(const QString &errMsg)
|
||||
@@ -179,7 +140,7 @@ void AccountListPage::addAccount(const QString &errMsg)
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionUploadSkin_triggered()
|
||||
void AccountListPage::on_uploadSkinBtn_clicked()
|
||||
{
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() > 0)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QDialog>
|
||||
#include <memory>
|
||||
|
||||
#include "pages/BasePage.h"
|
||||
@@ -30,7 +30,7 @@ class AccountListPage;
|
||||
|
||||
class AuthenticateTask;
|
||||
|
||||
class AccountListPage : public QMainWindow, public BasePage
|
||||
class AccountListPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -59,21 +59,17 @@ public:
|
||||
return "Getting-Started#adding-an-account";
|
||||
}
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent * event) override;
|
||||
QMenu * createPopupMenu() override;
|
||||
|
||||
public
|
||||
slots:
|
||||
void on_actionAdd_triggered();
|
||||
void on_addAccountBtn_clicked();
|
||||
|
||||
void on_actionRemove_triggered();
|
||||
void on_rmAccountBtn_clicked();
|
||||
|
||||
void on_actionSetDefault_triggered();
|
||||
void on_setDefaultBtn_clicked();
|
||||
|
||||
void on_actionNoDefault_triggered();
|
||||
void on_noDefaultBtn_clicked();
|
||||
|
||||
void on_actionUploadSkin_triggered();
|
||||
void on_uploadSkinBtn_clicked();
|
||||
|
||||
void listChanged();
|
||||
|
||||
@@ -85,7 +81,6 @@ protected:
|
||||
|
||||
protected
|
||||
slots:
|
||||
void ShowContextMenu(const QPoint &pos);
|
||||
void addAccount(const QString& errMsg="");
|
||||
|
||||
private:
|
||||
|
||||
@@ -1,88 +1,122 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AccountListPage</class>
|
||||
<widget class="QMainWindow" name="AccountListPage">
|
||||
<widget class="QWidget" name="AccountListPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
<width>694</width>
|
||||
<height>609</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="VersionListView" name="listView"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="WideBar" name="toolBar">
|
||||
<attribute name="toolBarArea">
|
||||
<enum>RightToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionAdd"/>
|
||||
<addaction name="actionRemove"/>
|
||||
<addaction name="actionSetDefault"/>
|
||||
<addaction name="actionNoDefault"/>
|
||||
<addaction name="actionUploadSkin"/>
|
||||
</widget>
|
||||
<action name="actionAdd">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemove">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSetDefault">
|
||||
<property name="text">
|
||||
<string>Set Default</string>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNoDefault">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Default</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUploadSkin">
|
||||
<property name="text">
|
||||
<string>Upload Skin</string>
|
||||
</property>
|
||||
</action>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string notr="true">Tab 1</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="welcomeLabel">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Welcome! If you're new here, you can click the &quot;Add&quot; button to add your Mojang or Minecraft account.</p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QTreeView" name="listView"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="manageAcctsBtnBox">
|
||||
<item>
|
||||
<widget class="QPushButton" name="addAccountBtn">
|
||||
<property name="text">
|
||||
<string>&Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="rmAccountBtn">
|
||||
<property name="text">
|
||||
<string>&Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="buttonSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="setDefaultBtn">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Set the currently selected account as the active account. The active account is the account that is used to log in (unless it is overridden in an instance-specific setting).</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Set Default</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="noDefaultBtn">
|
||||
<property name="toolTip">
|
||||
<string>Set no default account. This will cause MultiMC to prompt you to select an account every time you launch an instance that doesn't have its own default set.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&No Default</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uploadSkinBtn">
|
||||
<property name="toolTip">
|
||||
<string>Opens a dialog to select and upload a skin image to the selected account.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Upload Skin</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>VersionListView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>widgets/VersionListView.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>WideBar</class>
|
||||
<extends>QToolBar</extends>
|
||||
<header>widgets/WideBar.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -13,7 +13,6 @@ CustomCommandsPage::CustomCommandsPage(QWidget* parent): QWidget(parent)
|
||||
auto tabWidget = new QTabWidget(this);
|
||||
tabWidget->setObjectName(QStringLiteral("tabWidget"));
|
||||
commands = new CustomCommands(this);
|
||||
commands->setContentsMargins(6, 6, 6, 6);
|
||||
tabWidget->addTab(commands, "Foo");
|
||||
tabWidget->tabBar()->hide();
|
||||
verticalLayout->addWidget(tabWidget);
|
||||
|
||||
@@ -78,12 +78,12 @@ MultiMCPage::MultiMCPage(QWidget *parent) : QWidget(parent), ui(new Ui::MultiMCP
|
||||
}
|
||||
connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview()));
|
||||
connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview()));
|
||||
connect(ui->languageBox, SIGNAL(currentIndexChanged(int)), SLOT(languageIndexChanged(int)));
|
||||
}
|
||||
|
||||
MultiMCPage::~MultiMCPage()
|
||||
{
|
||||
delete ui;
|
||||
delete defaultFormat;
|
||||
}
|
||||
|
||||
bool MultiMCPage::apply()
|
||||
@@ -147,6 +147,19 @@ void MultiMCPage::on_modsDirBrowseBtn_clicked()
|
||||
}
|
||||
}
|
||||
|
||||
void MultiMCPage::languageIndexChanged(int index)
|
||||
{
|
||||
auto languageCode = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString();
|
||||
if(languageCode.isEmpty())
|
||||
{
|
||||
qWarning() << "Unknown language at index" << index;
|
||||
return;
|
||||
}
|
||||
auto translations = MMC->translations();
|
||||
translations->selectLanguage(languageCode);
|
||||
translations->updateLanguage(languageCode);
|
||||
}
|
||||
|
||||
void MultiMCPage::refreshUpdateChannelList()
|
||||
{
|
||||
// Stop listening for selection changes. It's going to change a lot while we update it and
|
||||
@@ -223,6 +236,10 @@ void MultiMCPage::applySettings()
|
||||
{
|
||||
auto s = MMC->settings();
|
||||
|
||||
// Language
|
||||
auto langCode = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString();
|
||||
s->set("Language", langCode.isEmpty() ? "en" : langCode);
|
||||
|
||||
if (ui->resetNotificationsBtn->isChecked())
|
||||
{
|
||||
s->set("ShownNotifications", QString());
|
||||
@@ -315,6 +332,12 @@ void MultiMCPage::applySettings()
|
||||
void MultiMCPage::loadSettings()
|
||||
{
|
||||
auto s = MMC->settings();
|
||||
// Language
|
||||
{
|
||||
ui->languageBox->setModel(m_languageModel.get());
|
||||
ui->languageBox->setCurrentIndex(ui->languageBox->findData(s->get("Language").toString()));
|
||||
}
|
||||
|
||||
// Updates
|
||||
ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
|
||||
m_currentUpdateChannel = s->get("UpdateChannel").toString();
|
||||
|
||||
@@ -68,6 +68,8 @@ slots:
|
||||
void on_modsDirBrowseBtn_clicked();
|
||||
void on_iconsDirBrowseBtn_clicked();
|
||||
|
||||
void languageIndexChanged(int index);
|
||||
|
||||
/*!
|
||||
* Updates the list of update channels in the combo box.
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>514</width>
|
||||
<width>467</width>
|
||||
<height>629</height>
|
||||
</rect>
|
||||
</property>
|
||||
@@ -228,6 +228,18 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Language:</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="languageBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="themeBox">
|
||||
<property name="title">
|
||||
@@ -558,6 +570,7 @@
|
||||
<tabstop>resetNotificationsBtn</tabstop>
|
||||
<tabstop>sortLastLaunchedBtn</tabstop>
|
||||
<tabstop>sortByNameBtn</tabstop>
|
||||
<tabstop>languageBox</tabstop>
|
||||
<tabstop>themeComboBox</tabstop>
|
||||
<tabstop>themeComboBoxColors</tabstop>
|
||||
<tabstop>showConsoleCheck</tabstop>
|
||||
|
||||
224
application/pages/global/PackagesPage.cpp
Normal file
224
application/pages/global/PackagesPage.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
/* Copyright 2015-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "PackagesPage.h"
|
||||
#include "ui_PackagesPage.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "dialogs/ProgressDialog.h"
|
||||
#include "VersionProxyModel.h"
|
||||
|
||||
#include "meta/Index.h"
|
||||
#include "meta/VersionList.h"
|
||||
#include "meta/Version.h"
|
||||
#include "Env.h"
|
||||
#include "MultiMC.h"
|
||||
|
||||
using namespace Meta;
|
||||
|
||||
static QString formatRequires(const VersionPtr &version)
|
||||
{
|
||||
QStringList lines;
|
||||
auto & reqs = version->requires();
|
||||
auto iter = reqs.begin();
|
||||
while (iter != reqs.end())
|
||||
{
|
||||
auto &uid = iter->uid;
|
||||
auto &version = iter->equalsVersion;
|
||||
const QString readable = ENV.metadataIndex()->hasUid(uid) ? ENV.metadataIndex()->get(uid)->humanReadable() : uid;
|
||||
if(!version.isEmpty())
|
||||
{
|
||||
lines.append(QString("%1 (%2)").arg(readable, version));
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.append(QString("%1").arg(readable));
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
PackagesPage::PackagesPage(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::PackagesPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
|
||||
m_fileProxy = new QSortFilterProxyModel(this);
|
||||
m_fileProxy->setSortRole(Qt::DisplayRole);
|
||||
m_fileProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_fileProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_fileProxy->setFilterRole(Qt::DisplayRole);
|
||||
m_fileProxy->setFilterKeyColumn(0);
|
||||
m_fileProxy->sort(0);
|
||||
m_fileProxy->setSourceModel(ENV.metadataIndex().get());
|
||||
ui->indexView->setModel(m_fileProxy);
|
||||
|
||||
m_filterProxy = new QSortFilterProxyModel(this);
|
||||
m_filterProxy->setSortRole(VersionList::SortRole);
|
||||
m_filterProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_filterProxy->setFilterRole(Qt::DisplayRole);
|
||||
m_filterProxy->setFilterKeyColumn(0);
|
||||
m_filterProxy->sort(0, Qt::DescendingOrder);
|
||||
ui->versionsView->setModel(m_filterProxy);
|
||||
|
||||
m_versionProxy = new VersionProxyModel(this);
|
||||
m_filterProxy->setSourceModel(m_versionProxy);
|
||||
|
||||
connect(ui->indexView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateCurrentVersionList);
|
||||
connect(ui->versionsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateVersion);
|
||||
connect(m_filterProxy, &QSortFilterProxyModel::dataChanged, this, &PackagesPage::versionListDataChanged);
|
||||
|
||||
updateCurrentVersionList(QModelIndex());
|
||||
updateVersion();
|
||||
}
|
||||
|
||||
PackagesPage::~PackagesPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
QIcon PackagesPage::icon() const
|
||||
{
|
||||
return MMC->getThemedIcon("packages");
|
||||
}
|
||||
|
||||
void PackagesPage::on_refreshIndexBtn_clicked()
|
||||
{
|
||||
ENV.metadataIndex()->load(Net::Mode::Online);
|
||||
}
|
||||
void PackagesPage::on_refreshFileBtn_clicked()
|
||||
{
|
||||
VersionListPtr list = ui->indexView->currentIndex().data(Index::ListPtrRole).value<VersionListPtr>();
|
||||
if (!list)
|
||||
{
|
||||
return;
|
||||
}
|
||||
list->load(Net::Mode::Online);
|
||||
}
|
||||
void PackagesPage::on_refreshVersionBtn_clicked()
|
||||
{
|
||||
VersionPtr version = ui->versionsView->currentIndex().data(VersionList::VersionPtrRole).value<VersionPtr>();
|
||||
if (!version)
|
||||
{
|
||||
return;
|
||||
}
|
||||
version->load(Net::Mode::Online);
|
||||
}
|
||||
|
||||
void PackagesPage::on_fileSearchEdit_textChanged(const QString &search)
|
||||
{
|
||||
if (search.isEmpty())
|
||||
{
|
||||
m_fileProxy->setFilterFixedString(QString());
|
||||
}
|
||||
else
|
||||
{
|
||||
QStringList parts = search.split(' ');
|
||||
std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape);
|
||||
m_fileProxy->setFilterRegExp(".*" + parts.join(".*") + ".*");
|
||||
}
|
||||
}
|
||||
void PackagesPage::on_versionSearchEdit_textChanged(const QString &search)
|
||||
{
|
||||
if (search.isEmpty())
|
||||
{
|
||||
m_filterProxy->setFilterFixedString(QString());
|
||||
}
|
||||
else
|
||||
{
|
||||
QStringList parts = search.split(' ');
|
||||
std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape);
|
||||
m_filterProxy->setFilterRegExp(".*" + parts.join(".*") + ".*");
|
||||
}
|
||||
}
|
||||
|
||||
void PackagesPage::updateCurrentVersionList(const QModelIndex &index)
|
||||
{
|
||||
if (index.isValid())
|
||||
{
|
||||
VersionListPtr list = index.data(Index::ListPtrRole).value<VersionListPtr>();
|
||||
ui->versionsBox->setEnabled(true);
|
||||
ui->refreshFileBtn->setEnabled(true);
|
||||
ui->fileUidLabel->setEnabled(true);
|
||||
ui->fileUid->setText(list->uid());
|
||||
ui->fileNameLabel->setEnabled(true);
|
||||
ui->fileName->setText(list->name());
|
||||
m_versionProxy->setSourceModel(list.get());
|
||||
ui->refreshFileBtn->setText(tr("Refresh %1").arg(list->humanReadable()));
|
||||
list->load(Net::Mode::Offline);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->versionsBox->setEnabled(false);
|
||||
ui->refreshFileBtn->setEnabled(false);
|
||||
ui->fileUidLabel->setEnabled(false);
|
||||
ui->fileUid->clear();
|
||||
ui->fileNameLabel->setEnabled(false);
|
||||
ui->fileName->clear();
|
||||
m_versionProxy->setSourceModel(nullptr);
|
||||
ui->refreshFileBtn->setText(tr("Refresh"));
|
||||
}
|
||||
}
|
||||
|
||||
void PackagesPage::versionListDataChanged(const QModelIndex &tl, const QModelIndex &br)
|
||||
{
|
||||
if (QItemSelection(tl, br).contains(ui->versionsView->currentIndex()))
|
||||
{
|
||||
updateVersion();
|
||||
}
|
||||
}
|
||||
|
||||
void PackagesPage::updateVersion()
|
||||
{
|
||||
VersionPtr version = std::dynamic_pointer_cast<Version>(
|
||||
ui->versionsView->currentIndex().data(VersionList::VersionPointerRole).value<BaseVersionPtr>());
|
||||
if (version)
|
||||
{
|
||||
ui->refreshVersionBtn->setEnabled(true);
|
||||
ui->versionVersionLabel->setEnabled(true);
|
||||
ui->versionVersion->setText(version->version());
|
||||
ui->versionTimeLabel->setEnabled(true);
|
||||
ui->versionTime->setText(version->time().toString("yyyy-MM-dd HH:mm"));
|
||||
ui->versionTypeLabel->setEnabled(true);
|
||||
ui->versionType->setText(version->type());
|
||||
ui->versionRequiresLabel->setEnabled(true);
|
||||
ui->versionRequires->setText(formatRequires(version));
|
||||
ui->refreshVersionBtn->setText(tr("Refresh %1").arg(version->version()));
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->refreshVersionBtn->setEnabled(false);
|
||||
ui->versionVersionLabel->setEnabled(false);
|
||||
ui->versionVersion->clear();
|
||||
ui->versionTimeLabel->setEnabled(false);
|
||||
ui->versionTime->clear();
|
||||
ui->versionTypeLabel->setEnabled(false);
|
||||
ui->versionType->clear();
|
||||
ui->versionRequiresLabel->setEnabled(false);
|
||||
ui->versionRequires->clear();
|
||||
ui->refreshVersionBtn->setText(tr("Refresh"));
|
||||
}
|
||||
}
|
||||
|
||||
void PackagesPage::openedImpl()
|
||||
{
|
||||
ENV.metadataIndex()->load(Net::Mode::Offline);
|
||||
}
|
||||
57
application/pages/global/PackagesPage.h
Normal file
57
application/pages/global/PackagesPage.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/* Copyright 2015-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "pages/BasePage.h"
|
||||
|
||||
namespace Ui {
|
||||
class PackagesPage;
|
||||
}
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
class VersionProxyModel;
|
||||
|
||||
class PackagesPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PackagesPage(QWidget *parent = 0);
|
||||
~PackagesPage();
|
||||
|
||||
QString id() const override { return "packages-global"; }
|
||||
QString displayName() const override { return tr("Packages"); }
|
||||
QIcon icon() const override;
|
||||
void openedImpl() override;
|
||||
|
||||
private slots:
|
||||
void on_refreshIndexBtn_clicked();
|
||||
void on_refreshFileBtn_clicked();
|
||||
void on_refreshVersionBtn_clicked();
|
||||
void on_fileSearchEdit_textChanged(const QString &search);
|
||||
void on_versionSearchEdit_textChanged(const QString &search);
|
||||
void updateCurrentVersionList(const QModelIndex &index);
|
||||
void versionListDataChanged(const QModelIndex &tl, const QModelIndex &br);
|
||||
|
||||
private:
|
||||
Ui::PackagesPage *ui;
|
||||
QSortFilterProxyModel *m_fileProxy;
|
||||
QSortFilterProxyModel *m_filterProxy;
|
||||
VersionProxyModel *m_versionProxy;
|
||||
|
||||
void updateVersion();
|
||||
};
|
||||
252
application/pages/global/PackagesPage.ui
Normal file
252
application/pages/global/PackagesPage.ui
Normal file
@@ -0,0 +1,252 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PackagesPage</class>
|
||||
<widget class="QWidget" name="PackagesPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>636</width>
|
||||
<height>621</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Tab 1</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="2">
|
||||
<widget class="QGroupBox" name="versionsBox">
|
||||
<property name="title">
|
||||
<string>Versions</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="versionSearchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search...</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="versionsView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QPushButton" name="refreshVersionBtn">
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="versionVersionLabel">
|
||||
<property name="text">
|
||||
<string>Version:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="versionVersion">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="versionTimeLabel">
|
||||
<property name="text">
|
||||
<string>Time:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="versionTime">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="versionTypeLabel">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="versionType">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="versionRequiresLabel">
|
||||
<property name="text">
|
||||
<string>Dependencies:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="versionRequires">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QGroupBox" name="versionListsBox">
|
||||
<property name="title">
|
||||
<string>Resources</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="fileSearchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search...</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="indexView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QPushButton" name="refreshFileBtn">
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="fileUidLabel">
|
||||
<property name="text">
|
||||
<string>UID:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="fileUid">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="fileNameLabel">
|
||||
<property name="text">
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="fileName">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QPushButton" name="refreshIndexBtn">
|
||||
<property name="text">
|
||||
<string>Refresh Index</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "MultiMC.h"
|
||||
#include "Env.h"
|
||||
|
||||
ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage)
|
||||
{
|
||||
@@ -76,9 +75,6 @@ void ProxyPage::applySettings()
|
||||
s->set("ProxyPort", ui->proxyPortEdit->value());
|
||||
s->set("ProxyUser", ui->proxyUserEdit->text());
|
||||
s->set("ProxyPass", ui->proxyPassEdit->text());
|
||||
|
||||
ENV.updateProxySettings(proxyType, ui->proxyAddrEdit->text(), ui->proxyPortEdit->value(),
|
||||
ui->proxyUserEdit->text(), ui->proxyPassEdit->text());
|
||||
}
|
||||
void ProxyPage::loadSettings()
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user