mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-09-09 03:48:56 -04:00
Merge 0f5a890051d968c4b6bec9121fb3f718ed6777ad into 79b7e277f1f06f6b315e293b029423fe35e57431
This commit is contained in:
commit
290a0f1f09
@ -106,6 +106,20 @@ int Mod::compare(const Resource& other, SortType type) const
|
||||
return compare_result;
|
||||
break;
|
||||
}
|
||||
case SortType::REQUIRED_BY: {
|
||||
if (requiredByCount() > cast_other->requiredByCount())
|
||||
return 1;
|
||||
if (requiredByCount() < cast_other->requiredByCount())
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
case SortType::REQUIRES: {
|
||||
if (requiresCount() > cast_other->requiresCount())
|
||||
return 1;
|
||||
if (requiresCount() < cast_other->requiresCount())
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -284,3 +298,24 @@ bool Mod::valid() const
|
||||
{
|
||||
return !m_local_details.mod_id.isEmpty();
|
||||
}
|
||||
|
||||
QStringList Mod::dependencies() const
|
||||
{
|
||||
return details().dependencies;
|
||||
}
|
||||
int Mod::requiredByCount() const
|
||||
{
|
||||
return m_requiredByCount;
|
||||
}
|
||||
int Mod::requiresCount() const
|
||||
{
|
||||
return m_requiresCount;
|
||||
}
|
||||
void Mod::setRequiredByCount(int value)
|
||||
{
|
||||
m_requiredByCount = value;
|
||||
}
|
||||
void Mod::setRequiresCount(int value)
|
||||
{
|
||||
m_requiresCount = value;
|
||||
}
|
||||
|
@ -72,6 +72,13 @@ class Mod : public Resource {
|
||||
auto loaders() const -> QString;
|
||||
auto mcVersions() const -> QString;
|
||||
auto releaseType() const -> QString;
|
||||
QStringList dependencies() const;
|
||||
|
||||
int requiredByCount() const;
|
||||
int requiresCount() const;
|
||||
|
||||
void setRequiredByCount(int value);
|
||||
void setRequiresCount(int value);
|
||||
|
||||
/** Get the intneral path to the mod's icon file*/
|
||||
QString iconPath() const { return m_local_details.icon_file; }
|
||||
@ -104,4 +111,7 @@ class Mod : public Resource {
|
||||
bool wasEverUsed = false;
|
||||
bool wasReadAttempt = false;
|
||||
} mutable m_packImageCacheKey;
|
||||
|
||||
int m_requiredByCount = 0;
|
||||
int m_requiresCount = 0;
|
||||
};
|
||||
|
@ -142,6 +142,8 @@ struct ModDetails {
|
||||
/* Path of mod logo */
|
||||
QString icon_file = {};
|
||||
|
||||
QStringList dependencies = {};
|
||||
|
||||
ModDetails() = default;
|
||||
|
||||
/** Metadata should be handled manually to properly set the mod status. */
|
||||
@ -156,6 +158,7 @@ struct ModDetails {
|
||||
, issue_tracker(other.issue_tracker)
|
||||
, licenses(other.licenses)
|
||||
, icon_file(other.icon_file)
|
||||
, dependencies(other.dependencies)
|
||||
{}
|
||||
|
||||
ModDetails& operator=(const ModDetails& other) = default;
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include "ModFolderModel.h"
|
||||
|
||||
#include <FileSystem.h>
|
||||
#include <qabstractitemmodel.h>
|
||||
#include <QDebug>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QHeaderView>
|
||||
@ -48,25 +49,34 @@
|
||||
#include <QThreadPool>
|
||||
#include <QUrl>
|
||||
#include <QUuid>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include "minecraft/Component.h"
|
||||
#include "minecraft/mod/Resource.h"
|
||||
#include "minecraft/mod/ResourceFolderModel.h"
|
||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
ModFolderModel::ModFolderModel(const QDir& 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", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
|
||||
"Minecraft Versions", "Release Type" });
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"),
|
||||
tr("Size"), tr("Side"), tr("Loaders"), tr("Minecraft Versions"), tr("Release Type") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION,
|
||||
SortType::DATE, SortType::PROVIDER, SortType::SIZE, SortType::SIDE,
|
||||
SortType::LOADERS, SortType::MC_VERSIONS, SortType::RELEASE_TYPE };
|
||||
"Minecraft Versions", "Release Type", "Requires", "Required by" });
|
||||
m_column_names_translated =
|
||||
QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("Side"),
|
||||
tr("Loaders"), tr("Minecraft Versions"), tr("Release Type"), tr("Requires "), tr("Required by") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE,
|
||||
SortType::PROVIDER, SortType::SIZE, SortType::SIDE, SortType::LOADERS, SortType::MC_VERSIONS,
|
||||
SortType::RELEASE_TYPE, SortType::REQUIRES, SortType::REQUIRED_BY };
|
||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
|
||||
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive,
|
||||
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true, true, true, true, true, true, true, true };
|
||||
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive,
|
||||
QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true, true, true, true, true, true, true, true, true, true };
|
||||
|
||||
connect(this, &ModFolderModel::parseFinished, this, &ModFolderModel::onParseFinished);
|
||||
}
|
||||
|
||||
QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
||||
@ -110,8 +120,15 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
||||
case ReleaseTypeColumn: {
|
||||
return at(row).releaseType();
|
||||
}
|
||||
case SizeColumn:
|
||||
case SizeColumn: {
|
||||
return at(row).sizeStr();
|
||||
}
|
||||
case RequiredByColumn: {
|
||||
return at(row).requiredByCount();
|
||||
}
|
||||
case RequiresColumn: {
|
||||
return at(row).requiresCount();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@ -168,6 +185,8 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
|
||||
case McVersionsColumn:
|
||||
case ReleaseTypeColumn:
|
||||
case SizeColumn:
|
||||
case RequiredByColumn:
|
||||
case RequiresColumn:
|
||||
return columnNames().at(section);
|
||||
default:
|
||||
return QVariant();
|
||||
@ -195,6 +214,10 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
|
||||
return tr("The release type.");
|
||||
case SizeColumn:
|
||||
return tr("The size of the mod.");
|
||||
case RequiredByColumn:
|
||||
return tr("Number of mods for what this is needed.");
|
||||
case RequiresColumn:
|
||||
return tr("Number of mods that this requires.");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@ -240,3 +263,178 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
|
||||
|
||||
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
||||
}
|
||||
|
||||
Mod* findById(QList<Mod*> mods, QString modId)
|
||||
{
|
||||
auto found = std::find_if(mods.begin(), mods.end(), [modId](Mod* m) { return m->mod_id() == modId; });
|
||||
return found != mods.end() ? *found : nullptr;
|
||||
}
|
||||
|
||||
void ModFolderModel::onParseFinished()
|
||||
{
|
||||
if (hasPendingParseTasks()) {
|
||||
return;
|
||||
}
|
||||
auto mods = allMods();
|
||||
|
||||
auto findByProjectID = [mods](QVariant modId, ModPlatform::ResourceProvider provider) -> Mod* {
|
||||
auto found = std::find_if(mods.begin(), mods.end(), [modId, provider](Mod* m) {
|
||||
return m->metadata() && m->metadata()->provider == provider && m->metadata()->project_id == modId;
|
||||
});
|
||||
return found != mods.end() ? *found : nullptr;
|
||||
};
|
||||
for (auto mod : mods) {
|
||||
auto id = mod->mod_id();
|
||||
for (auto dep : mod->dependencies()) {
|
||||
auto d = findById(mods, dep);
|
||||
if (d) {
|
||||
m_requires[id] << d;
|
||||
m_requiredBy[d->mod_id()] << mod;
|
||||
}
|
||||
}
|
||||
if (mod->metadata()) {
|
||||
for (auto dep : mod->metadata()->dependencies) {
|
||||
if (dep.type == ModPlatform::DependencyType::REQUIRED) {
|
||||
auto d = findByProjectID(dep.addonId, mod->metadata()->provider);
|
||||
if (d) {
|
||||
m_requires[id] << d;
|
||||
m_requiredBy[d->mod_id()] << mod;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto mod : mods) {
|
||||
auto id = mod->mod_id();
|
||||
mod->setRequiredByCount(m_requiredBy[id].count());
|
||||
mod->setRequiresCount(m_requires[id].count());
|
||||
int row = m_resources_index[mod->internal_id()];
|
||||
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
||||
}
|
||||
}
|
||||
QList<Mod*> collectMods(QList<Mod*> mods, QHash<QString, QSet<Mod*>> relation, std::set<QString>& seen)
|
||||
{
|
||||
QList<Mod*> affectedList = {};
|
||||
for (auto mod : mods) {
|
||||
auto id = mod->mod_id();
|
||||
if (seen.count(id) == 0) {
|
||||
seen.insert(id);
|
||||
for (auto affected : relation[id]) {
|
||||
auto affectedId = affected->mod_id();
|
||||
|
||||
if (findById(mods, affectedId) == nullptr && seen.count(affectedId) == 0) {
|
||||
seen.insert(affectedId);
|
||||
affectedList << affected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// collect the affected mods until all of them are included in the list
|
||||
if (!affectedList.isEmpty()) {
|
||||
affectedList += collectMods(affectedList, relation, seen);
|
||||
}
|
||||
return affectedList;
|
||||
}
|
||||
|
||||
QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes, EnableAction action)
|
||||
{
|
||||
if (indexes.isEmpty())
|
||||
return {};
|
||||
|
||||
QModelIndexList affectedList = {};
|
||||
auto affectedMods = selectedMods(indexes);
|
||||
std::set<QString> seen;
|
||||
|
||||
switch (action) {
|
||||
case EnableAction::ENABLE: {
|
||||
affectedMods << collectMods(affectedMods, m_requires, seen);
|
||||
break;
|
||||
}
|
||||
case EnableAction::DISABLE: {
|
||||
affectedMods << collectMods(affectedMods, m_requiredBy, seen);
|
||||
break;
|
||||
}
|
||||
case EnableAction::TOGGLE: {
|
||||
return {}; // this function should not be called with TOGGLE
|
||||
}
|
||||
}
|
||||
bool shouldBeEnabled = action == EnableAction::ENABLE;
|
||||
for (auto affected : affectedMods) {
|
||||
auto affectedId = affected->mod_id();
|
||||
if (shouldBeEnabled != affected->enabled()) {
|
||||
auto row = m_resources_index[affected->internal_id()];
|
||||
affectedList << index(row, 0);
|
||||
}
|
||||
}
|
||||
return affectedList;
|
||||
}
|
||||
|
||||
bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action)
|
||||
{
|
||||
if (indexes.isEmpty())
|
||||
return {};
|
||||
|
||||
QModelIndexList affectedList = {};
|
||||
auto indexedMods = selectedMods(indexes);
|
||||
|
||||
QList<Mod*> toEnable = {};
|
||||
QList<Mod*> toDisable = {};
|
||||
std::set<QString> seen;
|
||||
|
||||
switch (action) {
|
||||
case EnableAction::ENABLE: {
|
||||
toEnable = indexedMods;
|
||||
break;
|
||||
}
|
||||
case EnableAction::DISABLE: {
|
||||
toDisable = indexedMods;
|
||||
break;
|
||||
}
|
||||
case EnableAction::TOGGLE: {
|
||||
for (auto mod : indexedMods) {
|
||||
if (mod->enabled()) {
|
||||
toDisable << mod;
|
||||
} else {
|
||||
toEnable << mod;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
toEnable << collectMods(toEnable, m_requires, seen);
|
||||
toDisable << collectMods(toDisable, m_requiredBy, seen);
|
||||
|
||||
toDisable.removeIf([toEnable](Mod* m) { return toEnable.contains(m); });
|
||||
auto toList = [this](QList<Mod*> mods, bool shouldBeEnabled) {
|
||||
QModelIndexList list;
|
||||
for (auto mod : mods) {
|
||||
if (shouldBeEnabled != mod->enabled()) {
|
||||
auto row = m_resources_index[mod->internal_id()];
|
||||
list << index(row, 0);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
};
|
||||
|
||||
auto disableStatus = ResourceFolderModel::setResourceEnabled(toList(toDisable, false), EnableAction::DISABLE);
|
||||
auto enableStatus = ResourceFolderModel::setResourceEnabled(toList(toEnable, true), EnableAction::ENABLE);
|
||||
return disableStatus && enableStatus;
|
||||
}
|
||||
|
||||
QStringList reqToList(QSet<Mod*> l)
|
||||
{
|
||||
QStringList req;
|
||||
for (auto m : l) {
|
||||
req << m->name();
|
||||
}
|
||||
return req;
|
||||
}
|
||||
QStringList ModFolderModel::requiresList(QString id)
|
||||
{
|
||||
return reqToList(m_requires[id]);
|
||||
}
|
||||
QStringList ModFolderModel::requiredByList(QString id)
|
||||
{
|
||||
return reqToList(m_requiredBy[id]);
|
||||
}
|
||||
|
@ -39,13 +39,15 @@
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QDir>
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
|
||||
#include "Mod.h"
|
||||
#include "ResourceFolderModel.h"
|
||||
#include "minecraft/Component.h"
|
||||
#include "minecraft/mod/Resource.h"
|
||||
|
||||
class BaseInstance;
|
||||
class QFileSystemWatcher;
|
||||
@ -69,6 +71,8 @@ class ModFolderModel : public ResourceFolderModel {
|
||||
LoadersColumn,
|
||||
McVersionsColumn,
|
||||
ReleaseTypeColumn,
|
||||
RequiresColumn,
|
||||
RequiredByColumn,
|
||||
NUM_COLUMNS
|
||||
};
|
||||
ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
|
||||
@ -85,8 +89,20 @@ class ModFolderModel : public ResourceFolderModel {
|
||||
|
||||
bool isValid();
|
||||
|
||||
bool setResourceEnabled(const QModelIndexList& indexes, EnableAction action) override;
|
||||
QModelIndexList getAffectedMods(const QModelIndexList& indexes, EnableAction action);
|
||||
|
||||
RESOURCE_HELPERS(Mod)
|
||||
|
||||
public:
|
||||
QStringList requiresList(QString id);
|
||||
QStringList requiredByList(QString id);
|
||||
|
||||
private slots:
|
||||
void onParseSucceeded(int ticket, QString resource_id) override;
|
||||
void onParseFinished();
|
||||
|
||||
private:
|
||||
QHash<QString, QSet<Mod*>> m_requiredBy;
|
||||
QHash<QString, QSet<Mod*>> m_requires;
|
||||
};
|
||||
|
@ -58,7 +58,21 @@ enum class ResourceStatus {
|
||||
UNKNOWN, // Default status
|
||||
};
|
||||
|
||||
enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, MC_VERSIONS, LOADERS, RELEASE_TYPE };
|
||||
enum class SortType {
|
||||
NAME,
|
||||
DATE,
|
||||
VERSION,
|
||||
ENABLED,
|
||||
PACK_FORMAT,
|
||||
PROVIDER,
|
||||
SIZE,
|
||||
SIDE,
|
||||
MC_VERSIONS,
|
||||
LOADERS,
|
||||
RELEASE_TYPE,
|
||||
REQUIRES,
|
||||
REQUIRED_BY,
|
||||
};
|
||||
|
||||
enum class EnableAction { ENABLE, DISABLE, TOGGLE };
|
||||
|
||||
@ -152,9 +166,6 @@ class Resource : public QObject {
|
||||
|
||||
bool isMoreThanOneHardLink() const;
|
||||
|
||||
auto mod_id() const -> QString { return m_mod_id; }
|
||||
void setModId(const QString& modId) { m_mod_id = modId; }
|
||||
|
||||
protected:
|
||||
/* The file corresponding to this resource. */
|
||||
QFileInfo m_file_info;
|
||||
@ -165,7 +176,6 @@ class Resource : public QObject {
|
||||
QString m_internal_id;
|
||||
/* Name as reported via the file name. In the absence of a better name, this is shown to the user. */
|
||||
QString m_name;
|
||||
QString m_mod_id;
|
||||
|
||||
/* The type of file we're dealing with. */
|
||||
ResourceType m_type = ResourceType::UNKNOWN;
|
||||
|
@ -882,6 +882,7 @@ QList<Resource*> ResourceFolderModel::allResources()
|
||||
result.append((resource.get()));
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<Resource*> ResourceFolderModel::selectedResources(const QModelIndexList& indexes)
|
||||
{
|
||||
QList<Resource*> result;
|
||||
|
@ -224,7 +224,6 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
||||
pDep->version.is_currently_selected = true;
|
||||
pDep->pack->versions = { pDep->version };
|
||||
pDep->pack->versionsLoaded = true;
|
||||
|
||||
} catch (const JSONValidationError& e) {
|
||||
removePack(dep.addonId);
|
||||
qDebug() << doc;
|
||||
|
@ -62,6 +62,36 @@ ModDetails ReadMCModInfo(QByteArray contents)
|
||||
for (auto author : authors) {
|
||||
details.authors.append(author.toString());
|
||||
}
|
||||
|
||||
if (details.mod_id.startsWith("mod_")) {
|
||||
details.mod_id = details.mod_id.mid(4);
|
||||
}
|
||||
|
||||
auto addDep = [&details](QString dep) {
|
||||
if (dep == "mod_MinecraftForge" || dep == "Forge")
|
||||
return;
|
||||
if (dep.contains(":")) {
|
||||
dep = dep.section(":", 1);
|
||||
}
|
||||
if (dep.contains("@")) {
|
||||
dep = dep.section("@", 0, 0);
|
||||
}
|
||||
if (dep.startsWith("mod_")) {
|
||||
dep = dep.mid(4);
|
||||
}
|
||||
details.dependencies.append(dep);
|
||||
};
|
||||
|
||||
if (firstObj.contains("requiredMods")) {
|
||||
for (auto dep : firstObj.value("requiredMods").toArray()) {
|
||||
addDep(dep.toString());
|
||||
}
|
||||
} else if (firstObj.contains("dependencies")) {
|
||||
for (auto dep : firstObj.value("dependencies").toArray()) {
|
||||
addDep(dep.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return details;
|
||||
};
|
||||
QJsonParseError jsonError;
|
||||
@ -199,6 +229,52 @@ ModDetails ReadMCModTOML(QByteArray contents)
|
||||
}
|
||||
details.icon_file = logoFile;
|
||||
|
||||
auto parseDep = [&details](toml::array* dependencies) {
|
||||
static const QStringList ignoreModIds = { "", "forge", "neoforge", "minecraft" };
|
||||
if (!dependencies) {
|
||||
return;
|
||||
}
|
||||
auto isNeoForgeDep = [](toml::table* t) {
|
||||
auto type = (*t)["type"].as_string();
|
||||
return type && type->get() == "required";
|
||||
};
|
||||
auto isForgeDep = [](toml::table* t) {
|
||||
auto mandatory = (*t)["mandatory"].as_boolean();
|
||||
return mandatory && mandatory->get();
|
||||
};
|
||||
for (auto& dep : *dependencies) {
|
||||
auto dep_table = dep.as_table();
|
||||
if (!dep_table) {
|
||||
continue;
|
||||
}
|
||||
auto modId = (*dep_table)["modId"].as_string();
|
||||
if (!modId || ignoreModIds.contains(modId->get())) {
|
||||
continue;
|
||||
}
|
||||
if (isNeoForgeDep(dep_table) || isForgeDep(dep_table)) {
|
||||
details.dependencies.append(QString::fromStdString(modId->get()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (tomlData.contains("dependencies")) {
|
||||
auto depValue = tomlData["dependencies"];
|
||||
if (auto array = depValue.as_array()) {
|
||||
parseDep(array);
|
||||
} else if (auto depTable = depValue.as_table()) {
|
||||
auto expectedKey = details.mod_id.toStdString();
|
||||
if (!depTable->contains(expectedKey)) {
|
||||
for (auto [k, v] : *depTable) {
|
||||
expectedKey = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (auto array = (*depTable)[expectedKey].as_array()) {
|
||||
parseDep(array);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
@ -286,6 +362,18 @@ ModDetails ReadFabricModInfo(QByteArray contents)
|
||||
details.icon_file = icon.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (object.contains("depends")) {
|
||||
auto depends = object.value("depends");
|
||||
if (depends.isObject()) {
|
||||
auto obj = depends.toObject();
|
||||
for (auto key : obj.keys()) {
|
||||
if (key != "fabricloader" && key != "minecraft" && !key.startsWith("fabric-")) {
|
||||
details.dependencies.append(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return details;
|
||||
}
|
||||
@ -373,6 +461,29 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
|
||||
details.icon_file = icon.toString();
|
||||
}
|
||||
}
|
||||
if (object.contains("depends")) {
|
||||
auto depends = object.value("depends");
|
||||
if (depends.isArray()) {
|
||||
auto array = depends.toArray();
|
||||
for (auto obj : array) {
|
||||
QString modId;
|
||||
if (obj.isString()) {
|
||||
modId = obj.toString();
|
||||
} else if (obj.isObject()) {
|
||||
auto objValue = obj.toObject();
|
||||
modId = objValue.value("id").toString();
|
||||
if (objValue.contains("optional") && objValue.value("optional").toBool()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (modId != "minecraft" && !modId.startsWith("quilt_")) {
|
||||
details.dependencies.append(modId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (const Exception& e) {
|
||||
|
@ -122,7 +122,7 @@ auto getModLoaderAsString(ModLoaderType type) -> const QString
|
||||
case Cauldron:
|
||||
return "cauldron";
|
||||
case LiteLoader:
|
||||
return "liteloader";
|
||||
return "liteloader";
|
||||
case Fabric:
|
||||
return "fabric";
|
||||
case Quilt:
|
||||
@ -185,4 +185,40 @@ Side SideUtils::fromString(QString side)
|
||||
return Side::UniversalSide;
|
||||
return Side::UniversalSide;
|
||||
}
|
||||
|
||||
QString DependencyTypeUtils::toString(DependencyType type)
|
||||
{
|
||||
switch (type) {
|
||||
case DependencyType::REQUIRED:
|
||||
return "REQUIRED";
|
||||
case DependencyType::OPTIONAL:
|
||||
return "OPTIONAL";
|
||||
case DependencyType::INCOMPATIBLE:
|
||||
return "INCOMPATIBLE";
|
||||
case DependencyType::EMBEDDED:
|
||||
return "EMBEDDED";
|
||||
case DependencyType::TOOL:
|
||||
return "TOOL";
|
||||
case DependencyType::INCLUDE:
|
||||
return "INCLUDE";
|
||||
case DependencyType::UNKNOWN:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
DependencyType DependencyTypeUtils::fromString(const QString& str)
|
||||
{
|
||||
static const QHash<QString, DependencyType> map = {
|
||||
{ "REQUIRED", DependencyType::REQUIRED },
|
||||
{ "OPTIONAL", DependencyType::OPTIONAL },
|
||||
{ "INCOMPATIBLE", DependencyType::INCOMPATIBLE },
|
||||
{ "EMBEDDED", DependencyType::EMBEDDED },
|
||||
{ "TOOL", DependencyType::TOOL },
|
||||
{ "INCLUDE", DependencyType::INCLUDE },
|
||||
{ "UNKNOWN", DependencyType::UNKNOWN },
|
||||
};
|
||||
|
||||
return map.value(str.toUpper(), DependencyType::UNKNOWN);
|
||||
}
|
||||
} // namespace ModPlatform
|
||||
|
@ -54,6 +54,11 @@ QString toString(Side side);
|
||||
Side fromString(QString side);
|
||||
} // namespace SideUtils
|
||||
|
||||
namespace DependencyTypeUtils {
|
||||
QString toString(DependencyType type);
|
||||
DependencyType fromString(const QString& str);
|
||||
} // namespace DependencyTypeUtils
|
||||
|
||||
namespace ProviderCapabilities {
|
||||
const char* name(ResourceProvider);
|
||||
QString readableName(ResourceProvider);
|
||||
|
@ -122,6 +122,7 @@ auto V1::createModFormat([[maybe_unused]] const QDir& index_dir,
|
||||
if (mod.version_number.isNull()) // on CurseForge, there is only a version name - not a version number
|
||||
mod.version_number = mod_version.version;
|
||||
|
||||
mod.dependencies = mod_version.dependencies;
|
||||
return mod;
|
||||
}
|
||||
|
||||
@ -190,6 +191,16 @@ void V1::updateModIndex(const QDir& index_dir, Mod& mod)
|
||||
return;
|
||||
}
|
||||
|
||||
toml::array deps;
|
||||
for (auto dep : mod.dependencies) {
|
||||
auto tbl = toml::table{ { "addonId", dep.addonId.toString().toStdString() },
|
||||
{ "type", ModPlatform::DependencyTypeUtils::toString(dep.type).toStdString() } };
|
||||
if (!dep.version.isEmpty()) {
|
||||
tbl.emplace("version", dep.version.toStdString());
|
||||
}
|
||||
deps.push_back(tbl);
|
||||
}
|
||||
|
||||
// Put TOML data into the file
|
||||
QTextStream in_stream(&index_file);
|
||||
{
|
||||
@ -200,6 +211,7 @@ void V1::updateModIndex(const QDir& index_dir, Mod& mod)
|
||||
{ "x-prismlauncher-mc-versions", mcVersions },
|
||||
{ "x-prismlauncher-release-type", mod.releaseType.toString().toStdString() },
|
||||
{ "x-prismlauncher-version-number", mod.version_number.toStdString() },
|
||||
{ "x-prismlauncher-dependencies", deps },
|
||||
{ "download",
|
||||
toml::table{
|
||||
{ "mode", mod.mode.toStdString() },
|
||||
@ -330,6 +342,21 @@ auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod
|
||||
return {};
|
||||
}
|
||||
}
|
||||
{ // dependencies
|
||||
auto deps = table["x-prismlauncher-dependencies"].as_array();
|
||||
if (deps) {
|
||||
for (auto&& depNode : *deps) {
|
||||
auto dep = depNode.as_table();
|
||||
if (dep) {
|
||||
ModPlatform::Dependency d;
|
||||
d.addonId = stringEntry(*dep, "addonId");
|
||||
d.version = stringEntry(*dep, "version");
|
||||
d.type = ModPlatform::DependencyTypeUtils::fromString(stringEntry(*dep, "type"));
|
||||
mod.dependencies << d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
@ -55,6 +55,8 @@ class V1 {
|
||||
QVariant project_id{};
|
||||
QString version_number{};
|
||||
|
||||
QList<ModPlatform::Dependency> dependencies;
|
||||
|
||||
public:
|
||||
// This is a totally heuristic, but should work for now.
|
||||
auto isValid() const -> bool { return !slug.isEmpty() && !project_id.isNull(); }
|
||||
|
@ -37,6 +37,7 @@
|
||||
*/
|
||||
|
||||
#include "ModFolderPage.h"
|
||||
#include "minecraft/mod/Resource.h"
|
||||
#include "ui/dialogs/ExportToModListDialog.h"
|
||||
#include "ui_ExternalResourcesPage.h"
|
||||
|
||||
@ -90,7 +91,7 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
|
||||
auto depsDisabled = APPLICATION->settings()->getSetting("ModDependenciesDisabled");
|
||||
ui->actionVerifyItemDependencies->setVisible(!depsDisabled->get().toBool());
|
||||
connect(depsDisabled.get(), &Setting::SettingChanged, this,
|
||||
[this](const Setting& setting, const QVariant& value) { ui->actionVerifyItemDependencies->setVisible(!value.toBool()); });
|
||||
[this](const Setting&, const QVariant& value) { ui->actionVerifyItemDependencies->setVisible(!value.toBool()); });
|
||||
|
||||
updateMenu->addAction(ui->actionResetItemMetadata);
|
||||
connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata);
|
||||
@ -118,7 +119,7 @@ void ModFolderPage::updateFrame(const QModelIndex& current, [[maybe_unused]] con
|
||||
auto sourceCurrent = m_filterModel->mapToSource(current);
|
||||
int row = sourceCurrent.row();
|
||||
const Mod& mod = m_model->at(row);
|
||||
ui->frame->updateWithMod(mod);
|
||||
ui->frame->updateWithMod(mod, m_model->requiresList(mod.mod_id()), m_model->requiredByList(mod.mod_id()));
|
||||
}
|
||||
|
||||
void ModFolderPage::removeItems(const QItemSelection& selection)
|
||||
@ -133,7 +134,22 @@ void ModFolderPage::removeItems(const QItemSelection& selection)
|
||||
if (response != QMessageBox::Yes)
|
||||
return;
|
||||
}
|
||||
m_model->deleteResources(selection.indexes());
|
||||
|
||||
auto indexes = selection.indexes();
|
||||
auto affected = m_model->getAffectedMods(indexes, EnableAction::DISABLE);
|
||||
if (!affected.isEmpty()) {
|
||||
auto response = CustomMessageBox::selectable(this, tr("Confirm Disable"),
|
||||
tr("The mods you are tring to disable are required by %1 mods.\n"
|
||||
"Do you want to disable them?")
|
||||
.arg(affected.length()),
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
|
||||
if (response != QMessageBox::Yes) {
|
||||
m_model->setResourceEnabled(affected, EnableAction::DISABLE);
|
||||
}
|
||||
}
|
||||
m_model->deleteResources(indexes);
|
||||
}
|
||||
|
||||
void ModFolderPage::downloadMods()
|
||||
|
@ -54,28 +54,34 @@ void setupLinkToolTip(QLabel* label)
|
||||
});
|
||||
}
|
||||
|
||||
InfoFrame::InfoFrame(QWidget* parent) : QFrame(parent), ui(new Ui::InfoFrame)
|
||||
InfoFrame::InfoFrame(QWidget* parent) : QFrame(parent), m_ui(new Ui::InfoFrame)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->descriptionLabel->setHidden(true);
|
||||
ui->nameLabel->setHidden(true);
|
||||
ui->licenseLabel->setHidden(true);
|
||||
ui->issueTrackerLabel->setHidden(true);
|
||||
m_ui->setupUi(this);
|
||||
m_ui->descriptionLabel->setHidden(true);
|
||||
m_ui->nameLabel->setHidden(true);
|
||||
m_ui->licenseLabel->setHidden(true);
|
||||
m_ui->issueTrackerLabel->setHidden(true);
|
||||
|
||||
setupLinkToolTip(ui->iconLabel);
|
||||
setupLinkToolTip(ui->descriptionLabel);
|
||||
setupLinkToolTip(ui->nameLabel);
|
||||
setupLinkToolTip(ui->licenseLabel);
|
||||
setupLinkToolTip(ui->issueTrackerLabel);
|
||||
setupLinkToolTip(m_ui->iconLabel);
|
||||
setupLinkToolTip(m_ui->descriptionLabel);
|
||||
setupLinkToolTip(m_ui->nameLabel);
|
||||
setupLinkToolTip(m_ui->licenseLabel);
|
||||
setupLinkToolTip(m_ui->issueTrackerLabel);
|
||||
updateHiddenState();
|
||||
connect(m_ui->moreInfoBtn, &QPushButton::clicked, this, [this]() {
|
||||
auto nextIndex = (m_ui->infoStacked->currentIndex() + 1) % 2;
|
||||
m_ui->infoStacked->setCurrentIndex(nextIndex);
|
||||
m_ui->moreInfoBtn->setText(nextIndex == 0 ? ">" : "<");
|
||||
});
|
||||
m_ui->moreInfoBtn->hide();
|
||||
}
|
||||
|
||||
InfoFrame::~InfoFrame()
|
||||
{
|
||||
delete ui;
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
void InfoFrame::updateWithMod(Mod const& m)
|
||||
void InfoFrame::updateWithMod(Mod const& m, QStringList requiresList, QStringList requiredByList)
|
||||
{
|
||||
if (m.type() == ResourceType::FOLDER) {
|
||||
clear();
|
||||
@ -141,6 +147,26 @@ void InfoFrame::updateWithMod(Mod const& m)
|
||||
issueTracker += "<a href=\"" + m.issueTracker() + "\">" + m.issueTracker() + "</a>";
|
||||
}
|
||||
setIssueTracker(issueTracker);
|
||||
if (requiredByList.isEmpty()) {
|
||||
m_ui->requiredGB->hide();
|
||||
} else {
|
||||
m_ui->requiredGB->show();
|
||||
m_ui->requiredView->clear();
|
||||
m_ui->requiredView->addItems(requiredByList);
|
||||
}
|
||||
|
||||
if (requiresList.isEmpty()) {
|
||||
m_ui->requiresGB->hide();
|
||||
} else {
|
||||
m_ui->requiresGB->show();
|
||||
m_ui->requiresView->clear();
|
||||
m_ui->requiresView->addItems(requiresList);
|
||||
}
|
||||
if (requiresList.isEmpty() && requiredByList.isEmpty()) {
|
||||
m_ui->infoStacked->setCurrentIndex(0);
|
||||
m_ui->moreInfoBtn->setText(">");
|
||||
}
|
||||
m_ui->moreInfoBtn->setHidden(requiresList.isEmpty() && requiredByList.isEmpty());
|
||||
}
|
||||
|
||||
void InfoFrame::updateWithResource(const Resource& resource)
|
||||
@ -227,7 +253,8 @@ void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack)
|
||||
setImage(resource_pack.image({ 64, 64 }));
|
||||
}
|
||||
|
||||
void InfoFrame::updateWithDataPack(DataPack& data_pack) {
|
||||
void InfoFrame::updateWithDataPack(DataPack& data_pack)
|
||||
{
|
||||
setName(renderColorCodes(data_pack.name()));
|
||||
setDescription(renderColorCodes(data_pack.description()));
|
||||
setImage(data_pack.image({ 64, 64 }));
|
||||
@ -254,12 +281,13 @@ void InfoFrame::clear()
|
||||
setImage();
|
||||
setLicense();
|
||||
setIssueTracker();
|
||||
m_ui->moreInfoBtn->hide();
|
||||
}
|
||||
|
||||
void InfoFrame::updateHiddenState()
|
||||
{
|
||||
if (ui->descriptionLabel->isHidden() && ui->nameLabel->isHidden() && ui->licenseLabel->isHidden() &&
|
||||
ui->issueTrackerLabel->isHidden()) {
|
||||
if (m_ui->descriptionLabel->isHidden() && m_ui->nameLabel->isHidden() && m_ui->licenseLabel->isHidden() &&
|
||||
m_ui->issueTrackerLabel->isHidden()) {
|
||||
setHidden(true);
|
||||
} else {
|
||||
setHidden(false);
|
||||
@ -269,10 +297,10 @@ void InfoFrame::updateHiddenState()
|
||||
void InfoFrame::setName(QString text)
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
ui->nameLabel->setHidden(true);
|
||||
m_ui->nameLabel->setHidden(true);
|
||||
} else {
|
||||
ui->nameLabel->setText(text);
|
||||
ui->nameLabel->setHidden(false);
|
||||
m_ui->nameLabel->setText(text);
|
||||
m_ui->nameLabel->setHidden(false);
|
||||
}
|
||||
updateHiddenState();
|
||||
}
|
||||
@ -280,14 +308,14 @@ void InfoFrame::setName(QString text)
|
||||
void InfoFrame::setDescription(QString text)
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
ui->descriptionLabel->setHidden(true);
|
||||
m_ui->descriptionLabel->setHidden(true);
|
||||
updateHiddenState();
|
||||
return;
|
||||
} else {
|
||||
ui->descriptionLabel->setHidden(false);
|
||||
m_ui->descriptionLabel->setHidden(false);
|
||||
updateHiddenState();
|
||||
}
|
||||
ui->descriptionLabel->setToolTip("");
|
||||
m_ui->descriptionLabel->setToolTip("");
|
||||
QString intermediatetext = text.trimmed();
|
||||
bool prev(false);
|
||||
QChar rem('\n');
|
||||
@ -309,8 +337,8 @@ void InfoFrame::setDescription(QString text)
|
||||
doc.setHtml(text);
|
||||
|
||||
if (doc.characterCount() > maxCharacterElide) {
|
||||
ui->descriptionLabel->setOpenExternalLinks(false);
|
||||
ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); // This allows injecting HTML here.
|
||||
m_ui->descriptionLabel->setOpenExternalLinks(false);
|
||||
m_ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); // This allows injecting HTML here.
|
||||
m_description = text;
|
||||
|
||||
// move the cursor to the character elide, doesn't see html
|
||||
@ -323,25 +351,25 @@ void InfoFrame::setDescription(QString text)
|
||||
cursor.insertHtml("<a href=\"#mod_desc\">...</a>");
|
||||
|
||||
labeltext.append(doc.toHtml());
|
||||
connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler);
|
||||
connect(m_ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler);
|
||||
} else {
|
||||
ui->descriptionLabel->setTextFormat(Qt::TextFormat::AutoText);
|
||||
m_ui->descriptionLabel->setTextFormat(Qt::TextFormat::AutoText);
|
||||
labeltext.append(finaltext);
|
||||
}
|
||||
ui->descriptionLabel->setText(labeltext);
|
||||
m_ui->descriptionLabel->setText(labeltext);
|
||||
}
|
||||
|
||||
void InfoFrame::setLicense(QString text)
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
ui->licenseLabel->setHidden(true);
|
||||
m_ui->licenseLabel->setHidden(true);
|
||||
updateHiddenState();
|
||||
return;
|
||||
} else {
|
||||
ui->licenseLabel->setHidden(false);
|
||||
m_ui->licenseLabel->setHidden(false);
|
||||
updateHiddenState();
|
||||
}
|
||||
ui->licenseLabel->setToolTip("");
|
||||
m_ui->licenseLabel->setToolTip("");
|
||||
QString intermediatetext = text.trimmed();
|
||||
bool prev(false);
|
||||
QChar rem('\n');
|
||||
@ -357,26 +385,26 @@ void InfoFrame::setLicense(QString text)
|
||||
QString labeltext;
|
||||
labeltext.reserve(300);
|
||||
if (finaltext.length() > 290) {
|
||||
ui->licenseLabel->setOpenExternalLinks(false);
|
||||
ui->licenseLabel->setTextFormat(Qt::TextFormat::RichText);
|
||||
m_ui->licenseLabel->setOpenExternalLinks(false);
|
||||
m_ui->licenseLabel->setTextFormat(Qt::TextFormat::RichText);
|
||||
m_license = text;
|
||||
// This allows injecting HTML here.
|
||||
labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>");
|
||||
connect(ui->licenseLabel, &QLabel::linkActivated, this, &InfoFrame::licenseEllipsisHandler);
|
||||
connect(m_ui->licenseLabel, &QLabel::linkActivated, this, &InfoFrame::licenseEllipsisHandler);
|
||||
} else {
|
||||
ui->licenseLabel->setTextFormat(Qt::TextFormat::AutoText);
|
||||
m_ui->licenseLabel->setTextFormat(Qt::TextFormat::AutoText);
|
||||
labeltext.append(finaltext);
|
||||
}
|
||||
ui->licenseLabel->setText(labeltext);
|
||||
m_ui->licenseLabel->setText(labeltext);
|
||||
}
|
||||
|
||||
void InfoFrame::setIssueTracker(QString text)
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
ui->issueTrackerLabel->setHidden(true);
|
||||
m_ui->issueTrackerLabel->setHidden(true);
|
||||
} else {
|
||||
ui->issueTrackerLabel->setText(text);
|
||||
ui->issueTrackerLabel->setHidden(false);
|
||||
m_ui->issueTrackerLabel->setText(text);
|
||||
m_ui->issueTrackerLabel->setHidden(false);
|
||||
}
|
||||
updateHiddenState();
|
||||
}
|
||||
@ -384,10 +412,10 @@ void InfoFrame::setIssueTracker(QString text)
|
||||
void InfoFrame::setImage(QPixmap img)
|
||||
{
|
||||
if (img.isNull()) {
|
||||
ui->iconLabel->setHidden(true);
|
||||
m_ui->iconLabel->setHidden(true);
|
||||
} else {
|
||||
ui->iconLabel->setHidden(false);
|
||||
ui->iconLabel->setPixmap(img);
|
||||
m_ui->iconLabel->setHidden(false);
|
||||
m_ui->iconLabel->setPixmap(img);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ class InfoFrame : public QFrame {
|
||||
|
||||
void clear();
|
||||
|
||||
void updateWithMod(Mod const& m);
|
||||
void updateWithMod(Mod const& m, QStringList requiresList = {}, QStringList requiredByList = {});
|
||||
void updateWithResource(Resource const& resource);
|
||||
void updateWithResourcePack(ResourcePack& rp);
|
||||
void updateWithDataPack(DataPack& rp);
|
||||
@ -78,7 +78,7 @@ class InfoFrame : public QFrame {
|
||||
void updateHiddenState();
|
||||
|
||||
private:
|
||||
Ui::InfoFrame* ui;
|
||||
Ui::InfoFrame* m_ui;
|
||||
QString m_description;
|
||||
QString m_license;
|
||||
class QMessageBox* m_current_box = nullptr;
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>527</width>
|
||||
<height>113</height>
|
||||
<height>130</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -19,7 +19,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>120</height>
|
||||
<height>130</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
@ -35,6 +35,169 @@
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="moreInfoBtn">
|
||||
<property name="text">
|
||||
<string>></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="2">
|
||||
<widget class="QStackedWidget" name="infoStacked">
|
||||
<widget class="QWidget" name="base">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="descriptionLabel">
|
||||
<property name="toolTip">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QLabel" name="issueTrackerLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="licenseLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="extra">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="requiresGB">
|
||||
<property name="title">
|
||||
<string>Requires</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QListWidget" name="requiresView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="resizeMode">
|
||||
<enum>QListView::Adjust</enum>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="viewMode">
|
||||
<enum>QListView::IconMode</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="requiredGB">
|
||||
<property name="title">
|
||||
<string>Required by</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QListWidget" name="requiredView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="movement">
|
||||
<enum>QListView::Static</enum>
|
||||
</property>
|
||||
<property name="resizeMode">
|
||||
<enum>QListView::Adjust</enum>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="viewMode">
|
||||
<enum>QListView::IconMode</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="2">
|
||||
<widget class="QLabel" name="iconLabel">
|
||||
<property name="minimumSize">
|
||||
@ -60,97 +223,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="descriptionLabel">
|
||||
<property name="toolTip">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="licenseLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="issueTrackerLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user