mirror of
https://github.com/UltimMC/Launcher.git
synced 2025-12-13 12:12:14 +00:00
Compare commits
36 Commits
feature/ve
...
feature/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f26fff8d0 | ||
|
|
62e1bf327d | ||
|
|
280e0e6e36 | ||
|
|
3a67990acd | ||
|
|
23eab74e6d | ||
|
|
b9d4293552 | ||
|
|
5110b58def | ||
|
|
791a8227b6 | ||
|
|
725ec35635 | ||
|
|
739a86f171 | ||
|
|
48b2f95129 | ||
|
|
497d9bec02 | ||
|
|
c01d020afc | ||
|
|
ee83d432f6 | ||
|
|
8ee11b1a8e | ||
|
|
0b86a7ebf3 | ||
|
|
63330bf111 | ||
|
|
f74e3db804 | ||
|
|
a55fa04353 | ||
|
|
fde43c993e | ||
|
|
917f148fc4 | ||
|
|
34611c00e3 | ||
|
|
44a7c5867b | ||
|
|
75ddbc8851 | ||
|
|
2f1d31cf43 | ||
|
|
e7c5b266c8 | ||
|
|
384979bf94 | ||
|
|
b5a16935b7 | ||
|
|
320637e8dc | ||
|
|
77f3f028fa | ||
|
|
2a96e16902 | ||
|
|
1ed84eddd5 | ||
|
|
7b52b8689b | ||
|
|
d21700ee91 | ||
|
|
f87c890912 | ||
|
|
306b98edac |
@@ -32,7 +32,7 @@ set(CMAKE_C_STANDARD_REQUIRED true)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
include(GenerateExportHeader)
|
||||
set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
|
||||
if(UNIX AND APPLE)
|
||||
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
|
||||
endif()
|
||||
@@ -46,7 +46,7 @@ set(MultiMC_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetc
|
||||
######## Set version numbers ########
|
||||
set(MultiMC_VERSION_MAJOR 0)
|
||||
set(MultiMC_VERSION_MINOR 6)
|
||||
set(MultiMC_VERSION_HOTFIX 5)
|
||||
set(MultiMC_VERSION_HOTFIX 6)
|
||||
|
||||
# Build number
|
||||
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
|
||||
|
||||
@@ -443,6 +443,8 @@ set(FLAME_SOURCES
|
||||
modplatform/flame/PackManifest.cpp
|
||||
modplatform/flame/FileResolvingTask.h
|
||||
modplatform/flame/FileResolvingTask.cpp
|
||||
modplatform/flame/UrlResolvingTask.h
|
||||
modplatform/flame/UrlResolvingTask.cpp
|
||||
)
|
||||
|
||||
add_unit_test(Index
|
||||
|
||||
@@ -234,7 +234,7 @@ void InstanceList::deleteInstance(const InstanceId& id)
|
||||
auto inst = getInstanceById(id);
|
||||
if(!inst)
|
||||
{
|
||||
qDebug() << "Cannot delete instance" << id << " No such instance is present.";
|
||||
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -819,6 +819,7 @@ bool InstanceList::commitStagedInstance(const QString& path, const QString& inst
|
||||
instanceSet.insert(instID);
|
||||
m_groups.insert(groupName);
|
||||
emit instancesChanged();
|
||||
emit instanceSelectRequest(instID);
|
||||
}
|
||||
saveGroupList();
|
||||
return true;
|
||||
|
||||
@@ -129,6 +129,7 @@ public:
|
||||
signals:
|
||||
void dataIsInvalid();
|
||||
void instancesChanged();
|
||||
void instanceSelectRequest(QString instanceId);
|
||||
void groupsChanged(QSet<QString> groups);
|
||||
|
||||
public slots:
|
||||
|
||||
@@ -635,6 +635,9 @@ void ComponentList::componentDataChanged()
|
||||
qWarning() << "ComponentList got dataChenged signal from a non-Component!";
|
||||
return;
|
||||
}
|
||||
if(objPtr->getID() == "net.minecraft") {
|
||||
emit minecraftChanged();
|
||||
}
|
||||
// figure out which one is it... in a seriously dumb way.
|
||||
int index = 0;
|
||||
for (auto component: d->components)
|
||||
|
||||
@@ -104,6 +104,9 @@ public:
|
||||
/// if there is a save scheduled, do it now.
|
||||
void saveNow();
|
||||
|
||||
signals:
|
||||
void minecraftChanged();
|
||||
|
||||
public:
|
||||
/// get the profile component by id
|
||||
Component * getComponent(const QString &id);
|
||||
|
||||
@@ -648,8 +648,7 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
|
||||
auto i = sessionRef.u.properties.begin();
|
||||
while (i != sessionRef.u.properties.end())
|
||||
{
|
||||
if(i.key() == "preferredLanguage")
|
||||
{
|
||||
if(i.value().length() <= 3) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -113,9 +113,6 @@ public:
|
||||
|
||||
virtual JavaVersion getJavaVersion() const;
|
||||
|
||||
signals:
|
||||
void versionReloaded();
|
||||
|
||||
protected:
|
||||
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
|
||||
QStringList validLaunchMethods();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "FileResolvingTask.h"
|
||||
#include "Json.h"
|
||||
|
||||
const char * metabase = "https://cursemeta.dries007.net";
|
||||
namespace {
|
||||
const char * metabase = "https://cursemeta.dries007.net";
|
||||
}
|
||||
|
||||
Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess)
|
||||
: m_toProcess(toProcess)
|
||||
@@ -34,70 +36,14 @@ void Flame::FileResolvingTask::netJobFinished()
|
||||
int index = 0;
|
||||
for(auto & bytes: results)
|
||||
{
|
||||
auto & out = m_toProcess.files[index];
|
||||
try
|
||||
{
|
||||
auto doc = Json::requireDocument(bytes);
|
||||
auto obj = Json::requireObject(doc);
|
||||
auto & out = m_toProcess.files[index];
|
||||
// result code signifies true failure.
|
||||
if(obj.contains("code"))
|
||||
{
|
||||
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a negative result:";
|
||||
qCritical() << bytes;
|
||||
failed = true;
|
||||
continue;
|
||||
}
|
||||
out.fileName = Json::requireString(obj, "FileNameOnDisk");
|
||||
QString rawUrl = Json::requireString(obj, "DownloadURL");
|
||||
out.url = QUrl(rawUrl, QUrl::TolerantMode);
|
||||
if(!out.url.isValid())
|
||||
{
|
||||
throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
|
||||
}
|
||||
// This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
|
||||
// It is also optional
|
||||
QJsonObject projObj = Json::ensureObject(obj, "_Project", {});
|
||||
if(!projObj.isEmpty())
|
||||
{
|
||||
QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower();
|
||||
if(strType == "singlefile")
|
||||
{
|
||||
out.type = File::Type::SingleFile;
|
||||
}
|
||||
else if(strType == "ctoc")
|
||||
{
|
||||
out.type = File::Type::Ctoc;
|
||||
}
|
||||
else if(strType == "cmod2")
|
||||
{
|
||||
out.type = File::Type::Cmod2;
|
||||
}
|
||||
else if(strType == "mod")
|
||||
{
|
||||
out.type = File::Type::Mod;
|
||||
}
|
||||
else if(strType == "folder")
|
||||
{
|
||||
out.type = File::Type::Folder;
|
||||
}
|
||||
else if(strType == "modpack")
|
||||
{
|
||||
out.type = File::Type::Modpack;
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of unknown file type:" << strType;
|
||||
out.type = File::Type::Unknown;
|
||||
failed = true;
|
||||
continue;
|
||||
}
|
||||
out.targetFolder = Json::ensureString(projObj, "Path", "mods");
|
||||
}
|
||||
out.resolved = true;
|
||||
failed &= (!out.parseFromBytes(bytes));
|
||||
}
|
||||
catch (const JSONValidationError &e)
|
||||
{
|
||||
auto & out = m_toProcess.files[index];
|
||||
|
||||
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:";
|
||||
qCritical() << e.cause();
|
||||
qCritical() << "JSON:";
|
||||
|
||||
@@ -64,3 +64,63 @@ void Flame::loadManifest(Flame::Manifest & m, const QString &filepath)
|
||||
}
|
||||
loadManifestV1(m, obj);
|
||||
}
|
||||
|
||||
bool Flame::File::parseFromBytes(const QByteArray& bytes)
|
||||
{
|
||||
auto doc = Json::requireDocument(bytes);
|
||||
auto obj = Json::requireObject(doc);
|
||||
// result code signifies true failure.
|
||||
if(obj.contains("code"))
|
||||
{
|
||||
qCritical() << "Resolving of" << projectId << fileId << "failed because of a negative result:";
|
||||
qCritical() << bytes;
|
||||
return false;
|
||||
}
|
||||
fileName = Json::requireString(obj, "FileNameOnDisk");
|
||||
QString rawUrl = Json::requireString(obj, "DownloadURL");
|
||||
url = QUrl(rawUrl, QUrl::TolerantMode);
|
||||
if(!url.isValid())
|
||||
{
|
||||
throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
|
||||
}
|
||||
// This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
|
||||
// It is also optional
|
||||
QJsonObject projObj = Json::ensureObject(obj, "_Project", {});
|
||||
if(!projObj.isEmpty())
|
||||
{
|
||||
QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower();
|
||||
if(strType == "singlefile")
|
||||
{
|
||||
type = File::Type::SingleFile;
|
||||
}
|
||||
else if(strType == "ctoc")
|
||||
{
|
||||
type = File::Type::Ctoc;
|
||||
}
|
||||
else if(strType == "cmod2")
|
||||
{
|
||||
type = File::Type::Cmod2;
|
||||
}
|
||||
else if(strType == "mod")
|
||||
{
|
||||
type = File::Type::Mod;
|
||||
}
|
||||
else if(strType == "folder")
|
||||
{
|
||||
type = File::Type::Folder;
|
||||
}
|
||||
else if(strType == "modpack")
|
||||
{
|
||||
type = File::Type::Modpack;
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Resolving of" << projectId << fileId << "failed because of unknown file type:" << strType;
|
||||
type = File::Type::Unknown;
|
||||
return false;
|
||||
}
|
||||
targetFolder = Json::ensureString(projObj, "Path", "mods");
|
||||
}
|
||||
resolved = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ namespace Flame
|
||||
{
|
||||
struct File
|
||||
{
|
||||
// NOTE: throws JSONValidationError
|
||||
bool parseFromBytes(const QByteArray &bytes);
|
||||
|
||||
int projectId = 0;
|
||||
int fileId = 0;
|
||||
// NOTE: the opposite to 'optional'. This is at the time of writing unused.
|
||||
|
||||
175
api/logic/modplatform/flame/UrlResolvingTask.cpp
Normal file
175
api/logic/modplatform/flame/UrlResolvingTask.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#include "UrlResolvingTask.h"
|
||||
#include <QtXml>
|
||||
#include <Json.h>
|
||||
|
||||
|
||||
namespace {
|
||||
const char * metabase = "https://cursemeta.dries007.net";
|
||||
}
|
||||
|
||||
Flame::UrlResolvingTask::UrlResolvingTask(const QString& toProcess)
|
||||
: m_url(toProcess)
|
||||
{
|
||||
}
|
||||
|
||||
void Flame::UrlResolvingTask::executeTask()
|
||||
{
|
||||
resolveUrl();
|
||||
}
|
||||
|
||||
void Flame::UrlResolvingTask::resolveUrl()
|
||||
{
|
||||
setStatus(tr("Resolving URL..."));
|
||||
setProgress(0, 1);
|
||||
QUrl actualUrl(m_url);
|
||||
if(actualUrl.host() != "www.curseforge.com") {
|
||||
emitFailed(tr("Not a Twitch URL."));
|
||||
return;
|
||||
}
|
||||
m_dljob.reset(new NetJob("URL resolver"));
|
||||
|
||||
bool weAreDigging = false;
|
||||
needle = QString();
|
||||
|
||||
if(m_url.startsWith("https://")) {
|
||||
if(m_url.endsWith("?client=y")) {
|
||||
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download?client=y
|
||||
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088?client=y
|
||||
m_url.chop(9);
|
||||
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download
|
||||
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088
|
||||
}
|
||||
if(m_url.endsWith("/download")) {
|
||||
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download -> need to dig inside html...
|
||||
weAreDigging = true;
|
||||
needle = m_url;
|
||||
needle.replace("https://", "twitch://");
|
||||
needle.replace("/download", "/download-client/");
|
||||
m_url.append("?client=y");
|
||||
} else if (m_url.contains("/download/")) {
|
||||
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088
|
||||
m_url.replace("/download/", "/download-client/");
|
||||
}
|
||||
}
|
||||
else if(m_url.startsWith("twitch://")) {
|
||||
// twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
|
||||
m_url.replace(0, 9, "https://");
|
||||
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
|
||||
}
|
||||
auto dl = Net::Download::makeByteArray(QUrl(m_url), &results);
|
||||
m_dljob->addNetAction(dl);
|
||||
if(weAreDigging) {
|
||||
connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processHTML);
|
||||
} else {
|
||||
connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCCIP);
|
||||
}
|
||||
m_dljob->start();
|
||||
}
|
||||
|
||||
void Flame::UrlResolvingTask::processHTML()
|
||||
{
|
||||
QString htmlDoc = QString::fromUtf8(results);
|
||||
auto index = htmlDoc.indexOf(needle);
|
||||
if(index < 0) {
|
||||
emitFailed(tr("Couldn't find the needle in the haystack..."));
|
||||
return;
|
||||
}
|
||||
auto indexStart = index;
|
||||
int indexEnd = -1;
|
||||
while((index + 1) < htmlDoc.size() && htmlDoc[index] != '"') {
|
||||
index ++;
|
||||
if(htmlDoc[index] == '"') {
|
||||
indexEnd = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(indexEnd > 0) {
|
||||
QString found = htmlDoc.mid(indexStart, indexEnd - indexStart);
|
||||
qDebug() << "Found needle: " << found;
|
||||
// twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
|
||||
m_url = found;
|
||||
resolveUrl();
|
||||
return;
|
||||
}
|
||||
emitFailed(tr("Couldn't find the end of the needle in the haystack..."));
|
||||
return;
|
||||
}
|
||||
|
||||
void Flame::UrlResolvingTask::processCCIP()
|
||||
{
|
||||
QDomDocument doc;
|
||||
if (!doc.setContent(results)) {
|
||||
qDebug() << results;
|
||||
emitFailed(tr("Resolving failed."));
|
||||
return;
|
||||
}
|
||||
auto packageNode = doc.namedItem("package");
|
||||
if(!packageNode.isElement()) {
|
||||
emitFailed(tr("Resolving failed: missing package root element."));
|
||||
return;
|
||||
}
|
||||
auto projectNode = packageNode.namedItem("project");
|
||||
if(!projectNode.isElement()) {
|
||||
emitFailed(tr("Resolving failed: missing project element."));
|
||||
return;
|
||||
}
|
||||
auto attribs = projectNode.attributes();
|
||||
|
||||
auto projectIdNode = attribs.namedItem("id");
|
||||
if(!projectIdNode.isAttr()) {
|
||||
emitFailed(tr("Resolving failed: missing id attribute."));
|
||||
return;
|
||||
}
|
||||
auto fileIdNode = attribs.namedItem("file");
|
||||
if(!fileIdNode.isAttr()) {
|
||||
emitFailed(tr("Resolving failed: missing file attribute."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto projectId = projectIdNode.nodeValue();
|
||||
auto fileId = fileIdNode.nodeValue();
|
||||
bool success = true;
|
||||
m_result.projectId = projectId.toInt(&success);
|
||||
if(!success) {
|
||||
emitFailed(tr("Failed to resove projectId as a number."));
|
||||
return;
|
||||
}
|
||||
m_result.fileId = fileId.toInt(&success);
|
||||
if(!success) {
|
||||
emitFailed(tr("Failed to resove fileId as a number."));
|
||||
return;
|
||||
}
|
||||
qDebug() << "Resolved" << m_url << "as" << m_result.projectId << "/" << m_result.fileId;
|
||||
resolveIDs();
|
||||
}
|
||||
|
||||
void Flame::UrlResolvingTask::resolveIDs()
|
||||
{
|
||||
setStatus(tr("Resolving mod IDs..."));
|
||||
m_dljob.reset(new NetJob("Mod id resolver"));
|
||||
auto projectIdStr = QString::number(m_result.projectId);
|
||||
auto fileIdStr = QString::number(m_result.fileId);
|
||||
QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr);
|
||||
auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results);
|
||||
m_dljob->addNetAction(dl);
|
||||
connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCursemeta);
|
||||
m_dljob->start();
|
||||
}
|
||||
|
||||
void Flame::UrlResolvingTask::processCursemeta()
|
||||
{
|
||||
try {
|
||||
if(m_result.parseFromBytes(results)) {
|
||||
emitSucceeded();
|
||||
qDebug() << results;
|
||||
return;
|
||||
}
|
||||
} catch (const JSONValidationError &e) {
|
||||
|
||||
qCritical() << "Resolving of" << m_result.projectId << m_result.fileId << "failed because of a parsing error:";
|
||||
qCritical() << e.cause();
|
||||
qCritical() << "JSON:";
|
||||
qCritical() << results;
|
||||
}
|
||||
emitFailed(tr("Failed to resolve the modpack file."));
|
||||
}
|
||||
43
api/logic/modplatform/flame/UrlResolvingTask.h
Normal file
43
api/logic/modplatform/flame/UrlResolvingTask.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "PackManifest.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
namespace Flame
|
||||
{
|
||||
class MULTIMC_LOGIC_EXPORT UrlResolvingTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UrlResolvingTask(const QString &toProcess);
|
||||
virtual ~UrlResolvingTask() {};
|
||||
|
||||
const Flame::File &getResults() const
|
||||
{
|
||||
return m_result;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
|
||||
protected slots:
|
||||
void processCCIP();
|
||||
void processHTML();
|
||||
void processCursemeta();
|
||||
|
||||
private:
|
||||
void resolveUrl();
|
||||
void resolveIDs();
|
||||
|
||||
private: /* data */
|
||||
QString m_url;
|
||||
QString needle;
|
||||
Flame::File m_result;
|
||||
QByteArray results;
|
||||
NetJobPtr m_dljob;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,8 +36,10 @@ QString INIFile::unescape(QString orig)
|
||||
{
|
||||
if(c == 'n')
|
||||
out += '\n';
|
||||
else if (c == 't')
|
||||
else if(c == 't')
|
||||
out += '\t';
|
||||
else if(c == '#')
|
||||
out += '#';
|
||||
else
|
||||
out += c;
|
||||
prev = 0;
|
||||
@@ -67,6 +69,8 @@ QString INIFile::escape(QString orig)
|
||||
out += "\\t";
|
||||
else if(c == '\\')
|
||||
out += "\\\\";
|
||||
else if(c == '#')
|
||||
out += "\\#";
|
||||
else
|
||||
out += c;
|
||||
}
|
||||
@@ -120,7 +124,15 @@ bool INIFile::loadFile(QByteArray file)
|
||||
{
|
||||
QString &lineRaw = lines[i];
|
||||
// Ignore comments.
|
||||
QString line = lineRaw.left(lineRaw.indexOf('#')).trimmed();
|
||||
int commentIndex = 0;
|
||||
QString line = lineRaw;
|
||||
// Search for comments until no more escaped # are available
|
||||
while((commentIndex = line.indexOf('#', commentIndex + 1)) != -1) {
|
||||
if(commentIndex > 0 && line.at(commentIndex - 1) == '\\') {
|
||||
continue;
|
||||
}
|
||||
line = line.left(lineRaw.indexOf('#')).trimmed();
|
||||
}
|
||||
|
||||
int eqPos = line.indexOf('=');
|
||||
if (eqPos == -1)
|
||||
|
||||
@@ -26,6 +26,7 @@ slots:
|
||||
QTest::newRow("Plain text") << "Lorem ipsum dolor sit amet.";
|
||||
QTest::newRow("Escape sequences") << "Lorem\n\t\n\\n\\tAAZ\nipsum dolor\n\nsit amet.";
|
||||
QTest::newRow("Escape sequences 2") << "\"\n\n\"";
|
||||
QTest::newRow("Hashtags") << "some data#something";
|
||||
}
|
||||
void test_Escape()
|
||||
{
|
||||
@@ -40,7 +41,7 @@ slots:
|
||||
void test_SaveLoad()
|
||||
{
|
||||
QString a = "a";
|
||||
QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\";
|
||||
QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\#thisIsNotAComment";
|
||||
QString filename = "test_SaveLoad.ini";
|
||||
|
||||
// save
|
||||
|
||||
@@ -131,7 +131,14 @@ void DownloadTask::processDownloadedVersionInfo()
|
||||
QObject::connect(netJob.get(), &NetJob::progress, this, &DownloadTask::fileDownloadProgressChanged);
|
||||
QObject::connect(netJob.get(), &NetJob::failed, this, &DownloadTask::fileDownloadFailed);
|
||||
|
||||
setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
|
||||
if(netJob->size() == 1) // Translation issues... see https://github.com/MultiMC/MultiMC5/issues/1701
|
||||
{
|
||||
setStatus(tr("Downloading one update file."));
|
||||
}
|
||||
else
|
||||
{
|
||||
setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
|
||||
}
|
||||
qDebug() << "Begin downloading update files to" << m_updateFilesDir.path();
|
||||
m_filesNetJob = netJob;
|
||||
m_filesNetJob->start();
|
||||
|
||||
@@ -271,12 +271,10 @@ SET(MULTIMC_UIS
|
||||
dialogs/UpdateDialog.ui
|
||||
dialogs/NotificationDialog.ui
|
||||
dialogs/SkinUploadDialog.ui
|
||||
dialogs/VersionSelectDialog.ui
|
||||
|
||||
# Widgets/other
|
||||
widgets/CustomCommands.ui
|
||||
widgets/MCModInfoFrame.ui
|
||||
widgets/VersionSelectWidget.ui
|
||||
)
|
||||
|
||||
set(MULTIMC_QRCS
|
||||
|
||||
@@ -702,6 +702,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
|
||||
// model reset -> selection is invalid. All the instance pointers are wrong.
|
||||
connect(MMC->instances().get(), &InstanceList::dataIsInvalid, this, &MainWindow::selectionBad);
|
||||
|
||||
// handle newly added instances
|
||||
connect(MMC->instances().get(), &InstanceList::instanceSelectRequest, this, &MainWindow::instanceSelectRequest);
|
||||
|
||||
// When the global settings page closes, we want to know about it and update our state
|
||||
connect(MMC, &MultiMC::globalSettingsClosed, this, &MainWindow::globalSettingsClosed);
|
||||
|
||||
@@ -1664,6 +1667,7 @@ void MainWindow::on_actionDeleteInstance_triggered()
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto id = m_selectedInstance->id();
|
||||
auto response = CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("CAREFUL!"),
|
||||
@@ -1674,7 +1678,7 @@ void MainWindow::on_actionDeleteInstance_triggered()
|
||||
)->exec();
|
||||
if (response == QMessageBox::Yes)
|
||||
{
|
||||
MMC->instances()->deleteInstance(m_selectedInstance->id());
|
||||
MMC->instances()->deleteInstance(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1836,6 +1840,11 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::instanceSelectRequest(QString id)
|
||||
{
|
||||
setSelectedInstanceById(id);
|
||||
}
|
||||
|
||||
void MainWindow::instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
|
||||
{
|
||||
auto current = view->selectionModel()->currentIndex();
|
||||
|
||||
@@ -152,6 +152,8 @@ private slots:
|
||||
|
||||
void instanceChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
|
||||
void instanceSelectRequest(QString id);
|
||||
|
||||
void instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
|
||||
|
||||
void selectionBad();
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
#include <pages/modplatform/ImportPage.h>
|
||||
#include <pages/modplatform/TechnicPage.h>
|
||||
|
||||
|
||||
|
||||
NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent)
|
||||
: QDialog(parent), ui(new Ui::NewInstanceDialog)
|
||||
{
|
||||
@@ -94,8 +96,15 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
|
||||
|
||||
if(!url.isEmpty())
|
||||
{
|
||||
m_container->selectPage("import");
|
||||
importPage->setUrl(url);
|
||||
QUrl actualUrl(url);
|
||||
if(actualUrl.host() == "www.curseforge.com") {
|
||||
m_container->selectPage("twitch");
|
||||
twitchPage->setUrl(url);
|
||||
}
|
||||
else {
|
||||
m_container->selectPage("import");
|
||||
importPage->setUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
updateDialogState();
|
||||
@@ -119,13 +128,13 @@ void NewInstanceDialog::accept()
|
||||
QList<BasePage *> NewInstanceDialog::getPages()
|
||||
{
|
||||
importPage = new ImportPage(this);
|
||||
twitchPage = new TwitchPage(this);
|
||||
return
|
||||
{
|
||||
new VanillaPage(this),
|
||||
new FTBPage(this),
|
||||
importPage,
|
||||
new TwitchPage(this),
|
||||
new TechnicPage(this)
|
||||
twitchPage,
|
||||
new FTBPage(this)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ class NewInstanceDialog;
|
||||
class PageContainer;
|
||||
class QDialogButtonBox;
|
||||
class ImportPage;
|
||||
class TwitchPage;
|
||||
|
||||
class NewInstanceDialog : public QDialog, public BasePageProvider
|
||||
{
|
||||
@@ -67,6 +68,7 @@ private:
|
||||
|
||||
QString InstIconKey;
|
||||
ImportPage *importPage = nullptr;
|
||||
TwitchPage *twitchPage = nullptr;
|
||||
std::unique_ptr<InstanceTask> creationTask;
|
||||
|
||||
bool importIcon = false;
|
||||
|
||||
@@ -14,9 +14,14 @@
|
||||
*/
|
||||
|
||||
#include "VersionSelectDialog.h"
|
||||
#include "ui_VersionSelectDialog.h"
|
||||
|
||||
#include "dialogs/ProgressDialog.h"
|
||||
#include <QtWidgets/QButtonGroup>
|
||||
#include <QtWidgets/QDialogButtonBox>
|
||||
#include <QtWidgets/QHBoxLayout>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QVBoxLayout>
|
||||
|
||||
#include <dialogs/ProgressDialog.h>
|
||||
#include "CustomMessageBox.h"
|
||||
|
||||
#include <BaseVersion.h>
|
||||
@@ -26,42 +31,72 @@
|
||||
#include "MultiMC.h"
|
||||
#include <VersionProxyModel.h>
|
||||
#include <widgets/VersionSelectWidget.h>
|
||||
#include <QPushButton>
|
||||
|
||||
VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent, bool cancelable)
|
||||
: QDialog(parent), ui(new Ui::VersionSelectDialog), m_vlist(vlist)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setObjectName(QStringLiteral("VersionSelectDialog"));
|
||||
resize(400, 347);
|
||||
m_verticalLayout = new QVBoxLayout(this);
|
||||
m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
|
||||
|
||||
ui->setupUi(this);
|
||||
m_versionWidget = new VersionSelectWidget(parent);
|
||||
m_verticalLayout->addWidget(m_versionWidget);
|
||||
|
||||
m_horizontalLayout = new QHBoxLayout();
|
||||
m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
|
||||
|
||||
m_refreshButton = new QPushButton(this);
|
||||
m_refreshButton->setObjectName(QStringLiteral("refreshButton"));
|
||||
m_horizontalLayout->addWidget(m_refreshButton);
|
||||
|
||||
m_buttonBox = new QDialogButtonBox(this);
|
||||
m_buttonBox->setObjectName(QStringLiteral("buttonBox"));
|
||||
m_buttonBox->setOrientation(Qt::Horizontal);
|
||||
m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
|
||||
m_horizontalLayout->addWidget(m_buttonBox);
|
||||
|
||||
m_verticalLayout->addLayout(m_horizontalLayout);
|
||||
|
||||
retranslate();
|
||||
|
||||
QObject::connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
|
||||
QObject::connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
|
||||
|
||||
QMetaObject::connectSlotsByName(this);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setWindowTitle(title);
|
||||
|
||||
m_vlist = vlist;
|
||||
|
||||
if (!cancelable)
|
||||
{
|
||||
ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
|
||||
m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
VersionSelectDialog::~VersionSelectDialog()
|
||||
void VersionSelectDialog::retranslate()
|
||||
{
|
||||
delete ui;
|
||||
// FIXME: overrides custom title given in constructor!
|
||||
setWindowTitle(tr("Choose Version"));
|
||||
m_refreshButton->setToolTip(tr("Reloads the version list."));
|
||||
m_refreshButton->setText(tr("&Refresh"));
|
||||
}
|
||||
|
||||
void VersionSelectDialog::setCurrentVersion(const QString& version)
|
||||
{
|
||||
ui->versionSelect->setCurrentVersion(version);
|
||||
m_currentVersion = version;
|
||||
m_versionWidget->setCurrentVersion(version);
|
||||
}
|
||||
|
||||
void VersionSelectDialog::setEmptyString(QString emptyString)
|
||||
{
|
||||
ui->versionSelect->setEmptyString(emptyString);
|
||||
m_versionWidget->setEmptyString(emptyString);
|
||||
}
|
||||
|
||||
void VersionSelectDialog::setEmptyErrorString(QString emptyErrorString)
|
||||
{
|
||||
ui->versionSelect->setEmptyErrorString(emptyErrorString);
|
||||
m_versionWidget->setEmptyErrorString(emptyErrorString);
|
||||
}
|
||||
|
||||
void VersionSelectDialog::setResizeOn(int column)
|
||||
@@ -72,30 +107,35 @@ void VersionSelectDialog::setResizeOn(int column)
|
||||
int VersionSelectDialog::exec()
|
||||
{
|
||||
QDialog::open();
|
||||
ui->versionSelect->initialize(m_vlist);
|
||||
m_versionWidget->initialize(m_vlist);
|
||||
if(resizeOnColumn != -1)
|
||||
{
|
||||
ui->versionSelect->setResizeOn(resizeOnColumn);
|
||||
m_versionWidget->setResizeOn(resizeOnColumn);
|
||||
}
|
||||
return QDialog::exec();
|
||||
}
|
||||
|
||||
void VersionSelectDialog::selectRecommended()
|
||||
{
|
||||
m_versionWidget->selectRecommended();
|
||||
}
|
||||
|
||||
BaseVersionPtr VersionSelectDialog::selectedVersion() const
|
||||
{
|
||||
return ui->versionSelect->selectedVersion();
|
||||
return m_versionWidget->selectedVersion();
|
||||
}
|
||||
|
||||
void VersionSelectDialog::on_refreshButton_clicked()
|
||||
{
|
||||
m_versionWidget->loadList();
|
||||
}
|
||||
|
||||
void VersionSelectDialog::setExactFilter(BaseVersionList::ModelRoles role, QString filter)
|
||||
{
|
||||
ui->versionSelect->setExactFilter(role, filter);
|
||||
m_versionWidget->setExactFilter(role, filter);
|
||||
}
|
||||
|
||||
void VersionSelectDialog::setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter)
|
||||
{
|
||||
ui->versionSelect->setFuzzyFilter(role, filter);
|
||||
m_versionWidget->setFuzzyFilter(role, filter);
|
||||
}
|
||||
|
||||
void VersionSelectDialog::setFilterBoxVisible(bool visible)
|
||||
{
|
||||
ui->versionSelect->setFilterBoxVisible(visible);
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class VersionSelectDialog : public QDialog
|
||||
|
||||
public:
|
||||
explicit VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent = 0, bool cancelable = true);
|
||||
virtual ~VersionSelectDialog();
|
||||
virtual ~VersionSelectDialog() {};
|
||||
|
||||
int exec() override;
|
||||
|
||||
@@ -53,12 +53,23 @@ public:
|
||||
void setEmptyErrorString(QString emptyErrorString);
|
||||
void setResizeOn(int column);
|
||||
|
||||
void setFilterBoxVisible(bool visible);
|
||||
private slots:
|
||||
void on_refreshButton_clicked();
|
||||
|
||||
private:
|
||||
Ui::VersionSelectDialog *ui;
|
||||
void retranslate();
|
||||
void selectRecommended();
|
||||
|
||||
private:
|
||||
QString m_currentVersion;
|
||||
VersionSelectWidget *m_versionWidget = nullptr;
|
||||
QVBoxLayout *m_verticalLayout = nullptr;
|
||||
QHBoxLayout *m_horizontalLayout = nullptr;
|
||||
QPushButton *m_refreshButton = nullptr;
|
||||
QDialogButtonBox *m_buttonBox = nullptr;
|
||||
|
||||
BaseVersionList *m_vlist = nullptr;
|
||||
|
||||
VersionProxyModel *m_proxyModel = nullptr;
|
||||
|
||||
int resizeOnColumn = -1;
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>VersionSelectDialog</class>
|
||||
<widget class="QDialog" name="VersionSelectDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="VersionSelectWidget" name="versionSelect" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>VersionSelectWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/VersionSelectWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>VersionSelectDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>VersionSelectDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -220,6 +220,8 @@ VisualGroup *GroupView::categoryAt(const QPoint &pos, VisualGroup::HitResults &
|
||||
|
||||
QString GroupView::groupNameAt(const QPoint &point)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
VisualGroup::HitResults hitresult;
|
||||
auto group = categoryAt(point + offset(), hitresult);
|
||||
if(group && (hitresult & (VisualGroup::HeaderHit | VisualGroup::BodyHit)))
|
||||
@@ -246,7 +248,7 @@ int GroupView::itemWidth() const
|
||||
|
||||
void GroupView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
// endCategoryEditor();
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QPoint visualPos = event->pos();
|
||||
QPoint geometryPos = event->pos() + offset();
|
||||
@@ -295,6 +297,8 @@ void GroupView::mousePressEvent(QMouseEvent *event)
|
||||
|
||||
void GroupView::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QPoint topLeft;
|
||||
QPoint visualPos = event->pos();
|
||||
QPoint geometryPos = event->pos() + offset();
|
||||
@@ -351,6 +355,8 @@ void GroupView::mouseMoveEvent(QMouseEvent *event)
|
||||
|
||||
void GroupView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QPoint visualPos = event->pos();
|
||||
QPoint geometryPos = event->pos() + offset();
|
||||
QPersistentModelIndex index = indexAt(visualPos);
|
||||
@@ -405,6 +411,8 @@ void GroupView::mouseReleaseEvent(QMouseEvent *event)
|
||||
|
||||
void GroupView::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QModelIndex index = indexAt(event->pos());
|
||||
if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index))
|
||||
{
|
||||
@@ -528,6 +536,8 @@ void GroupView::resizeEvent(QResizeEvent *event)
|
||||
|
||||
void GroupView::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
if (!isDragEventAccepted(event))
|
||||
{
|
||||
return;
|
||||
@@ -539,6 +549,8 @@ void GroupView::dragEnterEvent(QDragEnterEvent *event)
|
||||
|
||||
void GroupView::dragMoveEvent(QDragMoveEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
if (!isDragEventAccepted(event))
|
||||
{
|
||||
return;
|
||||
@@ -550,12 +562,16 @@ void GroupView::dragMoveEvent(QDragMoveEvent *event)
|
||||
|
||||
void GroupView::dragLeaveEvent(QDragLeaveEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
m_lastDragPosition = QPoint();
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void GroupView::dropEvent(QDropEvent *event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
m_lastDragPosition = QPoint();
|
||||
|
||||
stopAutoScroll();
|
||||
@@ -606,6 +622,8 @@ void GroupView::dropEvent(QDropEvent *event)
|
||||
|
||||
void GroupView::startDrag(Qt::DropActions supportedActions)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QModelIndexList indexes = selectionModel()->selectedIndexes();
|
||||
if(indexes.count() == 0)
|
||||
return;
|
||||
@@ -651,11 +669,15 @@ void GroupView::startDrag(Qt::DropActions supportedActions)
|
||||
|
||||
QRect GroupView::visualRect(const QModelIndex &index) const
|
||||
{
|
||||
const_cast<GroupView*>(this)->executeDelayedItemsLayout();
|
||||
|
||||
return geometryRect(index).translated(-offset());
|
||||
}
|
||||
|
||||
QRect GroupView::geometryRect(const QModelIndex &index) const
|
||||
{
|
||||
const_cast<GroupView*>(this)->executeDelayedItemsLayout();
|
||||
|
||||
if (!index.isValid() || isIndexHidden(index) || index.column() > 0)
|
||||
{
|
||||
return QRect();
|
||||
@@ -695,9 +717,10 @@ QModelIndex GroupView::indexAt(const QPoint &point) const
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
void GroupView::setSelection(const QRect &rect,
|
||||
const QItemSelectionModel::SelectionFlags commands)
|
||||
void GroupView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
for (int i = 0; i < model()->rowCount(); ++i)
|
||||
{
|
||||
QModelIndex index = model()->index(i, 0);
|
||||
@@ -732,8 +755,7 @@ QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) cons
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices,
|
||||
QRect *r) const
|
||||
QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const
|
||||
{
|
||||
Q_ASSERT(r);
|
||||
QRect &rect = *r;
|
||||
|
||||
@@ -30,6 +30,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
#endif
|
||||
|
||||
// initialize Qt
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Package: multimc
|
||||
Version: 1.2-1
|
||||
Version: 1.3-1
|
||||
Architecture: all
|
||||
Maintainer: Petr Mrázek <peterix@gmail.com>
|
||||
Section: games
|
||||
Priority: optional
|
||||
Installed-Size: 75
|
||||
Depends: zenity, desktop-file-utils
|
||||
Depends: zenity, desktop-file-utils, qt5-default
|
||||
Recommends: openjdk-8-jre
|
||||
Homepage: http://multimc.org
|
||||
Description: A local install wrapper for MultiMC
|
||||
|
||||
@@ -4,9 +4,9 @@ A simple ubuntu package for MultiMC that wraps the contains a script that downlo
|
||||
It contains a `.desktop` file, an icon, and a simple script that does the heavy lifting.
|
||||
|
||||
# How to build this?
|
||||
You need dpkg utils. Rename the `multimc` folder to `multimc_1.2-1` and then run:
|
||||
You need dpkg utils. Rename the `multimc` folder to `multimc_1.3-1` and then run:
|
||||
```
|
||||
fakeroot dpkg-deb --build multimc_1.2-1
|
||||
fakeroot dpkg-deb --build multimc_1.3-1
|
||||
```
|
||||
|
||||
Replace the version with whatever is appropriate.
|
||||
|
||||
@@ -105,7 +105,6 @@ void JavaPage::on_javaDetectBtn_clicked()
|
||||
JavaInstallPtr java;
|
||||
|
||||
VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true);
|
||||
vselect.setFilterBoxVisible(false);
|
||||
vselect.setResizeOn(2);
|
||||
vselect.exec();
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "MultiMC.h"
|
||||
#include "Env.h"
|
||||
|
||||
ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage)
|
||||
{
|
||||
@@ -75,6 +76,9 @@ void ProxyPage::applySettings()
|
||||
s->set("ProxyPort", ui->proxyPortEdit->value());
|
||||
s->set("ProxyUser", ui->proxyUserEdit->text());
|
||||
s->set("ProxyPass", ui->proxyPassEdit->text());
|
||||
|
||||
ENV.updateProxySettings(proxyType, ui->proxyAddrEdit->text(), ui->proxyPortEdit->value(),
|
||||
ui->proxyUserEdit->text(), ui->proxyPassEdit->text());
|
||||
}
|
||||
void ProxyPage::loadSettings()
|
||||
{
|
||||
|
||||
@@ -226,7 +226,6 @@ void InstanceSettingsPage::on_javaDetectBtn_clicked()
|
||||
JavaInstallPtr java;
|
||||
|
||||
VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true);
|
||||
vselect.setFilterBoxVisible(false);
|
||||
vselect.setResizeOn(2);
|
||||
vselect.exec();
|
||||
|
||||
|
||||
@@ -286,6 +286,38 @@ void ScreenshotsPage::on_uploadBtn_clicked()
|
||||
|
||||
QList<ScreenshotPtr> uploaded;
|
||||
auto job = NetJobPtr(new NetJob("Screenshot Upload"));
|
||||
if(selection.size() < 2)
|
||||
{
|
||||
auto item = selection.at(0);
|
||||
auto info = m_model->fileInfo(item);
|
||||
auto screenshot = std::make_shared<ScreenShot>(info);
|
||||
job->addNetAction(ImgurUpload::make(screenshot));
|
||||
|
||||
m_uploadActive = true;
|
||||
ProgressDialog dialog(this);
|
||||
if(dialog.execWithTask(job.get()) != QDialog::Accepted)
|
||||
{
|
||||
CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"),
|
||||
tr("Unknown error"), QMessageBox::Warning)->exec();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto link = screenshot->m_url;
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
clipboard->setText(link);
|
||||
CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("Upload finished"),
|
||||
tr("The <a href=\"%1\">link to the uploaded screenshot</a> has been placed in your clipboard.")
|
||||
.arg(link),
|
||||
QMessageBox::Information
|
||||
)->exec();
|
||||
}
|
||||
|
||||
m_uploadActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto item : selection)
|
||||
{
|
||||
auto info = m_model->fileInfo(item);
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "minecraft/Mod.h"
|
||||
#include "icons/IconList.h"
|
||||
#include "Exception.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include "MultiMC.h"
|
||||
|
||||
@@ -108,26 +109,18 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
|
||||
|
||||
reloadComponentList();
|
||||
|
||||
if (m_profile)
|
||||
{
|
||||
auto proxy = new IconProxy(ui->packageView);
|
||||
proxy->setSourceModel(m_profile.get());
|
||||
ui->packageView->setModel(proxy);
|
||||
ui->packageView->installEventFilter(this);
|
||||
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
|
||||
auto smodel = ui->packageView->selectionModel();
|
||||
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
|
||||
updateVersionControls();
|
||||
// select first item.
|
||||
preselect(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
disableVersionControls();
|
||||
}
|
||||
connect(m_inst, &MinecraftInstance::versionReloaded, this,
|
||||
&VersionPage::updateVersionControls);
|
||||
auto proxy = new IconProxy(ui->packageView);
|
||||
proxy->setSourceModel(m_profile.get());
|
||||
ui->packageView->setModel(proxy);
|
||||
ui->packageView->installEventFilter(this);
|
||||
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
|
||||
auto smodel = ui->packageView->selectionModel();
|
||||
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
|
||||
|
||||
updateVersionControls();
|
||||
preselect(0);
|
||||
connect(m_profile.get(), &ComponentList::minecraftChanged, this, &VersionPage::updateVersionControls);
|
||||
}
|
||||
|
||||
VersionPage::~VersionPage()
|
||||
@@ -180,18 +173,21 @@ void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex &
|
||||
|
||||
void VersionPage::updateVersionControls()
|
||||
{
|
||||
ui->fabricBtn->setEnabled(true);
|
||||
ui->forgeBtn->setEnabled(true);
|
||||
ui->liteloaderBtn->setEnabled(true);
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void VersionPage::disableVersionControls()
|
||||
{
|
||||
ui->fabricBtn->setEnabled(false);
|
||||
ui->forgeBtn->setEnabled(false);
|
||||
ui->liteloaderBtn->setEnabled(false);
|
||||
ui->reloadBtn->setEnabled(false);
|
||||
// FIXME: this is a dirty hack
|
||||
if(m_profile) {
|
||||
auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft"));
|
||||
bool newCraft = minecraftVersion >= Version("1.14");
|
||||
bool oldCraft = minecraftVersion <= Version("1.12.2");
|
||||
ui->fabricBtn->setEnabled(newCraft);
|
||||
ui->forgeBtn->setEnabled(oldCraft);
|
||||
ui->liteloaderBtn->setEnabled(oldCraft);
|
||||
}
|
||||
else {
|
||||
ui->fabricBtn->setEnabled(false);
|
||||
ui->forgeBtn->setEnabled(false);
|
||||
ui->liteloaderBtn->setEnabled(false);
|
||||
ui->reloadBtn->setEnabled(false);
|
||||
}
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
@@ -317,18 +313,15 @@ void VersionPage::on_changeVersionBtn_clicked()
|
||||
on_liteloaderBtn_clicked();
|
||||
return;
|
||||
}
|
||||
VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this);
|
||||
if (uid == "net.fabricmc.intermediary")
|
||||
{
|
||||
on_fabricBtn_clicked();
|
||||
return;
|
||||
vselect.setEmptyString(tr("No Fabric Loader versions are currently available."));
|
||||
vselect.setEmptyErrorString(tr("Couldn't load or download the Fabric Loader version lists!"));
|
||||
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
|
||||
}
|
||||
|
||||
VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this);
|
||||
vselect.setEmptyString(tr("No %1 versions are currently available.").arg(name));
|
||||
vselect.setEmptyErrorString(tr("Couldn't load or download the %1 version lists!").arg(name));
|
||||
|
||||
auto currentVersion = patch->getVersion();
|
||||
if(!currentVersion.isEmpty() && !currentVersion.isNull())
|
||||
if(!currentVersion.isEmpty())
|
||||
{
|
||||
vselect.setCurrentVersion(currentVersion);
|
||||
}
|
||||
@@ -379,7 +372,6 @@ void VersionPage::on_forgeBtn_clicked()
|
||||
return;
|
||||
}
|
||||
VersionSelectDialog vselect(vlist.get(), tr("Select Forge version"), this);
|
||||
vselect.setFilterBoxVisible(false);
|
||||
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
|
||||
vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft"));
|
||||
vselect.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!"));
|
||||
|
||||
@@ -66,7 +66,6 @@ private slots:
|
||||
void on_downloadBtn_clicked();
|
||||
|
||||
void updateVersionControls();
|
||||
void disableVersionControls();
|
||||
void on_changeVersionBtn_clicked();
|
||||
|
||||
private:
|
||||
|
||||
@@ -75,6 +75,11 @@ void ImportPage::updateState()
|
||||
}
|
||||
else
|
||||
{
|
||||
if(input.endsWith("?client=y")) {
|
||||
input.chop(9);
|
||||
input.append("/file");
|
||||
url = QUrl::fromUserInput(input);
|
||||
}
|
||||
// hook, line and sinker.
|
||||
QFileInfo fi(url.fileName());
|
||||
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
|
||||
#include "MultiMC.h"
|
||||
#include "dialogs/NewInstanceDialog.h"
|
||||
#include <InstanceImportTask.h>
|
||||
|
||||
TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->checkButton, &QPushButton::clicked, this, &TwitchPage::triggerCheck);
|
||||
}
|
||||
|
||||
TwitchPage::~TwitchPage()
|
||||
@@ -17,10 +19,42 @@ TwitchPage::~TwitchPage()
|
||||
|
||||
bool TwitchPage::shouldDisplay() const
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TwitchPage::openedImpl()
|
||||
{
|
||||
dialog->setSuggestedPack();
|
||||
}
|
||||
|
||||
void TwitchPage::triggerCheck(bool)
|
||||
{
|
||||
if(m_modIdResolver) {
|
||||
return;
|
||||
}
|
||||
auto task = new Flame::UrlResolvingTask(ui->lineEdit->text());
|
||||
connect(task, &Task::finished, this, &TwitchPage::checkDone);
|
||||
m_modIdResolver.reset(task);
|
||||
task->start();
|
||||
}
|
||||
|
||||
void TwitchPage::setUrl(const QString& url)
|
||||
{
|
||||
ui->lineEdit->setText(url);
|
||||
triggerCheck(true);
|
||||
}
|
||||
|
||||
void TwitchPage::checkDone()
|
||||
{
|
||||
auto result = m_modIdResolver->getResults();
|
||||
auto formatted = QString("Project %1, File %2").arg(result.projectId).arg(result.fileId);
|
||||
if(result.resolved && result.type == Flame::File::Type::Modpack) {
|
||||
ui->twitchLabel->setText(formatted);
|
||||
QFileInfo fi(result.fileName);
|
||||
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(result.url));
|
||||
} else {
|
||||
ui->twitchLabel->setPixmap(QPixmap(QString::fromUtf8(":/assets/deadglitch")));
|
||||
dialog->setSuggestedPack();
|
||||
}
|
||||
m_modIdResolver.reset();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "pages/BasePage.h"
|
||||
#include <MultiMC.h>
|
||||
#include "tasks/Task.h"
|
||||
#include "modplatform/flame/UrlResolvingTask.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
@@ -37,7 +38,7 @@ public:
|
||||
virtual ~TwitchPage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Twitch");
|
||||
return tr("Twitch URL");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
@@ -55,7 +56,14 @@ public:
|
||||
|
||||
void openedImpl() override;
|
||||
|
||||
void setUrl(const QString & url);
|
||||
|
||||
private slots:
|
||||
void triggerCheck(bool checked);
|
||||
void checkDone();
|
||||
|
||||
private:
|
||||
Ui::TwitchPage *ui = nullptr;
|
||||
NewInstanceDialog* dialog = nullptr;
|
||||
shared_qobject_ptr<Flame::UrlResolvingTask> m_modIdResolver;
|
||||
};
|
||||
|
||||
@@ -10,9 +10,25 @@
|
||||
<height>405</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Twitch URL:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="3">
|
||||
<widget class="QLabel" name="twitchLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>40</pointsize>
|
||||
@@ -26,8 +42,19 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="checkButton">
|
||||
<property name="text">
|
||||
<string>Check</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>lineEdit</tabstop>
|
||||
<tabstop>checkButton</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../resources/assets/assets.qrc"/>
|
||||
</resources>
|
||||
|
||||
@@ -16,7 +16,15 @@ VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
connect(ui->versionSelect, &VersionSelectWidget::selectedVersionChanged, this, &VanillaPage::setSelectedVersion);
|
||||
connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &VanillaPage::setSelectedVersion);
|
||||
filterChanged();
|
||||
connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
|
||||
connect(ui->betaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
|
||||
connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
|
||||
connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
|
||||
connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
|
||||
connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
|
||||
connect(ui->refreshBtn, &QPushButton::clicked, this, &VanillaPage::refresh);
|
||||
}
|
||||
|
||||
void VanillaPage::openedImpl()
|
||||
@@ -24,7 +32,7 @@ void VanillaPage::openedImpl()
|
||||
if(!initialized)
|
||||
{
|
||||
auto vlist = ENV.metadataIndex()->get("net.minecraft");
|
||||
ui->versionSelect->initialize(vlist.get());
|
||||
ui->versionList->initialize(vlist.get());
|
||||
initialized = true;
|
||||
}
|
||||
else
|
||||
@@ -33,6 +41,30 @@ void VanillaPage::openedImpl()
|
||||
}
|
||||
}
|
||||
|
||||
void VanillaPage::refresh()
|
||||
{
|
||||
ui->versionList->loadList();
|
||||
}
|
||||
|
||||
void VanillaPage::filterChanged()
|
||||
{
|
||||
QStringList out;
|
||||
if(ui->alphaFilter->isChecked())
|
||||
out << "(old_alpha)";
|
||||
if(ui->betaFilter->isChecked())
|
||||
out << "(old_beta)";
|
||||
if(ui->snapshotFilter->isChecked())
|
||||
out << "(snapshot)";
|
||||
if(ui->oldSnapshotFilter->isChecked())
|
||||
out << "(old_snapshot)";
|
||||
if(ui->releaseFilter->isChecked())
|
||||
out << "(release)";
|
||||
if(ui->experimentsFilter->isChecked())
|
||||
out << "(experiment)";
|
||||
auto regexp = out.join('|');
|
||||
ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false));
|
||||
}
|
||||
|
||||
VanillaPage::~VanillaPage()
|
||||
{
|
||||
delete ui;
|
||||
|
||||
@@ -59,7 +59,11 @@ public:
|
||||
public slots:
|
||||
void setSelectedVersion(BaseVersionPtr version);
|
||||
|
||||
private slots:
|
||||
void filterChanged();
|
||||
|
||||
private:
|
||||
void refresh();
|
||||
void suggestCurrent();
|
||||
|
||||
private:
|
||||
|
||||
@@ -10,16 +10,137 @@
|
||||
<height>607</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="VersionSelectWidget" name="versionSelect">
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string notr="true"/>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Filter</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="releaseFilter">
|
||||
<property name="text">
|
||||
<string>Releases</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="snapshotFilter">
|
||||
<property name="text">
|
||||
<string>Snapshots</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="oldSnapshotFilter">
|
||||
<property name="text">
|
||||
<string>Old Snapshots</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="betaFilter">
|
||||
<property name="text">
|
||||
<string>Betas</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alphaFilter">
|
||||
<property name="text">
|
||||
<string>Alphas</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="experimentsFilter">
|
||||
<property name="text">
|
||||
<string>Experiments</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refreshBtn">
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="VersionSelectWidget" name="versionList" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -33,6 +154,16 @@
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>releaseFilter</tabstop>
|
||||
<tabstop>snapshotFilter</tabstop>
|
||||
<tabstop>oldSnapshotFilter</tabstop>
|
||||
<tabstop>betaFilter</tabstop>
|
||||
<tabstop>alphaFilter</tabstop>
|
||||
<tabstop>experimentsFilter</tabstop>
|
||||
<tabstop>refreshBtn</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -1,65 +1,76 @@
|
||||
#include "VersionSelectWidget.h"
|
||||
#include "ui_VersionSelectWidget.h"
|
||||
|
||||
#include "BaseVersion.h"
|
||||
#include "dialogs/CustomMessageBox.h"
|
||||
#include "VersionProxyModel.h"
|
||||
#include <QProgressBar>
|
||||
#include <QVBoxLayout>
|
||||
#include "VersionListView.h"
|
||||
#include <QHeaderView>
|
||||
#include <VersionProxyModel.h>
|
||||
#include <dialogs/CustomMessageBox.h>
|
||||
|
||||
VersionSelectWidget::VersionSelectWidget(QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::VersionSelectWidget)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setObjectName(QStringLiteral("VersionSelectWidget"));
|
||||
verticalLayout = new QVBoxLayout(this);
|
||||
verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
|
||||
verticalLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_proxyModel = new VersionProxyModel(this);
|
||||
ui->setupUi(this);
|
||||
ui->listView->setModel(m_proxyModel);
|
||||
|
||||
ui->loadProgressBar->setVisible(false);
|
||||
listView = new VersionListView(this);
|
||||
listView->setObjectName(QStringLiteral("listView"));
|
||||
listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
listView->setAlternatingRowColors(true);
|
||||
listView->setRootIsDecorated(false);
|
||||
listView->setItemsExpandable(false);
|
||||
listView->setWordWrap(true);
|
||||
listView->header()->setCascadingSectionResizes(true);
|
||||
listView->header()->setStretchLastSection(false);
|
||||
listView->setModel(m_proxyModel);
|
||||
verticalLayout->addWidget(listView);
|
||||
|
||||
connect(ui->listView->selectionModel(), &QItemSelectionModel::currentRowChanged,
|
||||
this, &VersionSelectWidget::currentRowChanged);
|
||||
connect(ui->releasesCheckbox, &QCheckBox::stateChanged, this, &VersionSelectWidget::filterChanged);
|
||||
connect(ui->snapshotsCheckbox, &QCheckBox::stateChanged, this, &VersionSelectWidget::filterChanged);
|
||||
connect(ui->oldSnapshotsCheckbox, &QCheckBox::stateChanged, this, &VersionSelectWidget::filterChanged);
|
||||
connect(ui->betasCheckbox, &QCheckBox::stateChanged, this, &VersionSelectWidget::filterChanged);
|
||||
connect(ui->alphasCheckbox, &QCheckBox::stateChanged, this, &VersionSelectWidget::filterChanged);
|
||||
connect(ui->refreshButton, &QPushButton::clicked, this, &VersionSelectWidget::loadList);
|
||||
sneakyProgressBar = new QProgressBar(this);
|
||||
sneakyProgressBar->setObjectName(QStringLiteral("sneakyProgressBar"));
|
||||
sneakyProgressBar->setFormat(QStringLiteral("%p%"));
|
||||
verticalLayout->addWidget(sneakyProgressBar);
|
||||
sneakyProgressBar->setHidden(true);
|
||||
connect(listView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &VersionSelectWidget::currentRowChanged);
|
||||
|
||||
QMetaObject::connectSlotsByName(this);
|
||||
}
|
||||
|
||||
void VersionSelectWidget::setCurrentVersion(const QString& version)
|
||||
{
|
||||
m_proxyModel->setCurrentVersion(version);
|
||||
m_currentVersion = version;
|
||||
m_proxyModel->setCurrentVersion(version);
|
||||
}
|
||||
|
||||
void VersionSelectWidget::setEmptyString(QString emptyString)
|
||||
{
|
||||
ui->listView->setEmptyString(emptyString);
|
||||
listView->setEmptyString(emptyString);
|
||||
}
|
||||
|
||||
void VersionSelectWidget::setEmptyErrorString(QString emptyErrorString)
|
||||
{
|
||||
ui->listView->setEmptyErrorString(emptyErrorString);
|
||||
listView->setEmptyErrorString(emptyErrorString);
|
||||
}
|
||||
|
||||
VersionSelectWidget::~VersionSelectWidget()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void VersionSelectWidget::setResizeOn(int column)
|
||||
{
|
||||
ui->listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::ResizeToContents);
|
||||
listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::ResizeToContents);
|
||||
resizeOnColumn = column;
|
||||
ui->listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch);
|
||||
listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch);
|
||||
}
|
||||
|
||||
void VersionSelectWidget::initialize(BaseVersionList *vlist)
|
||||
{
|
||||
m_vlist = vlist;
|
||||
m_proxyModel->setSourceModel(vlist);
|
||||
ui->listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
ui->listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch);
|
||||
listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch);
|
||||
|
||||
if (!m_vlist->isLoaded())
|
||||
{
|
||||
@@ -69,7 +80,7 @@ void VersionSelectWidget::initialize(BaseVersionList *vlist)
|
||||
{
|
||||
if (m_proxyModel->rowCount() == 0)
|
||||
{
|
||||
ui->listView->setEmptyMode(VersionListView::String);
|
||||
listView->setEmptyMode(VersionListView::String);
|
||||
}
|
||||
preselect();
|
||||
}
|
||||
@@ -95,16 +106,16 @@ void VersionSelectWidget::loadList()
|
||||
{
|
||||
loadTask->start();
|
||||
}
|
||||
ui->loadProgressBar->setHidden(false);
|
||||
sneakyProgressBar->setHidden(false);
|
||||
}
|
||||
|
||||
void VersionSelectWidget::onTaskSucceeded()
|
||||
{
|
||||
if (m_proxyModel->rowCount() == 0)
|
||||
{
|
||||
ui->listView->setEmptyMode(VersionListView::String);
|
||||
listView->setEmptyMode(VersionListView::String);
|
||||
}
|
||||
ui->loadProgressBar->setHidden(true);
|
||||
sneakyProgressBar->setHidden(true);
|
||||
preselect();
|
||||
loadTask = nullptr;
|
||||
}
|
||||
@@ -117,8 +128,8 @@ void VersionSelectWidget::onTaskFailed(const QString& reason)
|
||||
|
||||
void VersionSelectWidget::changeProgress(qint64 current, qint64 total)
|
||||
{
|
||||
ui->loadProgressBar->setMaximum(total);
|
||||
ui->loadProgressBar->setValue(current);
|
||||
sneakyProgressBar->setMaximum(total);
|
||||
sneakyProgressBar->setValue(current);
|
||||
}
|
||||
|
||||
void VersionSelectWidget::currentRowChanged(const QModelIndex& current, const QModelIndex&)
|
||||
@@ -147,9 +158,8 @@ void VersionSelectWidget::selectCurrent()
|
||||
if(idx.isValid())
|
||||
{
|
||||
preselectedAlready = true;
|
||||
ui->listView->selectionModel()->setCurrentIndex(idx,
|
||||
QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
||||
ui->listView->scrollTo(idx, QAbstractItemView::PositionAtCenter);
|
||||
listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
||||
listView->scrollTo(idx, QAbstractItemView::PositionAtCenter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,9 +169,8 @@ void VersionSelectWidget::selectRecommended()
|
||||
if(idx.isValid())
|
||||
{
|
||||
preselectedAlready = true;
|
||||
ui->listView->selectionModel()->setCurrentIndex(idx,
|
||||
QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
||||
ui->listView->scrollTo(idx, QAbstractItemView::PositionAtCenter);
|
||||
listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
||||
listView->scrollTo(idx, QAbstractItemView::PositionAtCenter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +181,7 @@ bool VersionSelectWidget::hasVersions() const
|
||||
|
||||
BaseVersionPtr VersionSelectWidget::selectedVersion() const
|
||||
{
|
||||
auto currentIndex = ui->listView->selectionModel()->currentIndex();
|
||||
auto currentIndex = listView->selectionModel()->currentIndex();
|
||||
auto variant = m_proxyModel->data(currentIndex, BaseVersionList::VersionPointerRole);
|
||||
return variant.value<BaseVersionPtr>();
|
||||
}
|
||||
@@ -191,25 +200,3 @@ void VersionSelectWidget::setFilter(BaseVersionList::ModelRoles role, Filter *fi
|
||||
{
|
||||
m_proxyModel->setFilter(role, filter);
|
||||
}
|
||||
|
||||
void VersionSelectWidget::filterChanged()
|
||||
{
|
||||
QStringList out;
|
||||
if(ui->alphasCheckbox->isChecked())
|
||||
out << "(old_alpha)";
|
||||
if(ui->betasCheckbox->isChecked())
|
||||
out << "(old_beta)";
|
||||
if(ui->snapshotsCheckbox->isChecked())
|
||||
out << "(snapshot)";
|
||||
if(ui->oldSnapshotsCheckbox->isChecked())
|
||||
out << "(old_snapshot)";
|
||||
if(ui->releasesCheckbox->isChecked())
|
||||
out << "(release)";
|
||||
auto regexp = out.join('|');
|
||||
setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false));
|
||||
}
|
||||
|
||||
void VersionSelectWidget::setFilterBoxVisible(bool visible)
|
||||
{
|
||||
ui->filterBox->setVisible(visible);
|
||||
}
|
||||
@@ -25,11 +25,6 @@ class QVBoxLayout;
|
||||
class QProgressBar;
|
||||
class Filter;
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class VersionSelectWidget;
|
||||
}
|
||||
|
||||
class VersionSelectWidget: public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -55,7 +50,6 @@ public:
|
||||
void setEmptyString(QString emptyString);
|
||||
void setEmptyErrorString(QString emptyErrorString);
|
||||
void setResizeOn(int column);
|
||||
void setFilterBoxVisible(bool visible);
|
||||
|
||||
signals:
|
||||
void selectedVersionChanged(BaseVersionPtr version);
|
||||
@@ -68,7 +62,6 @@ private slots:
|
||||
void onTaskFailed(const QString &reason);
|
||||
void changeProgress(qint64 current, qint64 total);
|
||||
void currentRowChanged(const QModelIndex ¤t, const QModelIndex &);
|
||||
void filterChanged();
|
||||
|
||||
private:
|
||||
void preselect();
|
||||
@@ -81,5 +74,8 @@ private:
|
||||
Task * loadTask;
|
||||
bool preselectedAlready = false;
|
||||
|
||||
Ui::VersionSelectWidget *ui;
|
||||
private:
|
||||
QVBoxLayout *verticalLayout = nullptr;
|
||||
VersionListView *listView = nullptr;
|
||||
QProgressBar *sneakyProgressBar = nullptr;
|
||||
};
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>VersionSelectWidget</class>
|
||||
<widget class="QWidget" name="VersionSelectWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>740</width>
|
||||
<height>614</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="sideBox">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="filterBox" native="true">
|
||||
<layout class="QVBoxLayout" name="_2">
|
||||
<item alignment="Qt::AlignHCenter">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Filter</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="releasesCheckbox">
|
||||
<property name="text">
|
||||
<string>Releases</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="snapshotsCheckbox">
|
||||
<property name="text">
|
||||
<string>Snapshots</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="oldSnapshotsCheckbox">
|
||||
<property name="text">
|
||||
<string>Old Snapshots</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="betasCheckbox">
|
||||
<property name="text">
|
||||
<string>Betas</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alphasCheckbox">
|
||||
<property name="text">
|
||||
<string>Alphas</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refreshButton">
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QProgressBar" name="loadProgressBar">
|
||||
<property name="value">
|
||||
<number>24</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="VersionListView" name="listView">
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="itemsExpandable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="headerHidden">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerCascadingSectionResizes">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>VersionListView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>widgets/VersionListView.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
90
changelog.md
90
changelog.md
@@ -1,8 +1,91 @@
|
||||
# MultiMC 0.6.5
|
||||
# MultiMC 0.6.6
|
||||
|
||||
This release is mostly the smaller things that have accumulated over time, along with a big change in linux packaging.
|
||||
|
||||
No 1.13+ Forge news yet. That's going to be a major overhaul of many of the internals of MultiMC.
|
||||
|
||||
## **IMPORTANT**
|
||||
|
||||
On linux, MultiMC no longer bundles the Qt libraries. This fixes many issues, but it might not run after the update unless you have the required libraries installed.
|
||||
|
||||
Make sure you have the following packages before you update:
|
||||
|
||||
- Arch: `qt5-base`
|
||||
- Debian/Ubuntu: `qt5-default`
|
||||
- CentOS/RHEL/Fedora: `qt5-qtbase-gui`
|
||||
- Suse: `libqt5-qtbase`
|
||||
|
||||
MultiMC on linux is built with Qt 5.4 and older versions of Qt will not work.
|
||||
|
||||
This should be a massive improvement to system integration on linux and resolves GH-1784, GH-2605, GH-1979, GH-2271, GH-1992, GH-1816 and their many duplicates.
|
||||
|
||||
### New or changed features
|
||||
|
||||
- GH-2487: No is now the default button when deleting instances.
|
||||
|
||||
- It is now possible to launch with profilers in offline mode.
|
||||
|
||||
- Massively improved support for icon formats when importing and exporting instances.
|
||||
|
||||
All of the formats MultiMC supports are now supported in exported instances too, instead of just PNG.
|
||||
|
||||
- Added the pocket fox icon.
|
||||
|
||||
We still have the big one under the staircase. It's cute. Just hide your chickens.
|
||||
|
||||
- Global settings can be opened from instance settings where appropriate.
|
||||
|
||||
Many people use the instance overrides where using the global settings would be more appropriate. Hopefully this makes it clearer that the instance settings are overrides for the global settings.
|
||||
|
||||
- Added direct Fabric loader support.
|
||||
|
||||
Much overdue. It's good. Fabric mod metadata is also supported in the mod pages.
|
||||
|
||||
- MultiMC now recognizes the new `experimental` Minecraft versions.
|
||||
|
||||
Go mess with the combat experiment. It's interesting.
|
||||
|
||||
- Added Twitch URL as an option to the Add Instance dialog.
|
||||
|
||||
You can now drag the purple download buttons from CurseForge into MultiMC and get a modpack out of it. Much easier!
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Translation folder is now created sooner, making first launch translation fetch work again.
|
||||
|
||||
- GH-2716: MultiMC will no longer try to censor values shorter than 4 characters in logs.
|
||||
|
||||
It was actually leaking information and destroying the logs instead of helping.
|
||||
|
||||
- GH-2551: Trim server name and IP before saving them.
|
||||
|
||||
- GH-2591: Fix multiple potential memory leaks and crashes related to destroying objects with Qt memory lifecycle model.
|
||||
|
||||
- `run.sh` on linux now passes all arguments to MultiMC.
|
||||
|
||||
- Adding a disabled mod duplicate now replaces the existing mod.
|
||||
|
||||
- GH-2592: Newly created instances are now selected again. This was a very old regression.
|
||||
|
||||
- GH-689: MultiMC no longer creates an imgur album for single screenshot uploads.
|
||||
|
||||
- GH-1813: `#` is now saved properly when used in instance notes.
|
||||
|
||||
- GH-2515: Deleting an instance externally while the delete dialog is open no longer leads to some other instance being deleted when you click OK.
|
||||
|
||||
- GH-2499: Proxy settings are applied immediately and no longer need an application restart.
|
||||
|
||||
- GH-1701: When downloading updates, the text now reflects the number of downloaded files better.
|
||||
|
||||
- Icon scaling issues on macOS should now be fixed.
|
||||
|
||||
# Previous releases
|
||||
|
||||
## MultiMC 0.6.5
|
||||
|
||||
Finalizing the translation workflow improvements and adding fixes for sounds missing in old game versions.
|
||||
|
||||
### New or changed features
|
||||
#### New or changed features
|
||||
|
||||
- UI for the language settings has been unified across the application
|
||||
|
||||
@@ -12,7 +95,6 @@ Finalizing the translation workflow improvements and adding fixes for sounds mis
|
||||
|
||||
Also, a minor issue with the reconstruction being done twice per launch has been fixed.
|
||||
|
||||
# Previous releases
|
||||
|
||||
## MultiMC 0.6.4
|
||||
|
||||
@@ -1088,4 +1170,4 @@ Long time coming, this release brought a lot of incremental improvements and fix
|
||||
- Added additional information to the about dialog.
|
||||
|
||||
## MultiMC 0.0
|
||||
- Initial release.
|
||||
- Initial release.
|
||||
|
||||
Submodule libraries/libnbtplusplus updated: 92f8d57227...508eda8316
Reference in New Issue
Block a user