Data pack management (#1905)
|
@ -815,6 +815,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||||
m_settings->registerSetting("RPDownloadGeometry", "");
|
m_settings->registerSetting("RPDownloadGeometry", "");
|
||||||
m_settings->registerSetting("TPDownloadGeometry", "");
|
m_settings->registerSetting("TPDownloadGeometry", "");
|
||||||
m_settings->registerSetting("ShaderDownloadGeometry", "");
|
m_settings->registerSetting("ShaderDownloadGeometry", "");
|
||||||
|
m_settings->registerSetting("DataPackDownloadGeometry", "");
|
||||||
|
|
||||||
|
// data pack window
|
||||||
|
// in future, more pages may be added - so this name is chosen to avoid needing migration
|
||||||
|
m_settings->registerSetting("WorldManagementGeometry", "");
|
||||||
|
|
||||||
// HACK: This code feels so stupid is there a less stupid way of doing this?
|
// HACK: This code feels so stupid is there a less stupid way of doing this?
|
||||||
{
|
{
|
||||||
|
|
|
@ -338,6 +338,8 @@ set(MINECRAFT_SOURCES
|
||||||
minecraft/mod/ResourceFolderModel.cpp
|
minecraft/mod/ResourceFolderModel.cpp
|
||||||
minecraft/mod/DataPack.h
|
minecraft/mod/DataPack.h
|
||||||
minecraft/mod/DataPack.cpp
|
minecraft/mod/DataPack.cpp
|
||||||
|
minecraft/mod/DataPackFolderModel.h
|
||||||
|
minecraft/mod/DataPackFolderModel.cpp
|
||||||
minecraft/mod/ResourcePack.h
|
minecraft/mod/ResourcePack.h
|
||||||
minecraft/mod/ResourcePack.cpp
|
minecraft/mod/ResourcePack.cpp
|
||||||
minecraft/mod/ResourcePackFolderModel.h
|
minecraft/mod/ResourcePackFolderModel.h
|
||||||
|
@ -359,8 +361,6 @@ set(MINECRAFT_SOURCES
|
||||||
minecraft/mod/tasks/LocalResourceUpdateTask.cpp
|
minecraft/mod/tasks/LocalResourceUpdateTask.cpp
|
||||||
minecraft/mod/tasks/LocalDataPackParseTask.h
|
minecraft/mod/tasks/LocalDataPackParseTask.h
|
||||||
minecraft/mod/tasks/LocalDataPackParseTask.cpp
|
minecraft/mod/tasks/LocalDataPackParseTask.cpp
|
||||||
minecraft/mod/tasks/LocalResourcePackParseTask.h
|
|
||||||
minecraft/mod/tasks/LocalResourcePackParseTask.cpp
|
|
||||||
minecraft/mod/tasks/LocalTexturePackParseTask.h
|
minecraft/mod/tasks/LocalTexturePackParseTask.h
|
||||||
minecraft/mod/tasks/LocalTexturePackParseTask.cpp
|
minecraft/mod/tasks/LocalTexturePackParseTask.cpp
|
||||||
minecraft/mod/tasks/LocalShaderPackParseTask.h
|
minecraft/mod/tasks/LocalShaderPackParseTask.h
|
||||||
|
@ -917,6 +917,8 @@ SET(LAUNCHER_SOURCES
|
||||||
ui/pages/instance/VersionPage.h
|
ui/pages/instance/VersionPage.h
|
||||||
ui/pages/instance/ManagedPackPage.cpp
|
ui/pages/instance/ManagedPackPage.cpp
|
||||||
ui/pages/instance/ManagedPackPage.h
|
ui/pages/instance/ManagedPackPage.h
|
||||||
|
ui/pages/instance/DataPackPage.h
|
||||||
|
ui/pages/instance/DataPackPage.cpp
|
||||||
ui/pages/instance/TexturePackPage.h
|
ui/pages/instance/TexturePackPage.h
|
||||||
ui/pages/instance/TexturePackPage.cpp
|
ui/pages/instance/TexturePackPage.cpp
|
||||||
ui/pages/instance/ResourcePackPage.h
|
ui/pages/instance/ResourcePackPage.h
|
||||||
|
@ -987,6 +989,8 @@ SET(LAUNCHER_SOURCES
|
||||||
ui/pages/modplatform/ShaderPackPage.cpp
|
ui/pages/modplatform/ShaderPackPage.cpp
|
||||||
ui/pages/modplatform/ShaderPackModel.cpp
|
ui/pages/modplatform/ShaderPackModel.cpp
|
||||||
|
|
||||||
|
ui/pages/modplatform/DataPackPage.cpp
|
||||||
|
ui/pages/modplatform/DataPackModel.cpp
|
||||||
|
|
||||||
ui/pages/modplatform/ModpackProviderBasePage.h
|
ui/pages/modplatform/ModpackProviderBasePage.h
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
|
#include <ui/pages/instance/DataPackPage.h>
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "ui/pages/BasePage.h"
|
#include "ui/pages/BasePage.h"
|
||||||
#include "ui/pages/BasePageProvider.h"
|
#include "ui/pages/BasePageProvider.h"
|
||||||
|
@ -36,6 +37,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
|
||||||
values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList()));
|
values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList()));
|
||||||
values.append(new NilModFolderPage(onesix.get(), onesix->nilModList()));
|
values.append(new NilModFolderPage(onesix.get(), onesix->nilModList()));
|
||||||
values.append(new ResourcePackPage(onesix.get(), onesix->resourcePackList()));
|
values.append(new ResourcePackPage(onesix.get(), onesix->resourcePackList()));
|
||||||
|
values.append(new GlobalDataPackPage(onesix.get()));
|
||||||
values.append(new TexturePackPage(onesix.get(), onesix->texturePackList()));
|
values.append(new TexturePackPage(onesix.get(), onesix->texturePackList()));
|
||||||
values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList()));
|
values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList()));
|
||||||
values.append(new NotesPage(onesix.get()));
|
values.append(new NotesPage(onesix.get()));
|
||||||
|
|
|
@ -250,6 +250,12 @@ void MinecraftInstance::loadSpecificSettings()
|
||||||
m_settings->registerSetting("ExportOptionalFiles", true);
|
m_settings->registerSetting("ExportOptionalFiles", true);
|
||||||
m_settings->registerSetting("ExportRecommendedRAM");
|
m_settings->registerSetting("ExportRecommendedRAM");
|
||||||
|
|
||||||
|
auto dataPacksEnabled = m_settings->registerSetting("GlobalDataPacksEnabled", false);
|
||||||
|
auto dataPacksPath = m_settings->registerSetting("GlobalDataPacksPath", "");
|
||||||
|
|
||||||
|
connect(dataPacksEnabled.get(), &Setting::SettingChanged, this, [this] { m_data_pack_list.reset(); });
|
||||||
|
connect(dataPacksPath.get(), &Setting::SettingChanged, this, [this] { m_data_pack_list.reset(); });
|
||||||
|
|
||||||
// Join server on launch, this does not have a global override
|
// Join server on launch, this does not have a global override
|
||||||
m_settings->registerSetting("OverrideModDownloadLoaders", false);
|
m_settings->registerSetting("OverrideModDownloadLoaders", false);
|
||||||
m_settings->registerSetting("ModDownloadLoaders", "[]");
|
m_settings->registerSetting("ModDownloadLoaders", "[]");
|
||||||
|
@ -396,6 +402,16 @@ QString MinecraftInstance::nilModsDir() const
|
||||||
return FS::PathCombine(gameRoot(), "nilmods");
|
return FS::PathCombine(gameRoot(), "nilmods");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString MinecraftInstance::dataPacksDir()
|
||||||
|
{
|
||||||
|
QString relativePath = settings()->get("GlobalDataPacksPath").toString();
|
||||||
|
|
||||||
|
if (relativePath.isEmpty())
|
||||||
|
relativePath = "datapacks";
|
||||||
|
|
||||||
|
return QDir(gameRoot()).filePath(relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
QString MinecraftInstance::resourcePacksDir() const
|
QString MinecraftInstance::resourcePacksDir() const
|
||||||
{
|
{
|
||||||
return FS::PathCombine(gameRoot(), "resourcepacks");
|
return FS::PathCombine(gameRoot(), "resourcepacks");
|
||||||
|
@ -1247,9 +1263,18 @@ std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
|
||||||
return m_shader_pack_list;
|
return m_shader_pack_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DataPackFolderModel> MinecraftInstance::dataPackList()
|
||||||
|
{
|
||||||
|
if (!m_data_pack_list && settings()->get("GlobalDataPacksEnabled").toBool()) {
|
||||||
|
bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||||
|
m_data_pack_list.reset(new DataPackFolderModel(dataPacksDir(), this, isIndexed, true));
|
||||||
|
}
|
||||||
|
return m_data_pack_list;
|
||||||
|
}
|
||||||
|
|
||||||
QList<std::shared_ptr<ResourceFolderModel>> MinecraftInstance::resourceLists()
|
QList<std::shared_ptr<ResourceFolderModel>> MinecraftInstance::resourceLists()
|
||||||
{
|
{
|
||||||
return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList() };
|
return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList(), dataPackList() };
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<WorldList> MinecraftInstance::worldList()
|
std::shared_ptr<WorldList> MinecraftInstance::worldList()
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <java/JavaVersion.h>
|
#include <java/JavaVersion.h>
|
||||||
|
#include <minecraft/mod/DataPackFolderModel.h>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
|
@ -80,6 +81,7 @@ class MinecraftInstance : public BaseInstance {
|
||||||
QString modsRoot() const override;
|
QString modsRoot() const override;
|
||||||
QString coreModsDir() const;
|
QString coreModsDir() const;
|
||||||
QString nilModsDir() const;
|
QString nilModsDir() const;
|
||||||
|
QString dataPacksDir();
|
||||||
QString modsCacheLocation() const;
|
QString modsCacheLocation() const;
|
||||||
QString libDir() const;
|
QString libDir() const;
|
||||||
QString worldDir() const;
|
QString worldDir() const;
|
||||||
|
@ -116,6 +118,7 @@ class MinecraftInstance : public BaseInstance {
|
||||||
std::shared_ptr<ResourcePackFolderModel> resourcePackList();
|
std::shared_ptr<ResourcePackFolderModel> resourcePackList();
|
||||||
std::shared_ptr<TexturePackFolderModel> texturePackList();
|
std::shared_ptr<TexturePackFolderModel> texturePackList();
|
||||||
std::shared_ptr<ShaderPackFolderModel> shaderPackList();
|
std::shared_ptr<ShaderPackFolderModel> shaderPackList();
|
||||||
|
std::shared_ptr<DataPackFolderModel> dataPackList();
|
||||||
QList<std::shared_ptr<ResourceFolderModel>> resourceLists();
|
QList<std::shared_ptr<ResourceFolderModel>> resourceLists();
|
||||||
std::shared_ptr<WorldList> worldList();
|
std::shared_ptr<WorldList> worldList();
|
||||||
std::shared_ptr<GameOptions> gameOptionsModel();
|
std::shared_ptr<GameOptions> gameOptionsModel();
|
||||||
|
@ -166,6 +169,7 @@ class MinecraftInstance : public BaseInstance {
|
||||||
mutable std::shared_ptr<ResourcePackFolderModel> m_resource_pack_list;
|
mutable std::shared_ptr<ResourcePackFolderModel> m_resource_pack_list;
|
||||||
mutable std::shared_ptr<ShaderPackFolderModel> m_shader_pack_list;
|
mutable std::shared_ptr<ShaderPackFolderModel> m_shader_pack_list;
|
||||||
mutable std::shared_ptr<TexturePackFolderModel> m_texture_pack_list;
|
mutable std::shared_ptr<TexturePackFolderModel> m_texture_pack_list;
|
||||||
|
mutable std::shared_ptr<DataPackFolderModel> m_data_pack_list;
|
||||||
mutable std::shared_ptr<WorldList> m_world_list;
|
mutable std::shared_ptr<WorldList> m_world_list;
|
||||||
mutable std::shared_ptr<GameOptions> m_game_options;
|
mutable std::shared_ptr<GameOptions> m_game_options;
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,7 +25,9 @@
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
#include "MTPixmapCache.h"
|
||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
|
#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
|
||||||
|
|
||||||
// Values taken from:
|
// Values taken from:
|
||||||
// https://minecraft.wiki/w/Pack_format#List_of_data_pack_formats
|
// https://minecraft.wiki/w/Pack_format#List_of_data_pack_formats
|
||||||
|
@ -93,6 +95,51 @@ void DataPack::setDescription(QString new_description)
|
||||||
m_description = new_description;
|
m_description = new_description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DataPack::setImage(QImage new_image) const
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_data_lock);
|
||||||
|
|
||||||
|
Q_ASSERT(!new_image.isNull());
|
||||||
|
|
||||||
|
if (m_pack_image_cache_key.key.isValid())
|
||||||
|
PixmapCache::instance().remove(m_pack_image_cache_key.key);
|
||||||
|
|
||||||
|
// scale the image to avoid flooding the pixmapcache
|
||||||
|
auto pixmap =
|
||||||
|
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
|
||||||
|
|
||||||
|
m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap);
|
||||||
|
m_pack_image_cache_key.was_ever_used = true;
|
||||||
|
|
||||||
|
// This can happen if the pixmap is too big to fit in the cache :c
|
||||||
|
if (!m_pack_image_cache_key.key.isValid()) {
|
||||||
|
qWarning() << "Could not insert a image cache entry! Ignoring it.";
|
||||||
|
m_pack_image_cache_key.was_ever_used = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap DataPack::image(QSize size, Qt::AspectRatioMode mode) const
|
||||||
|
{
|
||||||
|
QPixmap cached_image;
|
||||||
|
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
|
||||||
|
if (size.isNull())
|
||||||
|
return cached_image;
|
||||||
|
return cached_image.scaled(size, mode, Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No valid image we can get
|
||||||
|
if (!m_pack_image_cache_key.was_ever_used) {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
qDebug() << "Data Pack" << name() << "Had it's image evicted from the cache. reloading...";
|
||||||
|
PixmapCache::markCacheMissByEviciton();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Imaged got evicted from the cache. Re-process it and retry.
|
||||||
|
DataPackUtils::processPackPNG(this);
|
||||||
|
return image(size);
|
||||||
|
}
|
||||||
|
|
||||||
std::pair<Version, Version> DataPack::compatibleVersions() const
|
std::pair<Version, Version> DataPack::compatibleVersions() const
|
||||||
{
|
{
|
||||||
if (!s_pack_format_versions.contains(m_pack_format)) {
|
if (!s_pack_format_versions.contains(m_pack_format)) {
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "Resource.h"
|
#include "Resource.h"
|
||||||
|
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
#include <QPixmapCache>
|
||||||
|
|
||||||
class Version;
|
class Version;
|
||||||
|
|
||||||
|
@ -41,17 +42,23 @@ class DataPack : public Resource {
|
||||||
/** Gets the numerical ID of the pack format. */
|
/** Gets the numerical ID of the pack format. */
|
||||||
[[nodiscard]] int packFormat() const { return m_pack_format; }
|
[[nodiscard]] int packFormat() const { return m_pack_format; }
|
||||||
/** Gets, respectively, the lower and upper versions supported by the set pack format. */
|
/** Gets, respectively, the lower and upper versions supported by the set pack format. */
|
||||||
[[nodiscard]] std::pair<Version, Version> compatibleVersions() const;
|
[[nodiscard]] virtual std::pair<Version, Version> compatibleVersions() const;
|
||||||
|
|
||||||
/** Gets the description of the data pack. */
|
/** Gets the description of the data pack. */
|
||||||
[[nodiscard]] QString description() const { return m_description; }
|
[[nodiscard]] QString description() const { return m_description; }
|
||||||
|
|
||||||
|
/** Gets the image of the data pack, converted to a QPixmap for drawing, and scaled to size. */
|
||||||
|
[[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
||||||
|
|
||||||
/** Thread-safe. */
|
/** Thread-safe. */
|
||||||
void setPackFormat(int new_format_id);
|
void setPackFormat(int new_format_id);
|
||||||
|
|
||||||
/** Thread-safe. */
|
/** Thread-safe. */
|
||||||
void setDescription(QString new_description);
|
void setDescription(QString new_description);
|
||||||
|
|
||||||
|
/** Thread-safe. */
|
||||||
|
void setImage(QImage new_image) const;
|
||||||
|
|
||||||
bool valid() const override;
|
bool valid() const override;
|
||||||
|
|
||||||
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
|
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
|
||||||
|
@ -70,4 +77,14 @@ class DataPack : public Resource {
|
||||||
/** The data pack's description, as defined in the pack.mcmeta file.
|
/** The data pack's description, as defined in the pack.mcmeta file.
|
||||||
*/
|
*/
|
||||||
QString m_description;
|
QString m_description;
|
||||||
|
|
||||||
|
/** The data pack's image file cache key, for access in the QPixmapCache global instance.
|
||||||
|
*
|
||||||
|
* The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true),
|
||||||
|
* so as to tell whether a cache entry is inexistent or if it was just evicted from the cache.
|
||||||
|
*/
|
||||||
|
struct {
|
||||||
|
QPixmapCache::Key key;
|
||||||
|
bool was_ever_used = false;
|
||||||
|
} mutable m_pack_image_cache_key;
|
||||||
};
|
};
|
||||||
|
|
188
launcher/minecraft/mod/DataPackFolderModel.cpp
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 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 "DataPackFolderModel.h"
|
||||||
|
#include <qnamespace.h>
|
||||||
|
#include <qsize.h>
|
||||||
|
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QStyle>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "Version.h"
|
||||||
|
|
||||||
|
#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
|
||||||
|
|
||||||
|
DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
|
||||||
|
: ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
|
||||||
|
{
|
||||||
|
m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" });
|
||||||
|
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") });
|
||||||
|
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
|
||||||
|
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
|
||||||
|
QHeaderView::Interactive };
|
||||||
|
m_columnsHideable = { false, true, false, true, true };
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const
|
||||||
|
{
|
||||||
|
if (!validateIndex(index))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
int row = index.row();
|
||||||
|
int column = index.column();
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
switch (column) {
|
||||||
|
case NameColumn:
|
||||||
|
return m_resources[row]->name();
|
||||||
|
case PackFormatColumn: {
|
||||||
|
auto& resource = at(row);
|
||||||
|
auto pack_format = resource.packFormat();
|
||||||
|
if (pack_format == 0)
|
||||||
|
return tr("Unrecognized");
|
||||||
|
|
||||||
|
auto version_bounds = resource.compatibleVersions();
|
||||||
|
if (version_bounds.first.toString().isEmpty())
|
||||||
|
return QString::number(pack_format);
|
||||||
|
|
||||||
|
return QString("%1 (%2 - %3)")
|
||||||
|
.arg(QString::number(pack_format), version_bounds.first.toString(), version_bounds.second.toString());
|
||||||
|
}
|
||||||
|
case DateColumn:
|
||||||
|
return m_resources[row]->dateTimeChanged();
|
||||||
|
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case Qt::DecorationRole: {
|
||||||
|
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
|
||||||
|
return APPLICATION->getThemedIcon("status-yellow");
|
||||||
|
if (column == ImageColumn) {
|
||||||
|
return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case Qt::ToolTipRole: {
|
||||||
|
if (column == PackFormatColumn) {
|
||||||
|
//: The string being explained by this is in the format: ID (Lower version - Upper version)
|
||||||
|
return tr("The data pack format ID, as well as the Minecraft versions it was designed for.");
|
||||||
|
}
|
||||||
|
if (column == NameColumn) {
|
||||||
|
if (at(row).isSymLinkUnder(instDirPath())) {
|
||||||
|
return m_resources[row]->internal_id() +
|
||||||
|
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
||||||
|
"\nCanonical Path: %1")
|
||||||
|
.arg(at(row).fileinfo().canonicalFilePath());
|
||||||
|
;
|
||||||
|
}
|
||||||
|
if (at(row).isMoreThanOneHardLink()) {
|
||||||
|
return m_resources[row]->internal_id() +
|
||||||
|
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_resources[row]->internal_id();
|
||||||
|
}
|
||||||
|
case Qt::SizeHintRole:
|
||||||
|
if (column == ImageColumn) {
|
||||||
|
return QSize(32, 32);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
case Qt::CheckStateRole:
|
||||||
|
if (column == ActiveColumn)
|
||||||
|
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
|
||||||
|
else
|
||||||
|
return {};
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
switch (section) {
|
||||||
|
case ActiveColumn:
|
||||||
|
case NameColumn:
|
||||||
|
case PackFormatColumn:
|
||||||
|
case DateColumn:
|
||||||
|
case ImageColumn:
|
||||||
|
return columnNames().at(section);
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
case Qt::ToolTipRole:
|
||||||
|
switch (section) {
|
||||||
|
case ActiveColumn:
|
||||||
|
return tr("Is the data pack enabled? (Only valid for ZIPs)");
|
||||||
|
case NameColumn:
|
||||||
|
return tr("The name of the data pack.");
|
||||||
|
case PackFormatColumn:
|
||||||
|
//: The string being explained by this is in the format: ID (Lower version - Upper version)
|
||||||
|
return tr("The data pack format ID, as well as the Minecraft versions it was designed for.");
|
||||||
|
case DateColumn:
|
||||||
|
return tr("The date and time this data pack was last changed (or added).");
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case Qt::SizeHintRole:
|
||||||
|
if (section == ImageColumn) {
|
||||||
|
return QSize(64, 0);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DataPackFolderModel::columnCount(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource* DataPackFolderModel::createResource(const QFileInfo& file)
|
||||||
|
{
|
||||||
|
return new DataPack(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task* DataPackFolderModel::createParseTask(Resource& resource)
|
||||||
|
{
|
||||||
|
return new LocalDataPackParseTask(m_next_resolution_ticket, static_cast<DataPack*>(&resource));
|
||||||
|
}
|
62
launcher/minecraft/mod/DataPackFolderModel.h
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 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 "ResourceFolderModel.h"
|
||||||
|
|
||||||
|
#include "DataPack.h"
|
||||||
|
#include "ResourcePack.h"
|
||||||
|
|
||||||
|
class DataPackFolderModel : public ResourceFolderModel {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS };
|
||||||
|
|
||||||
|
explicit DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
|
||||||
|
|
||||||
|
virtual QString id() const override { return "datapacks"; }
|
||||||
|
|
||||||
|
[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
|
[[nodiscard]] int columnCount(const QModelIndex& parent) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] Resource* createResource(const QFileInfo& file) override;
|
||||||
|
[[nodiscard]] Task* createParseTask(Resource&) override;
|
||||||
|
|
||||||
|
RESOURCE_HELPERS(DataPack)
|
||||||
|
};
|
|
@ -6,8 +6,6 @@
|
||||||
#include "MTPixmapCache.h"
|
#include "MTPixmapCache.h"
|
||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
|
|
||||||
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
|
||||||
|
|
||||||
// Values taken from:
|
// Values taken from:
|
||||||
// https://minecraft.wiki/w/Pack_format#List_of_resource_pack_formats
|
// https://minecraft.wiki/w/Pack_format#List_of_resource_pack_formats
|
||||||
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
|
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
|
||||||
|
@ -29,62 +27,6 @@ static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
|
||||||
{ 34, { Version("24w21a"), Version("1.21") } }
|
{ 34, { Version("24w21a"), Version("1.21") } }
|
||||||
};
|
};
|
||||||
|
|
||||||
void ResourcePack::setPackFormat(int new_format_id)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_data_lock);
|
|
||||||
|
|
||||||
if (!s_pack_format_versions.contains(new_format_id)) {
|
|
||||||
qWarning() << "Pack format '" << new_format_id << "' is not a recognized resource pack id!";
|
|
||||||
}
|
|
||||||
|
|
||||||
m_pack_format = new_format_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ResourcePack::setImage(QImage new_image) const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_data_lock);
|
|
||||||
|
|
||||||
Q_ASSERT(!new_image.isNull());
|
|
||||||
|
|
||||||
if (m_pack_image_cache_key.key.isValid())
|
|
||||||
PixmapCache::instance().remove(m_pack_image_cache_key.key);
|
|
||||||
|
|
||||||
// scale the image to avoid flooding the pixmapcache
|
|
||||||
auto pixmap =
|
|
||||||
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
|
|
||||||
|
|
||||||
m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap);
|
|
||||||
m_pack_image_cache_key.was_ever_used = true;
|
|
||||||
|
|
||||||
// This can happen if the pixmap is too big to fit in the cache :c
|
|
||||||
if (!m_pack_image_cache_key.key.isValid()) {
|
|
||||||
qWarning() << "Could not insert a image cache entry! Ignoring it.";
|
|
||||||
m_pack_image_cache_key.was_ever_used = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
|
|
||||||
{
|
|
||||||
QPixmap cached_image;
|
|
||||||
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
|
|
||||||
if (size.isNull())
|
|
||||||
return cached_image;
|
|
||||||
return cached_image.scaled(size, mode, Qt::SmoothTransformation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// No valid image we can get
|
|
||||||
if (!m_pack_image_cache_key.was_ever_used) {
|
|
||||||
return {};
|
|
||||||
} else {
|
|
||||||
qDebug() << "Resource Pack" << name() << "Had it's image evicted from the cache. reloading...";
|
|
||||||
PixmapCache::markCacheMissByEviciton();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Imaged got evicted from the cache. Re-process it and retry.
|
|
||||||
ResourcePackUtils::processPackPNG(this);
|
|
||||||
return image(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<Version, Version> ResourcePack::compatibleVersions() const
|
std::pair<Version, Version> ResourcePack::compatibleVersions() const
|
||||||
{
|
{
|
||||||
if (!s_pack_format_versions.contains(m_pack_format)) {
|
if (!s_pack_format_versions.contains(m_pack_format)) {
|
||||||
|
|
|
@ -22,27 +22,7 @@ class ResourcePack : public DataPack {
|
||||||
ResourcePack(QFileInfo file_info) : DataPack(file_info) {}
|
ResourcePack(QFileInfo file_info) : DataPack(file_info) {}
|
||||||
|
|
||||||
/** Gets, respectively, the lower and upper versions supported by the set pack format. */
|
/** Gets, respectively, the lower and upper versions supported by the set pack format. */
|
||||||
[[nodiscard]] std::pair<Version, Version> compatibleVersions() const;
|
[[nodiscard]] std::pair<Version, Version> compatibleVersions() const override;
|
||||||
|
|
||||||
/** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */
|
|
||||||
[[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
|
||||||
|
|
||||||
/** Thread-safe. */
|
|
||||||
void setImage(QImage new_image) const;
|
|
||||||
|
|
||||||
/** Thread-safe. */
|
|
||||||
void setPackFormat(int new_format_id);
|
|
||||||
|
|
||||||
virtual QString directory() { return "/assets"; }
|
virtual QString directory() { return "/assets"; }
|
||||||
|
|
||||||
protected:
|
|
||||||
/** The resource pack's image file cache key, for access in the QPixmapCache global instance.
|
|
||||||
*
|
|
||||||
* The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true),
|
|
||||||
* so as to tell whether a cache entry is inexistent or if it was just evicted from the cache.
|
|
||||||
*/
|
|
||||||
struct {
|
|
||||||
QPixmapCache::Key key;
|
|
||||||
bool was_ever_used = false;
|
|
||||||
} mutable m_pack_image_cache_key;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
#include "minecraft/mod/ResourcePack.h"
|
#include "minecraft/mod/ResourcePack.h"
|
||||||
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
|
||||||
|
|
||||||
#include <quazip/quazip.h>
|
#include <quazip/quazip.h>
|
||||||
#include <quazip/quazipdir.h>
|
#include <quazip/quazipdir.h>
|
||||||
|
@ -82,29 +81,27 @@ bool processFolder(DataPack* pack, ProcessingLevel level)
|
||||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||||
return true; // only need basic info already checked
|
return true; // only need basic info already checked
|
||||||
}
|
}
|
||||||
if (auto rp = dynamic_cast<ResourcePack*>(pack)) {
|
auto png_invalid = [&pack]() {
|
||||||
auto png_invalid = [&pack]() {
|
qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
||||||
qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
return true; // the png is optional
|
||||||
return true; // the png is optional
|
};
|
||||||
};
|
|
||||||
|
|
||||||
QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png"));
|
QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png"));
|
||||||
if (image_file_info.exists() && image_file_info.isFile()) {
|
if (image_file_info.exists() && image_file_info.isFile()) {
|
||||||
QFile pack_png_file(image_file_info.filePath());
|
QFile pack_png_file(image_file_info.filePath());
|
||||||
if (!pack_png_file.open(QIODevice::ReadOnly))
|
if (!pack_png_file.open(QIODevice::ReadOnly))
|
||||||
return png_invalid(); // can't open pack.png file
|
return png_invalid(); // can't open pack.png file
|
||||||
|
|
||||||
auto data = pack_png_file.readAll();
|
auto data = pack_png_file.readAll();
|
||||||
|
|
||||||
bool pack_png_result = ResourcePackUtils::processPackPNG(rp, std::move(data));
|
bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data));
|
||||||
|
|
||||||
pack_png_file.close();
|
pack_png_file.close();
|
||||||
if (!pack_png_result) {
|
if (!pack_png_result) {
|
||||||
return png_invalid(); // pack.png invalid
|
return png_invalid(); // pack.png invalid
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return png_invalid(); // pack.png does not exists or is not a valid file.
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return png_invalid(); // pack.png does not exists or is not a valid file.
|
||||||
}
|
}
|
||||||
|
|
||||||
return true; // all tests passed
|
return true; // all tests passed
|
||||||
|
@ -153,32 +150,31 @@ bool processZIP(DataPack* pack, ProcessingLevel level)
|
||||||
zip.close();
|
zip.close();
|
||||||
return true; // only need basic info already checked
|
return true; // only need basic info already checked
|
||||||
}
|
}
|
||||||
if (auto rp = dynamic_cast<ResourcePack*>(pack)) {
|
|
||||||
auto png_invalid = [&pack]() {
|
|
||||||
qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
|
||||||
return true; // the png is optional
|
|
||||||
};
|
|
||||||
|
|
||||||
if (zip.setCurrentFile("pack.png")) {
|
auto png_invalid = [&pack]() {
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
||||||
qCritical() << "Failed to open file in zip.";
|
return true; // the png is optional
|
||||||
zip.close();
|
};
|
||||||
return png_invalid();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto data = file.readAll();
|
if (zip.setCurrentFile("pack.png")) {
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
bool pack_png_result = ResourcePackUtils::processPackPNG(rp, std::move(data));
|
qCritical() << "Failed to open file in zip.";
|
||||||
|
|
||||||
file.close();
|
|
||||||
zip.close();
|
zip.close();
|
||||||
if (!pack_png_result) {
|
return png_invalid();
|
||||||
return png_invalid(); // pack.png invalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
zip.close();
|
|
||||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto data = file.readAll();
|
||||||
|
|
||||||
|
bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data));
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
zip.close();
|
||||||
|
if (!pack_png_result) {
|
||||||
|
return png_invalid(); // pack.png invalid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
zip.close();
|
||||||
|
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||||
}
|
}
|
||||||
zip.close();
|
zip.close();
|
||||||
|
|
||||||
|
@ -195,7 +191,7 @@ bool processMCMeta(DataPack* pack, QByteArray&& raw_data)
|
||||||
auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
|
auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
|
||||||
|
|
||||||
pack->setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
|
pack->setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
|
||||||
pack->setDescription(ResourcePackUtils::processComponent(pack_obj.value("description")));
|
pack->setDescription(DataPackUtils::processComponent(pack_obj.value("description")));
|
||||||
} catch (Json::JsonException& e) {
|
} catch (Json::JsonException& e) {
|
||||||
qWarning() << "JsonException: " << e.what() << e.cause();
|
qWarning() << "JsonException: " << e.what() << e.cause();
|
||||||
return false;
|
return false;
|
||||||
|
@ -203,12 +199,171 @@ bool processMCMeta(DataPack* pack, QByteArray&& raw_data)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString buildStyle(const QJsonObject& obj)
|
||||||
|
{
|
||||||
|
QStringList styles;
|
||||||
|
if (auto color = Json::ensureString(obj, "color"); !color.isEmpty()) {
|
||||||
|
styles << QString("color: %1;").arg(color);
|
||||||
|
}
|
||||||
|
if (obj.contains("bold")) {
|
||||||
|
QString weight = "normal";
|
||||||
|
if (Json::ensureBoolean(obj, "bold", false)) {
|
||||||
|
weight = "bold";
|
||||||
|
}
|
||||||
|
styles << QString("font-weight: %1;").arg(weight);
|
||||||
|
}
|
||||||
|
if (obj.contains("italic")) {
|
||||||
|
QString style = "normal";
|
||||||
|
if (Json::ensureBoolean(obj, "italic", false)) {
|
||||||
|
style = "italic";
|
||||||
|
}
|
||||||
|
styles << QString("font-style: %1;").arg(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
return styles.isEmpty() ? "" : QString("style=\"%1\"").arg(styles.join(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString processComponent(const QJsonArray& value, bool strikethrough, bool underline)
|
||||||
|
{
|
||||||
|
QString result;
|
||||||
|
for (auto current : value)
|
||||||
|
result += processComponent(current, strikethrough, underline);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString processComponent(const QJsonObject& obj, bool strikethrough, bool underline)
|
||||||
|
{
|
||||||
|
underline = Json::ensureBoolean(obj, "underlined", underline);
|
||||||
|
strikethrough = Json::ensureBoolean(obj, "strikethrough", strikethrough);
|
||||||
|
|
||||||
|
QString result = Json::ensureString(obj, "text");
|
||||||
|
if (underline) {
|
||||||
|
result = QString("<u>%1</u>").arg(result);
|
||||||
|
}
|
||||||
|
if (strikethrough) {
|
||||||
|
result = QString("<s>%1</s>").arg(result);
|
||||||
|
}
|
||||||
|
// the extra needs to be a array
|
||||||
|
result += processComponent(Json::ensureArray(obj, "extra"), strikethrough, underline);
|
||||||
|
if (auto style = buildStyle(obj); !style.isEmpty()) {
|
||||||
|
result = QString("<span %1>%2</span>").arg(style, result);
|
||||||
|
}
|
||||||
|
if (obj.contains("clickEvent")) {
|
||||||
|
auto click_event = Json::ensureObject(obj, "clickEvent");
|
||||||
|
auto action = Json::ensureString(click_event, "action");
|
||||||
|
auto value = Json::ensureString(click_event, "value");
|
||||||
|
if (action == "open_url" && !value.isEmpty()) {
|
||||||
|
result = QString("<a href=\"%1\">%2</a>").arg(value, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString processComponent(const QJsonValue& value, bool strikethrough, bool underline)
|
||||||
|
{
|
||||||
|
if (value.isString()) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
if (value.isBool()) {
|
||||||
|
return value.toBool() ? "true" : "false";
|
||||||
|
}
|
||||||
|
if (value.isDouble()) {
|
||||||
|
return QString::number(value.toDouble());
|
||||||
|
}
|
||||||
|
if (value.isArray()) {
|
||||||
|
return processComponent(value.toArray(), strikethrough, underline);
|
||||||
|
}
|
||||||
|
if (value.isObject()) {
|
||||||
|
return processComponent(value.toObject(), strikethrough, underline);
|
||||||
|
}
|
||||||
|
qWarning() << "Invalid component type!";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool processPackPNG(const DataPack* pack, QByteArray&& raw_data)
|
||||||
|
{
|
||||||
|
auto img = QImage::fromData(raw_data);
|
||||||
|
if (!img.isNull()) {
|
||||||
|
pack->setImage(img);
|
||||||
|
} else {
|
||||||
|
qWarning() << "Failed to parse pack.png.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool processPackPNG(const DataPack* pack)
|
||||||
|
{
|
||||||
|
auto png_invalid = [&pack]() {
|
||||||
|
qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (pack->type()) {
|
||||||
|
case ResourceType::FOLDER: {
|
||||||
|
QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png"));
|
||||||
|
if (image_file_info.exists() && image_file_info.isFile()) {
|
||||||
|
QFile pack_png_file(image_file_info.filePath());
|
||||||
|
if (!pack_png_file.open(QIODevice::ReadOnly))
|
||||||
|
return png_invalid(); // can't open pack.png file
|
||||||
|
|
||||||
|
auto data = pack_png_file.readAll();
|
||||||
|
|
||||||
|
bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data));
|
||||||
|
|
||||||
|
pack_png_file.close();
|
||||||
|
if (!pack_png_result) {
|
||||||
|
return png_invalid(); // pack.png invalid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return png_invalid(); // pack.png does not exists or is not a valid file.
|
||||||
|
}
|
||||||
|
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
|
||||||
|
}
|
||||||
|
case ResourceType::ZIPFILE: {
|
||||||
|
QuaZip zip(pack->fileinfo().filePath());
|
||||||
|
if (!zip.open(QuaZip::mdUnzip))
|
||||||
|
return false; // can't open zip file
|
||||||
|
|
||||||
|
QuaZipFile file(&zip);
|
||||||
|
if (zip.setCurrentFile("pack.png")) {
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
qCritical() << "Failed to open file in zip.";
|
||||||
|
zip.close();
|
||||||
|
return png_invalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = file.readAll();
|
||||||
|
|
||||||
|
bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data));
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
if (!pack_png_result) {
|
||||||
|
return png_invalid(); // pack.png invalid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||||
|
}
|
||||||
|
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
qWarning() << "Invalid type for data pack parse task!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool validate(QFileInfo file)
|
bool validate(QFileInfo file)
|
||||||
{
|
{
|
||||||
DataPack dp{ file };
|
DataPack dp{ file };
|
||||||
return DataPackUtils::process(&dp, ProcessingLevel::BasicInfoOnly) && dp.valid();
|
return DataPackUtils::process(&dp, ProcessingLevel::BasicInfoOnly) && dp.valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool validateResourcePack(QFileInfo file)
|
||||||
|
{
|
||||||
|
ResourcePack rp{ file };
|
||||||
|
return DataPackUtils::process(&rp, ProcessingLevel::BasicInfoOnly) && rp.valid();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace DataPackUtils
|
} // namespace DataPackUtils
|
||||||
|
|
||||||
LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack* dp) : Task(false), m_token(token), m_data_pack(dp) {}
|
LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack* dp) : Task(false), m_token(token), m_data_pack(dp) {}
|
||||||
|
|
|
@ -39,9 +39,19 @@ bool processFolder(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full
|
||||||
|
|
||||||
bool processMCMeta(DataPack* pack, QByteArray&& raw_data);
|
bool processMCMeta(DataPack* pack, QByteArray&& raw_data);
|
||||||
|
|
||||||
|
QString processComponent(const QJsonValue& value, bool strikethrough = false, bool underline = false);
|
||||||
|
|
||||||
|
bool processPackPNG(const DataPack* pack, QByteArray&& raw_data);
|
||||||
|
|
||||||
|
/// processes ONLY the pack.png (rest of the pack may be invalid)
|
||||||
|
bool processPackPNG(const DataPack* pack);
|
||||||
|
|
||||||
/** Checks whether a file is valid as a data pack or not. */
|
/** Checks whether a file is valid as a data pack or not. */
|
||||||
bool validate(QFileInfo file);
|
bool validate(QFileInfo file);
|
||||||
|
|
||||||
|
/** Checks whether a file is valid as a resource pack or not. */
|
||||||
|
bool validateResourcePack(QFileInfo file);
|
||||||
|
|
||||||
} // namespace DataPackUtils
|
} // namespace DataPackUtils
|
||||||
|
|
||||||
class LocalDataPackParseTask : public Task {
|
class LocalDataPackParseTask : public Task {
|
||||||
|
|
|
@ -1,192 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "LocalResourcePackParseTask.h"
|
|
||||||
|
|
||||||
#include "FileSystem.h"
|
|
||||||
#include "Json.h"
|
|
||||||
#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
|
|
||||||
|
|
||||||
#include <quazip/quazip.h>
|
|
||||||
#include <quazip/quazipdir.h>
|
|
||||||
#include <quazip/quazipfile.h>
|
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
|
||||||
|
|
||||||
namespace ResourcePackUtils {
|
|
||||||
|
|
||||||
QString buildStyle(const QJsonObject& obj)
|
|
||||||
{
|
|
||||||
QStringList styles;
|
|
||||||
if (auto color = Json::ensureString(obj, "color"); !color.isEmpty()) {
|
|
||||||
styles << QString("color: %1;").arg(color);
|
|
||||||
}
|
|
||||||
if (obj.contains("bold")) {
|
|
||||||
QString weight = "normal";
|
|
||||||
if (Json::ensureBoolean(obj, "bold", false)) {
|
|
||||||
weight = "bold";
|
|
||||||
}
|
|
||||||
styles << QString("font-weight: %1;").arg(weight);
|
|
||||||
}
|
|
||||||
if (obj.contains("italic")) {
|
|
||||||
QString style = "normal";
|
|
||||||
if (Json::ensureBoolean(obj, "italic", false)) {
|
|
||||||
style = "italic";
|
|
||||||
}
|
|
||||||
styles << QString("font-style: %1;").arg(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
return styles.isEmpty() ? "" : QString("style=\"%1\"").arg(styles.join(" "));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString processComponent(const QJsonArray& value, bool strikethrough, bool underline)
|
|
||||||
{
|
|
||||||
QString result;
|
|
||||||
for (auto current : value)
|
|
||||||
result += processComponent(current, strikethrough, underline);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString processComponent(const QJsonObject& obj, bool strikethrough, bool underline)
|
|
||||||
{
|
|
||||||
underline = Json::ensureBoolean(obj, "underlined", underline);
|
|
||||||
strikethrough = Json::ensureBoolean(obj, "strikethrough", strikethrough);
|
|
||||||
|
|
||||||
QString result = Json::ensureString(obj, "text");
|
|
||||||
if (underline) {
|
|
||||||
result = QString("<u>%1</u>").arg(result);
|
|
||||||
}
|
|
||||||
if (strikethrough) {
|
|
||||||
result = QString("<s>%1</s>").arg(result);
|
|
||||||
}
|
|
||||||
// the extra needs to be a array
|
|
||||||
result += processComponent(Json::ensureArray(obj, "extra"), strikethrough, underline);
|
|
||||||
if (auto style = buildStyle(obj); !style.isEmpty()) {
|
|
||||||
result = QString("<span %1>%2</span>").arg(style, result);
|
|
||||||
}
|
|
||||||
if (obj.contains("clickEvent")) {
|
|
||||||
auto click_event = Json::ensureObject(obj, "clickEvent");
|
|
||||||
auto action = Json::ensureString(click_event, "action");
|
|
||||||
auto value = Json::ensureString(click_event, "value");
|
|
||||||
if (action == "open_url" && !value.isEmpty()) {
|
|
||||||
result = QString("<a href=\"%1\">%2</a>").arg(value, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString processComponent(const QJsonValue& value, bool strikethrough, bool underline)
|
|
||||||
{
|
|
||||||
if (value.isString()) {
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
if (value.isBool()) {
|
|
||||||
return value.toBool() ? "true" : "false";
|
|
||||||
}
|
|
||||||
if (value.isDouble()) {
|
|
||||||
return QString::number(value.toDouble());
|
|
||||||
}
|
|
||||||
if (value.isArray()) {
|
|
||||||
return processComponent(value.toArray(), strikethrough, underline);
|
|
||||||
}
|
|
||||||
if (value.isObject()) {
|
|
||||||
return processComponent(value.toObject(), strikethrough, underline);
|
|
||||||
}
|
|
||||||
qWarning() << "Invalid component type!";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool processPackPNG(const ResourcePack* pack, QByteArray&& raw_data)
|
|
||||||
{
|
|
||||||
auto img = QImage::fromData(raw_data);
|
|
||||||
if (!img.isNull()) {
|
|
||||||
pack->setImage(img);
|
|
||||||
} else {
|
|
||||||
qWarning() << "Failed to parse pack.png.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool processPackPNG(const ResourcePack* pack)
|
|
||||||
{
|
|
||||||
auto png_invalid = [&pack]() {
|
|
||||||
qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (pack->type()) {
|
|
||||||
case ResourceType::FOLDER: {
|
|
||||||
QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png"));
|
|
||||||
if (image_file_info.exists() && image_file_info.isFile()) {
|
|
||||||
QFile pack_png_file(image_file_info.filePath());
|
|
||||||
if (!pack_png_file.open(QIODevice::ReadOnly))
|
|
||||||
return png_invalid(); // can't open pack.png file
|
|
||||||
|
|
||||||
auto data = pack_png_file.readAll();
|
|
||||||
|
|
||||||
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
|
|
||||||
|
|
||||||
pack_png_file.close();
|
|
||||||
if (!pack_png_result) {
|
|
||||||
return png_invalid(); // pack.png invalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return png_invalid(); // pack.png does not exists or is not a valid file.
|
|
||||||
}
|
|
||||||
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
|
|
||||||
}
|
|
||||||
case ResourceType::ZIPFILE: {
|
|
||||||
QuaZip zip(pack->fileinfo().filePath());
|
|
||||||
if (!zip.open(QuaZip::mdUnzip))
|
|
||||||
return false; // can't open zip file
|
|
||||||
|
|
||||||
QuaZipFile file(&zip);
|
|
||||||
if (zip.setCurrentFile("pack.png")) {
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
qCritical() << "Failed to open file in zip.";
|
|
||||||
zip.close();
|
|
||||||
return png_invalid();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto data = file.readAll();
|
|
||||||
|
|
||||||
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
if (!pack_png_result) {
|
|
||||||
return png_invalid(); // pack.png invalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
|
||||||
}
|
|
||||||
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
qWarning() << "Invalid type for resource pack parse task!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool validate(QFileInfo file)
|
|
||||||
{
|
|
||||||
ResourcePack rp{ file };
|
|
||||||
return DataPackUtils::process(&rp, DataPackUtils::ProcessingLevel::BasicInfoOnly) && rp.valid();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ResourcePackUtils
|
|
|
@ -1,36 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "minecraft/mod/ResourcePack.h"
|
|
||||||
|
|
||||||
namespace ResourcePackUtils {
|
|
||||||
|
|
||||||
QString processComponent(const QJsonValue& value, bool strikethrough = false, bool underline = false);
|
|
||||||
bool processPackPNG(const ResourcePack* pack, QByteArray&& raw_data);
|
|
||||||
|
|
||||||
/// processes ONLY the pack.png (rest of the pack may be invalid)
|
|
||||||
bool processPackPNG(const ResourcePack* pack);
|
|
||||||
|
|
||||||
/** Checks whether a file is valid as a resource pack or not. */
|
|
||||||
bool validate(QFileInfo file);
|
|
||||||
} // namespace ResourcePackUtils
|
|
|
@ -25,7 +25,6 @@
|
||||||
|
|
||||||
#include "LocalDataPackParseTask.h"
|
#include "LocalDataPackParseTask.h"
|
||||||
#include "LocalModParseTask.h"
|
#include "LocalModParseTask.h"
|
||||||
#include "LocalResourcePackParseTask.h"
|
|
||||||
#include "LocalShaderPackParseTask.h"
|
#include "LocalShaderPackParseTask.h"
|
||||||
#include "LocalTexturePackParseTask.h"
|
#include "LocalTexturePackParseTask.h"
|
||||||
#include "LocalWorldSaveParseTask.h"
|
#include "LocalWorldSaveParseTask.h"
|
||||||
|
@ -46,7 +45,7 @@ PackedResourceType identify(QFileInfo file)
|
||||||
// mods can contain resource and data packs so they must be tested first
|
// mods can contain resource and data packs so they must be tested first
|
||||||
qDebug() << file.fileName() << "is a mod";
|
qDebug() << file.fileName() << "is a mod";
|
||||||
return PackedResourceType::Mod;
|
return PackedResourceType::Mod;
|
||||||
} else if (ResourcePackUtils::validate(file)) {
|
} else if (DataPackUtils::validateResourcePack(file)) {
|
||||||
qDebug() << file.fileName() << "is a resource pack";
|
qDebug() << file.fileName() << "is a resource pack";
|
||||||
return PackedResourceType::ResourcePack;
|
return PackedResourceType::ResourcePack;
|
||||||
} else if (TexturePackUtils::validate(file)) {
|
} else if (TexturePackUtils::validate(file)) {
|
||||||
|
|
|
@ -122,11 +122,13 @@ auto getModLoaderAsString(ModLoaderType type) -> const QString
|
||||||
case Cauldron:
|
case Cauldron:
|
||||||
return "cauldron";
|
return "cauldron";
|
||||||
case LiteLoader:
|
case LiteLoader:
|
||||||
return "liteloader";
|
return "liteloader";
|
||||||
case Fabric:
|
case Fabric:
|
||||||
return "fabric";
|
return "fabric";
|
||||||
case Quilt:
|
case Quilt:
|
||||||
return "quilt";
|
return "quilt";
|
||||||
|
case DataPack:
|
||||||
|
return "datapack";
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,13 +29,21 @@ class QIODevice;
|
||||||
|
|
||||||
namespace ModPlatform {
|
namespace ModPlatform {
|
||||||
|
|
||||||
enum ModLoaderType { NeoForge = 1 << 0, Forge = 1 << 1, Cauldron = 1 << 2, LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5 };
|
enum ModLoaderType {
|
||||||
|
NeoForge = 1 << 0,
|
||||||
|
Forge = 1 << 1,
|
||||||
|
Cauldron = 1 << 2,
|
||||||
|
LiteLoader = 1 << 3,
|
||||||
|
Fabric = 1 << 4,
|
||||||
|
Quilt = 1 << 5,
|
||||||
|
DataPack = 1 << 6
|
||||||
|
};
|
||||||
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
|
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
|
||||||
QList<ModLoaderType> modLoaderTypesToList(ModLoaderTypes flags);
|
QList<ModLoaderType> modLoaderTypesToList(ModLoaderTypes flags);
|
||||||
|
|
||||||
enum class ResourceProvider { MODRINTH, FLAME };
|
enum class ResourceProvider { MODRINTH, FLAME };
|
||||||
|
|
||||||
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK, MODPACK };
|
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK, MODPACK, DATA_PACK };
|
||||||
|
|
||||||
enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN };
|
enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN };
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,8 @@ class FlameAPI : public NetworkResourceAPI {
|
||||||
return 6552;
|
return 6552;
|
||||||
case ModPlatform::ResourceType::MODPACK:
|
case ModPlatform::ResourceType::MODPACK:
|
||||||
return 4471;
|
return 4471;
|
||||||
|
case ModPlatform::ResourceType::DATA_PACK:
|
||||||
|
return 6945;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +69,8 @@ class FlameAPI : public NetworkResourceAPI {
|
||||||
return 5;
|
return 5;
|
||||||
case ModPlatform::NeoForge:
|
case ModPlatform::NeoForge:
|
||||||
return 6;
|
return 6;
|
||||||
|
case ModPlatform::DataPack:
|
||||||
|
break; // not supported
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -96,8 +100,12 @@ class FlameAPI : public NetworkResourceAPI {
|
||||||
if (args.sorting.has_value())
|
if (args.sorting.has_value())
|
||||||
get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
|
get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
|
||||||
get_arguments.append("sortOrder=desc");
|
get_arguments.append("sortOrder=desc");
|
||||||
if (args.loaders.has_value() && args.loaders.value() != 0)
|
if (args.loaders.has_value()) {
|
||||||
get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(args.loaders.value())));
|
ModPlatform::ModLoaderTypes loaders = args.loaders.value();
|
||||||
|
loaders &= ~ModPlatform::ModLoaderType::DataPack;
|
||||||
|
if (loaders != 0)
|
||||||
|
get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(loaders)));
|
||||||
|
}
|
||||||
if (args.categoryIds.has_value() && !args.categoryIds->empty())
|
if (args.categoryIds.has_value() && !args.categoryIds->empty())
|
||||||
get_arguments.append(QString("categoryIds=[%1]").arg(args.categoryIds->join(",")));
|
get_arguments.append(QString("categoryIds=[%1]").arg(args.categoryIds->join(",")));
|
||||||
|
|
||||||
|
@ -115,7 +123,7 @@ class FlameAPI : public NetworkResourceAPI {
|
||||||
if (args.mcVersions.has_value())
|
if (args.mcVersions.has_value())
|
||||||
url += QString("&gameVersion=%1").arg(args.mcVersions.value().front().toString());
|
url += QString("&gameVersion=%1").arg(args.mcVersions.value().front().toString());
|
||||||
|
|
||||||
if (args.loaders.has_value() && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) {
|
if (args.loaders.has_value() && args.loaders.value() != ModPlatform::ModLoaderType::DataPack && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) {
|
||||||
int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loaders.value())));
|
int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loaders.value())));
|
||||||
url += QString("&modLoaderType=%1").arg(mappedModLoader);
|
url += QString("&modLoaderType=%1").arg(mappedModLoader);
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,8 @@ void PackInstallTask::copySettings()
|
||||||
break;
|
break;
|
||||||
case ModPlatform::LiteLoader:
|
case ModPlatform::LiteLoader:
|
||||||
break;
|
break;
|
||||||
|
case ModPlatform::DataPack:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
components->saveNow();
|
components->saveNow();
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,8 @@ class ModrinthAPI : public NetworkResourceAPI {
|
||||||
static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList
|
static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList
|
||||||
{
|
{
|
||||||
QStringList l;
|
QStringList l;
|
||||||
for (auto loader :
|
for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt, ModPlatform::LiteLoader,
|
||||||
{ ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt, ModPlatform::LiteLoader }) {
|
ModPlatform::DataPack }) {
|
||||||
if (types & loader) {
|
if (types & loader) {
|
||||||
l << getModLoaderAsString(loader);
|
l << getModLoaderAsString(loader);
|
||||||
}
|
}
|
||||||
|
@ -108,6 +108,8 @@ class ModrinthAPI : public NetworkResourceAPI {
|
||||||
return "resourcepack";
|
return "resourcepack";
|
||||||
case ModPlatform::ResourceType::SHADER_PACK:
|
case ModPlatform::ResourceType::SHADER_PACK:
|
||||||
return "shader";
|
return "shader";
|
||||||
|
case ModPlatform::ResourceType::DATA_PACK:
|
||||||
|
return "datapack";
|
||||||
case ModPlatform::ResourceType::MODPACK:
|
case ModPlatform::ResourceType::MODPACK:
|
||||||
return "modpack";
|
return "modpack";
|
||||||
default:
|
default:
|
||||||
|
@ -197,7 +199,8 @@ class ModrinthAPI : public NetworkResourceAPI {
|
||||||
|
|
||||||
static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool
|
static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool
|
||||||
{
|
{
|
||||||
return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt | ModPlatform::LiteLoader);
|
return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt | ModPlatform::LiteLoader |
|
||||||
|
ModPlatform::DataPack);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
|
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<file>scalable/copy.svg</file>
|
<file>scalable/copy.svg</file>
|
||||||
<file>scalable/coremods.svg</file>
|
<file>scalable/coremods.svg</file>
|
||||||
<file>scalable/custom-commands.svg</file>
|
<file>scalable/custom-commands.svg</file>
|
||||||
<file>scalable/environment-variables.svg</file>
|
<file>scalable/datapacks.svg</file>
|
||||||
<file>scalable/discord.svg</file>
|
<file>scalable/discord.svg</file>
|
||||||
<file>scalable/externaltools.svg</file>
|
<file>scalable/externaltools.svg</file>
|
||||||
<file>scalable/help.svg</file>
|
<file>scalable/help.svg</file>
|
||||||
|
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -9,7 +9,7 @@
|
||||||
<file>scalable/copy.svg</file>
|
<file>scalable/copy.svg</file>
|
||||||
<file>scalable/coremods.svg</file>
|
<file>scalable/coremods.svg</file>
|
||||||
<file>scalable/custom-commands.svg</file>
|
<file>scalable/custom-commands.svg</file>
|
||||||
<file>scalable/environment-variables.svg</file>
|
<file>scalable/datapacks.svg</file>
|
||||||
<file>scalable/discord.svg</file>
|
<file>scalable/discord.svg</file>
|
||||||
<file>scalable/externaltools.svg</file>
|
<file>scalable/externaltools.svg</file>
|
||||||
<file>scalable/help.svg</file>
|
<file>scalable/help.svg</file>
|
||||||
|
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -11,7 +11,7 @@
|
||||||
<file>scalable/copy.svg</file>
|
<file>scalable/copy.svg</file>
|
||||||
<file>scalable/coremods.svg</file>
|
<file>scalable/coremods.svg</file>
|
||||||
<file>scalable/custom-commands.svg</file>
|
<file>scalable/custom-commands.svg</file>
|
||||||
<file>scalable/environment-variables.svg</file>
|
<file>scalable/datapacks.svg</file>
|
||||||
<file>scalable/discord.svg</file>
|
<file>scalable/discord.svg</file>
|
||||||
<file>scalable/externaltools.svg</file>
|
<file>scalable/externaltools.svg</file>
|
||||||
<file>scalable/help.svg</file>
|
<file>scalable/help.svg</file>
|
||||||
|
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
@ -11,7 +11,7 @@
|
||||||
<file>scalable/copy.svg</file>
|
<file>scalable/copy.svg</file>
|
||||||
<file>scalable/coremods.svg</file>
|
<file>scalable/coremods.svg</file>
|
||||||
<file>scalable/custom-commands.svg</file>
|
<file>scalable/custom-commands.svg</file>
|
||||||
<file>scalable/environment-variables.svg</file>
|
<file>scalable/datapacks.svg</file>
|
||||||
<file>scalable/discord.svg</file>
|
<file>scalable/discord.svg</file>
|
||||||
<file>scalable/externaltools.svg</file>
|
<file>scalable/externaltools.svg</file>
|
||||||
<file>scalable/help.svg</file>
|
<file>scalable/help.svg</file>
|
||||||
|
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
@ -74,7 +74,7 @@
|
||||||
<file>scalable/screenshots.svg</file>
|
<file>scalable/screenshots.svg</file>
|
||||||
|
|
||||||
<file>scalable/custom-commands.svg</file>
|
<file>scalable/custom-commands.svg</file>
|
||||||
<file>scalable/environment-variables.svg</file>
|
<file>scalable/datapacks.svg</file>
|
||||||
|
|
||||||
<!-- The cat button. Freeware, http://findicons.com/icon/73096/black_cat -->
|
<!-- The cat button. Freeware, http://findicons.com/icon/73096/black_cat -->
|
||||||
<file>16x16/cat.png</file>
|
<file>16x16/cat.png</file>
|
||||||
|
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
@ -10,7 +10,7 @@
|
||||||
<file>scalable/copy.svg</file>
|
<file>scalable/copy.svg</file>
|
||||||
<file>scalable/coremods.svg</file>
|
<file>scalable/coremods.svg</file>
|
||||||
<file>scalable/custom-commands.svg</file>
|
<file>scalable/custom-commands.svg</file>
|
||||||
<file>scalable/environment-variables.svg</file>
|
<file>scalable/datapacks.svg</file>
|
||||||
<file>scalable/externaltools.svg</file>
|
<file>scalable/externaltools.svg</file>
|
||||||
<file>scalable/help.svg</file>
|
<file>scalable/help.svg</file>
|
||||||
<file>scalable/instance-settings.svg</file>
|
<file>scalable/instance-settings.svg</file>
|
||||||
|
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
@ -10,7 +10,7 @@
|
||||||
<file>scalable/copy.svg</file>
|
<file>scalable/copy.svg</file>
|
||||||
<file>scalable/coremods.svg</file>
|
<file>scalable/coremods.svg</file>
|
||||||
<file>scalable/custom-commands.svg</file>
|
<file>scalable/custom-commands.svg</file>
|
||||||
<file>scalable/environment-variables.svg</file>
|
<file>scalable/datapacks.svg</file>
|
||||||
<file>scalable/externaltools.svg</file>
|
<file>scalable/externaltools.svg</file>
|
||||||
<file>scalable/help.svg</file>
|
<file>scalable/help.svg</file>
|
||||||
<file>scalable/instance-settings.svg</file>
|
<file>scalable/instance-settings.svg</file>
|
||||||
|
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
@ -10,7 +10,7 @@
|
||||||
<file>scalable/copy.svg</file>
|
<file>scalable/copy.svg</file>
|
||||||
<file>scalable/coremods.svg</file>
|
<file>scalable/coremods.svg</file>
|
||||||
<file>scalable/custom-commands.svg</file>
|
<file>scalable/custom-commands.svg</file>
|
||||||
<file>scalable/environment-variables.svg</file>
|
<file>scalable/datapacks.svg</file>
|
||||||
<file>scalable/externaltools.svg</file>
|
<file>scalable/externaltools.svg</file>
|
||||||
<file>scalable/help.svg</file>
|
<file>scalable/help.svg</file>
|
||||||
<file>scalable/instance-settings.svg</file>
|
<file>scalable/instance-settings.svg</file>
|
||||||
|
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
@ -10,7 +10,7 @@
|
||||||
<file>scalable/copy.svg</file>
|
<file>scalable/copy.svg</file>
|
||||||
<file>scalable/coremods.svg</file>
|
<file>scalable/coremods.svg</file>
|
||||||
<file>scalable/custom-commands.svg</file>
|
<file>scalable/custom-commands.svg</file>
|
||||||
<file>scalable/environment-variables.svg</file>
|
<file>scalable/datapacks.svg</file>
|
||||||
<file>scalable/externaltools.svg</file>
|
<file>scalable/externaltools.svg</file>
|
||||||
<file>scalable/help.svg</file>
|
<file>scalable/help.svg</file>
|
||||||
<file>scalable/instance-settings.svg</file>
|
<file>scalable/instance-settings.svg</file>
|
||||||
|
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
@ -57,7 +57,7 @@ ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::share
|
||||||
{
|
{
|
||||||
setObjectName(QStringLiteral("ResourceDownloadDialog"));
|
setObjectName(QStringLiteral("ResourceDownloadDialog"));
|
||||||
|
|
||||||
resize(std::max(0.5 * parent->width(), 400.0), std::max(0.75 * parent->height(), 400.0));
|
resize(static_cast<int>(std::max(0.5 * parent->width(), 400.0)), static_cast<int>(std::max(0.75 * parent->height(), 400.0)));
|
||||||
|
|
||||||
setWindowIcon(APPLICATION->getThemedIcon("new"));
|
setWindowIcon(APPLICATION->getThemedIcon("new"));
|
||||||
|
|
||||||
|
@ -396,4 +396,28 @@ void ResourceDownloadDialog::setResourceMetadata(const std::shared_ptr<Metadata:
|
||||||
auto page = selectedPage();
|
auto page = selectedPage();
|
||||||
page->openProject(meta->project_id);
|
page->openProject(meta->project_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DataPackDownloadDialog::DataPackDownloadDialog(QWidget* parent,
|
||||||
|
const std::shared_ptr<DataPackFolderModel>& data_packs,
|
||||||
|
BaseInstance* instance)
|
||||||
|
: ResourceDownloadDialog(parent, data_packs), m_instance(instance)
|
||||||
|
{
|
||||||
|
setWindowTitle(dialogTitle());
|
||||||
|
|
||||||
|
initializeContainer();
|
||||||
|
connectButtons();
|
||||||
|
|
||||||
|
if (!geometrySaveKey().isEmpty())
|
||||||
|
restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toByteArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<BasePage*> DataPackDownloadDialog::getPages()
|
||||||
|
{
|
||||||
|
QList<BasePage*> pages;
|
||||||
|
pages.append(ModrinthDataPackPage::create(this, *m_instance));
|
||||||
|
if (APPLICATION->capabilities() & Application::SupportsFlame)
|
||||||
|
pages.append(FlameDataPackPage::create(this, *m_instance));
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ResourceDownload
|
} // namespace ResourceDownload
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include <QLayout>
|
#include <QLayout>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
|
#include "minecraft/mod/DataPackFolderModel.h"
|
||||||
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
|
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
|
||||||
#include "modplatform/ModIndex.h"
|
#include "modplatform/ModIndex.h"
|
||||||
#include "ui/pages/BasePageProvider.h"
|
#include "ui/pages/BasePageProvider.h"
|
||||||
|
@ -168,4 +169,21 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog {
|
||||||
BaseInstance* m_instance;
|
BaseInstance* m_instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DataPackDownloadDialog final : public ResourceDownloadDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DataPackDownloadDialog(QWidget* parent, const std::shared_ptr<DataPackFolderModel>& data_packs, BaseInstance* instance);
|
||||||
|
~DataPackDownloadDialog() override = default;
|
||||||
|
|
||||||
|
//: String that gets appended to the data pack download dialog title ("Download " + resourcesString())
|
||||||
|
[[nodiscard]] QString resourcesString() const override { return tr("data packs"); }
|
||||||
|
[[nodiscard]] QString geometrySaveKey() const override { return "DataPackDownloadGeometry"; }
|
||||||
|
|
||||||
|
QList<BasePage*> getPages() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BaseInstance* m_instance;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace ResourceDownload
|
} // namespace ResourceDownload
|
||||||
|
|
|
@ -32,17 +32,12 @@ static std::list<Version> mcVersions(BaseInstance* inst)
|
||||||
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() };
|
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() };
|
||||||
}
|
}
|
||||||
|
|
||||||
static QList<ModPlatform::ModLoaderType> mcLoadersList(BaseInstance* inst)
|
|
||||||
{
|
|
||||||
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoadersList();
|
|
||||||
}
|
|
||||||
|
|
||||||
ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent,
|
ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent,
|
||||||
BaseInstance* instance,
|
BaseInstance* instance,
|
||||||
const std::shared_ptr<ResourceFolderModel> resource_model,
|
const std::shared_ptr<ResourceFolderModel> resource_model,
|
||||||
QList<Resource*>& search_for,
|
QList<Resource*>& search_for,
|
||||||
bool include_deps,
|
bool include_deps,
|
||||||
bool filter_loaders)
|
QList<ModPlatform::ModLoaderType> loadersList)
|
||||||
: ReviewMessageBox(parent, tr("Confirm resources to update"), "")
|
: ReviewMessageBox(parent, tr("Confirm resources to update"), "")
|
||||||
, m_parent(parent)
|
, m_parent(parent)
|
||||||
, m_resource_model(resource_model)
|
, m_resource_model(resource_model)
|
||||||
|
@ -50,7 +45,7 @@ ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent,
|
||||||
, m_second_try_metadata(new ConcurrentTask("Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()))
|
, m_second_try_metadata(new ConcurrentTask("Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()))
|
||||||
, m_instance(instance)
|
, m_instance(instance)
|
||||||
, m_include_deps(include_deps)
|
, m_include_deps(include_deps)
|
||||||
, m_filter_loaders(filter_loaders)
|
, m_loadersList(std::move(loadersList))
|
||||||
{
|
{
|
||||||
ReviewMessageBox::setGeometry(0, 0, 800, 600);
|
ReviewMessageBox::setGeometry(0, 0, 800, 600);
|
||||||
|
|
||||||
|
@ -89,12 +84,10 @@ void ResourceUpdateDialog::checkCandidates()
|
||||||
}
|
}
|
||||||
|
|
||||||
auto versions = mcVersions(m_instance);
|
auto versions = mcVersions(m_instance);
|
||||||
auto loadersList = m_filter_loaders ? mcLoadersList(m_instance) : QList<ModPlatform::ModLoaderType>();
|
|
||||||
|
|
||||||
SequentialTask check_task(tr("Checking for updates"));
|
SequentialTask check_task(tr("Checking for updates"));
|
||||||
|
|
||||||
if (!m_modrinth_to_update.empty()) {
|
if (!m_modrinth_to_update.empty()) {
|
||||||
m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loadersList, m_resource_model));
|
m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, m_loadersList, m_resource_model));
|
||||||
connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this,
|
connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this,
|
||||||
[this](Resource* resource, QString reason, QUrl recover_url) {
|
[this](Resource* resource, QString reason, QUrl recover_url) {
|
||||||
m_failed_check_update.append({ resource, reason, recover_url });
|
m_failed_check_update.append({ resource, reason, recover_url });
|
||||||
|
@ -103,7 +96,7 @@ void ResourceUpdateDialog::checkCandidates()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_flame_to_update.empty()) {
|
if (!m_flame_to_update.empty()) {
|
||||||
m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loadersList, m_resource_model));
|
m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, m_loadersList, m_resource_model));
|
||||||
connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this,
|
connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this,
|
||||||
[this](Resource* resource, QString reason, QUrl recover_url) {
|
[this](Resource* resource, QString reason, QUrl recover_url) {
|
||||||
m_failed_check_update.append({ resource, reason, recover_url });
|
m_failed_check_update.append({ resource, reason, recover_url });
|
||||||
|
|
|
@ -21,7 +21,7 @@ class ResourceUpdateDialog final : public ReviewMessageBox {
|
||||||
std::shared_ptr<ResourceFolderModel> resource_model,
|
std::shared_ptr<ResourceFolderModel> resource_model,
|
||||||
QList<Resource*>& search_for,
|
QList<Resource*>& search_for,
|
||||||
bool include_deps,
|
bool include_deps,
|
||||||
bool filter_loaders);
|
QList<ModPlatform::ModLoaderType> loadersList = {});
|
||||||
|
|
||||||
void checkCandidates();
|
void checkCandidates();
|
||||||
|
|
||||||
|
@ -64,5 +64,5 @@ class ResourceUpdateDialog final : public ReviewMessageBox {
|
||||||
bool m_no_updates = false;
|
bool m_no_updates = false;
|
||||||
bool m_aborted = false;
|
bool m_aborted = false;
|
||||||
bool m_include_deps = false;
|
bool m_include_deps = false;
|
||||||
bool m_filter_loaders = false;
|
QList<ModPlatform::ModLoaderType> m_loadersList;
|
||||||
};
|
};
|
||||||
|
|
360
launcher/ui/pages/instance/DataPackPage.cpp
Normal file
|
@ -0,0 +1,360 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "DataPackPage.h"
|
||||||
|
#include "minecraft/PackProfile.h"
|
||||||
|
#include "ui_ExternalResourcesPage.h"
|
||||||
|
|
||||||
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
#include "ui/dialogs/ProgressDialog.h"
|
||||||
|
#include "ui/dialogs/ResourceDownloadDialog.h"
|
||||||
|
#include "ui/dialogs/ResourceUpdateDialog.h"
|
||||||
|
|
||||||
|
DataPackPage::DataPackPage(BaseInstance* instance, std::shared_ptr<DataPackFolderModel> model, QWidget* parent)
|
||||||
|
: ExternalResourcesPage(instance, model, parent), m_model(model)
|
||||||
|
{
|
||||||
|
ui->actionDownloadItem->setText(tr("Download Packs"));
|
||||||
|
ui->actionDownloadItem->setToolTip(tr("Download data packs from online mod platforms"));
|
||||||
|
ui->actionDownloadItem->setEnabled(true);
|
||||||
|
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
|
||||||
|
|
||||||
|
connect(ui->actionDownloadItem, &QAction::triggered, this, &DataPackPage::downloadDataPacks);
|
||||||
|
|
||||||
|
ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected data packs (all data packs if none are selected)"));
|
||||||
|
connect(ui->actionUpdateItem, &QAction::triggered, this, &DataPackPage::updateDataPacks);
|
||||||
|
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem);
|
||||||
|
|
||||||
|
auto updateMenu = new QMenu(this);
|
||||||
|
|
||||||
|
auto update = updateMenu->addAction(ui->actionUpdateItem->text());
|
||||||
|
connect(update, &QAction::triggered, this, &DataPackPage::updateDataPacks);
|
||||||
|
|
||||||
|
updateMenu->addAction(ui->actionResetItemMetadata);
|
||||||
|
connect(ui->actionResetItemMetadata, &QAction::triggered, this, &DataPackPage::deleteDataPackMetadata);
|
||||||
|
|
||||||
|
ui->actionUpdateItem->setMenu(updateMenu);
|
||||||
|
|
||||||
|
ui->actionChangeVersion->setToolTip(tr("Change a data pack's version."));
|
||||||
|
connect(ui->actionChangeVersion, &QAction::triggered, this, &DataPackPage::changeDataPackVersion);
|
||||||
|
ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataPackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
|
||||||
|
{
|
||||||
|
auto sourceCurrent = m_filterModel->mapToSource(current);
|
||||||
|
int row = sourceCurrent.row();
|
||||||
|
auto& dp = m_model->at(row);
|
||||||
|
ui->frame->updateWithDataPack(dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataPackPage::downloadDataPacks()
|
||||||
|
{
|
||||||
|
if (m_instance->typeName() != "Minecraft")
|
||||||
|
return; // this is a null instance or a legacy instance
|
||||||
|
|
||||||
|
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
|
||||||
|
|
||||||
|
m_downloadDialog = new ResourceDownload::DataPackDownloadDialog(this, m_model, m_instance);
|
||||||
|
connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close);
|
||||||
|
connect(m_downloadDialog, &QDialog::finished, this, &DataPackPage::downloadDialogFinished);
|
||||||
|
|
||||||
|
m_downloadDialog->open();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataPackPage::downloadDialogFinished(int result)
|
||||||
|
{
|
||||||
|
if (result) {
|
||||||
|
auto tasks = new ConcurrentTask(tr("Download Data Packs"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
|
||||||
|
connect(tasks, &Task::failed, [this, tasks](QString reason) {
|
||||||
|
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
|
||||||
|
tasks->deleteLater();
|
||||||
|
});
|
||||||
|
connect(tasks, &Task::aborted, [this, tasks]() {
|
||||||
|
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
|
||||||
|
tasks->deleteLater();
|
||||||
|
});
|
||||||
|
connect(tasks, &Task::succeeded, [this, tasks]() {
|
||||||
|
QStringList warnings = tasks->warnings();
|
||||||
|
if (warnings.count())
|
||||||
|
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
|
||||||
|
|
||||||
|
tasks->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (m_downloadDialog) {
|
||||||
|
for (auto& task : m_downloadDialog->getTasks()) {
|
||||||
|
tasks->addTask(task);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qWarning() << "ResourceDownloadDialog vanished before we could collect tasks!";
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressDialog loadDialog(this);
|
||||||
|
loadDialog.setSkipButton(true, tr("Abort"));
|
||||||
|
loadDialog.execWithTask(tasks);
|
||||||
|
|
||||||
|
m_model->update();
|
||||||
|
}
|
||||||
|
if (m_downloadDialog)
|
||||||
|
m_downloadDialog->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataPackPage::updateDataPacks()
|
||||||
|
{
|
||||||
|
if (m_instance->typeName() != "Minecraft")
|
||||||
|
return; // this is a null instance or a legacy instance
|
||||||
|
|
||||||
|
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
|
||||||
|
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
|
||||||
|
QMessageBox::critical(this, tr("Error"), tr("Data pack updates are unavailable when metadata is disabled!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_instance != nullptr && m_instance->isRunning()) {
|
||||||
|
auto response =
|
||||||
|
CustomMessageBox::selectable(this, tr("Confirm Update"),
|
||||||
|
tr("Updating data packs while the game is running may cause pack duplication and game crashes.\n"
|
||||||
|
"The old files may not be deleted as they are in use.\n"
|
||||||
|
"Are you sure you want to do this?"),
|
||||||
|
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||||
|
->exec();
|
||||||
|
|
||||||
|
if (response != QMessageBox::Yes)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
|
||||||
|
|
||||||
|
auto mods_list = m_model->selectedResources(selection);
|
||||||
|
bool use_all = mods_list.empty();
|
||||||
|
if (use_all)
|
||||||
|
mods_list = m_model->allResources();
|
||||||
|
|
||||||
|
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, { ModPlatform::ModLoaderType::DataPack });
|
||||||
|
update_dialog.checkCandidates();
|
||||||
|
|
||||||
|
if (update_dialog.aborted()) {
|
||||||
|
CustomMessageBox::selectable(this, tr("Aborted"), tr("The data pack updater was aborted!"), QMessageBox::Warning)->show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (update_dialog.noUpdates()) {
|
||||||
|
QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) };
|
||||||
|
if (mods_list.size() > 1) {
|
||||||
|
if (use_all) {
|
||||||
|
message = tr("All data packs are up-to-date! :)");
|
||||||
|
} else {
|
||||||
|
message = tr("All selected data packs are up-to-date! :)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CustomMessageBox::selectable(this, tr("Update checker"), message)->exec();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update_dialog.exec()) {
|
||||||
|
auto tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
|
||||||
|
connect(tasks, &Task::failed, [this, tasks](QString reason) {
|
||||||
|
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
|
||||||
|
tasks->deleteLater();
|
||||||
|
});
|
||||||
|
connect(tasks, &Task::aborted, [this, tasks]() {
|
||||||
|
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
|
||||||
|
tasks->deleteLater();
|
||||||
|
});
|
||||||
|
connect(tasks, &Task::succeeded, [this, tasks]() {
|
||||||
|
QStringList warnings = tasks->warnings();
|
||||||
|
if (warnings.count()) {
|
||||||
|
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
|
||||||
|
}
|
||||||
|
tasks->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (auto task : update_dialog.getTasks()) {
|
||||||
|
tasks->addTask(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressDialog loadDialog(this);
|
||||||
|
loadDialog.setSkipButton(true, tr("Abort"));
|
||||||
|
loadDialog.execWithTask(tasks);
|
||||||
|
|
||||||
|
m_model->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataPackPage::deleteDataPackMetadata()
|
||||||
|
{
|
||||||
|
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
|
||||||
|
auto selectionCount = m_model->selectedDataPacks(selection).length();
|
||||||
|
if (selectionCount == 0)
|
||||||
|
return;
|
||||||
|
if (selectionCount > 1) {
|
||||||
|
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
|
||||||
|
tr("You are about to remove the metadata for %1 data packs.\n"
|
||||||
|
"Are you sure?")
|
||||||
|
.arg(selectionCount),
|
||||||
|
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||||
|
->exec();
|
||||||
|
|
||||||
|
if (response != QMessageBox::Yes)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_model->deleteMetadata(selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataPackPage::changeDataPackVersion()
|
||||||
|
{
|
||||||
|
if (m_instance->typeName() != "Minecraft")
|
||||||
|
return; // this is a null instance or a legacy instance
|
||||||
|
|
||||||
|
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
|
||||||
|
QMessageBox::critical(this, tr("Error"), tr("Data pack updates are unavailable when metadata is disabled!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows();
|
||||||
|
|
||||||
|
if (rows.count() != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row());
|
||||||
|
|
||||||
|
if (resource.metadata() == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ResourceDownload::DataPackDownloadDialog mdownload(this, m_model, m_instance);
|
||||||
|
mdownload.setResourceMetadata(resource.metadata());
|
||||||
|
if (mdownload.exec()) {
|
||||||
|
auto tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
|
||||||
|
connect(tasks, &Task::failed, [this, tasks](QString reason) {
|
||||||
|
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
|
||||||
|
tasks->deleteLater();
|
||||||
|
});
|
||||||
|
connect(tasks, &Task::aborted, [this, tasks]() {
|
||||||
|
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
|
||||||
|
tasks->deleteLater();
|
||||||
|
});
|
||||||
|
connect(tasks, &Task::succeeded, [this, tasks]() {
|
||||||
|
QStringList warnings = tasks->warnings();
|
||||||
|
if (warnings.count())
|
||||||
|
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
|
||||||
|
|
||||||
|
tasks->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (auto& task : mdownload.getTasks()) {
|
||||||
|
tasks->addTask(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressDialog loadDialog(this);
|
||||||
|
loadDialog.setSkipButton(true, tr("Abort"));
|
||||||
|
loadDialog.execWithTask(tasks);
|
||||||
|
|
||||||
|
m_model->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalDataPackPage::GlobalDataPackPage(MinecraftInstance* instance, QWidget* parent) : QWidget(parent), m_instance(instance)
|
||||||
|
{
|
||||||
|
auto layout = new QVBoxLayout(this);
|
||||||
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
setLayout(layout);
|
||||||
|
|
||||||
|
connect(instance->settings()->getSetting("GlobalDataPacksEnabled").get(), &Setting::SettingChanged, this, [this] {
|
||||||
|
updateContent();
|
||||||
|
if (m_container != nullptr)
|
||||||
|
m_container->refreshContainer();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(instance->settings()->getSetting("GlobalDataPacksPath").get(), &Setting::SettingChanged, this,
|
||||||
|
&GlobalDataPackPage::updateContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GlobalDataPackPage::displayName() const
|
||||||
|
{
|
||||||
|
if (m_underlyingPage == nullptr)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return m_underlyingPage->displayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
QIcon GlobalDataPackPage::icon() const
|
||||||
|
{
|
||||||
|
if (m_underlyingPage == nullptr)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return m_underlyingPage->icon();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GlobalDataPackPage::helpPage() const
|
||||||
|
{
|
||||||
|
if (m_underlyingPage == nullptr)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return m_underlyingPage->helpPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GlobalDataPackPage::shouldDisplay() const
|
||||||
|
{
|
||||||
|
return m_instance->settings()->get("GlobalDataPacksEnabled").toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GlobalDataPackPage::apply()
|
||||||
|
{
|
||||||
|
return m_underlyingPage == nullptr || m_underlyingPage->apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlobalDataPackPage::openedImpl()
|
||||||
|
{
|
||||||
|
if (m_underlyingPage != nullptr)
|
||||||
|
m_underlyingPage->openedImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlobalDataPackPage::closedImpl()
|
||||||
|
{
|
||||||
|
if (m_underlyingPage != nullptr)
|
||||||
|
m_underlyingPage->closedImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlobalDataPackPage::updateContent()
|
||||||
|
{
|
||||||
|
if (m_underlyingPage != nullptr) {
|
||||||
|
if (m_container->selectedPage() == this)
|
||||||
|
m_underlyingPage->closedImpl();
|
||||||
|
|
||||||
|
m_underlyingPage->apply();
|
||||||
|
|
||||||
|
layout()->removeWidget(m_underlyingPage);
|
||||||
|
|
||||||
|
delete m_underlyingPage;
|
||||||
|
m_underlyingPage = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldDisplay()) {
|
||||||
|
m_underlyingPage = new DataPackPage(m_instance, m_instance->dataPackList());
|
||||||
|
m_underlyingPage->setParentContainer(m_container);
|
||||||
|
m_underlyingPage->updateExtraInfo = [this](QString id, QString value) { updateExtraInfo(std::move(id), std::move(value)); };
|
||||||
|
|
||||||
|
if (m_container->selectedPage() == this)
|
||||||
|
m_underlyingPage->openedImpl();
|
||||||
|
|
||||||
|
layout()->addWidget(m_underlyingPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void GlobalDataPackPage::setParentContainer(BasePageContainer* container)
|
||||||
|
{
|
||||||
|
BasePage::setParentContainer(container);
|
||||||
|
updateContent();
|
||||||
|
}
|
76
launcher/ui/pages/instance/DataPackPage.h
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include "ExternalResourcesPage.h"
|
||||||
|
#include "minecraft/mod/DataPackFolderModel.h"
|
||||||
|
#include "ui/dialogs/ResourceDownloadDialog.h"
|
||||||
|
|
||||||
|
class DataPackPage : public ExternalResourcesPage {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit DataPackPage(BaseInstance* instance, std::shared_ptr<DataPackFolderModel> model, QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
QString displayName() const override { return QObject::tr("Data packs"); }
|
||||||
|
QIcon icon() const override { return APPLICATION->getThemedIcon("datapacks"); }
|
||||||
|
QString id() const override { return "datapacks"; }
|
||||||
|
QString helpPage() const override { return "Data-packs"; }
|
||||||
|
bool shouldDisplay() const override { return true; }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateFrame(const QModelIndex& current, const QModelIndex& previous) override;
|
||||||
|
void downloadDataPacks();
|
||||||
|
void downloadDialogFinished(int result);
|
||||||
|
void updateDataPacks();
|
||||||
|
void deleteDataPackMetadata();
|
||||||
|
void changeDataPackVersion();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<DataPackFolderModel> m_model;
|
||||||
|
QPointer<ResourceDownload::DataPackDownloadDialog> m_downloadDialog;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs DataPackPage with GlobalDataPacksPath and shows/hides based on GlobalDataPacksEnabled.
|
||||||
|
*/
|
||||||
|
class GlobalDataPackPage : public QWidget, public BasePage {
|
||||||
|
public:
|
||||||
|
explicit GlobalDataPackPage(MinecraftInstance* instance, QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
QString displayName() const override;
|
||||||
|
QIcon icon() const override;
|
||||||
|
QString id() const override { return "datapacks"; }
|
||||||
|
QString helpPage() const override;
|
||||||
|
|
||||||
|
bool shouldDisplay() const override;
|
||||||
|
|
||||||
|
bool apply() override;
|
||||||
|
void openedImpl() override;
|
||||||
|
void closedImpl() override;
|
||||||
|
|
||||||
|
void setParentContainer(BasePageContainer* container) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateContent();
|
||||||
|
QVBoxLayout* layout() { return static_cast<QVBoxLayout*>(QWidget::layout()); }
|
||||||
|
|
||||||
|
MinecraftInstance* m_instance;
|
||||||
|
DataPackPage* m_underlyingPage = nullptr;
|
||||||
|
};
|
|
@ -225,7 +225,7 @@ void ModFolderPage::updateMods(bool includeDeps)
|
||||||
if (use_all)
|
if (use_all)
|
||||||
mods_list = m_model->allResources();
|
mods_list = m_model->allResources();
|
||||||
|
|
||||||
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps, true);
|
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps, profile->getModLoadersList());
|
||||||
update_dialog.checkCandidates();
|
update_dialog.checkCandidates();
|
||||||
|
|
||||||
if (update_dialog.aborted()) {
|
if (update_dialog.aborted()) {
|
||||||
|
|
|
@ -158,7 +158,7 @@ void ResourcePackPage::updateResourcePacks()
|
||||||
if (use_all)
|
if (use_all)
|
||||||
mods_list = m_model->allResources();
|
mods_list = m_model->allResources();
|
||||||
|
|
||||||
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false);
|
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false);
|
||||||
update_dialog.checkCandidates();
|
update_dialog.checkCandidates();
|
||||||
|
|
||||||
if (update_dialog.aborted()) {
|
if (update_dialog.aborted()) {
|
||||||
|
|
|
@ -155,7 +155,7 @@ void ShaderPackPage::updateShaderPacks()
|
||||||
if (use_all)
|
if (use_all)
|
||||||
mods_list = m_model->allResources();
|
mods_list = m_model->allResources();
|
||||||
|
|
||||||
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false);
|
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false);
|
||||||
update_dialog.checkCandidates();
|
update_dialog.checkCandidates();
|
||||||
|
|
||||||
if (update_dialog.aborted()) {
|
if (update_dialog.aborted()) {
|
||||||
|
|
|
@ -163,7 +163,7 @@ void TexturePackPage::updateTexturePacks()
|
||||||
if (use_all)
|
if (use_all)
|
||||||
mods_list = m_model->allResources();
|
mods_list = m_model->allResources();
|
||||||
|
|
||||||
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false);
|
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false);
|
||||||
update_dialog.checkCandidates();
|
update_dialog.checkCandidates();
|
||||||
|
|
||||||
if (update_dialog.aborted()) {
|
if (update_dialog.aborted()) {
|
||||||
|
|
|
@ -40,7 +40,9 @@
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
#include "ui_WorldListPage.h"
|
#include "ui_WorldListPage.h"
|
||||||
|
|
||||||
|
#include <ui/widgets/PageContainer.h>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
|
@ -49,6 +51,7 @@
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <Qt>
|
#include <Qt>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "tools/MCEditTool.h"
|
#include "tools/MCEditTool.h"
|
||||||
|
@ -57,6 +60,7 @@
|
||||||
#include "ui/GuiUtil.h"
|
#include "ui/GuiUtil.h"
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "DataPackPage.h"
|
||||||
|
|
||||||
class WorldListProxyModel : public QSortFilterProxyModel {
|
class WorldListProxyModel : public QSortFilterProxyModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -82,7 +86,7 @@ class WorldListProxyModel : public QSortFilterProxyModel {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
WorldListPage::WorldListPage(InstancePtr inst, std::shared_ptr<WorldList> worlds, QWidget* parent)
|
WorldListPage::WorldListPage(MinecraftInstancePtr inst, std::shared_ptr<WorldList> worlds, QWidget* parent)
|
||||||
: QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds)
|
: QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
@ -209,7 +213,7 @@ void WorldListPage::on_actionView_Folder_triggered()
|
||||||
DesktopServices::openPath(m_worlds->dir().absolutePath(), true);
|
DesktopServices::openPath(m_worlds->dir().absolutePath(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorldListPage::on_actionDatapacks_triggered()
|
void WorldListPage::on_actionData_Packs_triggered()
|
||||||
{
|
{
|
||||||
QModelIndex index = getSelectedWorld();
|
QModelIndex index = getSelectedWorld();
|
||||||
|
|
||||||
|
@ -217,12 +221,49 @@ void WorldListPage::on_actionDatapacks_triggered()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!worldSafetyNagQuestion(tr("Open World Datapacks Folder")))
|
if (!worldSafetyNagQuestion(tr("Manage Data Packs")))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
|
const QString fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
|
||||||
|
const QString folder = FS::PathCombine(fullPath, "datapacks");
|
||||||
|
|
||||||
DesktopServices::openPath(FS::PathCombine(fullPath, "datapacks"), true);
|
auto dialog = new QDialog(this);
|
||||||
|
dialog->setWindowTitle(tr("Data packs for %1").arg(m_worlds->data(index, WorldList::NameRole).toString()));
|
||||||
|
dialog->setWindowModality(Qt::WindowModal);
|
||||||
|
|
||||||
|
dialog->resize(static_cast<int>(std::max(0.5 * window()->width(), 400.0)),
|
||||||
|
static_cast<int>(std::max(0.75 * window()->height(), 400.0)));
|
||||||
|
dialog->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("DataPackDownloadGeometry").toByteArray()));
|
||||||
|
|
||||||
|
GenericPageProvider provider(dialog->windowTitle());
|
||||||
|
|
||||||
|
provider.addPageCreator([this, folder] {
|
||||||
|
bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||||
|
auto model = std::make_shared<DataPackFolderModel>(folder, m_inst.get(), isIndexed, true);
|
||||||
|
return new DataPackPage(m_inst.get(), std::move(model));
|
||||||
|
});
|
||||||
|
|
||||||
|
auto layout = new QVBoxLayout(dialog);
|
||||||
|
|
||||||
|
auto focusStealer = new QPushButton(dialog);
|
||||||
|
layout->addWidget(focusStealer);
|
||||||
|
focusStealer->setDefault(true);
|
||||||
|
focusStealer->hide();
|
||||||
|
|
||||||
|
auto pageContainer = new PageContainer(&provider, {}, dialog);
|
||||||
|
pageContainer->hidePageList();
|
||||||
|
layout->addWidget(pageContainer);
|
||||||
|
|
||||||
|
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close | QDialogButtonBox::Help);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::helpRequested, pageContainer, &PageContainer::help);
|
||||||
|
layout->addWidget(buttonBox);
|
||||||
|
|
||||||
|
dialog->setLayout(layout);
|
||||||
|
|
||||||
|
dialog->exec();
|
||||||
|
|
||||||
|
APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64());
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorldListPage::on_actionReset_Icon_triggered()
|
void WorldListPage::on_actionReset_Icon_triggered()
|
||||||
|
@ -335,7 +376,7 @@ void WorldListPage::worldChanged([[maybe_unused]] const QModelIndex& current, [[
|
||||||
ui->actionRemove->setEnabled(enable);
|
ui->actionRemove->setEnabled(enable);
|
||||||
ui->actionCopy->setEnabled(enable);
|
ui->actionCopy->setEnabled(enable);
|
||||||
ui->actionRename->setEnabled(enable);
|
ui->actionRename->setEnabled(enable);
|
||||||
ui->actionDatapacks->setEnabled(enable);
|
ui->actionData_Packs->setEnabled(enable);
|
||||||
bool hasIcon = !index.data(WorldList::IconFileRole).isNull();
|
bool hasIcon = !index.data(WorldList::IconFileRole).isNull();
|
||||||
ui->actionReset_Icon->setEnabled(enable && hasIcon);
|
ui->actionReset_Icon->setEnabled(enable && hasIcon);
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ class WorldListPage : public QMainWindow, public BasePage {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit WorldListPage(InstancePtr inst, std::shared_ptr<WorldList> worlds, QWidget* parent = 0);
|
explicit WorldListPage(MinecraftInstancePtr inst, std::shared_ptr<WorldList> worlds, QWidget* parent = 0);
|
||||||
virtual ~WorldListPage();
|
virtual ~WorldListPage();
|
||||||
|
|
||||||
virtual QString displayName() const override { return tr("Worlds"); }
|
virtual QString displayName() const override { return tr("Worlds"); }
|
||||||
|
@ -72,7 +72,7 @@ class WorldListPage : public QMainWindow, public BasePage {
|
||||||
QMenu* createPopupMenu() override;
|
QMenu* createPopupMenu() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
InstancePtr m_inst;
|
MinecraftInstancePtr m_inst;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QModelIndex getSelectedWorld();
|
QModelIndex getSelectedWorld();
|
||||||
|
@ -97,7 +97,7 @@ class WorldListPage : public QMainWindow, public BasePage {
|
||||||
void on_actionRename_triggered();
|
void on_actionRename_triggered();
|
||||||
void on_actionRefresh_triggered();
|
void on_actionRefresh_triggered();
|
||||||
void on_actionView_Folder_triggered();
|
void on_actionView_Folder_triggered();
|
||||||
void on_actionDatapacks_triggered();
|
void on_actionData_Packs_triggered();
|
||||||
void on_actionReset_Icon_triggered();
|
void on_actionReset_Icon_triggered();
|
||||||
void worldChanged(const QModelIndex& current, const QModelIndex& previous);
|
void worldChanged(const QModelIndex& current, const QModelIndex& previous);
|
||||||
void mceditState(LoggedProcess::State state);
|
void mceditState(LoggedProcess::State state);
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
<addaction name="actionCopy"/>
|
<addaction name="actionCopy"/>
|
||||||
<addaction name="actionRemove"/>
|
<addaction name="actionRemove"/>
|
||||||
<addaction name="actionMCEdit"/>
|
<addaction name="actionMCEdit"/>
|
||||||
<addaction name="actionDatapacks"/>
|
<addaction name="actionData_Packs"/>
|
||||||
<addaction name="actionReset_Icon"/>
|
<addaction name="actionReset_Icon"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionCopy_Seed"/>
|
<addaction name="actionCopy_Seed"/>
|
||||||
|
@ -146,12 +146,12 @@
|
||||||
<string>Remove world icon to make the game re-generate it on next load.</string>
|
<string>Remove world icon to make the game re-generate it on next load.</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionDatapacks">
|
<action name="actionData_Packs">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Datapacks</string>
|
<string>Data Packs</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Manage datapacks inside the world.</string>
|
<string>Manage data packs inside the world.</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
|
|
48
launcher/ui/pages/modplatform/DataPackModel.cpp
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
|
||||||
|
// SPDX-FileCopyrightText: 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
#include "DataPackModel.h"
|
||||||
|
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
namespace ResourceDownload {
|
||||||
|
|
||||||
|
DataPackResourceModel::DataPackResourceModel(BaseInstance const& base_inst, ResourceAPI* api)
|
||||||
|
: ResourceModel(api), m_base_instance(base_inst)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/******** Make data requests ********/
|
||||||
|
|
||||||
|
ResourceAPI::SearchArgs DataPackResourceModel::createSearchArguments()
|
||||||
|
{
|
||||||
|
auto sort = getCurrentSortingMethodByIndex();
|
||||||
|
return { ModPlatform::ResourceType::DATA_PACK, m_next_search_offset, m_search_term, sort, ModPlatform::ModLoaderType::DataPack };
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceAPI::VersionSearchArgs DataPackResourceModel::createVersionsArguments(const QModelIndex& entry)
|
||||||
|
{
|
||||||
|
auto& pack = m_packs[entry.row()];
|
||||||
|
return { *pack, {}, ModPlatform::ModLoaderType::DataPack };
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceAPI::ProjectInfoArgs DataPackResourceModel::createInfoArguments(const QModelIndex& entry)
|
||||||
|
{
|
||||||
|
auto& pack = m_packs[entry.row()];
|
||||||
|
return { *pack };
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataPackResourceModel::searchWithTerm(const QString& term, unsigned int sort)
|
||||||
|
{
|
||||||
|
if (m_search_term == term && m_search_term.isNull() == term.isNull() && m_current_sort_index == sort) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSearchTerm(term);
|
||||||
|
m_current_sort_index = sort;
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ResourceDownload
|
44
launcher/ui/pages/modplatform/DataPackModel.h
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
|
||||||
|
// SPDX-FileCopyrightText: 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
|
||||||
|
#include "BaseInstance.h"
|
||||||
|
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
|
#include "ui/pages/modplatform/ResourceModel.h"
|
||||||
|
|
||||||
|
class Version;
|
||||||
|
|
||||||
|
namespace ResourceDownload {
|
||||||
|
|
||||||
|
class DataPackResourceModel : public ResourceModel {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
DataPackResourceModel(BaseInstance const&, ResourceAPI*);
|
||||||
|
|
||||||
|
/* Ask the API for more information */
|
||||||
|
void searchWithTerm(const QString& term, unsigned int sort);
|
||||||
|
|
||||||
|
void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) override = 0;
|
||||||
|
void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) override = 0;
|
||||||
|
void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) override = 0;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
ResourceAPI::SearchArgs createSearchArguments() override;
|
||||||
|
ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override;
|
||||||
|
ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const BaseInstance& m_base_instance;
|
||||||
|
|
||||||
|
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ResourceDownload
|
43
launcher/ui/pages/modplatform/DataPackPage.cpp
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
|
||||||
|
// SPDX-FileCopyrightText: 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
#include "DataPackPage.h"
|
||||||
|
#include "ui_ResourcePage.h"
|
||||||
|
|
||||||
|
#include "DataPackModel.h"
|
||||||
|
|
||||||
|
#include "ui/dialogs/ResourceDownloadDialog.h"
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
namespace ResourceDownload {
|
||||||
|
|
||||||
|
DataPackResourcePage::DataPackResourcePage(DataPackDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) {}
|
||||||
|
|
||||||
|
/******** Callbacks to events in the UI (set up in the derived classes) ********/
|
||||||
|
|
||||||
|
void DataPackResourcePage::triggerSearch()
|
||||||
|
{
|
||||||
|
m_ui->packView->clearSelection();
|
||||||
|
m_ui->packDescription->clear();
|
||||||
|
m_ui->versionSelectionBox->clear();
|
||||||
|
|
||||||
|
updateSelectionButton();
|
||||||
|
|
||||||
|
static_cast<DataPackResourceModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt());
|
||||||
|
m_fetchProgress.watch(m_model->activeSearchJob().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<QString, QString> DataPackResourcePage::urlHandlers() const
|
||||||
|
{
|
||||||
|
QMap<QString, QString> map;
|
||||||
|
map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/resourcepack\\/([^\\/]+)\\/?"), "modrinth");
|
||||||
|
map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/texture-packs\\/([^\\/]+)\\/?"),
|
||||||
|
"curseforge");
|
||||||
|
map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge");
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ResourceDownload
|
52
launcher/ui/pages/modplatform/DataPackPage.h
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
|
||||||
|
// SPDX-FileCopyrightText: 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/pages/modplatform/DataPackModel.h"
|
||||||
|
#include "ui/pages/modplatform/ResourcePage.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ResourcePage;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ResourceDownload {
|
||||||
|
|
||||||
|
class DataPackDownloadDialog;
|
||||||
|
|
||||||
|
class DataPackResourcePage : public ResourcePage {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <typename T>
|
||||||
|
static T* create(DataPackDownloadDialog* dialog, BaseInstance& instance)
|
||||||
|
{
|
||||||
|
auto page = new T(dialog, instance);
|
||||||
|
auto model = static_cast<DataPackResourceModel*>(page->getModel());
|
||||||
|
|
||||||
|
connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated);
|
||||||
|
connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi);
|
||||||
|
connect(model, &QAbstractListModel::modelReset, page, &ResourcePage::modelReset);
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
//: The plural version of 'data pack'
|
||||||
|
[[nodiscard]] inline QString resourcesString() const override { return tr("data packs"); }
|
||||||
|
//: The singular version of 'data packs'
|
||||||
|
[[nodiscard]] inline QString resourceString() const override { return tr("data pack"); }
|
||||||
|
|
||||||
|
[[nodiscard]] bool supportsFiltering() const override { return false; };
|
||||||
|
|
||||||
|
[[nodiscard]] QMap<QString, QString> urlHandlers() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
DataPackResourcePage(DataPackDownloadDialog* dialog, BaseInstance& instance);
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void triggerSearch() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ResourceDownload
|
|
@ -170,4 +170,32 @@ auto FlameShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArr
|
||||||
return Json::ensureArray(obj.object(), "data");
|
return Json::ensureArray(obj.object(), "data");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FlameDataPackModel::FlameDataPackModel(const BaseInstance& base) : DataPackResourceModel(base, new FlameAPI) {}
|
||||||
|
|
||||||
|
void FlameDataPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
|
||||||
|
{
|
||||||
|
FlameMod::loadIndexedPack(m, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We already deal with the URLs when initializing the pack, due to the API response's structure
|
||||||
|
void FlameDataPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
|
||||||
|
{
|
||||||
|
FlameMod::loadBody(m, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlameDataPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
|
||||||
|
{
|
||||||
|
FlameMod::loadIndexedPackVersions(m, arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FlameDataPackModel::optedOut(const ModPlatform::IndexedVersion& ver) const
|
||||||
|
{
|
||||||
|
return isOptedOut(ver);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto FlameDataPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||||
|
{
|
||||||
|
return Json::ensureArray(obj.object(), "data");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ResourceDownload
|
} // namespace ResourceDownload
|
||||||
|
|
|
@ -93,4 +93,23 @@ class FlameShaderPackModel : public ShaderPackResourceModel {
|
||||||
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
|
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class FlameDataPackModel : public DataPackResourceModel {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FlameDataPackModel(const BaseInstance&);
|
||||||
|
~FlameDataPackModel() override = default;
|
||||||
|
|
||||||
|
bool optedOut(const ModPlatform::IndexedVersion& ver) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; }
|
||||||
|
[[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); }
|
||||||
|
|
||||||
|
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
|
||||||
|
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
|
||||||
|
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
|
||||||
|
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace ResourceDownload
|
} // namespace ResourceDownload
|
||||||
|
|
|
@ -152,6 +152,22 @@ void FlameTexturePackPage::openUrl(const QUrl& url)
|
||||||
TexturePackResourcePage::openUrl(url);
|
TexturePackResourcePage::openUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlameDataPackPage::openUrl(const QUrl& url)
|
||||||
|
{
|
||||||
|
if (url.scheme().isEmpty()) {
|
||||||
|
QString query = url.query(QUrl::FullyDecoded);
|
||||||
|
|
||||||
|
if (query.startsWith("remoteUrl=")) {
|
||||||
|
// attempt to resolve url from warning page
|
||||||
|
query.remove(0, 10);
|
||||||
|
DataPackResourcePage::openUrl({ QUrl::fromPercentEncoding(query.toUtf8()) }); // double decoding is necessary
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DataPackResourcePage::openUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
|
FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
|
||||||
: ShaderPackResourcePage(dialog, instance)
|
: ShaderPackResourcePage(dialog, instance)
|
||||||
{
|
{
|
||||||
|
@ -171,6 +187,25 @@ FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseI
|
||||||
m_ui->packDescription->setMetaEntry(metaEntryBase());
|
m_ui->packDescription->setMetaEntry(metaEntryBase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FlameDataPackPage::FlameDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance)
|
||||||
|
: DataPackResourcePage(dialog, instance)
|
||||||
|
{
|
||||||
|
m_model = new FlameDataPackModel(instance);
|
||||||
|
m_ui->packView->setModel(m_model);
|
||||||
|
|
||||||
|
addSortings();
|
||||||
|
|
||||||
|
// sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
|
||||||
|
// so it's best not to connect them in the parent's constructor...
|
||||||
|
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
|
||||||
|
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameDataPackPage::onSelectionChanged);
|
||||||
|
connect(m_ui->versionSelectionBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||||
|
&FlameDataPackPage::onVersionSelectionChanged);
|
||||||
|
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameDataPackPage::onResourceSelected);
|
||||||
|
|
||||||
|
m_ui->packDescription->setMetaEntry(metaEntryBase());
|
||||||
|
}
|
||||||
|
|
||||||
void FlameShaderPackPage::openUrl(const QUrl& url)
|
void FlameShaderPackPage::openUrl(const QUrl& url)
|
||||||
{
|
{
|
||||||
if (url.scheme().isEmpty()) {
|
if (url.scheme().isEmpty()) {
|
||||||
|
@ -206,6 +241,10 @@ auto FlameShaderPackPage::shouldDisplay() const -> bool
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
auto FlameDataPackPage::shouldDisplay() const -> bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<ModFilterWidget> FlameModPage::createFilterWidget()
|
std::unique_ptr<ModFilterWidget> FlameModPage::createFilterWidget()
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <ui/pages/modplatform/DataPackPage.h>
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
#include "modplatform/ResourceAPI.h"
|
#include "modplatform/ResourceAPI.h"
|
||||||
|
@ -180,4 +181,31 @@ class FlameShaderPackPage : public ShaderPackResourcePage {
|
||||||
void openUrl(const QUrl& url) override;
|
void openUrl(const QUrl& url) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class FlameDataPackPage : public DataPackResourcePage {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static FlameDataPackPage* create(DataPackDownloadDialog* dialog, BaseInstance& instance)
|
||||||
|
{
|
||||||
|
return DataPackResourcePage::create<FlameDataPackPage>(dialog, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlameDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance);
|
||||||
|
~FlameDataPackPage() override = default;
|
||||||
|
|
||||||
|
[[nodiscard]] bool shouldDisplay() const override;
|
||||||
|
|
||||||
|
[[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); }
|
||||||
|
[[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); }
|
||||||
|
[[nodiscard]] inline auto id() const -> QString override { return Flame::id(); }
|
||||||
|
[[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); }
|
||||||
|
[[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); }
|
||||||
|
|
||||||
|
[[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
|
||||||
|
|
||||||
|
void openUrl(const QUrl& url) override;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace ResourceDownload
|
} // namespace ResourceDownload
|
||||||
|
|
|
@ -118,4 +118,27 @@ auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJson
|
||||||
return obj.object().value("hits").toArray();
|
return obj.object().value("hits").toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModrinthDataPackModel::ModrinthDataPackModel(const BaseInstance& base) : DataPackResourceModel(base, new ModrinthAPI) {}
|
||||||
|
|
||||||
|
void ModrinthDataPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
|
||||||
|
{
|
||||||
|
::Modrinth::loadIndexedPack(m, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModrinthDataPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
|
||||||
|
{
|
||||||
|
::Modrinth::loadExtraPackData(m, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModrinthDataPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
|
||||||
|
{
|
||||||
|
::Modrinth::loadIndexedPackVersions(m, arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ModrinthDataPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||||
|
{
|
||||||
|
return obj.object().value("hits").toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace ResourceDownload
|
} // namespace ResourceDownload
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/pages/modplatform/DataPackModel.h"
|
||||||
#include "ui/pages/modplatform/ModModel.h"
|
#include "ui/pages/modplatform/ModModel.h"
|
||||||
#include "ui/pages/modplatform/ResourcePackModel.h"
|
#include "ui/pages/modplatform/ResourcePackModel.h"
|
||||||
#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h"
|
#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h"
|
||||||
|
@ -99,4 +100,22 @@ class ModrinthShaderPackModel : public ShaderPackResourceModel {
|
||||||
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
|
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ModrinthDataPackModel : public DataPackResourceModel {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ModrinthDataPackModel(const BaseInstance&);
|
||||||
|
~ModrinthDataPackModel() override = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; }
|
||||||
|
[[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); }
|
||||||
|
|
||||||
|
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
|
||||||
|
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
|
||||||
|
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
|
||||||
|
|
||||||
|
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace ResourceDownload
|
} // namespace ResourceDownload
|
||||||
|
|
|
@ -122,6 +122,25 @@ ModrinthShaderPackPage::ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog,
|
||||||
m_ui->packDescription->setMetaEntry(metaEntryBase());
|
m_ui->packDescription->setMetaEntry(metaEntryBase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModrinthDataPackPage::ModrinthDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance)
|
||||||
|
: DataPackResourcePage(dialog, instance)
|
||||||
|
{
|
||||||
|
m_model = new ModrinthDataPackModel(instance);
|
||||||
|
m_ui->packView->setModel(m_model);
|
||||||
|
|
||||||
|
addSortings();
|
||||||
|
|
||||||
|
// sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
|
||||||
|
// so it's best not to connect them in the parent's constructor...
|
||||||
|
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
|
||||||
|
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthDataPackPage::onSelectionChanged);
|
||||||
|
connect(m_ui->versionSelectionBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||||
|
&ModrinthDataPackPage::onVersionSelectionChanged);
|
||||||
|
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthDataPackPage::onResourceSelected);
|
||||||
|
|
||||||
|
m_ui->packDescription->setMetaEntry(metaEntryBase());
|
||||||
|
}
|
||||||
|
|
||||||
// I don't know why, but doing this on the parent class makes it so that
|
// I don't know why, but doing this on the parent class makes it so that
|
||||||
// other mod providers start loading before being selected, at least with
|
// other mod providers start loading before being selected, at least with
|
||||||
// my Qt, so we need to implement this in every derived class...
|
// my Qt, so we need to implement this in every derived class...
|
||||||
|
@ -141,6 +160,10 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
auto ModrinthDataPackPage::shouldDisplay() const -> bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<ModFilterWidget> ModrinthModPage::createFilterWidget()
|
std::unique_ptr<ModFilterWidget> ModrinthModPage::createFilterWidget()
|
||||||
{
|
{
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
|
|
||||||
#include "modplatform/ResourceAPI.h"
|
#include "modplatform/ResourceAPI.h"
|
||||||
|
|
||||||
|
#include "ui/pages/modplatform/DataPackPage.h"
|
||||||
#include "ui/pages/modplatform/ModPage.h"
|
#include "ui/pages/modplatform/ModPage.h"
|
||||||
#include "ui/pages/modplatform/ResourcePackPage.h"
|
#include "ui/pages/modplatform/ResourcePackPage.h"
|
||||||
#include "ui/pages/modplatform/ShaderPackPage.h"
|
#include "ui/pages/modplatform/ShaderPackPage.h"
|
||||||
|
@ -170,4 +171,27 @@ class ModrinthShaderPackPage : public ShaderPackResourcePage {
|
||||||
[[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
|
[[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ModrinthDataPackPage : public DataPackResourcePage {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static ModrinthDataPackPage* create(DataPackDownloadDialog* dialog, BaseInstance& instance)
|
||||||
|
{
|
||||||
|
return DataPackResourcePage::create<ModrinthDataPackPage>(dialog, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModrinthDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance);
|
||||||
|
~ModrinthDataPackPage() override = default;
|
||||||
|
|
||||||
|
[[nodiscard]] bool shouldDisplay() const override;
|
||||||
|
|
||||||
|
[[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); }
|
||||||
|
[[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); }
|
||||||
|
[[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); }
|
||||||
|
[[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); }
|
||||||
|
[[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); }
|
||||||
|
|
||||||
|
[[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace ResourceDownload
|
} // namespace ResourceDownload
|
||||||
|
|
|
@ -227,6 +227,12 @@ void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack)
|
||||||
setImage(resource_pack.image({ 64, 64 }));
|
setImage(resource_pack.image({ 64, 64 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InfoFrame::updateWithDataPack(DataPack& data_pack) {
|
||||||
|
setName(renderColorCodes(data_pack.name()));
|
||||||
|
setDescription(renderColorCodes(data_pack.description()));
|
||||||
|
setImage(data_pack.image({ 64, 64 }));
|
||||||
|
}
|
||||||
|
|
||||||
void InfoFrame::updateWithTexturePack(TexturePack& texture_pack)
|
void InfoFrame::updateWithTexturePack(TexturePack& texture_pack)
|
||||||
{
|
{
|
||||||
QString name = renderColorCodes(texture_pack.name());
|
QString name = renderColorCodes(texture_pack.name());
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
|
|
||||||
#include <QFrame>
|
#include <QFrame>
|
||||||
|
|
||||||
|
#include "minecraft/mod/DataPack.h"
|
||||||
#include "minecraft/mod/Mod.h"
|
#include "minecraft/mod/Mod.h"
|
||||||
#include "minecraft/mod/ResourcePack.h"
|
#include "minecraft/mod/ResourcePack.h"
|
||||||
#include "minecraft/mod/TexturePack.h"
|
#include "minecraft/mod/TexturePack.h"
|
||||||
|
@ -63,6 +64,7 @@ class InfoFrame : public QFrame {
|
||||||
void updateWithMod(Mod const& m);
|
void updateWithMod(Mod const& m);
|
||||||
void updateWithResource(Resource const& resource);
|
void updateWithResource(Resource const& resource);
|
||||||
void updateWithResourcePack(ResourcePack& rp);
|
void updateWithResourcePack(ResourcePack& rp);
|
||||||
|
void updateWithDataPack(DataPack& rp);
|
||||||
void updateWithTexturePack(TexturePack& tp);
|
void updateWithTexturePack(TexturePack& tp);
|
||||||
|
|
||||||
static QString renderColorCodes(QString input);
|
static QString renderColorCodes(QString input);
|
||||||
|
|
|
@ -36,7 +36,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "MinecraftSettingsWidget.h"
|
#include "MinecraftSettingsWidget.h"
|
||||||
|
#include "ui_MinecraftSettingsWidget.h"
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
@ -44,7 +46,6 @@
|
||||||
#include "minecraft/WorldList.h"
|
#include "minecraft/WorldList.h"
|
||||||
#include "minecraft/auth/AccountList.h"
|
#include "minecraft/auth/AccountList.h"
|
||||||
#include "settings/Setting.h"
|
#include "settings/Setting.h"
|
||||||
#include "ui_MinecraftSettingsWidget.h"
|
|
||||||
|
|
||||||
MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstancePtr instance, QWidget* parent)
|
MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstancePtr instance, QWidget* parent)
|
||||||
: QWidget(parent), m_instance(std::move(instance)), m_ui(new Ui::MinecraftSettingsWidget)
|
: QWidget(parent), m_instance(std::move(instance)), m_ui(new Ui::MinecraftSettingsWidget)
|
||||||
|
@ -57,6 +58,7 @@ MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstancePtr instance,
|
||||||
m_ui->openGlobalSettingsButton->setVisible(false);
|
m_ui->openGlobalSettingsButton->setVisible(false);
|
||||||
m_ui->instanceAccountGroupBox->hide();
|
m_ui->instanceAccountGroupBox->hide();
|
||||||
m_ui->serverJoinGroupBox->hide();
|
m_ui->serverJoinGroupBox->hide();
|
||||||
|
m_ui->globalDataPacksGroupBox->hide();
|
||||||
m_ui->loaderGroup->hide();
|
m_ui->loaderGroup->hide();
|
||||||
} else {
|
} else {
|
||||||
m_javaSettings = new JavaSettingsWidget(m_instance, this);
|
m_javaSettings = new JavaSettingsWidget(m_instance, this);
|
||||||
|
@ -97,6 +99,14 @@ MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstancePtr instance,
|
||||||
connect(m_ui->serverJoinAddressButton, &QAbstractButton::toggled, m_ui->serverJoinAddress, &QWidget::setEnabled);
|
connect(m_ui->serverJoinAddressButton, &QAbstractButton::toggled, m_ui->serverJoinAddress, &QWidget::setEnabled);
|
||||||
connect(m_ui->worldJoinButton, &QAbstractButton::toggled, m_ui->worldsCb, &QWidget::setEnabled);
|
connect(m_ui->worldJoinButton, &QAbstractButton::toggled, m_ui->worldsCb, &QWidget::setEnabled);
|
||||||
|
|
||||||
|
connect(m_ui->globalDataPacksGroupBox, &QGroupBox::toggled, this, [this](bool value) {
|
||||||
|
m_instance->settings()->set("GlobalDataPacksEnabled", value);
|
||||||
|
if (!value)
|
||||||
|
m_instance->settings()->reset("GlobalDataPacksPath");
|
||||||
|
});
|
||||||
|
connect(m_ui->dataPacksPathEdit, &QLineEdit::editingFinished, this, &MinecraftSettingsWidget::editedDataPacksPath);
|
||||||
|
connect(m_ui->dataPacksPathBrowse, &QPushButton::clicked, this, &MinecraftSettingsWidget::selectDataPacksFolder);
|
||||||
|
|
||||||
connect(m_ui->loaderGroup, &QGroupBox::toggled, this, [this](bool value) {
|
connect(m_ui->loaderGroup, &QGroupBox::toggled, this, [this](bool value) {
|
||||||
m_instance->settings()->set("OverrideModDownloadLoaders", value);
|
m_instance->settings()->set("OverrideModDownloadLoaders", value);
|
||||||
if (!value)
|
if (!value)
|
||||||
|
@ -267,6 +277,13 @@ void MinecraftSettingsWidget::loadSettings()
|
||||||
|
|
||||||
m_ui->legacySettingsGroupBox->setChecked(settings->get("OverrideLegacySettings").toBool());
|
m_ui->legacySettingsGroupBox->setChecked(settings->get("OverrideLegacySettings").toBool());
|
||||||
m_ui->onlineFixes->setChecked(settings->get("OnlineFixes").toBool());
|
m_ui->onlineFixes->setChecked(settings->get("OnlineFixes").toBool());
|
||||||
|
|
||||||
|
m_ui->globalDataPacksGroupBox->blockSignals(true);
|
||||||
|
m_ui->dataPacksPathEdit->blockSignals(true);
|
||||||
|
m_ui->globalDataPacksGroupBox->setChecked(settings->get("GlobalDataPacksEnabled").toBool());
|
||||||
|
m_ui->dataPacksPathEdit->setText(settings->get("GlobalDataPacksPath").toString());
|
||||||
|
m_ui->globalDataPacksGroupBox->blockSignals(false);
|
||||||
|
m_ui->dataPacksPathEdit->blockSignals(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MinecraftSettingsWidget::saveSettings()
|
void MinecraftSettingsWidget::saveSettings()
|
||||||
|
@ -487,6 +504,34 @@ bool MinecraftSettingsWidget::isQuickPlaySupported()
|
||||||
return m_instance->traits().contains("feature:is_quick_play_singleplayer");
|
return m_instance->traits().contains("feature:is_quick_play_singleplayer");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MinecraftSettingsWidget::editedDataPacksPath()
|
||||||
|
{
|
||||||
|
if (QDir::separator() != '/')
|
||||||
|
m_ui->dataPacksPathEdit->setText(m_ui->dataPacksPathEdit->text().replace(QDir::separator(), '/'));
|
||||||
|
|
||||||
|
m_instance->settings()->set("GlobalDataPacksPath", m_ui->dataPacksPathEdit->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinecraftSettingsWidget::selectDataPacksFolder()
|
||||||
|
{
|
||||||
|
QString path = QFileDialog::getExistingDirectory(this, tr("Select Global Data Packs Folder"), m_instance->gameRoot());
|
||||||
|
|
||||||
|
if (path.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// if it's inside the instance dir, set path relative to .minecraft
|
||||||
|
// (so that if it's directly in instance dir it will still lead with .. but more than two levels up are kept absolute)
|
||||||
|
|
||||||
|
const QUrl instanceRootUrl = QUrl::fromLocalFile(m_instance->instanceRoot());
|
||||||
|
const QUrl pathUrl = QUrl::fromLocalFile(path);
|
||||||
|
|
||||||
|
if (instanceRootUrl.isParentOf(pathUrl))
|
||||||
|
path = QDir(m_instance->gameRoot()).relativeFilePath(path);
|
||||||
|
|
||||||
|
m_ui->dataPacksPathEdit->setText(path);
|
||||||
|
m_instance->settings()->set("GlobalDataPacksPath", path);
|
||||||
|
}
|
||||||
|
|
||||||
void MinecraftSettingsWidget::selectedLoadersChanged()
|
void MinecraftSettingsWidget::selectedLoadersChanged()
|
||||||
{
|
{
|
||||||
QStringList loaders;
|
QStringList loaders;
|
||||||
|
|
|
@ -58,6 +58,8 @@ class MinecraftSettingsWidget : public QWidget {
|
||||||
bool isQuickPlaySupported();
|
bool isQuickPlaySupported();
|
||||||
private slots:
|
private slots:
|
||||||
void selectedLoadersChanged();
|
void selectedLoadersChanged();
|
||||||
|
void editedDataPacksPath();
|
||||||
|
void selectDataPacksFolder();
|
||||||
|
|
||||||
MinecraftInstancePtr m_instance;
|
MinecraftInstancePtr m_instance;
|
||||||
Ui::MinecraftSettingsWidget* m_ui;
|
Ui::MinecraftSettingsWidget* m_ui;
|
||||||
|
|
|
@ -58,9 +58,9 @@
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>-537</y>
|
<y>0</y>
|
||||||
<width>623</width>
|
<width>603</width>
|
||||||
<height>1007</height>
|
<height>1042</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
|
@ -252,6 +252,70 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="globalDataPacksGroupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>&Global Data Packs</string>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_18">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Allows installing data packs across all worlds if an applicable mod is installed.
|
||||||
|
It is most likely you will need to change the path - please refer to the mod's website.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_4">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>6</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Folder Path</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="dataPacksPathEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>datapacks</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="dataPacksPathBrowse">
|
||||||
|
<property name="text">
|
||||||
|
<string>Browse</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="gameTimeGroupBox">
|
<widget class="QGroupBox" name="gameTimeGroupBox">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
|
@ -489,8 +553,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>98</width>
|
<width>624</width>
|
||||||
<height>28</height>
|
<height>487</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -513,8 +577,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>299</width>
|
<width>624</width>
|
||||||
<height>499</height>
|
<height>487</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
|
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
|
|
||||||
#include <minecraft/mod/tasks/LocalResourcePackParseTask.h>
|
#include <minecraft/mod/tasks/LocalDataPackParseTask.h>
|
||||||
|
|
||||||
class MetaComponentParseTest : public QObject {
|
class MetaComponentParseTest : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -69,7 +69,7 @@ class MetaComponentParseTest : public QObject {
|
||||||
|
|
||||||
QString expected = expected_json.toString();
|
QString expected = expected_json.toString();
|
||||||
|
|
||||||
QString processed = ResourcePackUtils::processComponent(description_json);
|
QString processed = DataPackUtils::processComponent(description_json);
|
||||||
|
|
||||||
QCOMPARE(processed, expected);
|
QCOMPARE(processed, expected);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
|
|
||||||
#include <minecraft/mod/ResourcePack.h>
|
#include <minecraft/mod/ResourcePack.h>
|
||||||
#include <minecraft/mod/tasks/LocalResourcePackParseTask.h>
|
|
||||||
|
|
||||||
class ResourcePackParseTest : public QObject {
|
class ResourcePackParseTest : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|