mirror of
https://github.com/UltimMC/Launcher.git
synced 2025-12-13 20:22:13 +00:00
Compare commits
152 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d58481e0de | ||
|
|
08f85f1a93 | ||
|
|
bc98181ec2 | ||
|
|
6a095deea6 | ||
|
|
355e5e24da | ||
|
|
8bdff97ac0 | ||
|
|
6288805f37 | ||
|
|
0d157d86b5 | ||
|
|
f413e61cd8 | ||
|
|
3581f5384f | ||
|
|
480b298635 | ||
|
|
b54b25231e | ||
|
|
43628556ed | ||
|
|
b5adff14ab | ||
|
|
af5120c828 | ||
|
|
47ed2f48d4 | ||
|
|
0c9340a3d2 | ||
|
|
9cc5ebcdd1 | ||
|
|
c60647523e | ||
|
|
9165232ba4 | ||
|
|
31d0507e07 | ||
|
|
cca766cfd9 | ||
|
|
0d41bc5e13 | ||
|
|
e27309d08a | ||
|
|
dec6759e61 | ||
|
|
ce7917048a | ||
|
|
e6936212d6 | ||
|
|
1210d3abf1 | ||
|
|
ffe84d6ec7 | ||
|
|
19015de258 | ||
|
|
5c0c26cd25 | ||
|
|
4cc7427eb4 | ||
|
|
6531aaa7bc | ||
|
|
a35a2e877e | ||
|
|
4e93c4d012 | ||
|
|
7bb23b4142 | ||
|
|
b420f4bafb | ||
|
|
0e0a017175 | ||
|
|
137fe7e3c0 | ||
|
|
9689e5cea7 | ||
|
|
1ede75aa79 | ||
|
|
9ee3a84817 | ||
|
|
8750ca8b36 | ||
|
|
1747f413b9 | ||
|
|
6d975748c0 | ||
|
|
b050c7c075 | ||
|
|
84e0cb1daa | ||
|
|
5074a97cb3 | ||
|
|
441e8980b8 | ||
|
|
84c53273ce | ||
|
|
c291946d2a | ||
|
|
dfb30d9139 | ||
|
|
4ed67413ac | ||
|
|
d31184f9a4 | ||
|
|
b75ba53d4b | ||
|
|
9037873928 | ||
|
|
ce4a55bc3b | ||
|
|
6b82e942d0 | ||
|
|
a3ffa3d665 | ||
|
|
7d13e31198 | ||
|
|
40c9af1a8b | ||
|
|
f5f3149dcf | ||
|
|
7b00d47fe0 | ||
|
|
930d39b5f2 | ||
|
|
bafcf93eb1 | ||
|
|
bd93c3b4e0 | ||
|
|
09f7a426ab | ||
|
|
e4fd50e210 | ||
|
|
3ee5a63c5c | ||
|
|
7dfe73df0c | ||
|
|
c3e61536a3 | ||
|
|
a0e45c5d1d | ||
|
|
bf38021937 | ||
|
|
1e5b595923 | ||
|
|
d6c6653872 | ||
|
|
3b32730526 | ||
|
|
1703dbeb57 | ||
|
|
81fdde6fdd | ||
|
|
3d5869e1cf | ||
|
|
edc5378333 | ||
|
|
95febe5436 | ||
|
|
5b153a5165 | ||
|
|
decd4ae7ab | ||
|
|
2eec1df1a0 | ||
|
|
6fde775b90 | ||
|
|
80b3efff11 | ||
|
|
e4273d6a17 | ||
|
|
62e1bf327d | ||
|
|
280e0e6e36 | ||
|
|
3a67990acd | ||
|
|
23eab74e6d | ||
|
|
b9d4293552 | ||
|
|
5110b58def | ||
|
|
791a8227b6 | ||
|
|
725ec35635 | ||
|
|
739a86f171 | ||
|
|
48b2f95129 | ||
|
|
497d9bec02 | ||
|
|
c01d020afc | ||
|
|
ee83d432f6 | ||
|
|
8ee11b1a8e | ||
|
|
0b86a7ebf3 | ||
|
|
63330bf111 | ||
|
|
f74e3db804 | ||
|
|
a55fa04353 | ||
|
|
fde43c993e | ||
|
|
917f148fc4 | ||
|
|
34611c00e3 | ||
|
|
44a7c5867b | ||
|
|
75ddbc8851 | ||
|
|
2f1d31cf43 | ||
|
|
e7c5b266c8 | ||
|
|
384979bf94 | ||
|
|
b5a16935b7 | ||
|
|
320637e8dc | ||
|
|
77f3f028fa | ||
|
|
2a96e16902 | ||
|
|
1ed84eddd5 | ||
|
|
7b52b8689b | ||
|
|
d21700ee91 | ||
|
|
f87c890912 | ||
|
|
306b98edac | ||
|
|
ce12f1a734 | ||
|
|
c1953739d9 | ||
|
|
e8bf9cef24 | ||
|
|
8aa4b9dac5 | ||
|
|
4836ba22cd | ||
|
|
6c30076b6c | ||
|
|
83c48a0f1a | ||
|
|
d251097545 | ||
|
|
c35dbd972e | ||
|
|
0c5d121452 | ||
|
|
53ad5cb436 | ||
|
|
d87bd4b588 | ||
|
|
86850ef5d0 | ||
|
|
30fba4d407 | ||
|
|
932160818e | ||
|
|
59e1ed3d87 | ||
|
|
3470a3df96 | ||
|
|
61913daaf3 | ||
|
|
cb71dfa605 | ||
|
|
2be98baaba | ||
|
|
0ce637bf0e | ||
|
|
70ed30f9e6 | ||
|
|
414946cad9 | ||
|
|
1fc199697c | ||
|
|
9292d846b6 | ||
|
|
22db95ce3b | ||
|
|
597fe50d37 | ||
|
|
bf93ba02e9 | ||
|
|
07c1685ff1 | ||
|
|
a5e531d4f1 |
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"project_id": "MultiMC5",
|
||||
"conduit_uri": "http://ph.multimc.org"
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
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,8 +9,7 @@ resources/MultiMCLauncher.jar
|
||||
html/
|
||||
|
||||
# Project Files
|
||||
MultiMC5.kdev4
|
||||
MultiMC.pro.user
|
||||
*.pro.user
|
||||
CMakeLists.txt.user
|
||||
CMakeLists.txt.user.*
|
||||
/.project
|
||||
|
||||
38
.travis.yml
38
.travis.yml
@@ -1,38 +0,0 @@
|
||||
# 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 -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 -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}")
|
||||
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 5)
|
||||
set(MultiMC_VERSION_HOTFIX 8)
|
||||
|
||||
# Build number
|
||||
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
|
||||
@@ -77,6 +77,7 @@ 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 ################################
|
||||
|
||||
|
||||
48
README.md
48
README.md
@@ -13,21 +13,22 @@ 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 [workflowy](https://workflowy.com/s/2EyDMcp7CU) - there are many.
|
||||
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.
|
||||
|
||||
### Building
|
||||
If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
Just follow the existing formatting.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Translations
|
||||
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).
|
||||
Translations can be done [on crowdin](https://translate.multimc.org).
|
||||
|
||||
## 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.
|
||||
@@ -43,3 +44,36 @@ 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>
|
||||
|
||||
@@ -187,8 +187,7 @@ Qt::DropActions IconList::supportedDropActions() const
|
||||
return Qt::CopyAction;
|
||||
}
|
||||
|
||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
|
||||
const QModelIndex &parent)
|
||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
|
||||
@@ -80,7 +80,7 @@ protected slots:
|
||||
void fileChanged(const QString &path);
|
||||
void SettingChanged(const Setting & setting, QVariant value);
|
||||
private:
|
||||
std::shared_ptr<QFileSystemWatcher> m_watcher;
|
||||
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
|
||||
bool is_watching;
|
||||
QMap<QString, int> name_index;
|
||||
QVector<MMCIcon> icons;
|
||||
|
||||
@@ -102,3 +102,17 @@ void MMCIcon::replace(IconType new_type, const QString& key)
|
||||
m_images[new_type].filename = QString();
|
||||
m_images[new_type].key = key;
|
||||
}
|
||||
|
||||
QString MMCIcon::getFilePath() const
|
||||
{
|
||||
if(m_current_type == IconType::ToBeDeleted){
|
||||
return QString();
|
||||
}
|
||||
return m_images[m_current_type].filename;
|
||||
}
|
||||
|
||||
|
||||
bool MMCIcon::isBuiltIn() const
|
||||
{
|
||||
return m_current_type == IconType::Builtin;
|
||||
}
|
||||
|
||||
@@ -46,4 +46,6 @@ struct MULTIMC_GUI_EXPORT MMCIcon
|
||||
void remove(IconType rm_type);
|
||||
void replace(IconType new_type, QIcon icon, QString path = QString());
|
||||
void replace(IconType new_type, const QString &key);
|
||||
bool isBuiltIn() const;
|
||||
QString getFilePath() const;
|
||||
};
|
||||
|
||||
@@ -175,11 +175,6 @@ QString BaseInstance::instanceRoot() const
|
||||
return m_rootDir;
|
||||
}
|
||||
|
||||
InstancePtr BaseInstance::getSharedPtr()
|
||||
{
|
||||
return shared_from_this();
|
||||
}
|
||||
|
||||
SettingsObjectPtr BaseInstance::settings() const
|
||||
{
|
||||
return m_settings;
|
||||
@@ -253,7 +248,7 @@ QStringList BaseInstance::extraArguments() const
|
||||
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
|
||||
}
|
||||
|
||||
std::shared_ptr<LaunchTask> BaseInstance::getLaunchTask()
|
||||
shared_qobject_ptr<LaunchTask> BaseInstance::getLaunchTask()
|
||||
{
|
||||
return m_launchProcess;
|
||||
}
|
||||
|
||||
@@ -134,8 +134,6 @@ public:
|
||||
/// Sets the last launched time to 'val' milliseconds since epoch
|
||||
void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch());
|
||||
|
||||
InstancePtr getSharedPtr();
|
||||
|
||||
/*!
|
||||
* \brief Gets this instance's settings object.
|
||||
* This settings object stores instance-specific settings.
|
||||
@@ -147,10 +145,10 @@ public:
|
||||
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0;
|
||||
|
||||
/// returns a valid launcher (task container)
|
||||
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
|
||||
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
|
||||
|
||||
/// returns the current launch task (if any)
|
||||
std::shared_ptr<LaunchTask> getLaunchTask();
|
||||
shared_qobject_ptr<LaunchTask> getLaunchTask();
|
||||
|
||||
/*!
|
||||
* Create envrironment variables for running the instance
|
||||
@@ -241,7 +239,7 @@ signals:
|
||||
*/
|
||||
void propertiesChanged(BaseInstance *inst);
|
||||
|
||||
void launchTaskChanged(std::shared_ptr<LaunchTask>);
|
||||
void launchTaskChanged(shared_qobject_ptr<LaunchTask>);
|
||||
|
||||
void runningStatusChanged(bool running);
|
||||
|
||||
@@ -255,7 +253,7 @@ protected: /* data */
|
||||
SettingsObjectPtr m_settings;
|
||||
// InstanceFlags m_flags;
|
||||
bool m_isRunning = false;
|
||||
std::shared_ptr<LaunchTask> m_launchProcess;
|
||||
shared_qobject_ptr<LaunchTask> m_launchProcess;
|
||||
QDateTime m_timeStarted;
|
||||
|
||||
private: /* data */
|
||||
@@ -265,6 +263,6 @@ private: /* data */
|
||||
bool m_hasBrokenVersion = false;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<BaseInstance>)
|
||||
Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>)
|
||||
//Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
|
||||
//Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)
|
||||
|
||||
@@ -182,9 +182,11 @@ set(NEWS_SOURCES
|
||||
|
||||
# Icon interface
|
||||
set(ICONS_SOURCES
|
||||
# News System
|
||||
# Icons System and related code
|
||||
icons/IIconList.h
|
||||
icons/IIconList.cpp
|
||||
icons/IconUtils.h
|
||||
icons/IconUtils.cpp
|
||||
)
|
||||
|
||||
# Minecraft services status checker
|
||||
@@ -211,8 +213,10 @@ 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
|
||||
@@ -221,6 +225,7 @@ 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
|
||||
@@ -237,12 +242,16 @@ 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
|
||||
@@ -277,15 +286,21 @@ 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
|
||||
@@ -316,8 +331,8 @@ add_unit_test(Library
|
||||
)
|
||||
|
||||
# FIXME: shares data with FileSystem test
|
||||
add_unit_test(SimpleModList
|
||||
SOURCES minecraft/SimpleModList_test.cpp
|
||||
add_unit_test(ModFolderModel
|
||||
SOURCES minecraft/mod/ModFolderModel_test.cpp
|
||||
DATA testdata
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
@@ -424,15 +439,14 @@ set(META_SOURCES
|
||||
)
|
||||
|
||||
set(FTB_SOURCES
|
||||
modplatform/ftb/FtbPackFetchTask.h
|
||||
modplatform/ftb/FtbPackFetchTask.cpp
|
||||
modplatform/ftb/FtbPackInstallTask.h
|
||||
modplatform/ftb/FtbPackInstallTask.cpp
|
||||
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/FtbPrivatePackManager.h
|
||||
modplatform/ftb/FtbPrivatePackManager.cpp
|
||||
|
||||
modplatform/ftb/PackHelpers.h
|
||||
modplatform/legacy_ftb/PackHelpers.h
|
||||
)
|
||||
|
||||
set(FLAME_SOURCES
|
||||
@@ -475,8 +489,6 @@ 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,6 +174,11 @@ 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())
|
||||
@@ -294,7 +299,7 @@ QString NormalizePath(QString path)
|
||||
}
|
||||
}
|
||||
|
||||
QString badFilenameChars = "\"\\/?<>:*|!+\r\n";
|
||||
QString badFilenameChars = "\"\\/?<>:;*|!+\r\n";
|
||||
|
||||
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
||||
{
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
#include "pathmatcher/RegexpMatcher.h"
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves)
|
||||
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime)
|
||||
{
|
||||
m_origInstance = origInstance;
|
||||
m_keepPlaytime = keepPlaytime;
|
||||
|
||||
if(!copySaves)
|
||||
{
|
||||
@@ -46,6 +47,9 @@ 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);
|
||||
explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime);
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
@@ -28,4 +28,5 @@ private: /* data */
|
||||
QFuture<bool> m_copyFuture;
|
||||
QFutureWatcher<bool> m_copyFutureWatcher;
|
||||
std::unique_ptr<IPathMatcher> m_matcher;
|
||||
bool m_keepPlaytime;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "NullInstance.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "icons/IIconList.h"
|
||||
#include "icons/IconUtils.h"
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
// FIXME: this does not belong here, it's Minecraft/Flame specific
|
||||
@@ -393,8 +394,9 @@ void InstanceImportTask::processMultiMC()
|
||||
else
|
||||
{
|
||||
m_instIcon = instance.iconKey();
|
||||
auto importIconPath = FS::PathCombine(instance.instanceRoot(), m_instIcon + ".png");
|
||||
if (QFile::exists(importIconPath))
|
||||
|
||||
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
|
||||
if (!importIconPath.isNull() && QFile::exists(importIconPath))
|
||||
{
|
||||
// import icon
|
||||
auto iconList = ENV.icons();
|
||||
|
||||
@@ -100,6 +100,10 @@ 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();
|
||||
@@ -156,8 +160,8 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
||||
{
|
||||
return GroupId();
|
||||
}
|
||||
auto iter = m_groupMap.find(inst->id());
|
||||
if(iter != m_groupMap.end())
|
||||
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||
if(iter != m_instanceGroupIndex.end())
|
||||
{
|
||||
return *iter;
|
||||
}
|
||||
@@ -174,8 +178,8 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
auto iter = m_groupMap.find(inst->id());
|
||||
if(iter != m_groupMap.end())
|
||||
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||
if(iter != m_instanceGroupIndex.end())
|
||||
{
|
||||
if(*iter != name)
|
||||
{
|
||||
@@ -186,12 +190,12 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
else
|
||||
{
|
||||
changed = true;
|
||||
m_groupMap[id] = name;
|
||||
m_instanceGroupIndex[id] = name;
|
||||
}
|
||||
|
||||
if(changed)
|
||||
{
|
||||
m_groups.insert(name);
|
||||
m_groupNameCache.insert(name);
|
||||
auto idx = getInstIndex(inst.get());
|
||||
emit dataChanged(index(idx), index(idx), {GroupRole});
|
||||
saveGroupList();
|
||||
@@ -200,7 +204,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
|
||||
QStringList InstanceList::getGroups()
|
||||
{
|
||||
return m_groups.toList();
|
||||
return m_groupNameCache.toList();
|
||||
}
|
||||
|
||||
void InstanceList::deleteGroup(const QString& name)
|
||||
@@ -213,7 +217,7 @@ void InstanceList::deleteGroup(const QString& name)
|
||||
auto instGroupName = getInstanceGroup(instID);
|
||||
if(instGroupName == name)
|
||||
{
|
||||
m_groupMap.remove(instID);
|
||||
m_instanceGroupIndex.remove(instID);
|
||||
qDebug() << "Remove" << instID << "from group" << name;
|
||||
removed = true;
|
||||
auto idx = getInstIndex(instance.get());
|
||||
@@ -229,16 +233,21 @@ 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.";
|
||||
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_groupMap.remove(id))
|
||||
if(m_instanceGroupIndex.remove(id))
|
||||
{
|
||||
saveGroupList();
|
||||
}
|
||||
@@ -511,7 +520,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_groupMap.begin(); iter != m_groupMap.end(); iter++)
|
||||
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++)
|
||||
{
|
||||
QString id = iter.key();
|
||||
QString group = iter.value();
|
||||
@@ -544,7 +553,7 @@ void InstanceList::saveGroupList()
|
||||
auto name = iter.key();
|
||||
QJsonObject groupObj;
|
||||
QJsonArray instanceArr;
|
||||
groupObj.insert("hidden", QJsonValue(QString("false")));
|
||||
groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name)));
|
||||
for (auto item : list)
|
||||
{
|
||||
instanceArr.append(QJsonValue(item));
|
||||
@@ -568,7 +577,6 @@ void InstanceList::saveGroupList()
|
||||
void InstanceList::loadGroupList()
|
||||
{
|
||||
qDebug() << "Will load group list now.";
|
||||
QSet<QString> groupSet;
|
||||
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
|
||||
@@ -619,7 +627,8 @@ void InstanceList::loadGroupList()
|
||||
return;
|
||||
}
|
||||
|
||||
m_groupMap.clear();
|
||||
QSet<QString> groupSet;
|
||||
m_instanceGroupIndex.clear();
|
||||
|
||||
// Iterate through all the groups.
|
||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||
@@ -630,37 +639,35 @@ 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_groupMap[(*iter2).toString()] = groupName;
|
||||
m_instanceGroupIndex[(*iter2).toString()] = groupName;
|
||||
}
|
||||
}
|
||||
m_groupsLoaded = true;
|
||||
m_groups.unite(groupSet);
|
||||
m_groupNameCache.unite(groupSet);
|
||||
qDebug() << "Group list loaded.";
|
||||
}
|
||||
|
||||
@@ -685,6 +692,17 @@ 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
|
||||
@@ -815,10 +833,11 @@ bool InstanceList::commitStagedInstance(const QString& path, const QString& inst
|
||||
qWarning() << "Failed to move" << path << "to" << destination;
|
||||
return false;
|
||||
}
|
||||
m_groupMap[instID] = groupName;
|
||||
m_instanceGroupIndex[instID] = groupName;
|
||||
instanceSet.insert(instID);
|
||||
m_groups.insert(groupName);
|
||||
m_groupNameCache.insert(groupName);
|
||||
emit instancesChanged();
|
||||
emit instanceSelectRequest(instID);
|
||||
}
|
||||
saveGroupList();
|
||||
return true;
|
||||
|
||||
@@ -99,6 +99,8 @@ 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);
|
||||
|
||||
@@ -129,10 +131,12 @@ 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);
|
||||
@@ -153,12 +157,14 @@ private:
|
||||
int m_watchLevel = 0;
|
||||
bool m_dirty = false;
|
||||
QList<InstancePtr> m_instances;
|
||||
QSet<QString> m_groups;
|
||||
QSet<QString> m_groupNameCache;
|
||||
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
QString m_instDir;
|
||||
QFileSystemWatcher * m_watcher;
|
||||
QMap<InstanceId, GroupId> m_groupMap;
|
||||
// FIXME: this is so inefficient that looking at it is almost painful.
|
||||
QSet<QString> m_collapsedGroups;
|
||||
QMap<InstanceId, GroupId> m_instanceGroupIndex;
|
||||
QSet<InstanceId> instanceSet;
|
||||
bool m_groupsLoaded = false;
|
||||
bool m_instancesProbed = false;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include <QString>
|
||||
#include <QFileInfo>
|
||||
#include <QSet>
|
||||
#include "minecraft/Mod.h"
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include <functional>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "BaseInstance.h"
|
||||
#include "launch/LaunchTask.h"
|
||||
|
||||
class NullInstance: public BaseInstance
|
||||
{
|
||||
@@ -11,46 +12,46 @@ public:
|
||||
setVersionBroken(true);
|
||||
}
|
||||
virtual ~NullInstance() {};
|
||||
virtual void saveNow() override
|
||||
void saveNow() override
|
||||
{
|
||||
}
|
||||
virtual QString getStatusbarDescription() override
|
||||
QString getStatusbarDescription() override
|
||||
{
|
||||
return tr("Unknown instance type");
|
||||
};
|
||||
virtual QSet< QString > traits() const override
|
||||
QSet< QString > traits() const override
|
||||
{
|
||||
return {};
|
||||
};
|
||||
virtual QString instanceConfigFolder() const override
|
||||
QString instanceConfigFolder() const override
|
||||
{
|
||||
return instanceRoot();
|
||||
};
|
||||
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
|
||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override
|
||||
shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual QProcessEnvironment createEnvironment() override
|
||||
QProcessEnvironment createEnvironment() override
|
||||
{
|
||||
return QProcessEnvironment();
|
||||
}
|
||||
virtual QMap<QString, QString> getVariables() const override
|
||||
QMap<QString, QString> getVariables() const override
|
||||
{
|
||||
return QMap<QString, QString>();
|
||||
}
|
||||
virtual IPathMatcher::Ptr getLogFileMatcher() override
|
||||
IPathMatcher::Ptr getLogFileMatcher() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual QString getLogFileRoot() override
|
||||
QString getLogFileRoot() override
|
||||
{
|
||||
return instanceRoot();
|
||||
}
|
||||
virtual QString typeName() const override
|
||||
QString typeName() const override
|
||||
{
|
||||
return "Null";
|
||||
}
|
||||
|
||||
62
api/logic/icons/IconUtils.cpp
Normal file
62
api/logic/icons/IconUtils.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "IconUtils.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include <QDirIterator>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace {
|
||||
std::array<const char *, 6> validIconExtensions = {{
|
||||
"svg",
|
||||
"png",
|
||||
"ico",
|
||||
"gif",
|
||||
"jpg",
|
||||
"jpeg"
|
||||
}};
|
||||
}
|
||||
|
||||
namespace IconUtils{
|
||||
|
||||
QString findBestIconIn(const QString &folder, const QString & iconKey) {
|
||||
int best_found = validIconExtensions.size();
|
||||
QString best_filename;
|
||||
|
||||
QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
auto fileInfo = it.fileInfo();
|
||||
|
||||
if(fileInfo.completeBaseName() != iconKey)
|
||||
continue;
|
||||
|
||||
auto extension = fileInfo.suffix();
|
||||
|
||||
for(int i = 0; i < best_found; i++) {
|
||||
if(extension == validIconExtensions[i]) {
|
||||
best_found = i;
|
||||
qDebug() << i << " : " << fileInfo.fileName();
|
||||
best_filename = fileInfo.fileName();
|
||||
}
|
||||
}
|
||||
}
|
||||
return FS::PathCombine(folder, best_filename);
|
||||
}
|
||||
|
||||
QString getIconFilter() {
|
||||
QString out;
|
||||
QTextStream stream(&out);
|
||||
stream << '(';
|
||||
for(size_t i = 0; i < validIconExtensions.size() - 1; i++) {
|
||||
if(i > 0) {
|
||||
stream << " ";
|
||||
}
|
||||
stream << "*." << validIconExtensions[i];
|
||||
}
|
||||
stream << " *." << validIconExtensions[validIconExtensions.size() - 1];
|
||||
stream << ')';
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
14
api/logic/icons/IconUtils.h
Normal file
14
api/logic/icons/IconUtils.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
namespace IconUtils {
|
||||
|
||||
// Given a folder and an icon key, find 'best' of the icons with the given key in there and return its path
|
||||
MULTIMC_LOGIC_EXPORT QString findBestIconIn(const QString &folder, const QString & iconKey);
|
||||
|
||||
// Get icon file type filter for file browser dialogs
|
||||
MULTIMC_LOGIC_EXPORT QString getIconFilter();
|
||||
|
||||
}
|
||||
@@ -75,8 +75,8 @@ void JavaChecker::stderrReady()
|
||||
void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
|
||||
{
|
||||
killTimer.stop();
|
||||
QProcessPtr _process;
|
||||
_process.swap(process);
|
||||
QProcessPtr _process = process;
|
||||
process.reset();
|
||||
|
||||
JavaCheckResult result;
|
||||
{
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <QTimer>
|
||||
#include <memory>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
#include "JavaVersion.h"
|
||||
@@ -27,8 +29,8 @@ struct MULTIMC_LOGIC_EXPORT JavaCheckResult
|
||||
} validity = Validity::Errored;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<QProcess> QProcessPtr;
|
||||
typedef std::shared_ptr<JavaChecker> JavaCheckerPtr;
|
||||
typedef shared_qobject_ptr<QProcess> QProcessPtr;
|
||||
typedef shared_qobject_ptr<JavaChecker> JavaCheckerPtr;
|
||||
class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "tasks/Task.h"
|
||||
|
||||
class JavaCheckerJob;
|
||||
typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr;
|
||||
typedef shared_qobject_ptr<JavaCheckerJob> JavaCheckerJobPtr;
|
||||
|
||||
// FIXME: this just seems horribly redundant
|
||||
class JavaCheckerJob : public Task
|
||||
|
||||
@@ -149,7 +149,7 @@ void JavaListLoadTask::executeTask()
|
||||
JavaUtils ju;
|
||||
QList<QString> candidate_paths = ju.FindJavaPaths();
|
||||
|
||||
m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
|
||||
m_job = new JavaCheckerJob("Java detection");
|
||||
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
|
||||
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "JavaCheckerJob.h"
|
||||
#include "JavaInstall.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class JavaListLoadTask;
|
||||
@@ -75,7 +77,7 @@ public slots:
|
||||
void javaCheckerFinished();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<JavaCheckerJob> m_job;
|
||||
shared_qobject_ptr<JavaCheckerJob> m_job;
|
||||
JavaInstallList *m_list;
|
||||
JavaInstall *m_currentRecommended;
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ void CheckJava::executeTask()
|
||||
// if timestamps are not the same, or something is missing, check!
|
||||
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0)
|
||||
{
|
||||
m_JavaChecker = std::make_shared<JavaChecker>();
|
||||
m_JavaChecker = new JavaChecker();
|
||||
emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC);
|
||||
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
|
||||
m_JavaChecker->m_path = realJavaPath;
|
||||
|
||||
@@ -33,9 +33,9 @@ void LaunchTask::init()
|
||||
m_instance->setRunning(true);
|
||||
}
|
||||
|
||||
std::shared_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
|
||||
shared_qobject_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
|
||||
{
|
||||
std::shared_ptr<LaunchTask> proc(new LaunchTask(inst));
|
||||
shared_qobject_ptr<LaunchTask> proc(new LaunchTask(inst));
|
||||
proc->init();
|
||||
return proc;
|
||||
}
|
||||
@@ -44,12 +44,12 @@ LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance)
|
||||
{
|
||||
}
|
||||
|
||||
void LaunchTask::appendStep(std::shared_ptr<LaunchStep> step)
|
||||
void LaunchTask::appendStep(shared_qobject_ptr<LaunchStep> step)
|
||||
{
|
||||
m_steps.append(step);
|
||||
}
|
||||
|
||||
void LaunchTask::prependStep(std::shared_ptr<LaunchStep> step)
|
||||
void LaunchTask::prependStep(shared_qobject_ptr<LaunchStep> step)
|
||||
{
|
||||
m_steps.prepend(step);
|
||||
}
|
||||
|
||||
@@ -45,11 +45,11 @@ public:
|
||||
};
|
||||
|
||||
public: /* methods */
|
||||
static std::shared_ptr<LaunchTask> create(InstancePtr inst);
|
||||
static shared_qobject_ptr<LaunchTask> create(InstancePtr inst);
|
||||
virtual ~LaunchTask() {};
|
||||
|
||||
void appendStep(std::shared_ptr<LaunchStep> step);
|
||||
void prependStep(std::shared_ptr<LaunchStep> step);
|
||||
void appendStep(shared_qobject_ptr<LaunchStep> step);
|
||||
void prependStep(shared_qobject_ptr<LaunchStep> step);
|
||||
void setCensorFilter(QMap<QString, QString> filter);
|
||||
|
||||
InstancePtr instance()
|
||||
@@ -117,7 +117,7 @@ private: /*methods */
|
||||
protected: /* data */
|
||||
InstancePtr m_instance;
|
||||
shared_qobject_ptr<LogModel> m_logModel;
|
||||
QList <std::shared_ptr<LaunchStep>> m_steps;
|
||||
QList <shared_qobject_ptr<LaunchStep>> m_steps;
|
||||
QMap<QString, QString> m_censorFilter;
|
||||
int currentStep = -1;
|
||||
State state = NotStarted;
|
||||
|
||||
@@ -74,7 +74,7 @@ Meta::BaseEntity::~BaseEntity()
|
||||
|
||||
QUrl Meta::BaseEntity::url() const
|
||||
{
|
||||
return QUrl("https://v1.meta.multimc.org").resolved(localFilename());
|
||||
return QUrl("https://meta.multimc.org/v1/").resolved(localFilename());
|
||||
}
|
||||
|
||||
bool Meta::BaseEntity::loadLocalFile()
|
||||
|
||||
@@ -43,6 +43,8 @@ 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);
|
||||
}
|
||||
|
||||
@@ -635,6 +637,9 @@ 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)
|
||||
@@ -762,8 +767,9 @@ QVariant ComponentList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
case NameColumn:
|
||||
return d->components.at(row)->isEnabled() ? Qt::Checked : Qt::Unchecked;
|
||||
case NameColumn: {
|
||||
return patch->isEnabled() ? Qt::Checked : Qt::Unchecked;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@@ -773,7 +779,7 @@ QVariant ComponentList::data(const QModelIndex &index, int role) const
|
||||
switch (column)
|
||||
{
|
||||
case NameColumn:
|
||||
return d->components.at(row)->getName();
|
||||
return patch->getName();
|
||||
case VersionColumn:
|
||||
{
|
||||
if(patch->isCustom())
|
||||
@@ -853,21 +859,25 @@ 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())
|
||||
if(patch->canBeDisabled() && !d->interactionDisabled)
|
||||
{
|
||||
outFlags |= Qt::ItemIsUserCheckable;
|
||||
}
|
||||
@@ -1202,3 +1212,14 @@ 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,6 +104,9 @@ 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);
|
||||
@@ -131,6 +134,7 @@ private slots:
|
||||
void updateSucceeded();
|
||||
void updateFailed(const QString & error);
|
||||
void componentDataChanged();
|
||||
void disableInteraction(bool disable);
|
||||
|
||||
private:
|
||||
bool load();
|
||||
|
||||
@@ -38,5 +38,6 @@ struct ComponentListData
|
||||
QTimer m_saveTimer;
|
||||
shared_qobject_ptr<Task> m_updateTask;
|
||||
bool loaded = false;
|
||||
bool interactionDisabled = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -451,13 +451,17 @@ static bool getTrivialComponentChanges(const ComponentIndex & index, const Requi
|
||||
auto & comp = (*compIter);
|
||||
if(comp->getVersion() != req.equalsVersion)
|
||||
{
|
||||
if(comp->m_dependencyOnly)
|
||||
{
|
||||
decision = Decision::VersionNotSame;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(comp->isCustom()) {
|
||||
decision = Decision::LockedVersionNotSame;
|
||||
} else {
|
||||
if(comp->m_dependencyOnly)
|
||||
{
|
||||
decision = Decision::VersionNotSame;
|
||||
}
|
||||
else
|
||||
{
|
||||
decision = Decision::LockedVersionNotSame;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -586,6 +590,15 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
|
||||
{
|
||||
component->m_version = "3.1.2";
|
||||
}
|
||||
else if (add.uid == "net.fabricmc.intermediary")
|
||||
{
|
||||
auto minecraft = std::find_if(components.begin(), components.end(), [](ComponentPtr & cmp){
|
||||
return cmp->getID() == "net.minecraft";
|
||||
});
|
||||
if(minecraft != components.end()) {
|
||||
component->m_version = (*minecraft)->getVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
|
||||
// ############################################################################################################
|
||||
|
||||
@@ -21,12 +21,13 @@
|
||||
#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 "SimpleModList.h"
|
||||
#include "mod/ModFolderModel.h"
|
||||
#include "WorldList.h"
|
||||
|
||||
#include "icons/IIconList.h"
|
||||
@@ -550,37 +551,38 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session)
|
||||
out << "";
|
||||
}
|
||||
|
||||
if(loaderModList()->size())
|
||||
{
|
||||
out << "Mods:";
|
||||
for(auto & mod: loaderModList()->allMods())
|
||||
auto printModList = [&](const QString & label, ModFolderModel & model) {
|
||||
if(model.size())
|
||||
{
|
||||
if(!mod.enabled())
|
||||
continue;
|
||||
if(mod.type() == Mod::MOD_FOLDER)
|
||||
continue;
|
||||
// TODO: proper implementation would need to descend into folders.
|
||||
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;
|
||||
}
|
||||
|
||||
out << " " + mod.filename().completeBaseName();
|
||||
if(mod.enabled()) {
|
||||
out << u8" [✔️] " + mod.filename().completeBaseName();
|
||||
}
|
||||
else {
|
||||
out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)";
|
||||
}
|
||||
|
||||
}
|
||||
out << "";
|
||||
}
|
||||
out << "";
|
||||
}
|
||||
};
|
||||
|
||||
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 << "";
|
||||
}
|
||||
printModList("Mods", *(loaderModList().get()));
|
||||
printModList("Core Mods", *(coreModList().get()));
|
||||
|
||||
auto & jarMods = profile->getJarMods();
|
||||
if(jarMods.size())
|
||||
@@ -648,8 +650,7 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
|
||||
auto i = sessionRef.u.properties.begin();
|
||||
while (i != sessionRef.u.properties.end())
|
||||
{
|
||||
if(i.key() == "preferredLanguage")
|
||||
{
|
||||
if(i.value().length() <= 3) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
@@ -777,22 +778,22 @@ shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
|
||||
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
|
||||
{
|
||||
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr()));
|
||||
// FIXME: get rid of shared_from_this ...
|
||||
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
|
||||
auto pptr = process.get();
|
||||
|
||||
ENV.icons()->saveIcon(iconKey(), FS::PathCombine(gameRoot(), "icon.png"), "PNG");
|
||||
|
||||
// print a header
|
||||
{
|
||||
process->appendStep(std::make_shared<TextPrint>(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::MultiMC));
|
||||
process->appendStep(new TextPrint(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::MultiMC));
|
||||
}
|
||||
|
||||
// check java
|
||||
{
|
||||
auto step = std::make_shared<CheckJava>(pptr);
|
||||
process->appendStep(step);
|
||||
process->appendStep(new CheckJava(pptr));
|
||||
}
|
||||
|
||||
// check launch method
|
||||
@@ -800,14 +801,14 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
|
||||
QString method = launchMethod();
|
||||
if(!validMethods.contains(method))
|
||||
{
|
||||
process->appendStep(std::make_shared<TextPrint>(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal));
|
||||
process->appendStep(new TextPrint(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal));
|
||||
return process;
|
||||
}
|
||||
|
||||
// run pre-launch command if that's needed
|
||||
if(getPreLaunchCommand().size())
|
||||
{
|
||||
auto step = std::make_shared<PreLaunchCommand>(pptr);
|
||||
auto step = new PreLaunchCommand(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
process->appendStep(step);
|
||||
}
|
||||
@@ -815,42 +816,42 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
|
||||
// if we aren't in offline mode,.
|
||||
if(session->status != AuthSession::PlayableOffline)
|
||||
{
|
||||
process->appendStep(std::make_shared<ClaimAccount>(pptr, session));
|
||||
process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Online));
|
||||
process->appendStep(new ClaimAccount(pptr, session));
|
||||
process->appendStep(new Update(pptr, Net::Mode::Online));
|
||||
}
|
||||
else
|
||||
{
|
||||
process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Offline));
|
||||
process->appendStep(new Update(pptr, Net::Mode::Offline));
|
||||
}
|
||||
|
||||
// if there are any jar mods
|
||||
{
|
||||
auto step = std::make_shared<ModMinecraftJar>(pptr);
|
||||
process->appendStep(step);
|
||||
process->appendStep(new ModMinecraftJar(pptr));
|
||||
}
|
||||
|
||||
// if there are any jar mods
|
||||
{
|
||||
process->appendStep(new ScanModFolders(pptr));
|
||||
}
|
||||
|
||||
// print some instance info here...
|
||||
{
|
||||
auto step = std::make_shared<PrintInstanceInfo>(pptr, session);
|
||||
process->appendStep(step);
|
||||
process->appendStep(new PrintInstanceInfo(pptr, session));
|
||||
}
|
||||
|
||||
// create the server-resource-packs folder (workaround for Minecraft bug MCL-3732)
|
||||
{
|
||||
auto step = std::make_shared<CreateServerResourcePacksFolder>(pptr);
|
||||
process->appendStep(step);
|
||||
process->appendStep(new CreateServerResourcePacksFolder(pptr));
|
||||
}
|
||||
|
||||
// extract native jars if needed
|
||||
{
|
||||
auto step = std::make_shared<ExtractNatives>(pptr);
|
||||
process->appendStep(step);
|
||||
process->appendStep(new ExtractNatives(pptr));
|
||||
}
|
||||
|
||||
// reconstruct assets if needed
|
||||
{
|
||||
auto step = std::make_shared<ReconstructAssets>(pptr);
|
||||
process->appendStep(step);
|
||||
process->appendStep(new ReconstructAssets(pptr));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -858,14 +859,14 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
|
||||
auto method = launchMethod();
|
||||
if(method == "LauncherPart")
|
||||
{
|
||||
auto step = std::make_shared<LauncherPartLaunch>(pptr);
|
||||
auto step = new LauncherPartLaunch(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
step->setAuthSession(session);
|
||||
process->appendStep(step);
|
||||
}
|
||||
else if (method == "DirectJava")
|
||||
{
|
||||
auto step = std::make_shared<DirectJavaLaunch>(pptr);
|
||||
auto step = new DirectJavaLaunch(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
step->setAuthSession(session);
|
||||
process->appendStep(step);
|
||||
@@ -875,7 +876,7 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
|
||||
// run post-exit command if that's needed
|
||||
if(getPostExitCommand().size())
|
||||
{
|
||||
auto step = std::make_shared<PostLaunchCommand>(pptr);
|
||||
auto step = new PostLaunchCommand(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
process->appendStep(step);
|
||||
}
|
||||
@@ -898,43 +899,47 @@ JavaVersion MinecraftInstance::getJavaVersion() const
|
||||
return JavaVersion(settings()->get("JavaVersion").toString());
|
||||
}
|
||||
|
||||
std::shared_ptr<SimpleModList> MinecraftInstance::loaderModList() const
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const
|
||||
{
|
||||
if (!m_loader_mod_list)
|
||||
{
|
||||
m_loader_mod_list.reset(new SimpleModList(loaderModsDir()));
|
||||
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->update();
|
||||
return m_loader_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<SimpleModList> MinecraftInstance::coreModList() const
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const
|
||||
{
|
||||
if (!m_core_mod_list)
|
||||
{
|
||||
m_core_mod_list.reset(new SimpleModList(coreModsDir()));
|
||||
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->update();
|
||||
return m_core_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<SimpleModList> MinecraftInstance::resourcePackList() const
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const
|
||||
{
|
||||
if (!m_resource_pack_list)
|
||||
{
|
||||
m_resource_pack_list.reset(new SimpleModList(resourcePacksDir()));
|
||||
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->update();
|
||||
return m_resource_pack_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<SimpleModList> MinecraftInstance::texturePackList() const
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const
|
||||
{
|
||||
if (!m_texture_pack_list)
|
||||
{
|
||||
m_texture_pack_list.reset(new SimpleModList(texturePacksDir()));
|
||||
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->update();
|
||||
return m_texture_pack_list;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#pragma once
|
||||
#include "BaseInstance.h"
|
||||
#include <java/JavaVersion.h>
|
||||
#include "minecraft/Mod.h"
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include <QProcess>
|
||||
#include <QDir>
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class ModsModel;
|
||||
class SimpleModList;
|
||||
class ModFolderModel;
|
||||
class WorldList;
|
||||
class GameOptions;
|
||||
class LaunchStep;
|
||||
@@ -68,17 +67,16 @@ public:
|
||||
std::shared_ptr<ComponentList> getComponentList() const;
|
||||
|
||||
////// Mod Lists //////
|
||||
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<ModFolderModel> loaderModList() const;
|
||||
std::shared_ptr<ModFolderModel> coreModList() const;
|
||||
std::shared_ptr<ModFolderModel> resourcePackList() const;
|
||||
std::shared_ptr<ModFolderModel> texturePackList() const;
|
||||
std::shared_ptr<WorldList> worldList() const;
|
||||
std::shared_ptr<GameOptions> gameOptionsModel() const;
|
||||
|
||||
////// Launch stuff //////
|
||||
shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
|
||||
std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
|
||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
|
||||
QStringList extraArguments() const override;
|
||||
QStringList verboseDescription(AuthSessionPtr session) override;
|
||||
QList<Mod> getJarMods() const;
|
||||
@@ -113,9 +111,6 @@ public:
|
||||
|
||||
virtual JavaVersion getJavaVersion() const;
|
||||
|
||||
signals:
|
||||
void versionReloaded();
|
||||
|
||||
protected:
|
||||
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
|
||||
QStringList validLaunchMethods();
|
||||
@@ -126,11 +121,10 @@ private:
|
||||
|
||||
protected: // data
|
||||
std::shared_ptr<ComponentList> m_components;
|
||||
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<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<WorldList> m_world_list;
|
||||
mutable std::shared_ptr<GameOptions> m_game_options;
|
||||
};
|
||||
|
||||
@@ -1,378 +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 <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("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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -33,7 +33,7 @@ slots:
|
||||
|
||||
auto time_parsed = timeFromS3Time(timestamp);
|
||||
auto time_serialized = timeToS3Time(time_parsed);
|
||||
|
||||
|
||||
QCOMPARE(time_serialized, timestamp);
|
||||
}
|
||||
|
||||
|
||||
@@ -429,7 +429,3 @@ 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,7 +76,6 @@ 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);
|
||||
|
||||
54
api/logic/minecraft/launch/ScanModFolders.cpp
Normal file
54
api/logic/minecraft/launch/ScanModFolders.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
/* 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();
|
||||
}
|
||||
}
|
||||
42
api/logic/minecraft/launch/ScanModFolders.h
Normal file
42
api/logic/minecraft/launch/ScanModFolders.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/* 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,7 +21,6 @@
|
||||
#include "LegacyInstance.h"
|
||||
|
||||
#include "minecraft/legacy/LegacyModList.h"
|
||||
#include "minecraft/SimpleModList.h"
|
||||
#include "minecraft/WorldList.h"
|
||||
#include <MMCZip.h>
|
||||
#include <FileSystem.h>
|
||||
@@ -107,11 +106,6 @@ 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,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseInstance.h"
|
||||
#include "minecraft/Mod.h"
|
||||
#include "launch/LaunchTask.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class SimpleModList;
|
||||
class ModFolderModel;
|
||||
class LegacyModList;
|
||||
class WorldList;
|
||||
class Task;
|
||||
@@ -77,7 +77,6 @@ public:
|
||||
QString customBaseJar() const;
|
||||
|
||||
std::shared_ptr<LegacyModList> jarModList() const;
|
||||
QList<Mod> getJarMods() const;
|
||||
std::shared_ptr<WorldList> worldList() const;
|
||||
|
||||
/*!
|
||||
@@ -112,7 +111,7 @@ public:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override
|
||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ 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);
|
||||
}
|
||||
|
||||
@@ -34,15 +33,11 @@ LegacyModList::LegacyModList(const QString &dir, const QString &list_file)
|
||||
};
|
||||
typedef QList<OrderItem> OrderList;
|
||||
|
||||
static void internalSort(QList<Mod> &what)
|
||||
static void internalSort(QList<LegacyModList::Mod> &what)
|
||||
{
|
||||
auto predicate = [](const Mod &left, const Mod &right)
|
||||
auto predicate = [](const LegacyModList::Mod &left, const LegacyModList::Mod &right)
|
||||
{
|
||||
if (left.name() == right.name())
|
||||
{
|
||||
return left.mmc_id().localeAwareCompare(right.mmc_id()) < 0;
|
||||
}
|
||||
return left.name().localeAwareCompare(right.name()) < 0;
|
||||
return left.fileName().localeAwareCompare(right.fileName()) < 0;
|
||||
};
|
||||
std::sort(what.begin(), what.end(), predicate);
|
||||
}
|
||||
@@ -90,7 +85,6 @@ 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);
|
||||
@@ -124,48 +118,19 @@ bool LegacyModList::update()
|
||||
// remove from the actual folder contents list
|
||||
folderContents.takeAt(idx);
|
||||
// append the new mod
|
||||
orderedMods.append(Mod(info));
|
||||
if (isEnabled != item.enabled)
|
||||
orderOrStateChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
orderOrStateChanged = true;
|
||||
orderedMods.append(info);
|
||||
}
|
||||
}
|
||||
// if there are any untracked files...
|
||||
// if there are any untracked files... append them sorted at the end
|
||||
if (folderContents.size())
|
||||
{
|
||||
// the order surely changed!
|
||||
for (auto entry : folderContents)
|
||||
{
|
||||
newMods.append(Mod(entry));
|
||||
newMods.append(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,21 +19,14 @@
|
||||
#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,6 +7,7 @@
|
||||
#include "LegacyInstance.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
#include "LegacyModList.h"
|
||||
#include "classparser.h"
|
||||
|
||||
LegacyUpgradeTask::LegacyUpgradeTask(InstancePtr origInstance)
|
||||
@@ -96,10 +97,10 @@ void LegacyUpgradeTask::copyFinished()
|
||||
components->installCustomJar(jarPath);
|
||||
}
|
||||
|
||||
auto jarMods = legacyInst->getJarMods();
|
||||
auto jarMods = legacyInst->jarModList()->allMods();
|
||||
for(auto & jarMod: jarMods)
|
||||
{
|
||||
QString modPath = jarMod.filename().absoluteFilePath();
|
||||
QString modPath = jarMod.absoluteFilePath();
|
||||
qDebug() << "jarMod: " << modPath;
|
||||
components->installJarMods({modPath});
|
||||
}
|
||||
|
||||
298
api/logic/minecraft/mod/LocalModParseTask.cpp
Normal file
298
api/logic/minecraft/mod/LocalModParseTask.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
#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);
|
||||
}
|
||||
37
api/logic/minecraft/mod/LocalModParseTask.h
Normal file
37
api/logic/minecraft/mod/LocalModParseTask.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#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;
|
||||
};
|
||||
151
api/logic/minecraft/mod/Mod.cpp
Normal file
151
api/logic/minecraft/mod/Mod.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -16,8 +16,16 @@
|
||||
#pragma once
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
#include <QList>
|
||||
#include <memory>
|
||||
|
||||
class Mod
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
#include "ModDetails.h"
|
||||
|
||||
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Mod
|
||||
{
|
||||
public:
|
||||
enum ModType
|
||||
@@ -29,6 +37,7 @@ public:
|
||||
MOD_LITEMOD, //!< The mod is a litemod
|
||||
};
|
||||
|
||||
Mod() = default;
|
||||
Mod(const QFileInfo &file);
|
||||
|
||||
QFileInfo filename() const
|
||||
@@ -39,54 +48,14 @@ 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
|
||||
{
|
||||
@@ -98,45 +67,51 @@ 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);
|
||||
|
||||
// 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 ReadForgeInfo(QByteArray contents);
|
||||
void ReadLiteModInfo(QByteArray contents);
|
||||
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;
|
||||
}
|
||||
|
||||
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_mod_id;
|
||||
bool m_enabled = true;
|
||||
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;
|
||||
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;
|
||||
};
|
||||
17
api/logic/minecraft/mod/ModDetails.h
Normal file
17
api/logic/minecraft/mod/ModDetails.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#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;
|
||||
};
|
||||
18
api/logic/minecraft/mod/ModFolderLoadTask.cpp
Normal file
18
api/logic/minecraft/mod/ModFolderLoadTask.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#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();
|
||||
}
|
||||
29
api/logic/minecraft/mod/ModFolderLoadTask.h
Normal file
29
api/logic/minecraft/mod/ModFolderLoadTask.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#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;
|
||||
};
|
||||
@@ -13,7 +13,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "SimpleModList.h"
|
||||
#include "ModFolderModel.h"
|
||||
#include <FileSystem.h>
|
||||
#include <QMimeData>
|
||||
#include <QUrl>
|
||||
@@ -21,18 +21,21 @@
|
||||
#include <QString>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QDebug>
|
||||
#include "ModFolderLoadTask.h"
|
||||
#include <QThreadPool>
|
||||
#include <algorithm>
|
||||
#include "LocalModParseTask.h"
|
||||
|
||||
SimpleModList::SimpleModList(const QString &dir) : QAbstractListModel(), m_dir(dir)
|
||||
ModFolderModel::ModFolderModel(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 SimpleModList::startWatching()
|
||||
void ModFolderModel::startWatching()
|
||||
{
|
||||
if(is_watching)
|
||||
return;
|
||||
@@ -50,7 +53,7 @@ void SimpleModList::startWatching()
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleModList::stopWatching()
|
||||
void ModFolderModel::stopWatching()
|
||||
{
|
||||
if(!is_watching)
|
||||
return;
|
||||
@@ -66,39 +69,164 @@ void SimpleModList::stopWatching()
|
||||
}
|
||||
}
|
||||
|
||||
bool SimpleModList::update()
|
||||
bool ModFolderModel::update()
|
||||
{
|
||||
if (!isValid())
|
||||
if (!isValid()) {
|
||||
return false;
|
||||
|
||||
QList<Mod> newMods;
|
||||
m_dir.refresh();
|
||||
for (auto entry : m_dir.entryInfoList())
|
||||
{
|
||||
newMods.append(Mod(entry));
|
||||
}
|
||||
if(m_update) {
|
||||
scheduled_update = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
mods.swap(newMods);
|
||||
endResetModel();
|
||||
|
||||
emit changed();
|
||||
auto task = new ModFolderLoadTask(m_dir);
|
||||
m_update = task->result();
|
||||
QThreadPool *threadPool = QThreadPool::globalInstance();
|
||||
connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate);
|
||||
threadPool->start(task);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SimpleModList::directoryChanged(QString path)
|
||||
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)
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
bool SimpleModList::isValid()
|
||||
bool ModFolderModel::isValid()
|
||||
{
|
||||
return m_dir.exists() && m_dir.isReadable();
|
||||
}
|
||||
|
||||
// FIXME: this does not take disabled mod (with extra .disable extension) into account...
|
||||
bool SimpleModList::installMod(const QString &filename)
|
||||
bool ModFolderModel::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);
|
||||
@@ -123,8 +251,8 @@ bool SimpleModList::installMod(const QString &filename)
|
||||
qDebug() << "Cannot recognize mod type of" << originalPath << ", ignoring it.";
|
||||
return false;
|
||||
}
|
||||
auto newpath = FS::NormalizePath(FS::PathCombine(m_dir.path(), fileinfo.fileName()));
|
||||
|
||||
auto newpath = FS::NormalizePath(FS::PathCombine(m_dir.path(), fileinfo.fileName()));
|
||||
if(originalPath == newpath)
|
||||
{
|
||||
qDebug() << "Overwriting the mod (" << originalPath << ") with itself makes no sense...";
|
||||
@@ -133,7 +261,7 @@ bool SimpleModList::installMod(const QString &filename)
|
||||
|
||||
if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
|
||||
{
|
||||
if(QFile::exists(newpath))
|
||||
if(QFile::exists(newpath) || QFile::exists(newpath + QString(".disabled")))
|
||||
{
|
||||
if(!QFile::remove(newpath))
|
||||
{
|
||||
@@ -175,23 +303,31 @@ bool SimpleModList::installMod(const QString &filename)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimpleModList::enableMods(const QModelIndexList& indexes, bool enable)
|
||||
bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable)
|
||||
{
|
||||
if(interaction_disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
for (auto i: indexes)
|
||||
for (auto index: indexes)
|
||||
{
|
||||
Mod &m = mods[i.row()];
|
||||
m.enable(enable);
|
||||
emit dataChanged(i, i);
|
||||
if(index.column() != 0) {
|
||||
continue;
|
||||
}
|
||||
setModStatus(index.row(), enable);
|
||||
}
|
||||
emit changed();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SimpleModList::deleteMods(const QModelIndexList& indexes)
|
||||
bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
||||
{
|
||||
if(interaction_disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
@@ -200,16 +336,15 @@ bool SimpleModList::deleteMods(const QModelIndexList& indexes)
|
||||
Mod &m = mods[i.row()];
|
||||
m.destroy();
|
||||
}
|
||||
emit changed();
|
||||
return true;
|
||||
}
|
||||
|
||||
int SimpleModList::columnCount(const QModelIndex &parent) const
|
||||
int ModFolderModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
}
|
||||
|
||||
QVariant SimpleModList::data(const QModelIndex &index, int role) const
|
||||
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
@@ -227,8 +362,17 @@ QVariant SimpleModList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
case NameColumn:
|
||||
return mods[row].name();
|
||||
case VersionColumn:
|
||||
case VersionColumn: {
|
||||
switch(mods[row].type()) {
|
||||
case Mod::MOD_FOLDER:
|
||||
return tr("Folder");
|
||||
case Mod::MOD_SINGLEFILE:
|
||||
return tr("File");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return mods[row].version();
|
||||
}
|
||||
case DateColumn:
|
||||
return mods[row].dateTimeChanged();
|
||||
|
||||
@@ -252,7 +396,7 @@ QVariant SimpleModList::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
}
|
||||
|
||||
bool SimpleModList::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
|
||||
{
|
||||
@@ -261,17 +405,53 @@ bool SimpleModList::setData(const QModelIndex &index, const QVariant &value, int
|
||||
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
auto &mod = mods[index.row()];
|
||||
if (mod.enable(!mod.enabled()))
|
||||
{
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
}
|
||||
return setModStatus(index.row(), Toggle);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant SimpleModList::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
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
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
@@ -310,30 +490,37 @@ QVariant SimpleModList::headerData(int section, Qt::Orientation orientation, int
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags SimpleModList::flags(const QModelIndex &index) const
|
||||
Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||
if (index.isValid())
|
||||
return Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled |
|
||||
defaultFlags;
|
||||
auto flags = defaultFlags;
|
||||
if(interaction_disabled) {
|
||||
flags &= ~Qt::ItemIsDropEnabled;
|
||||
}
|
||||
else
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
{
|
||||
flags |= Qt::ItemIsDropEnabled;
|
||||
if(index.isValid()) {
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
Qt::DropActions SimpleModList::supportedDropActions() const
|
||||
Qt::DropActions ModFolderModel::supportedDropActions() const
|
||||
{
|
||||
// copy from outside, move from within and other mod lists
|
||||
return Qt::CopyAction | Qt::MoveAction;
|
||||
}
|
||||
|
||||
QStringList SimpleModList::mimeTypes() const
|
||||
QStringList ModFolderModel::mimeTypes() const
|
||||
{
|
||||
QStringList types;
|
||||
types << "text/uri-list";
|
||||
return types;
|
||||
}
|
||||
|
||||
bool SimpleModList::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
|
||||
bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
{
|
||||
@@ -16,13 +16,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "minecraft/Mod.h"
|
||||
#include "Mod.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
#include "ModFolderLoadTask.h"
|
||||
#include "LocalModParseTask.h"
|
||||
|
||||
class LegacyInstance;
|
||||
class BaseInstance;
|
||||
@@ -32,7 +36,7 @@ class QFileSystemWatcher;
|
||||
* A legacy mod list.
|
||||
* Backed by a folder.
|
||||
*/
|
||||
class MULTIMC_LOGIC_EXPORT SimpleModList : public QAbstractListModel
|
||||
class MULTIMC_LOGIC_EXPORT ModFolderModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -40,11 +44,16 @@ public:
|
||||
{
|
||||
ActiveColumn = 0,
|
||||
NameColumn,
|
||||
DateColumn,
|
||||
VersionColumn,
|
||||
DateColumn,
|
||||
NUM_COLUMNS
|
||||
};
|
||||
SimpleModList(const QString &dir);
|
||||
enum ModStatusAction {
|
||||
Disable,
|
||||
Enable,
|
||||
Toggle
|
||||
};
|
||||
ModFolderModel(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;
|
||||
@@ -76,9 +85,13 @@ 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.
|
||||
virtual bool update();
|
||||
bool update();
|
||||
|
||||
/**
|
||||
* Adds the given mod to the list at the given index - if the list supports custom ordering
|
||||
@@ -86,15 +99,15 @@ public:
|
||||
bool installMod(const QString& filename);
|
||||
|
||||
/// Deletes all the selected mods
|
||||
virtual bool deleteMods(const QModelIndexList &indexes);
|
||||
bool deleteMods(const QModelIndexList &indexes);
|
||||
|
||||
/// Enable or disable listed mods
|
||||
virtual bool enableMods(const QModelIndexList &indexes, bool enable = true);
|
||||
bool setModStatus(const QModelIndexList &indexes, ModStatusAction action);
|
||||
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
|
||||
virtual bool isValid();
|
||||
bool isValid();
|
||||
|
||||
QDir dir()
|
||||
{
|
||||
@@ -106,16 +119,31 @@ public:
|
||||
return mods;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void disableInteraction(bool disabled);
|
||||
|
||||
private
|
||||
slots:
|
||||
void directoryChanged(QString path);
|
||||
void finishUpdate();
|
||||
void finishModParse(int token);
|
||||
|
||||
signals:
|
||||
void changed();
|
||||
void updateFinished();
|
||||
|
||||
private:
|
||||
void resolveMod(Mod& m);
|
||||
bool setModStatus(int index, ModStatusAction action);
|
||||
|
||||
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/SimpleModList.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
|
||||
class SimpleModListTest : public QObject
|
||||
class ModFolderModelTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -32,7 +32,7 @@ slots:
|
||||
{
|
||||
QString folder = source;
|
||||
QTemporaryDir tempDir;
|
||||
SimpleModList m(tempDir.path());
|
||||
ModFolderModel m(tempDir.path());
|
||||
m.installMod(folder);
|
||||
verify(tempDir.path());
|
||||
}
|
||||
@@ -41,13 +41,13 @@ slots:
|
||||
{
|
||||
QString folder = source + '/';
|
||||
QTemporaryDir tempDir;
|
||||
SimpleModList m(tempDir.path());
|
||||
ModFolderModel m(tempDir.path());
|
||||
m.installMod(folder);
|
||||
verify(tempDir.path());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(SimpleModListTest)
|
||||
QTEST_GUILESS_MAIN(ModFolderModelTest)
|
||||
|
||||
#include "SimpleModList_test.moc"
|
||||
#include "ModFolderModel_test.moc"
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "FileResolvingTask.h"
|
||||
#include "Json.h"
|
||||
|
||||
const char * metabase = "https://cursemeta.dries007.net";
|
||||
namespace {
|
||||
const char * metabase = "https://cursemeta.dries007.net";
|
||||
}
|
||||
|
||||
Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess)
|
||||
: m_toProcess(toProcess)
|
||||
@@ -34,70 +36,14 @@ void Flame::FileResolvingTask::netJobFinished()
|
||||
int index = 0;
|
||||
for(auto & bytes: results)
|
||||
{
|
||||
auto & out = m_toProcess.files[index];
|
||||
try
|
||||
{
|
||||
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;
|
||||
failed &= (!out.parseFromBytes(bytes));
|
||||
}
|
||||
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,3 +64,63 @@ 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,6 +8,9 @@ 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,34 +1,36 @@
|
||||
#include "FtbPackFetchTask.h"
|
||||
#include <QDomDocument>
|
||||
#include "FtbPrivatePackManager.h"
|
||||
#include "PackFetchTask.h"
|
||||
#include "PrivatePackManager.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
#include "net/URLConstants.h"
|
||||
|
||||
void FtbPackFetchTask::fetch()
|
||||
namespace LegacyFTB {
|
||||
|
||||
void PackFetchTask::fetch()
|
||||
{
|
||||
publicPacks.clear();
|
||||
thirdPartyPacks.clear();
|
||||
|
||||
NetJob *netJob = new NetJob("FtbModpackFetch");
|
||||
NetJob *netJob = new NetJob("LegacyFTB::ModpackFetch");
|
||||
|
||||
QUrl publicPacksUrl = QUrl(URLConstants::FTB_CDN_BASE_URL + "static/modpacks.xml");
|
||||
QUrl publicPacksUrl = QUrl(URLConstants::LEGACY_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::FTB_CDN_BASE_URL + "static/thirdparty.xml");
|
||||
QUrl thirdPartyUrl = QUrl(URLConstants::LEGACY_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, &FtbPackFetchTask::fileDownloadFinished);
|
||||
QObject::connect(netJob, &NetJob::failed, this, &FtbPackFetchTask::fileDownloadFailed);
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, &PackFetchTask::fileDownloadFinished);
|
||||
QObject::connect(netJob, &NetJob::failed, this, &PackFetchTask::fileDownloadFailed);
|
||||
|
||||
jobPtr.reset(netJob);
|
||||
netJob->start();
|
||||
}
|
||||
|
||||
void FtbPackFetchTask::fetchPrivate(const QStringList & toFetch)
|
||||
void PackFetchTask::fetchPrivate(const QStringList & toFetch)
|
||||
{
|
||||
QString privatePackBaseUrl = URLConstants::FTB_CDN_BASE_URL + "static/%1.xml";
|
||||
QString privatePackBaseUrl = URLConstants::LEGACY_FTB_CDN_BASE_URL + "static/%1.xml";
|
||||
|
||||
for (auto &packCode: toFetch)
|
||||
{
|
||||
@@ -38,9 +40,9 @@ void FtbPackFetchTask::fetchPrivate(const QStringList & toFetch)
|
||||
|
||||
QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode]
|
||||
{
|
||||
FtbModpackList packs;
|
||||
parseAndAddPacks(*data, FtbPackType::Private, packs);
|
||||
foreach(FtbModpack currentPack, packs)
|
||||
ModpackList packs;
|
||||
parseAndAddPacks(*data, PackType::Private, packs);
|
||||
foreach(Modpack currentPack, packs)
|
||||
{
|
||||
currentPack.packCode = packCode;
|
||||
emit privateFileDownloadFinished(currentPack);
|
||||
@@ -65,18 +67,18 @@ void FtbPackFetchTask::fetchPrivate(const QStringList & toFetch)
|
||||
}
|
||||
}
|
||||
|
||||
void FtbPackFetchTask::fileDownloadFinished()
|
||||
void PackFetchTask::fileDownloadFinished()
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
||||
QStringList failedLists;
|
||||
|
||||
if(!parseAndAddPacks(publicModpacksXmlFileData, FtbPackType::Public, publicPacks))
|
||||
if(!parseAndAddPacks(publicModpacksXmlFileData, PackType::Public, publicPacks))
|
||||
{
|
||||
failedLists.append(tr("Public Packs"));
|
||||
}
|
||||
|
||||
if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, FtbPackType::ThirdParty, thirdPartyPacks))
|
||||
if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, PackType::ThirdParty, thirdPartyPacks))
|
||||
{
|
||||
failedLists.append(tr("Third Party Packs"));
|
||||
}
|
||||
@@ -91,7 +93,7 @@ void FtbPackFetchTask::fileDownloadFinished()
|
||||
}
|
||||
}
|
||||
|
||||
bool FtbPackFetchTask::parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list)
|
||||
bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list)
|
||||
{
|
||||
QDomDocument doc;
|
||||
|
||||
@@ -112,7 +114,7 @@ bool FtbPackFetchTask::parseAndAddPacks(QByteArray &data, FtbPackType packType,
|
||||
{
|
||||
QDomElement element = nodes.at(i).toElement();
|
||||
|
||||
FtbModpack modpack;
|
||||
Modpack modpack;
|
||||
modpack.name = element.attribute("name");
|
||||
modpack.currentVersion = element.attribute("version");
|
||||
modpack.mcVersion = element.attribute("mcVersion");
|
||||
@@ -161,8 +163,10 @@ bool FtbPackFetchTask::parseAndAddPacks(QByteArray &data, FtbPackType packType,
|
||||
return true;
|
||||
}
|
||||
|
||||
void FtbPackFetchTask::fileDownloadFailed(QString reason)
|
||||
void PackFetchTask::fileDownloadFailed(QString reason)
|
||||
{
|
||||
qWarning() << "Fetching FtbPacks failed:" << reason;
|
||||
qWarning() << "Fetching FTBPacks failed:" << reason;
|
||||
emit failed(reason);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,13 +6,15 @@
|
||||
#include <QObject>
|
||||
#include "PackHelpers.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT FtbPackFetchTask : public QObject {
|
||||
namespace LegacyFTB {
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT PackFetchTask : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FtbPackFetchTask() = default;
|
||||
virtual ~FtbPackFetchTask() = default;
|
||||
PackFetchTask() = default;
|
||||
virtual ~PackFetchTask() = default;
|
||||
|
||||
void fetch();
|
||||
void fetchPrivate(const QStringList &toFetch);
|
||||
@@ -23,18 +25,20 @@ private:
|
||||
QByteArray publicModpacksXmlFileData;
|
||||
QByteArray thirdPartyModpacksXmlFileData;
|
||||
|
||||
bool parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list);
|
||||
FtbModpackList publicPacks;
|
||||
FtbModpackList thirdPartyPacks;
|
||||
bool parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list);
|
||||
ModpackList publicPacks;
|
||||
ModpackList thirdPartyPacks;
|
||||
|
||||
protected slots:
|
||||
void fileDownloadFinished();
|
||||
void fileDownloadFailed(QString reason);
|
||||
|
||||
signals:
|
||||
void finished(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks);
|
||||
void finished(ModpackList publicPacks, ModpackList thirdPartyPacks);
|
||||
void failed(QString reason);
|
||||
|
||||
void privateFileDownloadFinished(FtbModpack modpack);
|
||||
void privateFileDownloadFinished(Modpack modpack);
|
||||
void privateFileDownloadFailed(QString reason, QString packCode);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -5,15 +5,17 @@
|
||||
#include <QStringList>
|
||||
#include <QMetaType>
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
//Header for structs etc...
|
||||
enum class FtbPackType
|
||||
enum class PackType
|
||||
{
|
||||
Public,
|
||||
ThirdParty,
|
||||
Private
|
||||
};
|
||||
|
||||
struct FtbModpack
|
||||
struct Modpack
|
||||
{
|
||||
QString name;
|
||||
QString description;
|
||||
@@ -31,11 +33,13 @@ struct FtbModpack
|
||||
bool bugged = false;
|
||||
bool broken = false;
|
||||
|
||||
FtbPackType type;
|
||||
PackType type;
|
||||
QString packCode;
|
||||
};
|
||||
|
||||
//We need it for the proxy model
|
||||
Q_DECLARE_METATYPE(FtbModpack)
|
||||
typedef QList<Modpack> ModpackList;
|
||||
|
||||
typedef QList<FtbModpack> FtbModpackList;
|
||||
}
|
||||
|
||||
//We need it for the proxy model
|
||||
Q_DECLARE_METATYPE(LegacyFTB::Modpack)
|
||||
@@ -1,28 +1,32 @@
|
||||
#include "FtbPackInstallTask.h"
|
||||
#include "PackInstallTask.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"
|
||||
|
||||
FtbPackInstallTask::FtbPackInstallTask(FtbModpack pack, QString version)
|
||||
#include <QtConcurrent>
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
PackInstallTask::PackInstallTask(Modpack pack, QString version)
|
||||
{
|
||||
m_pack = pack;
|
||||
m_version = version;
|
||||
}
|
||||
|
||||
void FtbPackInstallTask::executeTask()
|
||||
void PackInstallTask::executeTask()
|
||||
{
|
||||
downloadPack();
|
||||
}
|
||||
|
||||
void FtbPackInstallTask::downloadPack()
|
||||
void PackInstallTask::downloadPack()
|
||||
{
|
||||
setStatus(tr("Downloading zip for %1").arg(m_pack.name));
|
||||
|
||||
@@ -32,46 +36,46 @@ void FtbPackInstallTask::downloadPack()
|
||||
|
||||
entry->setStale(true);
|
||||
QString url;
|
||||
if(m_pack.type == FtbPackType::Private)
|
||||
if(m_pack.type == PackType::Private)
|
||||
{
|
||||
url = QString(URLConstants::FTB_CDN_BASE_URL + "privatepacks/%1").arg(packoffset);
|
||||
url = QString(URLConstants::LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(packoffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
url = QString(URLConstants::FTB_CDN_BASE_URL + "modpacks/%1").arg(packoffset);
|
||||
url = QString(URLConstants::LEGACY_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, &FtbPackInstallTask::onDownloadSucceeded);
|
||||
connect(job, &NetJob::failed, this, &FtbPackInstallTask::onDownloadFailed);
|
||||
connect(job, &NetJob::progress, this, &FtbPackInstallTask::onDownloadProgress);
|
||||
connect(job, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
||||
connect(job, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
||||
connect(job, &NetJob::progress, this, &PackInstallTask::onDownloadProgress);
|
||||
job->start();
|
||||
|
||||
progress(1, 4);
|
||||
}
|
||||
|
||||
void FtbPackInstallTask::onDownloadSucceeded()
|
||||
void PackInstallTask::onDownloadSucceeded()
|
||||
{
|
||||
abortable = false;
|
||||
unzip();
|
||||
}
|
||||
|
||||
void FtbPackInstallTask::onDownloadFailed(QString reason)
|
||||
void PackInstallTask::onDownloadFailed(QString reason)
|
||||
{
|
||||
abortable = false;
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
void FtbPackInstallTask::onDownloadProgress(qint64 current, qint64 total)
|
||||
void PackInstallTask::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 FtbPackInstallTask::unzip()
|
||||
void PackInstallTask::unzip()
|
||||
{
|
||||
progress(2, 4);
|
||||
setStatus(tr("Extracting modpack"));
|
||||
@@ -85,22 +89,22 @@ void FtbPackInstallTask::unzip()
|
||||
}
|
||||
|
||||
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &FtbPackInstallTask::onUnzipFinished);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &FtbPackInstallTask::onUnzipCanceled);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled);
|
||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||
}
|
||||
|
||||
void FtbPackInstallTask::onUnzipFinished()
|
||||
void PackInstallTask::onUnzipFinished()
|
||||
{
|
||||
install();
|
||||
}
|
||||
|
||||
void FtbPackInstallTask::onUnzipCanceled()
|
||||
void PackInstallTask::onUnzipCanceled()
|
||||
{
|
||||
emitAborted();
|
||||
}
|
||||
|
||||
void FtbPackInstallTask::install()
|
||||
void PackInstallTask::install()
|
||||
{
|
||||
progress(3, 4);
|
||||
setStatus(tr("Installing modpack"));
|
||||
@@ -197,7 +201,7 @@ void FtbPackInstallTask::install()
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
bool FtbPackInstallTask::abort()
|
||||
bool PackInstallTask::abort()
|
||||
{
|
||||
if(abortable)
|
||||
{
|
||||
@@ -205,3 +209,5 @@ bool FtbPackInstallTask::abort()
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,15 +6,17 @@
|
||||
#include "meta/Index.h"
|
||||
#include "meta/Version.h"
|
||||
#include "meta/VersionList.h"
|
||||
#include "modplatform/ftb/PackHelpers.h"
|
||||
#include "PackHelpers.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT FtbPackInstallTask : public InstanceTask
|
||||
namespace LegacyFTB {
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT PackInstallTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FtbPackInstallTask(FtbModpack pack, QString version);
|
||||
virtual ~FtbPackInstallTask(){}
|
||||
explicit PackInstallTask(Modpack pack, QString version);
|
||||
virtual ~PackInstallTask(){}
|
||||
|
||||
bool abort() override;
|
||||
|
||||
@@ -43,6 +45,8 @@ private: /* data */
|
||||
NetJobPtr netJobContainer;
|
||||
QString archivePath;
|
||||
|
||||
FtbModpack m_pack;
|
||||
Modpack m_pack;
|
||||
QString m_version;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "FtbPrivatePackManager.h"
|
||||
#include "PrivatePackManager.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
void FtbPrivatePackManager::load()
|
||||
namespace LegacyFTB {
|
||||
|
||||
void PrivatePackManager::load()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -18,7 +20,7 @@ void FtbPrivatePackManager::load()
|
||||
}
|
||||
}
|
||||
|
||||
void FtbPrivatePackManager::save() const
|
||||
void PrivatePackManager::save() const
|
||||
{
|
||||
if(!dirty)
|
||||
{
|
||||
@@ -35,3 +37,5 @@ void FtbPrivatePackManager::save() const
|
||||
qWarning() << "Failed to write third party FTB pack codes to" << m_filename;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,10 +5,12 @@
|
||||
#include <QFile>
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT FtbPrivatePackManager
|
||||
namespace LegacyFTB {
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT PrivatePackManager
|
||||
{
|
||||
public:
|
||||
~FtbPrivatePackManager()
|
||||
~PrivatePackManager()
|
||||
{
|
||||
save();
|
||||
}
|
||||
@@ -38,3 +40,5 @@ private:
|
||||
QString m_filename = "private_packs.txt";
|
||||
mutable bool dirty = false;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -29,7 +29,8 @@ 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 FTB_CDN_BASE_URL("https://ftb.forgecdn.net/FTB2/");
|
||||
|
||||
const QString LEGACY_FTB_CDN_BASE_URL("https://dist.creeper.host/FTB2/");
|
||||
|
||||
QString getJarPath(QString version);
|
||||
QString getLegacyJarUrl(QString version);
|
||||
|
||||
@@ -36,8 +36,10 @@ 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;
|
||||
@@ -67,6 +69,8 @@ QString INIFile::escape(QString orig)
|
||||
out += "\\t";
|
||||
else if(c == '\\')
|
||||
out += "\\\\";
|
||||
else if(c == '#')
|
||||
out += "\\#";
|
||||
else
|
||||
out += c;
|
||||
}
|
||||
@@ -120,7 +124,15 @@ bool INIFile::loadFile(QByteArray file)
|
||||
{
|
||||
QString &lineRaw = lines[i];
|
||||
// Ignore comments.
|
||||
QString line = lineRaw.left(lineRaw.indexOf('#')).trimmed();
|
||||
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();
|
||||
}
|
||||
|
||||
int eqPos = line.indexOf('=');
|
||||
if (eqPos == -1)
|
||||
|
||||
@@ -26,6 +26,7 @@ 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()
|
||||
{
|
||||
@@ -40,7 +41,7 @@ slots:
|
||||
void test_SaveLoad()
|
||||
{
|
||||
QString a = "a";
|
||||
QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\";
|
||||
QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\#thisIsNotAComment";
|
||||
QString filename = "test_SaveLoad.ini";
|
||||
|
||||
// save
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "BaseProfiler.h"
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
@@ -7,7 +8,7 @@ BaseProfiler::BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QOb
|
||||
{
|
||||
}
|
||||
|
||||
void BaseProfiler::beginProfiling(std::shared_ptr<LaunchTask> process)
|
||||
void BaseProfiler::beginProfiling(shared_qobject_ptr<LaunchTask> process)
|
||||
{
|
||||
beginProfilingImpl(process);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseExternalTool.h"
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
@@ -17,13 +18,13 @@ public:
|
||||
|
||||
public
|
||||
slots:
|
||||
void beginProfiling(std::shared_ptr<LaunchTask> process);
|
||||
void beginProfiling(shared_qobject_ptr<LaunchTask> process);
|
||||
void abortProfiling();
|
||||
|
||||
protected:
|
||||
QProcess *m_profilerProcess;
|
||||
|
||||
virtual void beginProfilingImpl(std::shared_ptr<LaunchTask> process) = 0;
|
||||
virtual void beginProfilingImpl(shared_qobject_ptr<LaunchTask> process) = 0;
|
||||
virtual void abortProfilingImpl();
|
||||
|
||||
signals:
|
||||
|
||||
@@ -17,7 +17,7 @@ private slots:
|
||||
void profilerFinished(int exit, QProcess::ExitStatus status);
|
||||
|
||||
protected:
|
||||
void beginProfilingImpl(std::shared_ptr<LaunchTask> process);
|
||||
void beginProfilingImpl(shared_qobject_ptr<LaunchTask> process);
|
||||
|
||||
private:
|
||||
int listeningPort = 0;
|
||||
@@ -47,7 +47,7 @@ void JProfiler::profilerFinished(int exit, QProcess::ExitStatus status)
|
||||
}
|
||||
}
|
||||
|
||||
void JProfiler::beginProfilingImpl(std::shared_ptr<LaunchTask> process)
|
||||
void JProfiler::beginProfilingImpl(shared_qobject_ptr<LaunchTask> process)
|
||||
{
|
||||
listeningPort = globalSettings->get("JProfilerPort").toInt();
|
||||
QProcess *profiler = new QProcess(this);
|
||||
|
||||
@@ -18,7 +18,7 @@ private slots:
|
||||
void profilerFinished(int exit, QProcess::ExitStatus status);
|
||||
|
||||
protected:
|
||||
void beginProfilingImpl(std::shared_ptr<LaunchTask> process);
|
||||
void beginProfilingImpl(shared_qobject_ptr<LaunchTask> process);
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ void JVisualVM::profilerFinished(int exit, QProcess::ExitStatus status)
|
||||
}
|
||||
}
|
||||
|
||||
void JVisualVM::beginProfilingImpl(std::shared_ptr<LaunchTask> process)
|
||||
void JVisualVM::beginProfilingImpl(shared_qobject_ptr<LaunchTask> process)
|
||||
{
|
||||
QProcess *profiler = new QProcess(this);
|
||||
QStringList profilerArgs =
|
||||
|
||||
@@ -33,7 +33,12 @@ struct Language
|
||||
Language(const QString & _key)
|
||||
{
|
||||
key = _key;
|
||||
locale = QLocale(key);
|
||||
if(key == "pt") {
|
||||
locale = QLocale("pt_PT");
|
||||
}
|
||||
else {
|
||||
locale = QLocale(key);
|
||||
}
|
||||
updated = (key == defaultLangCode);
|
||||
}
|
||||
|
||||
@@ -133,6 +138,7 @@ TranslationsModel::TranslationsModel(QString path, QObject* parent): QAbstractLi
|
||||
{
|
||||
d.reset(new Private);
|
||||
d->m_dir.setPath(path);
|
||||
FS::ensureFolderPathExists(path);
|
||||
reloadLocalFiles();
|
||||
|
||||
d->watcher = new QFileSystemWatcher(this);
|
||||
@@ -151,7 +157,7 @@ void TranslationsModel::translationDirChanged(const QString& path)
|
||||
selectLanguage(selectedLanguage());
|
||||
}
|
||||
|
||||
void TranslationsModel::indexRecieved()
|
||||
void TranslationsModel::indexReceived()
|
||||
{
|
||||
qDebug() << "Got translations index!";
|
||||
d->m_index_job.reset();
|
||||
@@ -558,7 +564,7 @@ void TranslationsModel::downloadIndex()
|
||||
d->m_index_task = Net::Download::makeCached(QUrl("https://files.multimc.org/translations/index_v2.json"), entry);
|
||||
d->m_index_job->addNetAction(d->m_index_task);
|
||||
connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed);
|
||||
connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexRecieved);
|
||||
connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived);
|
||||
d->m_index_job->start();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ private:
|
||||
TranslationsModel &operator=(const TranslationsModel &) = delete;
|
||||
|
||||
private slots:
|
||||
void indexRecieved();
|
||||
void indexReceived();
|
||||
void indexFailed(QString reason);
|
||||
void dlFailed(QString reason);
|
||||
void dlGood();
|
||||
|
||||
@@ -131,7 +131,14 @@ void DownloadTask::processDownloadedVersionInfo()
|
||||
QObject::connect(netJob.get(), &NetJob::progress, this, &DownloadTask::fileDownloadProgressChanged);
|
||||
QObject::connect(netJob.get(), &NetJob::failed, this, &DownloadTask::fileDownloadFailed);
|
||||
|
||||
setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
|
||||
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())));
|
||||
}
|
||||
qDebug() << "Begin downloading update files to" << m_updateFilesDir.path();
|
||||
m_filesNetJob = netJob;
|
||||
m_filesNetJob->start();
|
||||
|
||||
@@ -185,25 +185,6 @@ 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,13 +33,7 @@ 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;
|
||||
@@ -201,19 +195,4 @@ 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,14 +123,5 @@ 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)
|
||||
|
||||
@@ -64,9 +64,6 @@ SET(MULTIMC_SOURCES
|
||||
themes/SystemTheme.cpp
|
||||
themes/SystemTheme.h
|
||||
|
||||
# GUI - settings-specific wrappers for paged dialog
|
||||
SettingsUI.h
|
||||
|
||||
# Processes
|
||||
LaunchController.h
|
||||
LaunchController.cpp
|
||||
@@ -128,20 +125,14 @@ 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/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/legacy_ftb/Page.cpp
|
||||
pages/modplatform/legacy_ftb/Page.h
|
||||
pages/modplatform/legacy_ftb/ListModel.h
|
||||
pages/modplatform/legacy_ftb/ListModel.cpp
|
||||
pages/modplatform/ImportPage.cpp
|
||||
pages/modplatform/ImportPage.h
|
||||
|
||||
@@ -162,8 +153,6 @@ 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
|
||||
@@ -187,6 +176,8 @@ 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
|
||||
@@ -216,10 +207,15 @@ 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
|
||||
@@ -251,13 +247,10 @@ 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/FTBPage.ui
|
||||
pages/modplatform/TwitchPage.ui
|
||||
pages/modplatform/TechnicPage.ui
|
||||
pages/modplatform/legacy_ftb/Page.ui
|
||||
pages/modplatform/ImportPage.ui
|
||||
|
||||
# Dialogs
|
||||
@@ -281,7 +274,6 @@ SET(MULTIMC_UIS
|
||||
)
|
||||
|
||||
set(MULTIMC_QRCS
|
||||
resources/assets/assets.qrc
|
||||
resources/backgrounds/backgrounds.qrc
|
||||
resources/multimc/multimc.qrc
|
||||
resources/pe_dark/pe_dark.qrc
|
||||
|
||||
@@ -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(), "worlds", "worlds", tr("Worlds"), "Worlds"));
|
||||
values.append(new WorldListPage(onesix.get(), onesix->worldList()));
|
||||
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(), "worlds", "worlds", tr("Worlds"), "Worlds"));
|
||||
values.append(new WorldListPage(legacy.get(), legacy->worldList()));
|
||||
values.append(new ScreenshotsPage(FS::PathCombine(legacy->gameRoot(), "screenshots")));
|
||||
}
|
||||
auto logMatcher = inst->getLogFileMatcher();
|
||||
|
||||
@@ -50,6 +50,7 @@ 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.
|
||||
@@ -144,15 +145,18 @@ void InstanceWindow::on_btnLaunchMinecraftOffline_clicked()
|
||||
MMC->launch(m_instance, false, nullptr);
|
||||
}
|
||||
|
||||
void InstanceWindow::on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc)
|
||||
void InstanceWindow::on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask> proc)
|
||||
{
|
||||
m_proc = proc;
|
||||
}
|
||||
|
||||
void InstanceWindow::on_RunningState_changed(bool)
|
||||
void InstanceWindow::on_RunningState_changed(bool running)
|
||||
{
|
||||
updateLaunchButtons();
|
||||
m_container->refreshContainer();
|
||||
if(running) {
|
||||
selectPage("log");
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceWindow::on_closeButton_clicked()
|
||||
|
||||
@@ -52,7 +52,7 @@ slots:
|
||||
void on_btnKillMinecraft_clicked();
|
||||
void on_btnLaunchMinecraftOffline_clicked();
|
||||
|
||||
void on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc);
|
||||
void on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask> proc);
|
||||
void on_RunningState_changed(bool running);
|
||||
void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus);
|
||||
|
||||
@@ -63,7 +63,7 @@ private:
|
||||
void updateLaunchButtons();
|
||||
|
||||
private:
|
||||
std::shared_ptr<LaunchTask> m_proc;
|
||||
shared_qobject_ptr<LaunchTask> m_proc;
|
||||
InstancePtr m_instance;
|
||||
bool m_doNotSave = false;
|
||||
PageContainer *m_container = nullptr;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "InstanceWindow.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "JavaCommon.h"
|
||||
#include "SettingsUI.h"
|
||||
#include <QLineEdit>
|
||||
#include <QInputDialog>
|
||||
#include <tasks/Task.h>
|
||||
@@ -25,7 +24,7 @@ void LaunchController::executeTask()
|
||||
{
|
||||
if (!m_instance)
|
||||
{
|
||||
emitFailed(tr("No instance specified"));
|
||||
emitFailed(tr("No instance specified!"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -53,7 +52,7 @@ void LaunchController::login()
|
||||
if (reply == QMessageBox::Yes)
|
||||
{
|
||||
// Open the account manager.
|
||||
SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), m_parentWidget, "accounts");
|
||||
MMC->ShowGlobalSettings(m_parentWidget, "accounts");
|
||||
}
|
||||
}
|
||||
else if (account.get() == nullptr)
|
||||
@@ -75,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;
|
||||
}
|
||||
|
||||
@@ -84,8 +83,7 @@ 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.");
|
||||
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.");
|
||||
QString failReason = needLoginAgain;
|
||||
|
||||
while (tryagain)
|
||||
@@ -194,7 +192,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;
|
||||
}
|
||||
@@ -218,7 +216,7 @@ void LaunchController::launchInstance()
|
||||
connect(m_launcher.get(), &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested);
|
||||
|
||||
|
||||
m_launcher->prependStep(std::make_shared<TextPrint>(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC));
|
||||
m_launcher->prependStep(new TextPrint(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC));
|
||||
m_launcher->start();
|
||||
}
|
||||
|
||||
@@ -234,8 +232,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);
|
||||
@@ -246,7 +244,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);
|
||||
@@ -263,7 +261,7 @@ void LaunchController::readyForLaunch()
|
||||
msg.setModal(true);
|
||||
msg.exec();
|
||||
m_launcher->abort();
|
||||
emitFailed("Profiler startup failed");
|
||||
emitFailed("Profiler startup failed!");
|
||||
});
|
||||
profilerInstance->beginProfiling(m_launcher);
|
||||
}
|
||||
|
||||
@@ -57,5 +57,5 @@ private:
|
||||
QWidget * m_parentWidget = nullptr;
|
||||
InstanceWindow *m_console = nullptr;
|
||||
AuthSessionPtr m_session;
|
||||
std::shared_ptr <LaunchTask> m_launcher;
|
||||
shared_qobject_ptr<LaunchTask> m_launcher;
|
||||
};
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
#include "InstanceProxyModel.h"
|
||||
#include "JavaCommon.h"
|
||||
#include "LaunchController.h"
|
||||
#include "SettingsUI.h"
|
||||
#include "groupview/GroupView.h"
|
||||
#include "groupview/InstanceDelegate.h"
|
||||
#include "widgets/LabeledToolButton.h"
|
||||
@@ -289,6 +288,7 @@ 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,6 +346,7 @@ 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);
|
||||
@@ -574,9 +575,10 @@ public:
|
||||
{
|
||||
MainWindow->setObjectName(QStringLiteral("MainWindow"));
|
||||
}
|
||||
MainWindow->resize(694, 563);
|
||||
MainWindow->resize(800, 600);
|
||||
MainWindow->setWindowIcon(MMC->getThemedIcon("logo"));
|
||||
MainWindow->setWindowTitle("MultiMC 5");
|
||||
MainWindow->setAccessibleName("MultiMC");
|
||||
|
||||
createMainToolbar(MainWindow);
|
||||
|
||||
@@ -584,7 +586,6 @@ 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);
|
||||
@@ -653,6 +654,7 @@ 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);
|
||||
@@ -681,6 +683,10 @@ 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
|
||||
@@ -703,6 +709,12 @@ 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);
|
||||
|
||||
m_statusLeft = new QLabel(tr("No instance selected"), this);
|
||||
m_statusRight = new ServerStatus(this);
|
||||
statusBar()->addPermanentWidget(m_statusLeft, 1);
|
||||
@@ -823,6 +835,13 @@ MainWindow::~MainWindow()
|
||||
{
|
||||
}
|
||||
|
||||
QMenu * MainWindow::createPopupMenu()
|
||||
{
|
||||
QMenu* filteredMenu = QMainWindow::createPopupMenu();
|
||||
filteredMenu->removeAction( ui->mainToolBar->toggleViewAction() );
|
||||
return filteredMenu;
|
||||
}
|
||||
|
||||
void MainWindow::konamiTriggered()
|
||||
{
|
||||
// ENV.enableFeature("NewModsPage");
|
||||
@@ -902,15 +921,21 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
|
||||
void MainWindow::updateToolsMenu()
|
||||
{
|
||||
QToolButton *launchButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance));
|
||||
QToolButton *launchOfflineButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline));
|
||||
|
||||
if(!m_selectedInstance || m_selectedInstance->isRunning())
|
||||
{
|
||||
ui->actionLaunchInstance->setMenu(nullptr);
|
||||
ui->actionLaunchInstanceOffline->setMenu(nullptr);
|
||||
launchButton->setPopupMode(QToolButton::InstantPopup);
|
||||
launchOfflineButton->setPopupMode(QToolButton::InstantPopup);
|
||||
return;
|
||||
}
|
||||
|
||||
QMenu *launchMenu = ui->actionLaunchInstance->menu();
|
||||
QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu();
|
||||
launchButton->setPopupMode(QToolButton::MenuButtonPopup);
|
||||
launchOfflineButton->setPopupMode(QToolButton::MenuButtonPopup);
|
||||
if (launchMenu)
|
||||
{
|
||||
launchMenu->clear();
|
||||
@@ -919,21 +944,39 @@ void MainWindow::updateToolsMenu()
|
||||
{
|
||||
launchMenu = new QMenu(this);
|
||||
}
|
||||
if (launchOfflineMenu) {
|
||||
launchOfflineMenu->clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
launchOfflineMenu = new QMenu(this);
|
||||
}
|
||||
|
||||
QAction *normalLaunch = launchMenu->addAction(tr("Launch"));
|
||||
QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline"));
|
||||
connect(normalLaunch, &QAction::triggered, [this]()
|
||||
{
|
||||
MMC->launch(m_selectedInstance);
|
||||
MMC->launch(m_selectedInstance, true);
|
||||
});
|
||||
launchMenu->addSeparator()->setText(tr("Profilers"));
|
||||
connect(normalLaunchOffline, &QAction::triggered, [this]()
|
||||
{
|
||||
MMC->launch(m_selectedInstance, false);
|
||||
});
|
||||
QString profilersTitle = tr("Profilers");
|
||||
launchMenu->addSeparator()->setText(profilersTitle);
|
||||
launchOfflineMenu->addSeparator()->setText(profilersTitle);
|
||||
for (auto profiler : MMC->profilers().values())
|
||||
{
|
||||
QAction *profilerAction = launchMenu->addAction(profiler->name());
|
||||
QAction *profilerOfflineAction = launchOfflineMenu->addAction(profiler->name());
|
||||
QString error;
|
||||
if (!profiler->check(&error))
|
||||
{
|
||||
profilerAction->setDisabled(true);
|
||||
profilerAction->setToolTip(tr("Profiler not setup correctly. Go into settings, \"External Tools\"."));
|
||||
profilerOfflineAction->setDisabled(true);
|
||||
QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\".");
|
||||
profilerAction->setToolTip(profilerToolTip);
|
||||
profilerOfflineAction->setToolTip(profilerToolTip);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -941,9 +984,14 @@ void MainWindow::updateToolsMenu()
|
||||
{
|
||||
MMC->launch(m_selectedInstance, true, profiler.get());
|
||||
});
|
||||
connect(profilerOfflineAction, &QAction::triggered, [this, profiler]()
|
||||
{
|
||||
MMC->launch(m_selectedInstance, false, profiler.get());
|
||||
});
|
||||
}
|
||||
}
|
||||
ui->actionLaunchInstance->setMenu(launchMenu);
|
||||
ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu);
|
||||
}
|
||||
|
||||
QString profileInUseFilter(const QString & profile, bool used)
|
||||
@@ -1310,7 +1358,7 @@ void MainWindow::on_actionCopyInstance_triggered()
|
||||
if (!copyInstDlg.exec())
|
||||
return;
|
||||
|
||||
auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves());
|
||||
auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves(), copyInstDlg.shouldKeepPlaytime());
|
||||
copyTask->setName(copyInstDlg.instName());
|
||||
copyTask->setGroup(copyInstDlg.instGroup());
|
||||
copyTask->setIcon(copyInstDlg.iconKey());
|
||||
@@ -1543,7 +1591,11 @@ void MainWindow::checkForUpdates()
|
||||
|
||||
void MainWindow::on_actionSettings_triggered()
|
||||
{
|
||||
SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), this, "global-settings");
|
||||
MMC->ShowGlobalSettings(this, "global-settings");
|
||||
}
|
||||
|
||||
void MainWindow::globalSettingsClosed()
|
||||
{
|
||||
// FIXME: quick HACK to make this work. improve, optimize.
|
||||
MMC->instances()->loadList();
|
||||
proxymodel->invalidate();
|
||||
@@ -1579,7 +1631,7 @@ void MainWindow::on_actionScreenshots_triggered()
|
||||
|
||||
void MainWindow::on_actionManageAccounts_triggered()
|
||||
{
|
||||
SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), this, "accounts");
|
||||
MMC->ShowGlobalSettings(this, "accounts");
|
||||
}
|
||||
|
||||
void MainWindow::on_actionReportBug_triggered()
|
||||
@@ -1616,29 +1668,24 @@ 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!"),
|
||||
tr("About to delete: %1\nThis is permanent and will completely delete the instance.\n\nAre you sure?").arg(m_selectedInstance->name()),
|
||||
QMessageBox::Warning,
|
||||
QMessageBox::Yes | QMessageBox::No
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::No
|
||||
)->exec();
|
||||
if (response == QMessageBox::Yes)
|
||||
{
|
||||
MMC->instances()->deleteInstance(m_selectedInstance->id());
|
||||
MMC->instances()->deleteInstance(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1800,6 +1847,11 @@ 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();
|
||||
@@ -1849,7 +1901,7 @@ void MainWindow::checkInstancePathForProblems()
|
||||
warning.setDefaultButton(QMessageBox::Ok);
|
||||
warning.exec();
|
||||
}
|
||||
else if (pathfoldername.contains(QDir::tempPath()))
|
||||
else if (pathfoldername.startsWith(QDir::tempPath()) || pathfoldername.contains("/TempState/"))
|
||||
{
|
||||
QMessageBox warning(this);
|
||||
warning.setText(tr("Your instance folder is in a temporary folder: \'%1\'!").arg(QDir::tempPath()));
|
||||
|
||||
@@ -60,6 +60,9 @@ public:
|
||||
signals:
|
||||
void isClosing();
|
||||
|
||||
protected:
|
||||
QMenu * createPopupMenu() override;
|
||||
|
||||
private slots:
|
||||
void onCatToggled(bool);
|
||||
|
||||
@@ -109,8 +112,6 @@ private slots:
|
||||
|
||||
void newsButtonClicked();
|
||||
|
||||
void on_mainToolBar_visibilityChanged(bool);
|
||||
|
||||
void on_actionLaunchInstance_triggered();
|
||||
|
||||
void on_actionLaunchInstanceOffline_triggered();
|
||||
@@ -152,6 +153,8 @@ private slots:
|
||||
|
||||
void instanceChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
|
||||
void instanceSelectRequest(QString id);
|
||||
|
||||
void instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
|
||||
|
||||
void selectionBad();
|
||||
@@ -181,6 +184,8 @@ private slots:
|
||||
|
||||
void konamiTriggered();
|
||||
|
||||
void globalSettingsClosed();
|
||||
|
||||
private:
|
||||
void addInstance(QString url = QString());
|
||||
void activateInstance(InstancePtr instance);
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
#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"
|
||||
@@ -11,7 +15,6 @@
|
||||
#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"
|
||||
@@ -66,6 +69,8 @@
|
||||
#include <ganalytics.h>
|
||||
#include <sys.h>
|
||||
|
||||
#include "pagedialog/PageDialog.h"
|
||||
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
@@ -151,23 +156,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
|
||||
@@ -376,7 +381,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;
|
||||
}
|
||||
@@ -521,7 +526,6 @@ 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>();
|
||||
@@ -529,6 +533,10 @@ 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"));
|
||||
@@ -552,7 +560,8 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
|
||||
{
|
||||
":/icons/multimc/32x32/instances/",
|
||||
":/icons/multimc/50x50/instances/",
|
||||
":/icons/multimc/128x128/instances/"
|
||||
":/icons/multimc/128x128/instances/",
|
||||
":/icons/multimc/scalable/instances/"
|
||||
};
|
||||
m_icons.reset(new IconList(instFolders, setting->get().toString()));
|
||||
connect(setting.get(), &Setting::SettingChanged,[&](const Setting &, QVariant value)
|
||||
@@ -591,12 +600,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 rememer that we have to show him a dialog when the gui starts (if it does so)
|
||||
// and remember 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);
|
||||
@@ -938,7 +947,7 @@ bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *pro
|
||||
{
|
||||
if(m_updateRunning)
|
||||
{
|
||||
qDebug() << "Cannot launch instances while an update is running.";
|
||||
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
|
||||
}
|
||||
else if(instance->canLaunch())
|
||||
{
|
||||
@@ -987,7 +996,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()];
|
||||
@@ -1087,6 +1096,20 @@ void MultiMC::controllerFailed(const QString& error)
|
||||
}
|
||||
}
|
||||
|
||||
void MultiMC::ShowGlobalSettings(class QWidget* parent, QString open_page)
|
||||
{
|
||||
if(!m_globalSettingsProvider) {
|
||||
return;
|
||||
}
|
||||
emit globalSettingsAboutToOpen();
|
||||
{
|
||||
SettingsObject::Lock lock(MMC->settings());
|
||||
PageDialog dlg(m_globalSettingsProvider.get(), open_page, parent);
|
||||
dlg.exec();
|
||||
}
|
||||
emit globalSettingsClosed();
|
||||
}
|
||||
|
||||
MainWindow* MultiMC::showMainWindow(bool minimized)
|
||||
{
|
||||
if(m_mainWindow)
|
||||
|
||||
@@ -65,11 +65,6 @@ public:
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
std::shared_ptr<GenericPageProvider> globalSettingsPages() const
|
||||
{
|
||||
return m_globalSettingsProvider;
|
||||
}
|
||||
|
||||
qint64 timeSinceStart() const
|
||||
{
|
||||
return startTime.msecsTo(QDateTime::currentDateTime());
|
||||
@@ -146,8 +141,12 @@ public:
|
||||
void updateIsRunning(bool running);
|
||||
bool updatesAreAllowed();
|
||||
|
||||
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
|
||||
|
||||
signals:
|
||||
void updateAllowedChanged(bool status);
|
||||
void globalSettingsAboutToOpen();
|
||||
void globalSettingsClosed();
|
||||
|
||||
public slots:
|
||||
bool launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr);
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
#include "pages/BasePageProvider.h"
|
||||
#include "MultiMC.h"
|
||||
#include "pagedialog/PageDialog.h"
|
||||
#include "InstancePageProvider.h"
|
||||
#include <settings/SettingsObject.h>
|
||||
#include <BaseInstance.h>
|
||||
|
||||
/*
|
||||
* FIXME: this is a fragment. find a better place for it.
|
||||
*/
|
||||
namespace SettingsUI
|
||||
{
|
||||
template <typename T>
|
||||
void ShowPageDialog(T raw_provider, QWidget * parent, QString open_page = QString())
|
||||
{
|
||||
auto provider = std::dynamic_pointer_cast<BasePageProvider>(raw_provider);
|
||||
if(!provider)
|
||||
return;
|
||||
{
|
||||
SettingsObject::Lock lock(MMC->settings());
|
||||
PageDialog dlg(provider.get(), open_page, parent);
|
||||
dlg.exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,56 +23,43 @@
|
||||
|
||||
#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...
|
||||
static QString getCreditsHtml(QStringList patrons)
|
||||
QString getCreditsHtml(QStringList patrons)
|
||||
{
|
||||
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;
|
||||
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";
|
||||
for (QString patron : patrons)
|
||||
{
|
||||
patronsStr.append(QString("<p>%1</p>").arg(patron));
|
||||
stream << "<p>" << patron << "</p>\n";
|
||||
}
|
||||
|
||||
return creditsHtml.arg(patronsStr);
|
||||
}
|
||||
stream << "</center>\n";
|
||||
return output;
|
||||
}
|
||||
|
||||
static QString getLicenseHtml()
|
||||
QString getLicenseHtml()
|
||||
{
|
||||
HoeDown hoedown;
|
||||
QFile dataFile(":/documents/COPYING.md");
|
||||
@@ -81,6 +68,8 @@ static QString getLicenseHtml()
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
@@ -109,6 +98,15 @@ 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,28 +212,11 @@
|
||||
<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">
|
||||
@@ -257,13 +240,6 @@ p, li { white-space: pre-wrap; }
|
||||
<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>
|
||||
@@ -277,18 +253,7 @@ p, li { white-space: pre-wrap; }
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<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>
|
||||
<widget class="QTextEdit" name="redistributionText">
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
@@ -337,14 +302,11 @@ p, li { white-space: pre-wrap; }
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>creditsText</tabstop>
|
||||
<tabstop>translationInfo</tabstop>
|
||||
<tabstop>licenseText</tabstop>
|
||||
<tabstop>textEdit</tabstop>
|
||||
<tabstop>redistributionText</tabstop>
|
||||
<tabstop>aboutQt</tabstop>
|
||||
<tabstop>closeButton</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../resources/multimc/multimc.qrc"/>
|
||||
</resources>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -53,6 +53,7 @@ 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()
|
||||
@@ -123,3 +124,21 @@ 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,16 +40,19 @@ 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>240</height>
|
||||
<height>323</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -116,6 +116,13 @@
|
||||
</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">
|
||||
@@ -128,6 +135,13 @@
|
||||
</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 / Username</string>
|
||||
<string>Email</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user