Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into curse
This commit is contained in:
commit
90df092174
@ -3,6 +3,8 @@
|
|||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QPixmapCache>
|
#include <QPixmapCache>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
#include <QTime>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
#define GET_TYPE() \
|
#define GET_TYPE() \
|
||||||
Qt::ConnectionType type; \
|
Qt::ConnectionType type; \
|
||||||
@ -60,6 +62,8 @@ class PixmapCache final : public QObject {
|
|||||||
DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&)
|
DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&)
|
||||||
DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&)
|
DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&)
|
||||||
DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int)
|
DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int)
|
||||||
|
DEFINE_FUNC_NO_PARAM(markCacheMissByEviciton, bool)
|
||||||
|
DEFINE_FUNC_ONE_PARAM(setFastEvictionThreshold, bool, int)
|
||||||
|
|
||||||
// NOTE: Every function returns something non-void to simplify the macros.
|
// NOTE: Every function returns something non-void to simplify the macros.
|
||||||
private slots:
|
private slots:
|
||||||
@ -90,6 +94,43 @@ class PixmapCache final : public QObject {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark that a cache miss occurred because of a eviction if too many of these occur too fast the cache size is increased
|
||||||
|
* @return if the cache size was increased
|
||||||
|
*/
|
||||||
|
bool _markCacheMissByEviciton()
|
||||||
|
{
|
||||||
|
auto now = QTime::currentTime();
|
||||||
|
if (!m_last_cache_miss_by_eviciton.isNull()) {
|
||||||
|
auto diff = m_last_cache_miss_by_eviciton.msecsTo(now);
|
||||||
|
if (diff < 1000) { // less than a second ago
|
||||||
|
++m_consecutive_fast_evicitons;
|
||||||
|
} else {
|
||||||
|
m_consecutive_fast_evicitons = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_last_cache_miss_by_eviciton = now;
|
||||||
|
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
|
||||||
|
// double the cache size
|
||||||
|
auto newSize = _cacheLimit() * 2;
|
||||||
|
qDebug() << m_consecutive_fast_evicitons << "pixmap cache misses by eviction happened too fast, doubling cache size to"
|
||||||
|
<< newSize;
|
||||||
|
_setCacheLimit(newSize);
|
||||||
|
m_consecutive_fast_evicitons = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _setFastEvictionThreshold(int threshold)
|
||||||
|
{
|
||||||
|
m_consecutive_fast_evicitons_threshold = threshold;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static PixmapCache* s_instance;
|
static PixmapCache* s_instance;
|
||||||
|
QTime m_last_cache_miss_by_eviciton;
|
||||||
|
int m_consecutive_fast_evicitons = 0;
|
||||||
|
int m_consecutive_fast_evicitons_threshold = 15;
|
||||||
};
|
};
|
||||||
|
@ -85,7 +85,7 @@ void JavaChecker::performCheck()
|
|||||||
process->setProgram(m_path);
|
process->setProgram(m_path);
|
||||||
process->setProcessChannelMode(QProcess::SeparateChannels);
|
process->setProcessChannelMode(QProcess::SeparateChannels);
|
||||||
process->setProcessEnvironment(CleanEnviroment());
|
process->setProcessEnvironment(CleanEnviroment());
|
||||||
qDebug() << "Running java checker: " + m_path + args.join(" ");;
|
qDebug() << "Running java checker:" << m_path << args.join(" ");
|
||||||
|
|
||||||
connect(process.get(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &JavaChecker::finished);
|
connect(process.get(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &JavaChecker::finished);
|
||||||
connect(process.get(), &QProcess::errorOccurred, this, &JavaChecker::error);
|
connect(process.get(), &QProcess::errorOccurred, this, &JavaChecker::error);
|
||||||
|
@ -41,9 +41,11 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
#include "MTPixmapCache.h"
|
||||||
#include "MetadataHandler.h"
|
#include "MetadataHandler.h"
|
||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
#include "minecraft/mod/ModDetails.h"
|
#include "minecraft/mod/ModDetails.h"
|
||||||
|
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||||
|
|
||||||
static ModPlatform::ProviderCapabilities ProviderCaps;
|
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||||
|
|
||||||
@ -201,7 +203,10 @@ void Mod::finishResolvingWithDetails(ModDetails&& details)
|
|||||||
m_local_details = std::move(details);
|
m_local_details = std::move(details);
|
||||||
if (metadata)
|
if (metadata)
|
||||||
setMetadata(std::move(metadata));
|
setMetadata(std::move(metadata));
|
||||||
};
|
if (!iconPath().isEmpty()) {
|
||||||
|
m_pack_image_cache_key.was_read_attempt = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto Mod::provider() const -> std::optional<QString>
|
auto Mod::provider() const -> std::optional<QString>
|
||||||
{
|
{
|
||||||
@ -210,6 +215,56 @@ auto Mod::provider() const -> std::optional<QString>
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Mod::licenses() const -> const QList<ModLicense>&
|
||||||
|
{
|
||||||
|
return details().licenses;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Mod::issueTracker() const -> QString
|
||||||
|
{
|
||||||
|
return details().issue_tracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mod::setIcon(QImage new_image) const
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_data_lock);
|
||||||
|
|
||||||
|
Q_ASSERT(!new_image.isNull());
|
||||||
|
|
||||||
|
if (m_pack_image_cache_key.key.isValid())
|
||||||
|
PixmapCache::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));
|
||||||
|
|
||||||
|
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
|
||||||
|
m_pack_image_cache_key.was_ever_used = true;
|
||||||
|
m_pack_image_cache_key.was_read_attempt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
|
||||||
|
{
|
||||||
|
QPixmap cached_image;
|
||||||
|
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
||||||
|
if (size.isNull())
|
||||||
|
return cached_image;
|
||||||
|
return cached_image.scaled(size, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No valid image we can get
|
||||||
|
if ((!m_pack_image_cache_key.was_ever_used && m_pack_image_cache_key.was_read_attempt) || iconPath().isEmpty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (m_pack_image_cache_key.was_ever_used) {
|
||||||
|
qDebug() << "Mod" << name() << "Had it's icon evicted form the cache. reloading...";
|
||||||
|
PixmapCache::markCacheMissByEviciton();
|
||||||
|
}
|
||||||
|
// Image got evicted from the cache or an attempt to load it has not been made. load it and retry.
|
||||||
|
m_pack_image_cache_key.was_read_attempt = true;
|
||||||
|
ModUtils::loadIconFile(*this);
|
||||||
|
return icon(size);
|
||||||
|
}
|
||||||
|
|
||||||
bool Mod::valid() const
|
bool Mod::valid() const
|
||||||
{
|
{
|
||||||
return !m_local_details.mod_id.isEmpty();
|
return !m_local_details.mod_id.isEmpty();
|
||||||
|
@ -38,6 +38,10 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QPixmapCache>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
@ -64,6 +68,15 @@ public:
|
|||||||
auto authors() const -> QStringList;
|
auto authors() const -> QStringList;
|
||||||
auto status() const -> ModStatus;
|
auto status() const -> ModStatus;
|
||||||
auto provider() const -> std::optional<QString>;
|
auto provider() const -> std::optional<QString>;
|
||||||
|
auto licenses() const -> const QList<ModLicense>&;
|
||||||
|
auto issueTracker() const -> QString;
|
||||||
|
|
||||||
|
/** Get the intneral path to the mod's icon file*/
|
||||||
|
QString iconPath() const { return m_local_details.icon_file; };
|
||||||
|
/** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */
|
||||||
|
[[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
||||||
|
/** Thread-safe. */
|
||||||
|
void setIcon(QImage new_image) const;
|
||||||
|
|
||||||
auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
|
auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
|
||||||
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
|
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
|
||||||
@ -85,4 +98,13 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
ModDetails m_local_details;
|
ModDetails m_local_details;
|
||||||
|
|
||||||
|
mutable QMutex m_data_lock;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
QPixmapCache::Key key;
|
||||||
|
bool was_ever_used = false;
|
||||||
|
bool was_read_attempt = false;
|
||||||
|
} mutable m_pack_image_cache_key;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
#include "minecraft/mod/MetadataHandler.h"
|
#include "minecraft/mod/MetadataHandler.h"
|
||||||
|
|
||||||
@ -49,6 +50,84 @@ enum class ModStatus {
|
|||||||
Unknown, // Default status
|
Unknown, // Default status
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ModLicense {
|
||||||
|
QString name = {};
|
||||||
|
QString id = {};
|
||||||
|
QString url = {};
|
||||||
|
QString description = {};
|
||||||
|
|
||||||
|
ModLicense() {}
|
||||||
|
|
||||||
|
ModLicense(const QString license) {
|
||||||
|
// FIXME: come up with a better license parseing.
|
||||||
|
// handle SPDX identifiers? https://spdx.org/licenses/
|
||||||
|
auto parts = license.split(' ');
|
||||||
|
QStringList notNameParts = {};
|
||||||
|
for (auto part : parts) {
|
||||||
|
auto url = QUrl(part);
|
||||||
|
if (part.startsWith("(") && part.endsWith(")"))
|
||||||
|
url = QUrl(part.mid(1, part.size() - 2));
|
||||||
|
|
||||||
|
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
|
||||||
|
this->url = url.toString();
|
||||||
|
notNameParts.append(part);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto part : notNameParts) {
|
||||||
|
parts.removeOne(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto licensePart = parts.join(' ');
|
||||||
|
this->name = licensePart;
|
||||||
|
this->description = licensePart;
|
||||||
|
|
||||||
|
if (parts.size() == 1) {
|
||||||
|
this->id = parts.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ModLicense(const QString name, const QString id, const QString url, const QString description) {
|
||||||
|
this->name = name;
|
||||||
|
this->id = id;
|
||||||
|
this->url = url;
|
||||||
|
this->description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModLicense(const ModLicense& other)
|
||||||
|
: name(other.name)
|
||||||
|
, id(other.id)
|
||||||
|
, url(other.url)
|
||||||
|
, description(other.description)
|
||||||
|
{}
|
||||||
|
|
||||||
|
ModLicense& operator=(const ModLicense& other)
|
||||||
|
{
|
||||||
|
this->name = other.name;
|
||||||
|
this->id = other.id;
|
||||||
|
this->url = other.url;
|
||||||
|
this->description = other.description;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModLicense& operator=(const ModLicense&& other)
|
||||||
|
{
|
||||||
|
this->name = other.name;
|
||||||
|
this->id = other.id;
|
||||||
|
this->url = other.url;
|
||||||
|
this->description = other.description;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEmpty() {
|
||||||
|
return this->name.isEmpty() && this->id.isEmpty() && this->url.isEmpty() && this->description.isEmpty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct ModDetails
|
struct ModDetails
|
||||||
{
|
{
|
||||||
/* Mod ID as defined in the ModLoader-specific metadata */
|
/* Mod ID as defined in the ModLoader-specific metadata */
|
||||||
@ -72,6 +151,15 @@ struct ModDetails
|
|||||||
/* List of the author's names */
|
/* List of the author's names */
|
||||||
QStringList authors = {};
|
QStringList authors = {};
|
||||||
|
|
||||||
|
/* Issue Tracker URL */
|
||||||
|
QString issue_tracker = {};
|
||||||
|
|
||||||
|
/* License */
|
||||||
|
QList<ModLicense> licenses = {};
|
||||||
|
|
||||||
|
/* Path of mod logo */
|
||||||
|
QString icon_file = {};
|
||||||
|
|
||||||
/* Installation status of the mod */
|
/* Installation status of the mod */
|
||||||
ModStatus status = ModStatus::Unknown;
|
ModStatus status = ModStatus::Unknown;
|
||||||
|
|
||||||
@ -89,6 +177,9 @@ struct ModDetails
|
|||||||
, homeurl(other.homeurl)
|
, homeurl(other.homeurl)
|
||||||
, description(other.description)
|
, description(other.description)
|
||||||
, authors(other.authors)
|
, authors(other.authors)
|
||||||
|
, issue_tracker(other.issue_tracker)
|
||||||
|
, licenses(other.licenses)
|
||||||
|
, icon_file(other.icon_file)
|
||||||
, status(other.status)
|
, status(other.status)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@ -101,6 +192,9 @@ struct ModDetails
|
|||||||
this->homeurl = other.homeurl;
|
this->homeurl = other.homeurl;
|
||||||
this->description = other.description;
|
this->description = other.description;
|
||||||
this->authors = other.authors;
|
this->authors = other.authors;
|
||||||
|
this->issue_tracker = other.issue_tracker;
|
||||||
|
this->licenses = other.licenses;
|
||||||
|
this->icon_file = other.icon_file;
|
||||||
this->status = other.status;
|
this->status = other.status;
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
@ -115,6 +209,9 @@ struct ModDetails
|
|||||||
this->homeurl = other.homeurl;
|
this->homeurl = other.homeurl;
|
||||||
this->description = other.description;
|
this->description = other.description;
|
||||||
this->authors = other.authors;
|
this->authors = other.authors;
|
||||||
|
this->issue_tracker = other.issue_tracker;
|
||||||
|
this->licenses = other.licenses;
|
||||||
|
this->icon_file = other.icon_file;
|
||||||
this->status = other.status;
|
this->status = other.status;
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
#include "ModFolderModel.h"
|
#include "ModFolderModel.h"
|
||||||
|
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
|
#include <qheaderview.h>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
@ -52,12 +53,14 @@
|
|||||||
|
|
||||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
||||||
#include "modplatform/ModIndex.h"
|
|
||||||
|
|
||||||
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
|
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
|
||||||
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
|
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
|
||||||
{
|
{
|
||||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
|
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" });
|
||||||
|
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") });
|
||||||
|
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME , SortType::VERSION, SortType::DATE, SortType::PROVIDER};
|
||||||
|
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents};
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
||||||
@ -118,7 +121,9 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
|||||||
case Qt::DecorationRole: {
|
case Qt::DecorationRole: {
|
||||||
if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
||||||
return APPLICATION->getThemedIcon("status-yellow");
|
return APPLICATION->getThemedIcon("status-yellow");
|
||||||
|
if (column == ImageColumn) {
|
||||||
|
return at(row)->icon({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
case Qt::CheckStateRole:
|
case Qt::CheckStateRole:
|
||||||
@ -142,15 +147,12 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
|
|||||||
switch (section)
|
switch (section)
|
||||||
{
|
{
|
||||||
case ActiveColumn:
|
case ActiveColumn:
|
||||||
return QString();
|
|
||||||
case NameColumn:
|
case NameColumn:
|
||||||
return tr("Name");
|
|
||||||
case VersionColumn:
|
case VersionColumn:
|
||||||
return tr("Version");
|
|
||||||
case DateColumn:
|
case DateColumn:
|
||||||
return tr("Last changed");
|
|
||||||
case ProviderColumn:
|
case ProviderColumn:
|
||||||
return tr("Provider");
|
case ImageColumn:
|
||||||
|
return columnNames().at(section);
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ public:
|
|||||||
enum Columns
|
enum Columns
|
||||||
{
|
{
|
||||||
ActiveColumn = 0,
|
ActiveColumn = 0,
|
||||||
|
ImageColumn,
|
||||||
NameColumn,
|
NameColumn,
|
||||||
VersionColumn,
|
VersionColumn,
|
||||||
DateColumn,
|
DateColumn,
|
||||||
@ -77,6 +78,8 @@ public:
|
|||||||
};
|
};
|
||||||
ModFolderModel(const QString &dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
|
ModFolderModel(const QString &dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
|
||||||
|
|
||||||
|
virtual QString id() const override { return "mods"; }
|
||||||
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
|
@ -8,12 +8,15 @@
|
|||||||
#include <QStyle>
|
#include <QStyle>
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QMenu>
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
|
||||||
|
#include "QVariantUtils.h"
|
||||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||||
|
|
||||||
|
#include "settings/Setting.h"
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
|
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
|
||||||
@ -471,10 +474,10 @@ QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientatio
|
|||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole:
|
||||||
switch (section) {
|
switch (section) {
|
||||||
|
case ACTIVE_COLUMN:
|
||||||
case NAME_COLUMN:
|
case NAME_COLUMN:
|
||||||
return tr("Name");
|
|
||||||
case DATE_COLUMN:
|
case DATE_COLUMN:
|
||||||
return tr("Last modified");
|
return columnNames().at(section);
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -500,6 +503,75 @@ QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientatio
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ResourceFolderModel::setupHeaderAction(QAction* act, int column)
|
||||||
|
{
|
||||||
|
Q_ASSERT(act);
|
||||||
|
|
||||||
|
act->setText(columnNames().at(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceFolderModel::saveHiddenColumn(int column, bool hidden)
|
||||||
|
{
|
||||||
|
auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
|
||||||
|
auto setting = (m_instance->settings()->contains(setting_name)) ?
|
||||||
|
m_instance->settings()->getSetting(setting_name) : m_instance->settings()->registerSetting(setting_name);
|
||||||
|
|
||||||
|
auto hiddenColumns = setting->get().toStringList();
|
||||||
|
auto name = columnNames(false).at(column);
|
||||||
|
auto index = hiddenColumns.indexOf(name);
|
||||||
|
if (index >= 0 && !hidden) {
|
||||||
|
hiddenColumns.removeAt(index);
|
||||||
|
} else if ( index < 0 && hidden) {
|
||||||
|
hiddenColumns.append(name);
|
||||||
|
}
|
||||||
|
setting->set(hiddenColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceFolderModel::loadHiddenColumns(QTreeView *tree)
|
||||||
|
{
|
||||||
|
auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
|
||||||
|
auto setting = (m_instance->settings()->contains(setting_name)) ?
|
||||||
|
m_instance->settings()->getSetting(setting_name) : m_instance->settings()->registerSetting(setting_name);
|
||||||
|
|
||||||
|
auto hiddenColumns = setting->get().toStringList();
|
||||||
|
auto col_names = columnNames(false);
|
||||||
|
for (auto col_name : hiddenColumns) {
|
||||||
|
auto index = col_names.indexOf(col_name);
|
||||||
|
if (index >= 0)
|
||||||
|
tree->setColumnHidden(index, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
|
||||||
|
{
|
||||||
|
auto menu = new QMenu(tree);
|
||||||
|
|
||||||
|
menu->addSeparator()->setText(tr("Show / Hide Columns"));
|
||||||
|
|
||||||
|
for (int col = 0; col < columnCount(); ++col) {
|
||||||
|
auto act = new QAction(menu);
|
||||||
|
setupHeaderAction(act, col);
|
||||||
|
|
||||||
|
act->setCheckable(true);
|
||||||
|
act->setChecked(!tree->isColumnHidden(col));
|
||||||
|
|
||||||
|
connect(act, &QAction::toggled, tree, [this, col, tree](bool toggled){
|
||||||
|
tree->setColumnHidden(col, !toggled);
|
||||||
|
for(int c = 0; c < columnCount(); ++c) {
|
||||||
|
if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents)
|
||||||
|
tree->resizeColumnToContents(c);
|
||||||
|
}
|
||||||
|
saveHiddenColumn(col, !toggled);
|
||||||
|
});
|
||||||
|
|
||||||
|
menu->addAction(act);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* parent)
|
QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* parent)
|
||||||
{
|
{
|
||||||
return new ProxyModel(parent);
|
return new ProxyModel(parent);
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QTreeView>
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
@ -29,6 +32,8 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||||||
ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true);
|
ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true);
|
||||||
~ResourceFolderModel() override;
|
~ResourceFolderModel() override;
|
||||||
|
|
||||||
|
virtual QString id() const { return "resource"; }
|
||||||
|
|
||||||
/** Starts watching the paths for changes.
|
/** Starts watching the paths for changes.
|
||||||
*
|
*
|
||||||
* Returns whether starting to watch all the paths was successful.
|
* Returns whether starting to watch all the paths was successful.
|
||||||
@ -92,6 +97,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||||||
|
|
||||||
/* Basic columns */
|
/* Basic columns */
|
||||||
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
|
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
|
||||||
|
QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; };
|
||||||
|
|
||||||
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
|
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
|
||||||
[[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
|
[[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
|
||||||
@ -110,6 +116,11 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||||||
|
|
||||||
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
void setupHeaderAction(QAction* act, int column);
|
||||||
|
void saveHiddenColumn(int column, bool hidden);
|
||||||
|
void loadHiddenColumns(QTreeView* tree);
|
||||||
|
QMenu* createHeaderContextMenu(QTreeView* tree);
|
||||||
|
|
||||||
/** This creates a proxy model to filter / sort the model for a UI.
|
/** This creates a proxy model to filter / sort the model for a UI.
|
||||||
*
|
*
|
||||||
* The actual comparisons and filtering are done directly by the Resource, so to modify behavior go there instead!
|
* The actual comparisons and filtering are done directly by the Resource, so to modify behavior go there instead!
|
||||||
@ -117,6 +128,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||||||
QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr);
|
QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] SortType columnToSortKey(size_t column) const;
|
[[nodiscard]] SortType columnToSortKey(size_t column) const;
|
||||||
|
[[nodiscard]] QList<QHeaderView::ResizeMode> columnResizeModes() const { return m_column_resize_modes; }
|
||||||
|
|
||||||
class ProxyModel : public QSortFilterProxyModel {
|
class ProxyModel : public QSortFilterProxyModel {
|
||||||
public:
|
public:
|
||||||
@ -187,6 +199,9 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||||||
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
|
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
|
||||||
// As such, the order in with they appear is very important!
|
// As such, the order in with they appear is very important!
|
||||||
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE };
|
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE };
|
||||||
|
QStringList m_column_names = {"Enable", "Name", "Last Modified"};
|
||||||
|
QStringList m_column_names_translated = {tr("Enable"), tr("Name"), tr("Last Modified")};
|
||||||
|
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, QHeaderView::ResizeToContents };
|
||||||
|
|
||||||
bool m_can_interact = true;
|
bool m_can_interact = true;
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ void ResourcePack::setDescription(QString new_description)
|
|||||||
m_description = new_description;
|
m_description = new_description;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResourcePack::setImage(QImage new_image)
|
void ResourcePack::setImage(QImage new_image) const
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_data_lock);
|
QMutexLocker locker(&m_data_lock);
|
||||||
|
|
||||||
@ -49,7 +49,10 @@ void ResourcePack::setImage(QImage new_image)
|
|||||||
if (m_pack_image_cache_key.key.isValid())
|
if (m_pack_image_cache_key.key.isValid())
|
||||||
PixmapCache::instance().remove(m_pack_image_cache_key.key);
|
PixmapCache::instance().remove(m_pack_image_cache_key.key);
|
||||||
|
|
||||||
m_pack_image_cache_key.key = PixmapCache::instance().insert(QPixmap::fromImage(new_image));
|
// scale the image to avoid flooding the pixmapcache
|
||||||
|
auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
|
||||||
|
|
||||||
|
m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap);
|
||||||
m_pack_image_cache_key.was_ever_used = true;
|
m_pack_image_cache_key.was_ever_used = true;
|
||||||
|
|
||||||
// This can happen if the pixmap is too big to fit in the cache :c
|
// This can happen if the pixmap is too big to fit in the cache :c
|
||||||
@ -59,21 +62,25 @@ void ResourcePack::setImage(QImage new_image)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap ResourcePack::image(QSize size)
|
QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
|
||||||
{
|
{
|
||||||
QPixmap cached_image;
|
QPixmap cached_image;
|
||||||
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
|
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
|
||||||
if (size.isNull())
|
if (size.isNull())
|
||||||
return cached_image;
|
return cached_image;
|
||||||
return cached_image.scaled(size);
|
return cached_image.scaled(size, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No valid image we can get
|
// No valid image we can get
|
||||||
if (!m_pack_image_cache_key.was_ever_used)
|
if (!m_pack_image_cache_key.was_ever_used) {
|
||||||
return {};
|
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.
|
// Imaged got evicted from the cache. Re-process it and retry.
|
||||||
ResourcePackUtils::process(*this);
|
ResourcePackUtils::processPackPNG(*this);
|
||||||
return image(size);
|
return image(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class ResourcePack : public Resource {
|
|||||||
[[nodiscard]] QString description() const { return m_description; }
|
[[nodiscard]] QString description() const { return m_description; }
|
||||||
|
|
||||||
/** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */
|
/** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */
|
||||||
[[nodiscard]] QPixmap image(QSize 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);
|
||||||
@ -40,7 +40,7 @@ class ResourcePack : public Resource {
|
|||||||
void setDescription(QString new_description);
|
void setDescription(QString new_description);
|
||||||
|
|
||||||
/** Thread-safe. */
|
/** Thread-safe. */
|
||||||
void setImage(QImage new_image);
|
void setImage(QImage new_image) const;
|
||||||
|
|
||||||
bool valid() const override;
|
bool valid() const override;
|
||||||
|
|
||||||
@ -67,5 +67,5 @@ class ResourcePack : public Resource {
|
|||||||
struct {
|
struct {
|
||||||
QPixmapCache::Key key;
|
QPixmapCache::Key key;
|
||||||
bool was_ever_used = false;
|
bool was_ever_used = false;
|
||||||
} m_pack_image_cache_key;
|
} mutable m_pack_image_cache_key;
|
||||||
};
|
};
|
||||||
|
@ -35,6 +35,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ResourcePackFolderModel.h"
|
#include "ResourcePackFolderModel.h"
|
||||||
|
#include <qnamespace.h>
|
||||||
|
#include <qsize.h>
|
||||||
|
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QStyle>
|
#include <QStyle>
|
||||||
@ -48,7 +50,11 @@
|
|||||||
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance)
|
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance)
|
||||||
: ResourceFolderModel(QDir(dir), instance)
|
: ResourceFolderModel(QDir(dir), instance)
|
||||||
{
|
{
|
||||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
|
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::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||||
@ -84,9 +90,11 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
case Qt::DecorationRole: {
|
case Qt::DecorationRole: {
|
||||||
if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
||||||
return APPLICATION->getThemedIcon("status-yellow");
|
return APPLICATION->getThemedIcon("status-yellow");
|
||||||
|
if (column == ImageColumn) {
|
||||||
|
return at(row)->image({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
case Qt::ToolTipRole: {
|
case Qt::ToolTipRole: {
|
||||||
@ -94,7 +102,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
|||||||
//: The string being explained by this is in the format: ID (Lower version - Upper version)
|
//: The string being explained by this is in the format: ID (Lower version - Upper version)
|
||||||
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
|
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
|
||||||
}
|
}
|
||||||
if (column == NAME_COLUMN) {
|
if (column == NameColumn) {
|
||||||
if (at(row)->isSymLinkUnder(instDirPath())) {
|
if (at(row)->isSymLinkUnder(instDirPath())) {
|
||||||
return m_resources[row]->internal_id() +
|
return m_resources[row]->internal_id() +
|
||||||
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
||||||
@ -126,13 +134,11 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
|
|||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole:
|
||||||
switch (section) {
|
switch (section) {
|
||||||
case ActiveColumn:
|
case ActiveColumn:
|
||||||
return QString();
|
|
||||||
case NameColumn:
|
case NameColumn:
|
||||||
return tr("Name");
|
|
||||||
case PackFormatColumn:
|
case PackFormatColumn:
|
||||||
return tr("Pack Format");
|
|
||||||
case DateColumn:
|
case DateColumn:
|
||||||
return tr("Last changed");
|
case ImageColumn:
|
||||||
|
return columnNames().at(section);
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -151,6 +157,11 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
|
|||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
case Qt::SizeHintRole:
|
||||||
|
if (section == ImageColumn) {
|
||||||
|
return QSize(64,0);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ public:
|
|||||||
enum Columns
|
enum Columns
|
||||||
{
|
{
|
||||||
ActiveColumn = 0,
|
ActiveColumn = 0,
|
||||||
|
ImageColumn,
|
||||||
NameColumn,
|
NameColumn,
|
||||||
PackFormatColumn,
|
PackFormatColumn,
|
||||||
DateColumn,
|
DateColumn,
|
||||||
@ -19,6 +20,8 @@ public:
|
|||||||
|
|
||||||
explicit ResourcePackFolderModel(const QString &dir, BaseInstance* instance);
|
explicit ResourcePackFolderModel(const QString &dir, BaseInstance* instance);
|
||||||
|
|
||||||
|
virtual QString id() const override { return "resourcepacks"; }
|
||||||
|
|
||||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
[[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]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
|
@ -9,4 +9,6 @@ class ShaderPackFolderModel : public ResourceFolderModel {
|
|||||||
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance)
|
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance)
|
||||||
: ResourceFolderModel(QDir(dir), instance)
|
: ResourceFolderModel(QDir(dir), instance)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
virtual QString id() const override { return "shaderpacks"; }
|
||||||
};
|
};
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
#include "MTPixmapCache.h"
|
||||||
|
|
||||||
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
|
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
|
||||||
|
|
||||||
void TexturePack::setDescription(QString new_description)
|
void TexturePack::setDescription(QString new_description)
|
||||||
@ -32,34 +34,41 @@ void TexturePack::setDescription(QString new_description)
|
|||||||
m_description = new_description;
|
m_description = new_description;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TexturePack::setImage(QImage new_image)
|
void TexturePack::setImage(QImage new_image) const
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_data_lock);
|
QMutexLocker locker(&m_data_lock);
|
||||||
|
|
||||||
Q_ASSERT(!new_image.isNull());
|
Q_ASSERT(!new_image.isNull());
|
||||||
|
|
||||||
if (m_pack_image_cache_key.key.isValid())
|
if (m_pack_image_cache_key.key.isValid())
|
||||||
QPixmapCache::remove(m_pack_image_cache_key.key);
|
PixmapCache::remove(m_pack_image_cache_key.key);
|
||||||
|
|
||||||
m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image));
|
// scale the image to avoid flooding the pixmapcache
|
||||||
|
auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
|
||||||
|
|
||||||
|
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
|
||||||
m_pack_image_cache_key.was_ever_used = true;
|
m_pack_image_cache_key.was_ever_used = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap TexturePack::image(QSize size)
|
QPixmap TexturePack::image(QSize size, Qt::AspectRatioMode mode) const
|
||||||
{
|
{
|
||||||
QPixmap cached_image;
|
QPixmap cached_image;
|
||||||
if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
||||||
if (size.isNull())
|
if (size.isNull())
|
||||||
return cached_image;
|
return cached_image;
|
||||||
return cached_image.scaled(size);
|
return cached_image.scaled(size, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No valid image we can get
|
// No valid image we can get
|
||||||
if (!m_pack_image_cache_key.was_ever_used)
|
if (!m_pack_image_cache_key.was_ever_used) {
|
||||||
return {};
|
return {};
|
||||||
|
} else {
|
||||||
|
qDebug() << "Texture Pack" << name() << "Had it's image evicted from the cache. reloading...";
|
||||||
|
PixmapCache::markCacheMissByEviciton();
|
||||||
|
}
|
||||||
|
|
||||||
// Imaged got evicted from the cache. Re-process it and retry.
|
// Imaged got evicted from the cache. Re-process it and retry.
|
||||||
TexturePackUtils::process(*this);
|
TexturePackUtils::processPackPNG(*this);
|
||||||
return image(size);
|
return image(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,13 +40,13 @@ class TexturePack : public Resource {
|
|||||||
[[nodiscard]] QString description() const { return m_description; }
|
[[nodiscard]] QString description() const { return m_description; }
|
||||||
|
|
||||||
/** Gets the image of the texture pack, converted to a QPixmap for drawing, and scaled to size. */
|
/** Gets the image of the texture pack, converted to a QPixmap for drawing, and scaled to size. */
|
||||||
[[nodiscard]] QPixmap image(QSize size);
|
[[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
||||||
|
|
||||||
/** Thread-safe. */
|
/** Thread-safe. */
|
||||||
void setDescription(QString new_description);
|
void setDescription(QString new_description);
|
||||||
|
|
||||||
/** Thread-safe. */
|
/** Thread-safe. */
|
||||||
void setImage(QImage new_image);
|
void setImage(QImage new_image) const;
|
||||||
|
|
||||||
bool valid() const override;
|
bool valid() const override;
|
||||||
|
|
||||||
@ -65,5 +65,5 @@ class TexturePack : public Resource {
|
|||||||
struct {
|
struct {
|
||||||
QPixmapCache::Key key;
|
QPixmapCache::Key key;
|
||||||
bool was_ever_used = false;
|
bool was_ever_used = false;
|
||||||
} m_pack_image_cache_key;
|
} mutable m_pack_image_cache_key;
|
||||||
};
|
};
|
||||||
|
@ -33,6 +33,9 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
|
||||||
#include "TexturePackFolderModel.h"
|
#include "TexturePackFolderModel.h"
|
||||||
|
|
||||||
@ -41,7 +44,13 @@
|
|||||||
|
|
||||||
TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance)
|
TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance)
|
||||||
: ResourceFolderModel(QDir(dir), instance)
|
: ResourceFolderModel(QDir(dir), instance)
|
||||||
{}
|
{
|
||||||
|
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified" });
|
||||||
|
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") });
|
||||||
|
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
|
||||||
|
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Task* TexturePackFolderModel::createUpdateTask()
|
Task* TexturePackFolderModel::createUpdateTask()
|
||||||
{
|
{
|
||||||
@ -52,3 +61,96 @@ Task* TexturePackFolderModel::createParseTask(Resource& resource)
|
|||||||
{
|
{
|
||||||
return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast<TexturePack&>(resource));
|
return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast<TexturePack&>(resource));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QVariant TexturePackFolderModel::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 DateColumn:
|
||||||
|
return m_resources[row]->dateTimeChanged();
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case Qt::ToolTipRole:
|
||||||
|
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::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::CheckStateRole:
|
||||||
|
if (column == ActiveColumn) {
|
||||||
|
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
switch (section) {
|
||||||
|
case ActiveColumn:
|
||||||
|
case NameColumn:
|
||||||
|
case DateColumn:
|
||||||
|
case ImageColumn:
|
||||||
|
return columnNames().at(section);
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case Qt::ToolTipRole: {
|
||||||
|
switch (section) {
|
||||||
|
case ActiveColumn:
|
||||||
|
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||||
|
return tr("Is the resource enabled?");
|
||||||
|
case NameColumn:
|
||||||
|
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||||
|
return tr("The name of the resource.");
|
||||||
|
case DateColumn:
|
||||||
|
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||||
|
return tr("The date and time this resource was last changed (or added).");
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int TexturePackFolderModel::columnCount(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -38,12 +38,35 @@
|
|||||||
|
|
||||||
#include "ResourceFolderModel.h"
|
#include "ResourceFolderModel.h"
|
||||||
|
|
||||||
|
#include "TexturePack.h"
|
||||||
|
|
||||||
class TexturePackFolderModel : public ResourceFolderModel
|
class TexturePackFolderModel : public ResourceFolderModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
enum Columns
|
||||||
|
{
|
||||||
|
ActiveColumn = 0,
|
||||||
|
ImageColumn,
|
||||||
|
NameColumn,
|
||||||
|
DateColumn,
|
||||||
|
NUM_COLUMNS
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit TexturePackFolderModel(const QString &dir, std::shared_ptr<const BaseInstance> instance);
|
||||||
|
|
||||||
|
virtual QString id() const override { return "texturepacks"; }
|
||||||
|
|
||||||
|
[[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;
|
||||||
|
|
||||||
explicit TexturePackFolderModel(const QString &dir, BaseInstance* instance);
|
explicit TexturePackFolderModel(const QString &dir, BaseInstance* instance);
|
||||||
[[nodiscard]] Task* createUpdateTask() override;
|
[[nodiscard]] Task* createUpdateTask() override;
|
||||||
[[nodiscard]] Task* createParseTask(Resource&) override;
|
[[nodiscard]] Task* createParseTask(Resource&) override;
|
||||||
|
|
||||||
|
RESOURCE_HELPERS(TexturePack)
|
||||||
};
|
};
|
||||||
|
@ -52,6 +52,10 @@ ModDetails ReadMCModInfo(QByteArray contents)
|
|||||||
authors = firstObj.value("authors").toArray();
|
authors = firstObj.value("authors").toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (firstObj.contains("logoFile")) {
|
||||||
|
details.icon_file = firstObj.value("logoFile").toString();
|
||||||
|
}
|
||||||
|
|
||||||
for (auto author : authors) {
|
for (auto author : authors) {
|
||||||
details.authors.append(author.toString());
|
details.authors.append(author.toString());
|
||||||
}
|
}
|
||||||
@ -166,6 +170,31 @@ ModDetails ReadMCModTOML(QByteArray contents)
|
|||||||
}
|
}
|
||||||
details.homeurl = homeurl;
|
details.homeurl = homeurl;
|
||||||
|
|
||||||
|
QString issueTrackerURL = "";
|
||||||
|
if (auto issueTrackerURLDatum = tomlData["issueTrackerURL"].as_string()) {
|
||||||
|
issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
|
||||||
|
} else if (auto issueTrackerURLDatum = (*modsTable)["issueTrackerURL"].as_string()) {
|
||||||
|
issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
|
||||||
|
}
|
||||||
|
details.issue_tracker = issueTrackerURL;
|
||||||
|
|
||||||
|
QString license = "";
|
||||||
|
if (auto licenseDatum = tomlData["license"].as_string()) {
|
||||||
|
license = QString::fromStdString(licenseDatum->get());
|
||||||
|
} else if (auto licenseDatum =(*modsTable)["license"].as_string()) {
|
||||||
|
license = QString::fromStdString(licenseDatum->get());
|
||||||
|
}
|
||||||
|
if (!license.isEmpty())
|
||||||
|
details.licenses.append(ModLicense(license));
|
||||||
|
|
||||||
|
QString logoFile = "";
|
||||||
|
if (auto logoFileDatum = tomlData["logoFile"].as_string()) {
|
||||||
|
logoFile = QString::fromStdString(logoFileDatum->get());
|
||||||
|
} else if (auto logoFileDatum =(*modsTable)["logoFile"].as_string()) {
|
||||||
|
logoFile = QString::fromStdString(logoFileDatum->get());
|
||||||
|
}
|
||||||
|
details.icon_file = logoFile;
|
||||||
|
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,6 +230,57 @@ ModDetails ReadFabricModInfo(QByteArray contents)
|
|||||||
if (contact.contains("homepage")) {
|
if (contact.contains("homepage")) {
|
||||||
details.homeurl = contact.value("homepage").toString();
|
details.homeurl = contact.value("homepage").toString();
|
||||||
}
|
}
|
||||||
|
if (contact.contains("issues")) {
|
||||||
|
details.issue_tracker = contact.value("issues").toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object.contains("license")) {
|
||||||
|
auto license = object.value("license");
|
||||||
|
if (license.isArray()) {
|
||||||
|
for (auto l : license.toArray()) {
|
||||||
|
if (l.isString()) {
|
||||||
|
details.licenses.append(ModLicense(l.toString()));
|
||||||
|
} else if (l.isObject()) {
|
||||||
|
auto obj = l.toObject();
|
||||||
|
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
|
||||||
|
obj.value("url").toString(), obj.value("description").toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (license.isString()) {
|
||||||
|
details.licenses.append(ModLicense(license.toString()));
|
||||||
|
} else if (license.isObject()) {
|
||||||
|
auto obj = license.toObject();
|
||||||
|
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), obj.value("url").toString(),
|
||||||
|
obj.value("description").toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object.contains("icon")) {
|
||||||
|
auto icon = object.value("icon");
|
||||||
|
if (icon.isObject()) {
|
||||||
|
auto obj = icon.toObject();
|
||||||
|
// take the largest icon
|
||||||
|
int largest = 0;
|
||||||
|
for (auto key : obj.keys()) {
|
||||||
|
auto size = key.split('x').first().toInt();
|
||||||
|
if (size > largest) {
|
||||||
|
largest = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (largest > 0) {
|
||||||
|
auto key = QString::number(largest) + "x" + QString::number(largest);
|
||||||
|
details.icon_file = obj.value(key).toString();
|
||||||
|
} else { // parsing the sizes failed
|
||||||
|
// take the first
|
||||||
|
for (auto i : obj) {
|
||||||
|
details.icon_file = i.toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (icon.isString()) {
|
||||||
|
details.icon_file = icon.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return details;
|
return details;
|
||||||
@ -238,6 +318,58 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
|
|||||||
if (modContact.contains("homepage")) {
|
if (modContact.contains("homepage")) {
|
||||||
details.homeurl = Json::requireString(modContact.value("homepage"));
|
details.homeurl = Json::requireString(modContact.value("homepage"));
|
||||||
}
|
}
|
||||||
|
if (modContact.contains("issues")) {
|
||||||
|
details.issue_tracker = Json::requireString(modContact.value("issues"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modMetadata.contains("license")) {
|
||||||
|
auto license = modMetadata.value("license");
|
||||||
|
if (license.isArray()) {
|
||||||
|
for (auto l : license.toArray()) {
|
||||||
|
if (l.isString()) {
|
||||||
|
details.licenses.append(ModLicense(l.toString()));
|
||||||
|
} else if (l.isObject()) {
|
||||||
|
auto obj = l.toObject();
|
||||||
|
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
|
||||||
|
obj.value("url").toString(), obj.value("description").toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (license.isString()) {
|
||||||
|
details.licenses.append(ModLicense(license.toString()));
|
||||||
|
} else if (license.isObject()) {
|
||||||
|
auto obj = license.toObject();
|
||||||
|
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), obj.value("url").toString(),
|
||||||
|
obj.value("description").toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modMetadata.contains("icon")) {
|
||||||
|
auto icon = modMetadata.value("icon");
|
||||||
|
if (icon.isObject()) {
|
||||||
|
auto obj = icon.toObject();
|
||||||
|
// take the largest icon
|
||||||
|
int largest = 0;
|
||||||
|
for (auto key : obj.keys()) {
|
||||||
|
auto size = key.split('x').first().toInt();
|
||||||
|
if (size > largest) {
|
||||||
|
largest = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (largest > 0) {
|
||||||
|
auto key = QString::number(largest) + "x" + QString::number(largest);
|
||||||
|
details.icon_file = obj.value(key).toString();
|
||||||
|
} else { // parsing the sizes failed
|
||||||
|
// take the first
|
||||||
|
for (auto i : obj) {
|
||||||
|
details.icon_file = i.toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (icon.isString()) {
|
||||||
|
details.icon_file = icon.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
@ -515,6 +647,85 @@ bool validate(QFileInfo file)
|
|||||||
return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid();
|
return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool processIconPNG(const Mod& mod, QByteArray&& raw_data)
|
||||||
|
{
|
||||||
|
auto img = QImage::fromData(raw_data);
|
||||||
|
if (!img.isNull()) {
|
||||||
|
mod.setIcon(img);
|
||||||
|
} else {
|
||||||
|
qWarning() << "Failed to parse mod logo:" << mod.iconPath() << "from" << mod.name();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadIconFile(const Mod& mod) {
|
||||||
|
if (mod.iconPath().isEmpty()) {
|
||||||
|
qWarning() << "No Iconfile set, be sure to parse the mod first";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto png_invalid = [&mod]() {
|
||||||
|
qWarning() << "Mod at" << mod.fileinfo().filePath() << "does not have a valid icon";
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (mod.type()) {
|
||||||
|
case ResourceType::FOLDER:
|
||||||
|
{
|
||||||
|
QFileInfo icon_info(FS::PathCombine(mod.fileinfo().filePath(), mod.iconPath()));
|
||||||
|
if (icon_info.exists() && icon_info.isFile()) {
|
||||||
|
QFile icon(icon_info.filePath());
|
||||||
|
if (!icon.open(QIODevice::ReadOnly))
|
||||||
|
return false;
|
||||||
|
auto data = icon.readAll();
|
||||||
|
|
||||||
|
bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
|
||||||
|
|
||||||
|
icon.close();
|
||||||
|
|
||||||
|
if (!icon_result) {
|
||||||
|
return png_invalid(); // icon invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ResourceType::ZIPFILE:
|
||||||
|
{
|
||||||
|
QuaZip zip(mod.fileinfo().filePath());
|
||||||
|
if (!zip.open(QuaZip::mdUnzip))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QuaZipFile file(&zip);
|
||||||
|
|
||||||
|
if (zip.setCurrentFile(mod.iconPath())) {
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
qCritical() << "Failed to open file in zip.";
|
||||||
|
zip.close();
|
||||||
|
return png_invalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = file.readAll();
|
||||||
|
|
||||||
|
bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
if (!icon_result) {
|
||||||
|
return png_invalid(); // icon png invalid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return png_invalid(); // could not set icon as current file.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ResourceType::LITEMOD:
|
||||||
|
{
|
||||||
|
return false; // can lightmods even have icons?
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
qWarning() << "Invalid type for mod, can not load icon.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ModUtils
|
} // namespace ModUtils
|
||||||
|
|
||||||
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
|
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
|
||||||
|
@ -25,6 +25,9 @@ bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
|
|||||||
|
|
||||||
/** Checks whether a file is valid as a mod or not. */
|
/** Checks whether a file is valid as a mod or not. */
|
||||||
bool validate(QFileInfo file);
|
bool validate(QFileInfo file);
|
||||||
|
|
||||||
|
bool processIconPNG(const Mod& mod, QByteArray&& raw_data);
|
||||||
|
bool loadIconFile(const Mod& mod);
|
||||||
} // namespace ModUtils
|
} // namespace ModUtils
|
||||||
|
|
||||||
class LocalModParseTask : public Task {
|
class LocalModParseTask : public Task {
|
||||||
|
@ -165,15 +165,16 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level)
|
|||||||
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
|
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
zip.close();
|
||||||
if (!pack_png_result) {
|
if (!pack_png_result) {
|
||||||
return png_invalid(); // pack.png invalid
|
return png_invalid(); // pack.png invalid
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
zip.close();
|
||||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||||
}
|
}
|
||||||
|
|
||||||
zip.close();
|
zip.close();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +194,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
|
bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data)
|
||||||
{
|
{
|
||||||
auto img = QImage::fromData(raw_data);
|
auto img = QImage::fromData(raw_data);
|
||||||
if (!img.isNull()) {
|
if (!img.isNull()) {
|
||||||
@ -205,6 +206,68 @@ bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
|
|||||||
return true;
|
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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ResourceType::ZIPFILE:
|
||||||
|
{
|
||||||
|
Q_ASSERT(pack.type() == 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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
qWarning() << "Invalid type for resource pack parse task!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool validate(QFileInfo file)
|
bool validate(QFileInfo file)
|
||||||
{
|
{
|
||||||
ResourcePack rp{ file };
|
ResourcePack rp{ file };
|
||||||
|
@ -35,7 +35,10 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Ful
|
|||||||
bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||||
|
|
||||||
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
|
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
|
||||||
bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
|
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. */
|
/** Checks whether a file is valid as a resource pack or not. */
|
||||||
bool validate(QFileInfo file);
|
bool validate(QFileInfo file);
|
||||||
|
@ -131,6 +131,7 @@ bool processZIP(TexturePack& pack, ProcessingLevel level)
|
|||||||
bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
|
bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
zip.close();
|
||||||
if (!packPNG_result) {
|
if (!packPNG_result) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -147,7 +148,7 @@ bool processPackTXT(TexturePack& pack, QByteArray&& raw_data)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool processPackPNG(TexturePack& pack, QByteArray&& raw_data)
|
bool processPackPNG(const TexturePack& pack, QByteArray&& raw_data)
|
||||||
{
|
{
|
||||||
auto img = QImage::fromData(raw_data);
|
auto img = QImage::fromData(raw_data);
|
||||||
if (!img.isNull()) {
|
if (!img.isNull()) {
|
||||||
@ -159,6 +160,70 @@ bool processPackPNG(TexturePack& pack, QByteArray&& raw_data)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool processPackPNG(const TexturePack& pack)
|
||||||
|
{
|
||||||
|
auto png_invalid = [&pack]() {
|
||||||
|
qWarning() << "Texture 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 = TexturePackUtils::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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ResourceType::ZIPFILE:
|
||||||
|
{
|
||||||
|
Q_ASSERT(pack.type() == 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 = TexturePackUtils::processPackPNG(pack, std::move(data));
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
if (!pack_png_result) {
|
||||||
|
zip.close();
|
||||||
|
return png_invalid(); // pack.png invalid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
zip.close();
|
||||||
|
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
qWarning() << "Invalid type for resource pack parse task!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool validate(QFileInfo file)
|
bool validate(QFileInfo file)
|
||||||
{
|
{
|
||||||
TexturePack rp{ file };
|
TexturePack rp{ file };
|
||||||
|
@ -36,7 +36,10 @@ bool processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full
|
|||||||
bool processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
bool processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||||
|
|
||||||
bool processPackTXT(TexturePack& pack, QByteArray&& raw_data);
|
bool processPackTXT(TexturePack& pack, QByteArray&& raw_data);
|
||||||
bool processPackPNG(TexturePack& pack, QByteArray&& raw_data);
|
bool processPackPNG(const TexturePack& pack, QByteArray&& raw_data);
|
||||||
|
|
||||||
|
/// processes ONLY the pack.png (rest of the pack may be invalid)
|
||||||
|
bool processPackPNG(const TexturePack& pack);
|
||||||
|
|
||||||
/** Checks whether a file is valid as a texture pack or not. */
|
/** Checks whether a file is valid as a texture pack or not. */
|
||||||
bool validate(QFileInfo file);
|
bool validate(QFileInfo file);
|
||||||
|
@ -585,7 +585,7 @@
|
|||||||
<normaloff>.</normaloff>.</iconset>
|
<normaloff>.</normaloff>.</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Report a &Bug...</string>
|
<string>Report a Bug or Suggest a Feature</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Open the bug tracker to report a bug with %1.</string>
|
<string>Open the bug tracker to report a bug with %1.</string>
|
||||||
|
@ -60,6 +60,8 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
|
|||||||
m_filterModel->setSourceModel(m_model.get());
|
m_filterModel->setSourceModel(m_model.get());
|
||||||
m_filterModel->setFilterKeyColumn(-1);
|
m_filterModel->setFilterKeyColumn(-1);
|
||||||
ui->treeView->setModel(m_filterModel);
|
ui->treeView->setModel(m_filterModel);
|
||||||
|
// must come after setModel
|
||||||
|
ui->treeView->setResizeModes(m_model->columnResizeModes());
|
||||||
|
|
||||||
ui->treeView->installEventFilter(this);
|
ui->treeView->installEventFilter(this);
|
||||||
ui->treeView->sortByColumn(1, Qt::AscendingOrder);
|
ui->treeView->sortByColumn(1, Qt::AscendingOrder);
|
||||||
@ -87,6 +89,13 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
|
|||||||
connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra);
|
connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra);
|
||||||
|
|
||||||
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged);
|
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged);
|
||||||
|
|
||||||
|
auto viewHeader = ui->treeView->header();
|
||||||
|
viewHeader->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
|
||||||
|
connect(viewHeader, &QHeaderView::customContextMenuRequested, this, &ExternalResourcesPage::ShowHeaderContextMenu);
|
||||||
|
|
||||||
|
m_model->loadHiddenColumns(ui->treeView);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExternalResourcesPage::~ExternalResourcesPage()
|
ExternalResourcesPage::~ExternalResourcesPage()
|
||||||
@ -108,6 +117,13 @@ void ExternalResourcesPage::ShowContextMenu(const QPoint& pos)
|
|||||||
delete menu;
|
delete menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExternalResourcesPage::ShowHeaderContextMenu(const QPoint& pos)
|
||||||
|
{
|
||||||
|
auto menu = m_model->createHeaderContextMenu(ui->treeView);
|
||||||
|
menu->exec(ui->treeView->mapToGlobal(pos));
|
||||||
|
menu->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
void ExternalResourcesPage::openedImpl()
|
void ExternalResourcesPage::openedImpl()
|
||||||
{
|
{
|
||||||
m_model->startWatching();
|
m_model->startWatching();
|
||||||
@ -139,7 +155,6 @@ void ExternalResourcesPage::itemActivated(const QModelIndex&)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
|
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
|
||||||
m_model->setResourceEnabled(selection.indexes(), EnableAction::TOGGLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExternalResourcesPage::filterTextChanged(const QString& newContents)
|
void ExternalResourcesPage::filterTextChanged(const QString& newContents)
|
||||||
|
@ -61,6 +61,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
|
|||||||
virtual void viewConfigs();
|
virtual void viewConfigs();
|
||||||
|
|
||||||
void ShowContextMenu(const QPoint& pos);
|
void ShowContextMenu(const QPoint& pos);
|
||||||
|
void ShowHeaderContextMenu(const QPoint& pos);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
BaseInstance* m_instance = nullptr;
|
BaseInstance* m_instance = nullptr;
|
||||||
|
@ -62,6 +62,9 @@
|
|||||||
<property name="dragDropMode">
|
<property name="dragDropMode">
|
||||||
<enum>QAbstractItemView::DropOnly</enum>
|
<enum>QAbstractItemView::DropOnly</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="uniformRowHeights">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -57,7 +57,7 @@ public:
|
|||||||
virtual ~ImportPage();
|
virtual ~ImportPage();
|
||||||
virtual QString displayName() const override
|
virtual QString displayName() const override
|
||||||
{
|
{
|
||||||
return tr("Import from zip");
|
return tr("Import");
|
||||||
}
|
}
|
||||||
virtual QIcon icon() const override
|
virtual QIcon icon() const override
|
||||||
{
|
{
|
||||||
|
@ -47,6 +47,8 @@ InfoFrame::InfoFrame(QWidget *parent) :
|
|||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
ui->descriptionLabel->setHidden(true);
|
ui->descriptionLabel->setHidden(true);
|
||||||
ui->nameLabel->setHidden(true);
|
ui->nameLabel->setHidden(true);
|
||||||
|
ui->licenseLabel->setHidden(true);
|
||||||
|
ui->issueTrackerLabel->setHidden(true);
|
||||||
updateHiddenState();
|
updateHiddenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +90,41 @@ void InfoFrame::updateWithMod(Mod const& m)
|
|||||||
setDescription(m.description());
|
setDescription(m.description());
|
||||||
}
|
}
|
||||||
|
|
||||||
setImage();
|
setImage(m.icon({64,64}));
|
||||||
|
|
||||||
|
auto licenses = m.licenses();
|
||||||
|
QString licenseText = "";
|
||||||
|
if (!licenses.empty()) {
|
||||||
|
for (auto l : licenses) {
|
||||||
|
if (!licenseText.isEmpty()) {
|
||||||
|
licenseText += "\n"; // add newline between licenses
|
||||||
|
}
|
||||||
|
if (!l.name.isEmpty()) {
|
||||||
|
if (l.url.isEmpty()) {
|
||||||
|
licenseText += l.name;
|
||||||
|
} else {
|
||||||
|
licenseText += "<a href=\"" + l.url + "\">" + l.name + "</a>";
|
||||||
|
}
|
||||||
|
} else if (!l.url.isEmpty()) {
|
||||||
|
licenseText += "<a href=\"" + l.url + "\">" + l.url + "</a>";
|
||||||
|
}
|
||||||
|
if (!l.description.isEmpty() && l.description != l.name) {
|
||||||
|
licenseText += " " + l.description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!licenseText.isEmpty()) {
|
||||||
|
setLicense(tr("License: %1").arg(licenseText));
|
||||||
|
} else {
|
||||||
|
setLicense();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString issueTracker = "";
|
||||||
|
if (!m.issueTracker().isEmpty()) {
|
||||||
|
issueTracker += tr("Report issues to: ");
|
||||||
|
issueTracker += "<a href=\"" + m.issueTracker() + "\">" + m.issueTracker() + "</a>";
|
||||||
|
}
|
||||||
|
setIssueTracker(issueTracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InfoFrame::updateWithResource(const Resource& resource)
|
void InfoFrame::updateWithResource(const Resource& resource)
|
||||||
@ -177,16 +213,16 @@ void InfoFrame::clear()
|
|||||||
setName();
|
setName();
|
||||||
setDescription();
|
setDescription();
|
||||||
setImage();
|
setImage();
|
||||||
|
setLicense();
|
||||||
|
setIssueTracker();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InfoFrame::updateHiddenState()
|
void InfoFrame::updateHiddenState()
|
||||||
{
|
{
|
||||||
if(ui->descriptionLabel->isHidden() && ui->nameLabel->isHidden())
|
if (ui->descriptionLabel->isHidden() && ui->nameLabel->isHidden() && ui->licenseLabel->isHidden() &&
|
||||||
{
|
ui->issueTrackerLabel->isHidden()) {
|
||||||
setHidden(true);
|
setHidden(true);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
setHidden(false);
|
setHidden(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -251,6 +287,66 @@ void InfoFrame::setDescription(QString text)
|
|||||||
ui->descriptionLabel->setText(labeltext);
|
ui->descriptionLabel->setText(labeltext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InfoFrame::setLicense(QString text)
|
||||||
|
{
|
||||||
|
if(text.isEmpty())
|
||||||
|
{
|
||||||
|
ui->licenseLabel->setHidden(true);
|
||||||
|
updateHiddenState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ui->licenseLabel->setHidden(false);
|
||||||
|
updateHiddenState();
|
||||||
|
}
|
||||||
|
ui->licenseLabel->setToolTip("");
|
||||||
|
QString intermediatetext = text.trimmed();
|
||||||
|
bool prev(false);
|
||||||
|
QChar rem('\n');
|
||||||
|
QString finaltext;
|
||||||
|
finaltext.reserve(intermediatetext.size());
|
||||||
|
foreach(const QChar& c, intermediatetext)
|
||||||
|
{
|
||||||
|
if(c == rem && prev){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
prev = c == rem;
|
||||||
|
finaltext += c;
|
||||||
|
}
|
||||||
|
QString labeltext;
|
||||||
|
labeltext.reserve(300);
|
||||||
|
if(finaltext.length() > 290)
|
||||||
|
{
|
||||||
|
ui->licenseLabel->setOpenExternalLinks(false);
|
||||||
|
ui->licenseLabel->setTextFormat(Qt::TextFormat::RichText);
|
||||||
|
m_description = text;
|
||||||
|
// This allows injecting HTML here.
|
||||||
|
labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>");
|
||||||
|
QObject::connect(ui->licenseLabel, &QLabel::linkActivated, this, &InfoFrame::licenseEllipsisHandler);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ui->licenseLabel->setTextFormat(Qt::TextFormat::AutoText);
|
||||||
|
labeltext.append(finaltext);
|
||||||
|
}
|
||||||
|
ui->licenseLabel->setText(labeltext);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InfoFrame::setIssueTracker(QString text)
|
||||||
|
{
|
||||||
|
if(text.isEmpty())
|
||||||
|
{
|
||||||
|
ui->issueTrackerLabel->setHidden(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ui->issueTrackerLabel->setText(text);
|
||||||
|
ui->issueTrackerLabel->setHidden(false);
|
||||||
|
}
|
||||||
|
updateHiddenState();
|
||||||
|
}
|
||||||
|
|
||||||
void InfoFrame::setImage(QPixmap img)
|
void InfoFrame::setImage(QPixmap img)
|
||||||
{
|
{
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
@ -275,6 +371,20 @@ void InfoFrame::descriptionEllipsisHandler(QString link)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InfoFrame::licenseEllipsisHandler(QString link)
|
||||||
|
{
|
||||||
|
if(!m_current_box)
|
||||||
|
{
|
||||||
|
m_current_box = CustomMessageBox::selectable(this, "", m_license);
|
||||||
|
connect(m_current_box, &QMessageBox::finished, this, &InfoFrame::boxClosed);
|
||||||
|
m_current_box->show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_current_box->setText(m_license);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void InfoFrame::boxClosed(int result)
|
void InfoFrame::boxClosed(int result)
|
||||||
{
|
{
|
||||||
m_current_box = nullptr;
|
m_current_box = nullptr;
|
||||||
|
@ -36,6 +36,8 @@ class InfoFrame : public QFrame {
|
|||||||
void setName(QString text = {});
|
void setName(QString text = {});
|
||||||
void setDescription(QString text = {});
|
void setDescription(QString text = {});
|
||||||
void setImage(QPixmap img = {});
|
void setImage(QPixmap img = {});
|
||||||
|
void setLicense(QString text = {});
|
||||||
|
void setIssueTracker(QString text = {});
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
@ -48,6 +50,7 @@ class InfoFrame : public QFrame {
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void descriptionEllipsisHandler(QString link);
|
void descriptionEllipsisHandler(QString link);
|
||||||
|
void licenseEllipsisHandler(QString link);
|
||||||
void boxClosed(int result);
|
void boxClosed(int result);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -56,5 +59,6 @@ class InfoFrame : public QFrame {
|
|||||||
private:
|
private:
|
||||||
Ui::InfoFrame* ui;
|
Ui::InfoFrame* ui;
|
||||||
QString m_description;
|
QString m_description;
|
||||||
|
QString m_license;
|
||||||
class QMessageBox* m_current_box = nullptr;
|
class QMessageBox* m_current_box = nullptr;
|
||||||
};
|
};
|
||||||
|
@ -35,25 +35,28 @@
|
|||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item row="0" column="1">
|
<item row="0" column="0" rowspan="2">
|
||||||
<widget class="QLabel" name="nameLabel">
|
<widget class="QLabel" name="iconLabel">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>64</width>
|
||||||
|
<height>64</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string notr="true"/>
|
<string notr="true"/>
|
||||||
</property>
|
</property>
|
||||||
<property name="textFormat">
|
<property name="scaledContents">
|
||||||
<enum>Qt::RichText</enum>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="alignment">
|
<property name="margin">
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
<number>0</number>
|
||||||
</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>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -82,28 +85,69 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0" rowspan="2">
|
<item row="0" column="1">
|
||||||
<widget class="QLabel" name="iconLabel">
|
<widget class="QLabel" name="nameLabel">
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>0</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>64</width>
|
|
||||||
<height>64</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string notr="true"/>
|
<string notr="true"/>
|
||||||
</property>
|
</property>
|
||||||
<property name="scaledContents">
|
<property name="textFormat">
|
||||||
<bool>false</bool>
|
<enum>Qt::RichText</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="margin">
|
<property name="alignment">
|
||||||
<number>0</number>
|
<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>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -79,3 +79,12 @@ void ModListView::setModel ( QAbstractItemModel* model )
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModListView::setResizeModes(const QList<QHeaderView::ResizeMode> &modes)
|
||||||
|
{
|
||||||
|
auto head = header();
|
||||||
|
for(int i = 0; i < modes.count(); i++) {
|
||||||
|
head->setSectionResizeMode(i, modes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <QHeaderView>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
|
|
||||||
class ModListView: public QTreeView
|
class ModListView: public QTreeView
|
||||||
@ -22,4 +23,5 @@ class ModListView: public QTreeView
|
|||||||
public:
|
public:
|
||||||
explicit ModListView ( QWidget* parent = 0 );
|
explicit ModListView ( QWidget* parent = 0 );
|
||||||
virtual void setModel ( QAbstractItemModel* model );
|
virtual void setModel ( QAbstractItemModel* model );
|
||||||
|
virtual void setResizeModes (const QList<QHeaderView::ResizeMode>& modes);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user