NOISSUE Flatten gui and logic libraries into MultiMC

This commit is contained in:
Petr Mrázek
2021-07-25 19:11:59 +02:00
parent dd13368085
commit 20b9f2b42a
1113 changed files with 1228 additions and 1401 deletions

View File

@ -0,0 +1,27 @@
#include "Common.h"
// Origin: Qt
QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
qreal &widthUsed)
{
QStringList lines;
height = 0;
widthUsed = 0;
textLayout.beginLayout();
QString str = textLayout.text();
while (true)
{
QTextLine line = textLayout.createLine();
if (!line.isValid())
break;
if (line.textLength() == 0)
break;
line.setLineWidth(lineWidth);
line.setPosition(QPointF(0, height));
height += line.height();
lines.append(str.mid(line.textStart(), line.textLength()));
widthUsed = qMax(widthUsed, line.naturalTextWidth());
}
textLayout.endLayout();
return lines;
}

View File

@ -0,0 +1,6 @@
#pragma once
#include <QStringList>
#include <QTextLayout>
QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
qreal &widthUsed);

View File

@ -0,0 +1,49 @@
#include "CustomCommands.h"
#include "ui_CustomCommands.h"
CustomCommands::~CustomCommands()
{
delete ui;
}
CustomCommands::CustomCommands(QWidget* parent):
QWidget(parent),
ui(new Ui::CustomCommands)
{
ui->setupUi(this);
}
void CustomCommands::initialize(bool checkable, bool checked, const QString& prelaunch, const QString& wrapper, const QString& postexit)
{
ui->customCommandsGroupBox->setCheckable(checkable);
if(checkable)
{
ui->customCommandsGroupBox->setChecked(checked);
}
ui->preLaunchCmdTextBox->setText(prelaunch);
ui->wrapperCmdTextBox->setText(wrapper);
ui->postExitCmdTextBox->setText(postexit);
}
bool CustomCommands::checked() const
{
if(!ui->customCommandsGroupBox->isCheckable())
return true;
return ui->customCommandsGroupBox->isChecked();
}
QString CustomCommands::prelaunchCommand() const
{
return ui->preLaunchCmdTextBox->text();
}
QString CustomCommands::wrapperCommand() const
{
return ui->wrapperCmdTextBox->text();
}
QString CustomCommands::postexitCommand() const
{
return ui->postExitCmdTextBox->text();
}

View File

@ -0,0 +1,43 @@
/* Copyright 2018-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QWidget>
namespace Ui
{
class CustomCommands;
}
class CustomCommands : public QWidget
{
Q_OBJECT
public:
explicit CustomCommands(QWidget *parent = 0);
virtual ~CustomCommands();
void initialize(bool checkable, bool checked, const QString & prelaunch, const QString & wrapper, const QString & postexit);
bool checked() const;
QString prelaunchCommand() const;
QString wrapperCommand() const;
QString postexitCommand() const;
private:
Ui::CustomCommands *ui;
};

View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CustomCommands</class>
<widget class="QWidget" name="CustomCommands">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>518</width>
<height>646</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<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="QGroupBox" name="customCommandsGroupBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Cus&amp;tom Commands</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="2" column="0">
<widget class="QLabel" name="labelPostExitCmd">
<property name="text">
<string>Post-exit command:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="preLaunchCmdTextBox"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelPreLaunchCmd">
<property name="text">
<string>Pre-launch command:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="postExitCmdTextBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelWrapperCmd">
<property name="text">
<string>Wrapper command:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="wrapperCmdTextBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="labelCustomCmdsDescription">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pre-launch command runs before the instance launches and post-exit command runs after it exits.&lt;/p&gt;&lt;p&gt;Both will be run in MultiMC's working folder with extra environment variables:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;$INST_NAME - Name of the instance&lt;/li&gt;&lt;li&gt;$INST_ID - ID of the instance (its folder name)&lt;/li&gt;&lt;li&gt;$INST_DIR - absolute path of the instance&lt;/li&gt;&lt;li&gt;$INST_MC_DIR - absolute path of minecraft&lt;/li&gt;&lt;li&gt;$INST_JAVA - java binary used for launch&lt;/li&gt;&lt;li&gt;$INST_JAVA_ARGS - command-line parameters used for launch&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Wrapper command allows launching using an extra wrapper program (like 'optirun' on Linux)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</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>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,41 @@
#include "DropLabel.h"
#include <QMimeData>
#include <QDropEvent>
DropLabel::DropLabel(QWidget *parent) : QLabel(parent)
{
setAcceptDrops(true);
}
void DropLabel::dragEnterEvent(QDragEnterEvent *event)
{
event->acceptProposedAction();
}
void DropLabel::dragMoveEvent(QDragMoveEvent *event)
{
event->acceptProposedAction();
}
void DropLabel::dragLeaveEvent(QDragLeaveEvent *event)
{
event->accept();
}
void DropLabel::dropEvent(QDropEvent *event)
{
const QMimeData *mimeData = event->mimeData();
if (!mimeData)
{
return;
}
if (mimeData->hasUrls()) {
auto urls = mimeData->urls();
emit droppedURLs(urls);
}
event->acceptProposedAction();
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <QLabel>
class DropLabel : public QLabel
{
Q_OBJECT
public:
explicit DropLabel(QWidget *parent = nullptr);
signals:
void droppedURLs(QList<QUrl> urls);
protected:
void dropEvent(QDropEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
};

View File

@ -0,0 +1,25 @@
#include "FocusLineEdit.h"
#include <QDebug>
FocusLineEdit::FocusLineEdit(QWidget *parent) : QLineEdit(parent)
{
_selectOnMousePress = false;
}
void FocusLineEdit::focusInEvent(QFocusEvent *e)
{
QLineEdit::focusInEvent(e);
selectAll();
_selectOnMousePress = true;
}
void FocusLineEdit::mousePressEvent(QMouseEvent *me)
{
QLineEdit::mousePressEvent(me);
if (_selectOnMousePress)
{
selectAll();
_selectOnMousePress = false;
}
qDebug() << selectedText();
}

View File

@ -0,0 +1,17 @@
#include <QLineEdit>
class FocusLineEdit : public QLineEdit
{
Q_OBJECT
public:
FocusLineEdit(QWidget *parent);
virtual ~FocusLineEdit()
{
}
protected:
void focusInEvent(QFocusEvent *e);
void mousePressEvent(QMouseEvent *me);
bool _selectOnMousePress;
};

View File

@ -0,0 +1,43 @@
#include "IconLabel.h"
#include <QStyle>
#include <QStyleOption>
#include <QLayout>
#include <QPainter>
#include <QRect>
IconLabel::IconLabel(QWidget *parent, QIcon icon, QSize size)
: QWidget(parent), m_size(size), m_icon(icon)
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
}
QSize IconLabel::sizeHint() const
{
return m_size;
}
void IconLabel::setIcon(QIcon icon)
{
m_icon = icon;
update();
}
void IconLabel::paintEvent(QPaintEvent *)
{
QPainter p(this);
QRect rect = contentsRect();
int width = rect.width();
int height = rect.height();
if(width < height)
{
rect.setHeight(width);
rect.translate(0, (height - width) / 2);
}
else if (width > height)
{
rect.setWidth(height);
rect.translate((width - height) / 2, 0);
}
m_icon.paint(&p, rect);
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <QWidget>
#include <QIcon>
class QStyleOption;
/**
* This is a trivial widget that paints a QIcon of the specified size.
*/
class IconLabel : public QWidget
{
Q_OBJECT
public:
/// Create a line separator. orientation is the orientation of the line.
explicit IconLabel(QWidget *parent, QIcon icon, QSize size);
virtual QSize sizeHint() const;
virtual void paintEvent(QPaintEvent *);
void setIcon(QIcon icon);
private:
QSize m_size;
QIcon m_icon;
};

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>InstanceCardWidget</class>
<widget class="QWidget" name="InstanceCardWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>473</width>
<height>118</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" rowspan="2">
<widget class="QToolButton" name="iconButton">
<property name="iconSize">
<size>
<width>80</width>
<height>80</height>
</size>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>&amp;Name:</string>
</property>
<property name="buddy">
<cstring>instNameTextBox</cstring>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="instNameTextBox"/>
</item>
<item row="1" column="1">
<widget class="QLabel" name="groupLabel">
<property name="text">
<string>&amp;Group:</string>
</property>
<property name="buddy">
<cstring>groupBox</cstring>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="groupBox">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,428 @@
#include "JavaSettingsWidget.h"
#include <MultiMC.h>
#include <java/JavaInstall.h>
#include <dialogs/CustomMessageBox.h>
#include <java/JavaUtils.h>
#include <sys.h>
#include <QVBoxLayout>
#include <QGroupBox>
#include <QSpinBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QToolButton>
#include <widgets/VersionSelectWidget.h>
#include <FileSystem.h>
#include <QFileDialog>
JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent)
{
m_availableMemory = Sys::getSystemRam() / Sys::mebibyte;
goodIcon = MMC->getThemedIcon("status-good");
yellowIcon = MMC->getThemedIcon("status-yellow");
badIcon = MMC->getThemedIcon("status-bad");
setupUi();
connect(m_minMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int)));
connect(m_maxMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int)));
connect(m_permGenSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int)));
connect(m_versionWidget, &VersionSelectWidget::selectedVersionChanged, this, &JavaSettingsWidget::javaVersionSelected);
connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaBrowseBtn_clicked);
connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited);
connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked);
}
void JavaSettingsWidget::setupUi()
{
setObjectName(QStringLiteral("javaSettingsWidget"));
m_verticalLayout = new QVBoxLayout(this);
m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
m_versionWidget = new VersionSelectWidget(this);
m_verticalLayout->addWidget(m_versionWidget);
m_horizontalLayout = new QHBoxLayout();
m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
m_javaPathTextBox = new QLineEdit(this);
m_javaPathTextBox->setObjectName(QStringLiteral("javaPathTextBox"));
m_horizontalLayout->addWidget(m_javaPathTextBox);
m_javaBrowseBtn = new QPushButton(this);
m_javaBrowseBtn->setObjectName(QStringLiteral("javaBrowseBtn"));
m_horizontalLayout->addWidget(m_javaBrowseBtn);
m_javaStatusBtn = new QToolButton(this);
m_javaStatusBtn->setIcon(yellowIcon);
m_horizontalLayout->addWidget(m_javaStatusBtn);
m_verticalLayout->addLayout(m_horizontalLayout);
m_memoryGroupBox = new QGroupBox(this);
m_memoryGroupBox->setObjectName(QStringLiteral("memoryGroupBox"));
m_gridLayout_2 = new QGridLayout(m_memoryGroupBox);
m_gridLayout_2->setObjectName(QStringLiteral("gridLayout_2"));
m_labelMinMem = new QLabel(m_memoryGroupBox);
m_labelMinMem->setObjectName(QStringLiteral("labelMinMem"));
m_gridLayout_2->addWidget(m_labelMinMem, 0, 0, 1, 1);
m_minMemSpinBox = new QSpinBox(m_memoryGroupBox);
m_minMemSpinBox->setObjectName(QStringLiteral("minMemSpinBox"));
m_minMemSpinBox->setSuffix(QStringLiteral(" MB"));
m_minMemSpinBox->setMinimum(128);
m_minMemSpinBox->setMaximum(m_availableMemory);
m_minMemSpinBox->setSingleStep(128);
m_labelMinMem->setBuddy(m_minMemSpinBox);
m_gridLayout_2->addWidget(m_minMemSpinBox, 0, 1, 1, 1);
m_labelMaxMem = new QLabel(m_memoryGroupBox);
m_labelMaxMem->setObjectName(QStringLiteral("labelMaxMem"));
m_gridLayout_2->addWidget(m_labelMaxMem, 1, 0, 1, 1);
m_maxMemSpinBox = new QSpinBox(m_memoryGroupBox);
m_maxMemSpinBox->setObjectName(QStringLiteral("maxMemSpinBox"));
m_maxMemSpinBox->setSuffix(QStringLiteral(" MB"));
m_maxMemSpinBox->setMinimum(128);
m_maxMemSpinBox->setMaximum(m_availableMemory);
m_maxMemSpinBox->setSingleStep(128);
m_labelMaxMem->setBuddy(m_maxMemSpinBox);
m_gridLayout_2->addWidget(m_maxMemSpinBox, 1, 1, 1, 1);
m_labelPermGen = new QLabel(m_memoryGroupBox);
m_labelPermGen->setObjectName(QStringLiteral("labelPermGen"));
m_labelPermGen->setText(QStringLiteral("PermGen:"));
m_gridLayout_2->addWidget(m_labelPermGen, 2, 0, 1, 1);
m_labelPermGen->setVisible(false);
m_permGenSpinBox = new QSpinBox(m_memoryGroupBox);
m_permGenSpinBox->setObjectName(QStringLiteral("permGenSpinBox"));
m_permGenSpinBox->setSuffix(QStringLiteral(" MB"));
m_permGenSpinBox->setMinimum(64);
m_permGenSpinBox->setMaximum(m_availableMemory);
m_permGenSpinBox->setSingleStep(8);
m_gridLayout_2->addWidget(m_permGenSpinBox, 2, 1, 1, 1);
m_permGenSpinBox->setVisible(false);
m_verticalLayout->addWidget(m_memoryGroupBox);
retranslate();
}
void JavaSettingsWidget::initialize()
{
m_versionWidget->initialize(MMC->javalist().get());
m_versionWidget->setResizeOn(2);
auto s = MMC->settings();
// Memory
observedMinMemory = s->get("MinMemAlloc").toInt();
observedMaxMemory = s->get("MaxMemAlloc").toInt();
observedPermGenMemory = s->get("PermGen").toInt();
m_minMemSpinBox->setValue(observedMinMemory);
m_maxMemSpinBox->setValue(observedMaxMemory);
m_permGenSpinBox->setValue(observedPermGenMemory);
}
void JavaSettingsWidget::refresh()
{
m_versionWidget->loadList();
}
JavaSettingsWidget::ValidationStatus JavaSettingsWidget::validate()
{
switch(javaStatus)
{
default:
case JavaStatus::NotSet:
case JavaStatus::DoesNotExist:
case JavaStatus::DoesNotStart:
case JavaStatus::ReturnedInvalidData:
{
int button = CustomMessageBox::selectable(
this,
tr("No Java version selected"),
tr("You didn't select a Java version or selected something that doesn't work.\n"
"MultiMC will not be able to start Minecraft.\n"
"Do you wish to proceed without any Java?"
"\n\n"
"You can change the Java version in the settings later.\n"
),
QMessageBox::Warning,
QMessageBox::Yes | QMessageBox::No,
QMessageBox::NoButton
)->exec();
if(button == QMessageBox::No)
{
return ValidationStatus::Bad;
}
return ValidationStatus::JavaBad;
}
break;
case JavaStatus::Pending:
{
return ValidationStatus::Bad;
}
case JavaStatus::Good:
{
return ValidationStatus::AllOK;
}
}
}
QString JavaSettingsWidget::javaPath() const
{
return m_javaPathTextBox->text();
}
int JavaSettingsWidget::maxHeapSize() const
{
return m_maxMemSpinBox->value();
}
int JavaSettingsWidget::minHeapSize() const
{
return m_minMemSpinBox->value();
}
bool JavaSettingsWidget::permGenEnabled() const
{
return m_permGenSpinBox->isVisible();
}
int JavaSettingsWidget::permGenSize() const
{
return m_permGenSpinBox->value();
}
void JavaSettingsWidget::memoryValueChanged(int)
{
bool actuallyChanged = false;
int min = m_minMemSpinBox->value();
int max = m_maxMemSpinBox->value();
int permgen = m_permGenSpinBox->value();
QObject *obj = sender();
if (obj == m_minMemSpinBox && min != observedMinMemory)
{
observedMinMemory = min;
actuallyChanged = true;
if (min > max)
{
observedMaxMemory = min;
m_maxMemSpinBox->setValue(min);
}
}
else if (obj == m_maxMemSpinBox && max != observedMaxMemory)
{
observedMaxMemory = max;
actuallyChanged = true;
if (min > max)
{
observedMinMemory = max;
m_minMemSpinBox->setValue(max);
}
}
else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory)
{
observedPermGenMemory = permgen;
actuallyChanged = true;
}
if(actuallyChanged)
{
checkJavaPathOnEdit(m_javaPathTextBox->text());
}
}
void JavaSettingsWidget::javaVersionSelected(BaseVersionPtr version)
{
auto java = std::dynamic_pointer_cast<JavaInstall>(version);
if(!java)
{
return;
}
auto visible = java->id.requiresPermGen();
m_labelPermGen->setVisible(visible);
m_permGenSpinBox->setVisible(visible);
m_javaPathTextBox->setText(java->path);
checkJavaPath(java->path);
}
void JavaSettingsWidget::on_javaBrowseBtn_clicked()
{
QString filter;
#if defined Q_OS_WIN32
filter = "Java (javaw.exe)";
#else
filter = "Java (java)";
#endif
QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"), QString(), filter);
if(raw_path.isEmpty())
{
return;
}
QString cooked_path = FS::NormalizePath(raw_path);
m_javaPathTextBox->setText(cooked_path);
checkJavaPath(cooked_path);
}
void JavaSettingsWidget::on_javaStatusBtn_clicked()
{
QString text;
bool failed = false;
switch(javaStatus)
{
case JavaStatus::NotSet:
checkJavaPath(m_javaPathTextBox->text());
return;
case JavaStatus::DoesNotExist:
text += QObject::tr("The specified file either doesn't exist or is not a proper executable.");
failed = true;
break;
case JavaStatus::DoesNotStart:
{
text += QObject::tr("The specified java binary didn't start properly.<br />");
auto htmlError = m_result.errorLog;
if(!htmlError.isEmpty())
{
htmlError.replace('\n', "<br />");
text += QString("<font color=\"red\">%1</font>").arg(htmlError);
}
failed = true;
break;
}
case JavaStatus::ReturnedInvalidData:
{
text += QObject::tr("The specified java binary returned unexpected results:<br />");
auto htmlOut = m_result.outLog;
if(!htmlOut.isEmpty())
{
htmlOut.replace('\n', "<br />");
text += QString("<font color=\"red\">%1</font>").arg(htmlOut);
}
failed = true;
break;
}
case JavaStatus::Good:
text += QObject::tr("Java test succeeded!<br />Platform reported: %1<br />Java version "
"reported: %2<br />").arg(m_result.realPlatform, m_result.javaVersion.toString());
break;
case JavaStatus::Pending:
// TODO: abort here?
return;
}
CustomMessageBox::selectable(
this,
failed ? QObject::tr("Java test success") : QObject::tr("Java test failure"),
text,
failed ? QMessageBox::Critical : QMessageBox::Information
)->show();
}
void JavaSettingsWidget::setJavaStatus(JavaSettingsWidget::JavaStatus status)
{
javaStatus = status;
switch(javaStatus)
{
case JavaStatus::Good:
m_javaStatusBtn->setIcon(goodIcon);
break;
case JavaStatus::NotSet:
case JavaStatus::Pending:
m_javaStatusBtn->setIcon(yellowIcon);
break;
default:
m_javaStatusBtn->setIcon(badIcon);
break;
}
}
void JavaSettingsWidget::javaPathEdited(const QString& path)
{
checkJavaPathOnEdit(path);
}
void JavaSettingsWidget::checkJavaPathOnEdit(const QString& path)
{
auto realPath = FS::ResolveExecutable(path);
QFileInfo pathInfo(realPath);
if (pathInfo.baseName().toLower().contains("java"))
{
checkJavaPath(path);
}
else
{
if(!m_checker)
{
setJavaStatus(JavaStatus::NotSet);
}
}
}
void JavaSettingsWidget::checkJavaPath(const QString &path)
{
if(m_checker)
{
queuedCheck = path;
return;
}
auto realPath = FS::ResolveExecutable(path);
if(realPath.isNull())
{
setJavaStatus(JavaStatus::DoesNotExist);
return;
}
setJavaStatus(JavaStatus::Pending);
m_checker.reset(new JavaChecker());
m_checker->m_path = path;
m_checker->m_minMem = m_minMemSpinBox->value();
m_checker->m_maxMem = m_maxMemSpinBox->value();
if(m_permGenSpinBox->isVisible())
{
m_checker->m_permGen = m_permGenSpinBox->value();
}
connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaSettingsWidget::checkFinished);
m_checker->performCheck();
}
void JavaSettingsWidget::checkFinished(JavaCheckResult result)
{
m_result = result;
switch(result.validity)
{
case JavaCheckResult::Validity::Valid:
{
setJavaStatus(JavaStatus::Good);
break;
}
case JavaCheckResult::Validity::ReturnedInvalidData:
{
setJavaStatus(JavaStatus::ReturnedInvalidData);
break;
}
case JavaCheckResult::Validity::Errored:
{
setJavaStatus(JavaStatus::DoesNotStart);
break;
}
}
m_checker.reset();
if(!queuedCheck.isNull())
{
checkJavaPath(queuedCheck);
queuedCheck.clear();
}
}
void JavaSettingsWidget::retranslate()
{
m_memoryGroupBox->setTitle(tr("Memory"));
m_maxMemSpinBox->setToolTip(tr("The maximum amount of memory Minecraft is allowed to use."));
m_labelMinMem->setText(tr("Minimum memory allocation:"));
m_labelMaxMem->setText(tr("Maximum memory allocation:"));
m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with."));
m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes."));
m_javaBrowseBtn->setText(tr("Browse"));
}

View File

@ -0,0 +1,102 @@
#pragma once
#include <QWidget>
#include <java/JavaChecker.h>
#include <BaseVersion.h>
#include <QObjectPtr.h>
#include <QIcon>
class QLineEdit;
class VersionSelectWidget;
class QSpinBox;
class QPushButton;
class QVBoxLayout;
class QHBoxLayout;
class QGroupBox;
class QGridLayout;
class QLabel;
class QToolButton;
/**
* This is a widget for all the Java settings dialogs and pages.
*/
class JavaSettingsWidget : public QWidget
{
Q_OBJECT
public:
explicit JavaSettingsWidget(QWidget *parent);
virtual ~JavaSettingsWidget() {};
enum class JavaStatus
{
NotSet,
Pending,
Good,
DoesNotExist,
DoesNotStart,
ReturnedInvalidData
} javaStatus = JavaStatus::NotSet;
enum class ValidationStatus
{
Bad,
JavaBad,
AllOK
};
void refresh();
void initialize();
ValidationStatus validate();
void retranslate();
bool permGenEnabled() const;
int permGenSize() const;
int minHeapSize() const;
int maxHeapSize() const;
QString javaPath() const;
protected slots:
void memoryValueChanged(int);
void javaPathEdited(const QString &path);
void javaVersionSelected(BaseVersionPtr version);
void on_javaBrowseBtn_clicked();
void on_javaStatusBtn_clicked();
void checkFinished(JavaCheckResult result);
protected: /* methods */
void checkJavaPathOnEdit(const QString &path);
void checkJavaPath(const QString &path);
void setJavaStatus(JavaStatus status);
void setupUi();
private: /* data */
VersionSelectWidget *m_versionWidget = nullptr;
QVBoxLayout *m_verticalLayout = nullptr;
QLineEdit * m_javaPathTextBox = nullptr;
QPushButton * m_javaBrowseBtn = nullptr;
QToolButton * m_javaStatusBtn = nullptr;
QHBoxLayout *m_horizontalLayout = nullptr;
QGroupBox *m_memoryGroupBox = nullptr;
QGridLayout *m_gridLayout_2 = nullptr;
QSpinBox *m_maxMemSpinBox = nullptr;
QLabel *m_labelMinMem = nullptr;
QLabel *m_labelMaxMem = nullptr;
QSpinBox *m_minMemSpinBox = nullptr;
QLabel *m_labelPermGen = nullptr;
QSpinBox *m_permGenSpinBox = nullptr;
QIcon goodIcon;
QIcon yellowIcon;
QIcon badIcon;
int observedMinMemory = 0;
int observedMaxMemory = 0;
int observedPermGenMemory = 0;
QString queuedCheck;
uint64_t m_availableMemory = 0ull;
shared_qobject_ptr<JavaChecker> m_checker;
JavaCheckResult m_result;
};

View File

@ -0,0 +1,115 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QLabel>
#include <QVBoxLayout>
#include <QResizeEvent>
#include <QStyleOption>
#include "LabeledToolButton.h"
#include <QApplication>
#include <QDebug>
/*
*
* Tool Button with a label on it, instead of the normal text rendering
*
*/
LabeledToolButton::LabeledToolButton(QWidget * parent)
: QToolButton(parent)
, m_label(new QLabel(this))
{
//QToolButton::setText(" ");
m_label->setWordWrap(true);
m_label->setMouseTracking(false);
m_label->setAlignment(Qt::AlignCenter);
m_label->setTextInteractionFlags(Qt::NoTextInteraction);
// somehow, this makes word wrap work in the QLabel. yay.
//m_label->setMinimumWidth(100);
}
QString LabeledToolButton::text() const
{
return m_label->text();
}
void LabeledToolButton::setText(const QString & text)
{
m_label->setText(text);
}
void LabeledToolButton::setIcon(QIcon icon)
{
m_icon = icon;
resetIcon();
}
/*!
\reimp
*/
QSize LabeledToolButton::sizeHint() const
{
/*
Q_D(const QToolButton);
if (d->sizeHint.isValid())
return d->sizeHint;
*/
ensurePolished();
int w = 0, h = 0;
QStyleOptionToolButton opt;
initStyleOption(&opt);
QSize sz =m_label->sizeHint();
w = sz.width();
h = sz.height();
opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height
if (popupMode() == MenuButtonPopup)
w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this);
QSize rawSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this);
QSize sizeHint = rawSize.expandedTo(QApplication::globalStrut());
return sizeHint;
}
void LabeledToolButton::resizeEvent(QResizeEvent * event)
{
m_label->setGeometry(QRect(4, 4, width()-8, height()-8));
if(!m_icon.isNull())
{
resetIcon();
}
QWidget::resizeEvent(event);
}
void LabeledToolButton::resetIcon()
{
auto iconSz = m_icon.actualSize(QSize(160, 80));
float w = iconSz.width();
float h = iconSz.height();
float ar = w/h;
// FIXME: hardcoded max size of 160x80
int newW = 80 * ar;
if(newW > 160)
newW = 160;
QSize newSz (newW, 80);
auto pixmap = m_icon.pixmap(newSz);
m_label->setPixmap(pixmap);
m_label->setMinimumHeight(80);
m_label->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
}

View File

@ -0,0 +1,40 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QPushButton>
#include <QToolButton>
class QLabel;
class LabeledToolButton : public QToolButton
{
Q_OBJECT
QLabel * m_label;
QIcon m_icon;
public:
LabeledToolButton(QWidget * parent = 0);
QString text() const;
void setText(const QString & text);
void setIcon(QIcon icon);
virtual QSize sizeHint() const;
protected:
void resizeEvent(QResizeEvent * event);
void resetIcon();
};

View File

@ -0,0 +1,66 @@
#include "LanguageSelectionWidget.h"
#include <QVBoxLayout>
#include <QTreeView>
#include <QHeaderView>
#include <QLabel>
#include "MultiMC.h"
#include "translations/TranslationsModel.h"
LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) :
QWidget(parent)
{
verticalLayout = new QVBoxLayout(this);
verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
languageView = new QTreeView(this);
languageView->setObjectName(QStringLiteral("languageView"));
languageView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
languageView->setAlternatingRowColors(true);
languageView->setRootIsDecorated(false);
languageView->setItemsExpandable(false);
languageView->setWordWrap(true);
languageView->header()->setCascadingSectionResizes(true);
languageView->header()->setStretchLastSection(false);
verticalLayout->addWidget(languageView);
helpUsLabel = new QLabel(this);
helpUsLabel->setObjectName(QStringLiteral("helpUsLabel"));
helpUsLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
helpUsLabel->setOpenExternalLinks(true);
helpUsLabel->setWordWrap(true);
verticalLayout->addWidget(helpUsLabel);
auto translations = MMC->translations();
auto index = translations->selectedIndex();
languageView->setModel(translations.get());
languageView->setCurrentIndex(index);
languageView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
languageView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageSelectionWidget::languageRowChanged);
verticalLayout->setContentsMargins(0,0,0,0);
}
QString LanguageSelectionWidget::getSelectedLanguageKey() const
{
auto translations = MMC->translations();
return translations->data(languageView->currentIndex(), Qt::UserRole).toString();
}
void LanguageSelectionWidget::retranslate()
{
QString text = tr("Don't see your language or the quality is poor?<br/><a href=\"%1\">Help us with translations!</a>")
.arg("https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC");
helpUsLabel->setText(text);
}
void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, const QModelIndex& previous)
{
if (current == previous)
{
return;
}
auto translations = MMC->translations();
QString key = translations->data(current, Qt::UserRole).toString();
translations->selectLanguage(key);
translations->updateLanguage(key);
}

View File

@ -0,0 +1,41 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QWidget>
class QVBoxLayout;
class QTreeView;
class QLabel;
class LanguageSelectionWidget: public QWidget
{
Q_OBJECT
public:
explicit LanguageSelectionWidget(QWidget *parent = 0);
virtual ~LanguageSelectionWidget() { };
QString getSelectedLanguageKey() const;
void retranslate();
protected slots:
void languageRowChanged(const QModelIndex &current, const QModelIndex &previous);
private:
QVBoxLayout *verticalLayout = nullptr;
QTreeView *languageView = nullptr;
QLabel *helpUsLabel = nullptr;
};

View File

@ -0,0 +1,37 @@
#include "LineSeparator.h"
#include <QStyle>
#include <QStyleOption>
#include <QLayout>
#include <QPainter>
void LineSeparator::initStyleOption(QStyleOption *option) const
{
option->initFrom(this);
// in a horizontal layout, the line is vertical (and vice versa)
if (m_orientation == Qt::Vertical)
option->state |= QStyle::State_Horizontal;
}
LineSeparator::LineSeparator(QWidget *parent, Qt::Orientation orientation)
: QWidget(parent), m_orientation(orientation)
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
}
QSize LineSeparator::sizeHint() const
{
QStyleOption opt;
initStyleOption(&opt);
const int extent =
style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, &opt, parentWidget());
return QSize(extent, extent);
}
void LineSeparator::paintEvent(QPaintEvent *)
{
QPainter p(this);
QStyleOption opt;
initStyleOption(&opt);
style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, &p, parentWidget());
}

View File

@ -0,0 +1,18 @@
#pragma once
#include <QWidget>
class QStyleOption;
class LineSeparator : public QWidget
{
Q_OBJECT
public:
/// Create a line separator. orientation is the orientation of the line.
explicit LineSeparator(QWidget *parent, Qt::Orientation orientation = Qt::Horizontal);
QSize sizeHint() const;
void paintEvent(QPaintEvent *);
void initStyleOption(QStyleOption *option) const;
private:
Qt::Orientation m_orientation = Qt::Horizontal;
};

View File

@ -0,0 +1,144 @@
#include "LogView.h"
#include <QTextBlock>
#include <QScrollBar>
LogView::LogView(QWidget* parent) : QPlainTextEdit(parent)
{
setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
m_defaultFormat = new QTextCharFormat(currentCharFormat());
}
LogView::~LogView()
{
delete m_defaultFormat;
}
void LogView::setWordWrap(bool wrapping)
{
if(wrapping)
{
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setLineWrapMode(QPlainTextEdit::WidgetWidth);
}
else
{
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setLineWrapMode(QPlainTextEdit::NoWrap);
}
}
void LogView::setModel(QAbstractItemModel* model)
{
if(m_model)
{
disconnect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate);
disconnect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted);
disconnect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted);
disconnect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved);
}
m_model = model;
if(m_model)
{
connect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate);
connect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted);
connect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted);
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved);
connect(m_model, &QAbstractItemModel::destroyed, this, &LogView::modelDestroyed);
}
repopulate();
}
QAbstractItemModel * LogView::model() const
{
return m_model;
}
void LogView::modelDestroyed(QObject* model)
{
if(m_model == model)
{
setModel(nullptr);
}
}
void LogView::repopulate()
{
auto doc = document();
doc->clear();
if(!m_model)
{
return;
}
rowsInserted(QModelIndex(), 0, m_model->rowCount() - 1);
}
void LogView::rowsAboutToBeInserted(const QModelIndex& parent, int first, int last)
{
Q_UNUSED(parent)
Q_UNUSED(first)
Q_UNUSED(last)
QScrollBar *bar = verticalScrollBar();
int max_bar = bar->maximum();
int val_bar = bar->value();
if (m_scroll)
{
m_scroll = (max_bar - val_bar) <= 1;
}
else
{
m_scroll = val_bar == max_bar;
}
}
void LogView::rowsInserted(const QModelIndex& parent, int first, int last)
{
for(int i = first; i <= last; i++)
{
auto idx = m_model->index(i, 0, parent);
auto text = m_model->data(idx, Qt::DisplayRole).toString();
QTextCharFormat format(*m_defaultFormat);
auto font = m_model->data(idx, Qt::FontRole);
if(font.isValid())
{
format.setFont(font.value<QFont>());
}
auto fg = m_model->data(idx, Qt::TextColorRole);
if(fg.isValid())
{
format.setForeground(fg.value<QColor>());
}
auto bg = m_model->data(idx, Qt::BackgroundRole);
if(bg.isValid())
{
format.setBackground(bg.value<QColor>());
}
auto workCursor = textCursor();
workCursor.movePosition(QTextCursor::End);
workCursor.insertText(text, format);
workCursor.insertBlock();
}
if(m_scroll && !m_scrolling)
{
m_scrolling = true;
QMetaObject::invokeMethod( this, "scrollToBottom", Qt::QueuedConnection);
}
}
void LogView::rowsRemoved(const QModelIndex& parent, int first, int last)
{
// TODO: some day... maybe
Q_UNUSED(parent)
Q_UNUSED(first)
Q_UNUSED(last)
}
void LogView::scrollToBottom()
{
m_scrolling = false;
verticalScrollBar()->setSliderPosition(verticalScrollBar()->maximum());
}
void LogView::findNext(const QString& what, bool reverse)
{
find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0));
}

View File

@ -0,0 +1,36 @@
#pragma once
#include <QPlainTextEdit>
#include <QAbstractItemView>
class QAbstractItemModel;
class LogView: public QPlainTextEdit
{
Q_OBJECT
public:
explicit LogView(QWidget *parent = nullptr);
virtual ~LogView();
virtual void setModel(QAbstractItemModel *model);
QAbstractItemModel *model() const;
public slots:
void setWordWrap(bool wrapping);
void findNext(const QString & what, bool reverse);
void scrollToBottom();
protected slots:
void repopulate();
// note: this supports only appending
void rowsInserted(const QModelIndex &parent, int first, int last);
void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last);
// note: this supports only removing from front
void rowsRemoved(const QModelIndex &parent, int first, int last);
void modelDestroyed(QObject * model);
protected:
QAbstractItemModel *m_model = nullptr;
QTextCharFormat *m_defaultFormat = nullptr;
bool m_scroll = false;
bool m_scrolling = false;
};

View File

@ -0,0 +1,167 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QMessageBox>
#include <QtGui>
#include "MCModInfoFrame.h"
#include "ui_MCModInfoFrame.h"
#include "dialogs/CustomMessageBox.h"
void MCModInfoFrame::updateWithMod(Mod &m)
{
if (m.type() == m.MOD_FOLDER)
{
clear();
return;
}
QString text = "";
QString name = "";
if (m.name().isEmpty())
name = m.mmc_id();
else
name = m.name();
if (m.homeurl().isEmpty())
text = name;
else
text = "<a href=\"" + m.homeurl() + "\">" + name + "</a>";
if (!m.authors().isEmpty())
text += " by " + m.authors().join(", ");
setModText(text);
if (m.description().isEmpty())
{
setModDescription(QString());
}
else
{
setModDescription(m.description());
}
}
void MCModInfoFrame::clear()
{
setModText(QString());
setModDescription(QString());
}
MCModInfoFrame::MCModInfoFrame(QWidget *parent) :
QFrame(parent),
ui(new Ui::MCModInfoFrame)
{
ui->setupUi(this);
ui->label_ModDescription->setHidden(true);
ui->label_ModText->setHidden(true);
updateHiddenState();
}
MCModInfoFrame::~MCModInfoFrame()
{
delete ui;
}
void MCModInfoFrame::updateHiddenState()
{
if(ui->label_ModDescription->isHidden() && ui->label_ModText->isHidden())
{
setHidden(true);
}
else
{
setHidden(false);
}
}
void MCModInfoFrame::setModText(QString text)
{
if(text.isEmpty())
{
ui->label_ModText->setHidden(true);
}
else
{
ui->label_ModText->setText(text);
ui->label_ModText->setHidden(false);
}
updateHiddenState();
}
void MCModInfoFrame::setModDescription(QString text)
{
if(text.isEmpty())
{
ui->label_ModDescription->setHidden(true);
updateHiddenState();
return;
}
else
{
ui->label_ModDescription->setHidden(false);
updateHiddenState();
}
ui->label_ModDescription->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->label_ModDescription->setOpenExternalLinks(false);
ui->label_ModDescription->setTextFormat(Qt::TextFormat::RichText);
desc = text;
// This allows injecting HTML here.
labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>");
QObject::connect(ui->label_ModDescription, &QLabel::linkActivated, this, &MCModInfoFrame::modDescEllipsisHandler);
}
else
{
ui->label_ModDescription->setTextFormat(Qt::TextFormat::PlainText);
labeltext.append(finaltext);
}
ui->label_ModDescription->setText(labeltext);
}
void MCModInfoFrame::modDescEllipsisHandler(const QString &link)
{
if(!currentBox)
{
currentBox = CustomMessageBox::selectable(this, QString(), desc);
connect(currentBox, &QMessageBox::finished, this, &MCModInfoFrame::boxClosed);
currentBox->show();
}
else
{
currentBox->setText(desc);
}
}
void MCModInfoFrame::boxClosed(int result)
{
currentBox = nullptr;
}

View File

@ -0,0 +1,52 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QFrame>
#include "minecraft/mod/Mod.h"
namespace Ui
{
class MCModInfoFrame;
}
class MCModInfoFrame : public QFrame
{
Q_OBJECT
public:
explicit MCModInfoFrame(QWidget *parent = 0);
~MCModInfoFrame();
void setModText(QString text);
void setModDescription(QString text);
void updateWithMod(Mod &m);
void clear();
public slots:
void modDescEllipsisHandler(const QString& link );
void boxClosed(int result);
private:
void updateHiddenState();
private:
Ui::MCModInfoFrame *ui;
QString desc;
class QMessageBox * currentBox = nullptr;
};

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MCModInfoFrame</class>
<widget class="QFrame" name="MCModInfoFrame">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>527</width>
<height>113</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>120</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>6</number>
</property>
<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="QLabel" name="label_ModText">
<property name="text">
<string notr="true"/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_ModDescription">
<property name="toolTip">
<string notr="true"/>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,66 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ModListView.h"
#include <QHeaderView>
#include <QMouseEvent>
#include <QPainter>
#include <QDrag>
#include <QRect>
ModListView::ModListView ( QWidget* parent )
:QTreeView ( parent )
{
setAllColumnsShowFocus ( true );
setExpandsOnDoubleClick ( false );
setRootIsDecorated ( false );
setSortingEnabled ( true );
setAlternatingRowColors ( true );
setSelectionMode ( QAbstractItemView::ExtendedSelection );
setHeaderHidden ( false );
setSelectionBehavior(QAbstractItemView::SelectRows);
setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOn );
setHorizontalScrollBarPolicy ( Qt::ScrollBarAsNeeded );
setDropIndicatorShown(true);
setDragEnabled(true);
setDragDropMode(QAbstractItemView::DropOnly);
viewport()->setAcceptDrops(true);
}
void ModListView::setModel ( QAbstractItemModel* model )
{
QTreeView::setModel ( model );
auto head = header();
head->setStretchLastSection(false);
// HACK: this is true for the checkbox column of mod lists
auto string = model->headerData(0,head->orientation()).toString();
if(head->count() < 1)
{
return;
}
if(!string.size())
{
head->setSectionResizeMode(0, QHeaderView::ResizeToContents);
head->setSectionResizeMode(1, QHeaderView::Stretch);
for(int i = 2; i < head->count(); i++)
head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
}
else
{
head->setSectionResizeMode(0, QHeaderView::Stretch);
for(int i = 1; i < head->count(); i++)
head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
}
}

View File

@ -0,0 +1,25 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QTreeView>
class ModListView: public QTreeView
{
Q_OBJECT
public:
explicit ModListView ( QWidget* parent = 0 );
virtual void setModel ( QAbstractItemModel* model );
};

View File

@ -0,0 +1,239 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "PageContainer.h"
#include <QStackedLayout>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QUrl>
#include <QStyledItemDelegate>
#include <QListView>
#include <QLineEdit>
#include <QLabel>
#include <QDialogButtonBox>
#include <QGridLayout>
#include "MultiMC.h"
#include "settings/SettingsObject.h"
#include "widgets/IconLabel.h"
#include "PageContainer_p.h"
#include <MultiMC.h>
#include <DesktopServices.h>
class PageEntryFilterModel : public QSortFilterProxyModel
{
public:
explicit PageEntryFilterModel(QObject *parent = 0) : QSortFilterProxyModel(parent)
{
}
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
const QString pattern = filterRegExp().pattern();
const auto model = static_cast<PageModel *>(sourceModel());
const auto page = model->pages().at(sourceRow);
if (!page->shouldDisplay())
return false;
// Regular contents check, then check page-filter.
return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
}
};
PageContainer::PageContainer(BasePageProvider *pageProvider, QString defaultId,
QWidget *parent)
: QWidget(parent)
{
createUI();
m_model = new PageModel(this);
m_proxyModel = new PageEntryFilterModel(this);
int counter = 0;
auto pages = pageProvider->getPages();
for (auto page : pages)
{
page->stackIndex = m_pageStack->addWidget(dynamic_cast<QWidget *>(page));
page->listIndex = counter;
page->setParentContainer(this);
counter++;
}
m_model->setPages(pages);
m_proxyModel->setSourceModel(m_model);
m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_pageList->setIconSize(QSize(pageIconSize, pageIconSize));
m_pageList->setSelectionMode(QAbstractItemView::SingleSelection);
m_pageList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
m_pageList->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
m_pageList->setModel(m_proxyModel);
connect(m_pageList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex, QModelIndex)),
this, SLOT(currentChanged(QModelIndex)));
m_pageStack->setStackingMode(QStackedLayout::StackOne);
m_pageList->setFocus();
selectPage(defaultId);
}
bool PageContainer::selectPage(QString pageId)
{
// now find what we want to have selected...
auto page = m_model->findPageEntryById(pageId);
QModelIndex index;
if (page)
{
index = m_proxyModel->mapFromSource(m_model->index(page->listIndex));
}
if(!index.isValid())
{
index = m_proxyModel->index(0, 0);
}
if (index.isValid())
{
m_pageList->setCurrentIndex(index);
return true;
}
return false;
}
void PageContainer::refreshContainer()
{
m_proxyModel->invalidate();
if(!m_currentPage->shouldDisplay())
{
auto index = m_proxyModel->index(0, 0);
if(index.isValid())
{
m_pageList->setCurrentIndex(index);
}
else
{
// FIXME: unhandled corner case: what to do when there's no page to select?
}
}
}
void PageContainer::createUI()
{
m_pageStack = new QStackedLayout;
m_pageList = new PageView;
m_header = new QLabel();
m_iconHeader = new IconLabel(this, QIcon(), QSize(24, 24));
QFont headerLabelFont = m_header->font();
headerLabelFont.setBold(true);
const int pointSize = headerLabelFont.pointSize();
if (pointSize > 0)
headerLabelFont.setPointSize(pointSize + 2);
m_header->setFont(headerLabelFont);
QHBoxLayout *headerHLayout = new QHBoxLayout;
const int leftMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutLeftMargin);
headerHLayout->addSpacerItem(new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored));
headerHLayout->addWidget(m_header);
headerHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
headerHLayout->addWidget(m_iconHeader);
const int rightMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutRightMargin);
headerHLayout->addSpacerItem(new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored));
headerHLayout->setContentsMargins(0, 6, 0, 0);
m_pageStack->setMargin(0);
m_pageStack->addWidget(new QWidget(this));
m_layout = new QGridLayout;
m_layout->addLayout(headerHLayout, 0, 1, 1, 1);
m_layout->addWidget(m_pageList, 0, 0, 2, 1);
m_layout->addLayout(m_pageStack, 1, 1, 1, 1);
m_layout->setColumnStretch(1, 4);
m_layout->setContentsMargins(0,0,0,6);
setLayout(m_layout);
}
void PageContainer::addButtons(QWidget *buttons)
{
m_layout->addWidget(buttons, 2, 0, 1, 2);
}
void PageContainer::addButtons(QLayout *buttons)
{
m_layout->addLayout(buttons, 2, 0, 1, 2);
}
void PageContainer::showPage(int row)
{
if (m_currentPage)
{
m_currentPage->closed();
}
if (row != -1)
{
m_currentPage = m_model->pages().at(row);
}
else
{
m_currentPage = nullptr;
}
if (m_currentPage)
{
m_pageStack->setCurrentIndex(m_currentPage->stackIndex);
m_header->setText(m_currentPage->displayName());
m_iconHeader->setIcon(m_currentPage->icon());
m_currentPage->opened();
}
else
{
m_pageStack->setCurrentIndex(0);
m_header->setText(QString());
m_iconHeader->setIcon(MMC->getThemedIcon("bug"));
}
}
void PageContainer::help()
{
if (m_currentPage)
{
QString pageId = m_currentPage->helpPage();
if (pageId.isEmpty())
return;
DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/wiki/" + pageId));
}
}
void PageContainer::currentChanged(const QModelIndex &current)
{
showPage(current.isValid() ? m_proxyModel->mapToSource(current).row() : -1);
}
bool PageContainer::prepareToClose()
{
if(!saveAll())
{
return false;
}
if (m_currentPage)
{
m_currentPage->closed();
}
return true;
}
bool PageContainer::saveAll()
{
for (auto page : m_model->pages())
{
if (!page->apply())
return false;
}
return true;
}

View File

@ -0,0 +1,89 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QWidget>
#include <QModelIndex>
#include "pages/BasePageProvider.h"
#include "pages/BasePageContainer.h"
class QLayout;
class IconLabel;
class QSortFilterProxyModel;
class PageModel;
class QLabel;
class QListView;
class QLineEdit;
class QStackedLayout;
class QGridLayout;
class PageContainer : public QWidget, public BasePageContainer
{
Q_OBJECT
public:
explicit PageContainer(BasePageProvider *pageProvider, QString defaultId = QString(),
QWidget *parent = 0);
virtual ~PageContainer() {}
void addButtons(QWidget * buttons);
void addButtons(QLayout * buttons);
/*
* Save any unsaved state and prepare to be closed.
* @return true if everything can be saved, false if there is something that requires attention
*/
bool prepareToClose();
bool saveAll();
/* request close - used by individual pages */
bool requestClose() override
{
if(m_container)
{
return m_container->requestClose();
}
return false;
}
virtual bool selectPage(QString pageId) override;
void refreshContainer() override;
virtual void setParentContainer(BasePageContainer * container)
{
m_container = container;
};
private:
void createUI();
public slots:
void help();
private slots:
void currentChanged(const QModelIndex &current);
void showPage(int row);
private:
BasePageContainer * m_container = nullptr;
BasePage * m_currentPage = 0;
QSortFilterProxyModel *m_proxyModel;
PageModel *m_model;
QStackedLayout *m_pageStack;
QListView *m_pageList;
QLabel *m_header;
IconLabel *m_iconHeader;
QGridLayout *m_layout;
};

View File

@ -0,0 +1,123 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QListView>
#include <QStyledItemDelegate>
#include <QEvent>
#include <QScrollBar>
class BasePage;
const int pageIconSize = 24;
class PageViewDelegate : public QStyledItemDelegate
{
public:
PageViewDelegate(QObject *parent) : QStyledItemDelegate(parent)
{
}
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSize size = QStyledItemDelegate::sizeHint(option, index);
size.setHeight(qMax(size.height(), 32));
return size;
}
};
class PageModel : public QAbstractListModel
{
public:
PageModel(QObject *parent = 0) : QAbstractListModel(parent)
{
QPixmap empty(pageIconSize, pageIconSize);
empty.fill(Qt::transparent);
m_emptyIcon = QIcon(empty);
}
virtual ~PageModel() {}
int rowCount(const QModelIndex &parent = QModelIndex()) const
{
return parent.isValid() ? 0 : m_pages.size();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
{
switch (role)
{
case Qt::DisplayRole:
return m_pages.at(index.row())->displayName();
case Qt::DecorationRole:
{
QIcon icon = m_pages.at(index.row())->icon();
if (icon.isNull())
icon = m_emptyIcon;
// HACK: fixes icon stretching on windows. TODO: report Qt bug for this
return QIcon(icon.pixmap(QSize(48,48)));
}
}
return QVariant();
}
void setPages(const QList<BasePage *> &pages)
{
beginResetModel();
m_pages = pages;
endResetModel();
}
const QList<BasePage *> &pages() const
{
return m_pages;
}
BasePage * findPageEntryById(QString id)
{
for(auto page: m_pages)
{
if (page->id() == id)
return page;
}
return nullptr;
}
QList<BasePage *> m_pages;
QIcon m_emptyIcon;
};
class PageView : public QListView
{
public:
PageView(QWidget *parent = 0) : QListView(parent)
{
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
setItemDelegate(new PageViewDelegate(this));
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
virtual QSize sizeHint() const
{
int width = sizeHintForColumn(0) + frameWidth() * 2 + 5;
if (verticalScrollBar()->isVisible())
width += verticalScrollBar()->width();
return QSize(width, 100);
}
virtual bool eventFilter(QObject *obj, QEvent *event)
{
if (obj == verticalScrollBar() &&
(event->type() == QEvent::Show || event->type() == QEvent::Hide))
updateGeometry();
return QListView::eventFilter(obj, event);
}
};

View File

@ -0,0 +1,73 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#include "ProgressWidget.h"
#include <QProgressBar>
#include <QLabel>
#include <QVBoxLayout>
#include <QEventLoop>
#include "tasks/Task.h"
ProgressWidget::ProgressWidget(QWidget *parent)
: QWidget(parent)
{
m_label = new QLabel(this);
m_label->setWordWrap(true);
m_bar = new QProgressBar(this);
m_bar->setMinimum(0);
m_bar->setMaximum(100);
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(m_label);
layout->addWidget(m_bar);
layout->addStretch();
setLayout(layout);
}
void ProgressWidget::start(std::shared_ptr<Task> task)
{
if (m_task)
{
disconnect(m_task.get(), 0, this, 0);
}
m_task = task;
connect(m_task.get(), &Task::finished, this, &ProgressWidget::handleTaskFinish);
connect(m_task.get(), &Task::status, this, &ProgressWidget::handleTaskStatus);
connect(m_task.get(), &Task::progress, this, &ProgressWidget::handleTaskProgress);
connect(m_task.get(), &Task::destroyed, this, &ProgressWidget::taskDestroyed);
if (!m_task->isRunning())
{
QMetaObject::invokeMethod(m_task.get(), "start", Qt::QueuedConnection);
}
}
bool ProgressWidget::exec(std::shared_ptr<Task> task)
{
QEventLoop loop;
connect(task.get(), &Task::finished, &loop, &QEventLoop::quit);
start(task);
if (task->isRunning())
{
loop.exec();
}
return task->wasSuccessful();
}
void ProgressWidget::handleTaskFinish()
{
if (!m_task->wasSuccessful())
{
m_label->setText(m_task->failReason());
}
}
void ProgressWidget::handleTaskStatus(const QString &status)
{
m_label->setText(status);
}
void ProgressWidget::handleTaskProgress(qint64 current, qint64 total)
{
m_bar->setMaximum(total);
m_bar->setValue(current);
}
void ProgressWidget::taskDestroyed()
{
m_task = nullptr;
}

View File

@ -0,0 +1,32 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#pragma once
#include <QWidget>
#include <memory>
class Task;
class QProgressBar;
class QLabel;
class ProgressWidget : public QWidget
{
Q_OBJECT
public:
explicit ProgressWidget(QWidget *parent = nullptr);
public slots:
void start(std::shared_ptr<Task> task);
bool exec(std::shared_ptr<Task> task);
private slots:
void handleTaskFinish();
void handleTaskStatus(const QString &status);
void handleTaskProgress(qint64 current, qint64 total);
void taskDestroyed();
private:
QLabel *m_label;
QProgressBar *m_bar;
std::shared_ptr<Task> m_task;
};

View File

@ -0,0 +1,179 @@
#include "ServerStatus.h"
#include "LineSeparator.h"
#include "IconLabel.h"
#include "status/StatusChecker.h"
#include <DesktopServices.h>
#include "MultiMC.h"
#include <QHBoxLayout>
#include <QFrame>
#include <QLabel>
#include <QMap>
#include <QToolButton>
#include <QAction>
class ClickableLabel : public QLabel
{
Q_OBJECT
public:
ClickableLabel(QWidget *parent) : QLabel(parent)
{
setCursor(Qt::PointingHandCursor);
}
~ClickableLabel(){};
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent *event)
{
emit clicked();
}
};
class ClickableIconLabel : public IconLabel
{
Q_OBJECT
public:
ClickableIconLabel(QWidget *parent, QIcon icon, QSize size) : IconLabel(parent, icon, size)
{
setCursor(Qt::PointingHandCursor);
}
~ClickableIconLabel(){};
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent *event)
{
emit clicked();
}
};
ServerStatus::ServerStatus(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f)
{
layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
goodIcon = MMC->getThemedIcon("status-good");
yellowIcon = MMC->getThemedIcon("status-yellow");
badIcon = MMC->getThemedIcon("status-bad");
addStatus("authserver.mojang.com", tr("Auth"));
addLine();
addStatus("session.minecraft.net", tr("Session"));
addLine();
addStatus("textures.minecraft.net", tr("Skins"));
addLine();
addStatus("api.mojang.com", tr("API"));
m_statusRefresh = new QToolButton(this);
m_statusRefresh->setCheckable(true);
m_statusRefresh->setToolButtonStyle(Qt::ToolButtonIconOnly);
m_statusRefresh->setIcon(MMC->getThemedIcon("refresh"));
layout->addWidget(m_statusRefresh);
setLayout(layout);
// Start status checker
m_statusChecker.reset(new StatusChecker());
{
auto reloader = m_statusChecker.get();
connect(reloader, &StatusChecker::statusChanged, this, &ServerStatus::StatusChanged);
connect(reloader, &StatusChecker::statusLoading, this, &ServerStatus::StatusReloading);
connect(m_statusRefresh, &QAbstractButton::clicked, this, &ServerStatus::reloadStatus);
m_statusChecker->startTimer(60000);
reloadStatus();
}
}
ServerStatus::~ServerStatus()
{
}
void ServerStatus::reloadStatus()
{
m_statusChecker->reloadStatus();
}
void ServerStatus::addLine()
{
layout->addWidget(new LineSeparator(this, Qt::Vertical));
}
void ServerStatus::addStatus(QString key, QString name)
{
{
auto label = new ClickableIconLabel(this, badIcon, QSize(16, 16));
label->setToolTip(key);
serverLabels[key] = label;
layout->addWidget(label);
connect(label,SIGNAL(clicked()),SLOT(clicked()));
}
{
auto label = new ClickableLabel(this);
label->setText(name);
label->setToolTip(key);
layout->addWidget(label);
connect(label,SIGNAL(clicked()),SLOT(clicked()));
}
}
void ServerStatus::clicked()
{
DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/wiki/Mojang-Services-Status"));
}
void ServerStatus::setStatus(QString key, int value)
{
if (!serverLabels.contains(key))
return;
IconLabel *label = serverLabels[key];
switch(value)
{
case 0:
label->setIcon(goodIcon);
break;
case 1:
label->setIcon(yellowIcon);
break;
default:
case 2:
label->setIcon(badIcon);
break;
}
}
void ServerStatus::StatusChanged(const QMap<QString, QString> statusEntries)
{
auto convertStatus = [&](QString status)->int
{
if (status == "green")
return 0;
else if (status == "yellow")
return 1;
else if (status == "red")
return 2;
return 2;
}
;
auto iter = statusEntries.begin();
while (iter != statusEntries.end())
{
QString key = iter.key();
auto value = convertStatus(iter.value());
setStatus(key, value);
iter++;
}
}
void ServerStatus::StatusReloading(bool is_reloading)
{
m_statusRefresh->setChecked(is_reloading);
}
#include "ServerStatus.moc"

View File

@ -0,0 +1,40 @@
#pragma once
#include <QString>
#include <QWidget>
#include <QMap>
#include <QIcon>
#include <memory>
class IconLabel;
class QToolButton;
class QHBoxLayout;
class StatusChecker;
class ServerStatus: public QWidget
{
Q_OBJECT
public:
explicit ServerStatus(QWidget *parent = nullptr, Qt::WindowFlags f = 0);
virtual ~ServerStatus();
public slots:
void reloadStatus();
void StatusChanged(const QMap<QString, QString> statuses);
void StatusReloading(bool is_reloading);
private slots:
void clicked();
private: /* methods */
void addLine();
void addStatus(QString key, QString name);
void setStatus(QString key, int value);
private: /* data */
QHBoxLayout * layout = nullptr;
QToolButton *m_statusRefresh = nullptr;
QMap<QString, IconLabel *> serverLabels;
QIcon goodIcon;
QIcon yellowIcon;
QIcon badIcon;
std::shared_ptr<StatusChecker> m_statusChecker;
};

View File

@ -0,0 +1,163 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QHeaderView>
#include <QApplication>
#include <QMouseEvent>
#include <QDrag>
#include <QPainter>
#include "VersionListView.h"
#include "Common.h"
VersionListView::VersionListView(QWidget *parent)
:QTreeView ( parent )
{
m_emptyString = tr("No versions are currently available.");
}
void VersionListView::rowsInserted(const QModelIndex &parent, int start, int end)
{
m_itemCount += end-start+1;
updateEmptyViewPort();
QTreeView::rowsInserted(parent, start, end);
}
void VersionListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
m_itemCount -= end-start+1;
updateEmptyViewPort();
QTreeView::rowsInserted(parent, start, end);
}
void VersionListView::setModel(QAbstractItemModel *model)
{
m_itemCount = model->rowCount();
updateEmptyViewPort();
QTreeView::setModel(model);
}
void VersionListView::reset()
{
if(model())
{
m_itemCount = model()->rowCount();
}
else {
m_itemCount = 0;
}
updateEmptyViewPort();
QTreeView::reset();
}
void VersionListView::setEmptyString(QString emptyString)
{
m_emptyString = emptyString;
updateEmptyViewPort();
}
void VersionListView::setEmptyErrorString(QString emptyErrorString)
{
m_emptyErrorString = emptyErrorString;
updateEmptyViewPort();
}
void VersionListView::setEmptyMode(VersionListView::EmptyMode mode)
{
m_emptyMode = mode;
updateEmptyViewPort();
}
void VersionListView::updateEmptyViewPort()
{
#ifndef QT_NO_ACCESSIBILITY
setAccessibleDescription(currentEmptyString());
#endif /* !QT_NO_ACCESSIBILITY */
if(!m_itemCount)
{
viewport()->update();
}
}
void VersionListView::paintEvent(QPaintEvent *event)
{
if(m_itemCount)
{
QTreeView::paintEvent(event);
}
else
{
paintInfoLabel(event);
}
}
QString VersionListView::currentEmptyString() const
{
if(m_itemCount) {
return QString();
}
switch(m_emptyMode)
{
default:
case VersionListView::Empty:
return QString();
case VersionListView::String:
return m_emptyString;
case VersionListView::ErrorString:
return m_emptyErrorString;
}
}
void VersionListView::paintInfoLabel(QPaintEvent *event) const
{
QString emptyString = currentEmptyString();
//calculate the rect for the overlay
QPainter painter(viewport());
painter.setRenderHint(QPainter::Antialiasing, true);
QFont font("sans", 20);
font.setBold(true);
QRect bounds = viewport()->geometry();
bounds.moveTop(0);
auto innerBounds = bounds;
innerBounds.adjust(10, 10, -10, -10);
QColor background = QApplication::palette().color(QPalette::Foreground);
QColor foreground = QApplication::palette().color(QPalette::Base);
foreground.setAlpha(190);
painter.setFont(font);
auto fontMetrics = painter.fontMetrics();
auto textRect = fontMetrics.boundingRect(innerBounds, Qt::AlignHCenter | Qt::TextWordWrap, emptyString);
textRect.moveCenter(bounds.center());
auto wrapRect = textRect;
wrapRect.adjust(-10, -10, 10, 10);
//check if we are allowed to draw in our area
if (!event->rect().intersects(wrapRect)) {
return;
}
painter.setBrush(QBrush(background));
painter.setPen(foreground);
painter.drawRoundedRect(wrapRect, 5.0, 5.0);
painter.setPen(foreground);
painter.setFont(font);
painter.drawText(textRect, Qt::AlignHCenter | Qt::TextWordWrap, emptyString);
}

View File

@ -0,0 +1,56 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QTreeView>
class VersionListView : public QTreeView
{
Q_OBJECT
public:
explicit VersionListView(QWidget *parent = 0);
virtual void paintEvent(QPaintEvent *event) override;
virtual void setModel(QAbstractItemModel* model) override;
enum EmptyMode
{
Empty,
String,
ErrorString
};
void setEmptyString(QString emptyString);
void setEmptyErrorString(QString emptyErrorString);
void setEmptyMode(EmptyMode mode);
public slots:
virtual void reset() override;
protected slots:
virtual void rowsAboutToBeRemoved(const QModelIndex & parent, int start, int end) override;
virtual void rowsInserted(const QModelIndex &parent, int start, int end) override;
private: /* methods */
void paintInfoLabel(QPaintEvent *event) const;
void updateEmptyViewPort();
QString currentEmptyString() const;
private: /* variables */
int m_itemCount = 0;
QString m_emptyString;
QString m_emptyErrorString;
EmptyMode m_emptyMode = Empty;
};

View File

@ -0,0 +1,202 @@
#include "VersionSelectWidget.h"
#include <QProgressBar>
#include <QVBoxLayout>
#include "VersionListView.h"
#include <QHeaderView>
#include <VersionProxyModel.h>
#include <dialogs/CustomMessageBox.h>
VersionSelectWidget::VersionSelectWidget(QWidget* parent)
: QWidget(parent)
{
setObjectName(QStringLiteral("VersionSelectWidget"));
verticalLayout = new QVBoxLayout(this);
verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
verticalLayout->setContentsMargins(0, 0, 0, 0);
m_proxyModel = new VersionProxyModel(this);
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);
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_currentVersion = version;
m_proxyModel->setCurrentVersion(version);
}
void VersionSelectWidget::setEmptyString(QString emptyString)
{
listView->setEmptyString(emptyString);
}
void VersionSelectWidget::setEmptyErrorString(QString emptyErrorString)
{
listView->setEmptyErrorString(emptyErrorString);
}
VersionSelectWidget::~VersionSelectWidget()
{
}
void VersionSelectWidget::setResizeOn(int column)
{
listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::ResizeToContents);
resizeOnColumn = column;
listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch);
}
void VersionSelectWidget::initialize(BaseVersionList *vlist)
{
m_vlist = vlist;
m_proxyModel->setSourceModel(vlist);
listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch);
if (!m_vlist->isLoaded())
{
loadList();
}
else
{
if (m_proxyModel->rowCount() == 0)
{
listView->setEmptyMode(VersionListView::String);
}
preselect();
}
}
void VersionSelectWidget::closeEvent(QCloseEvent * event)
{
QWidget::closeEvent(event);
}
void VersionSelectWidget::loadList()
{
auto newTask = m_vlist->getLoadTask();
if (!newTask)
{
return;
}
loadTask = newTask.get();
connect(loadTask, &Task::succeeded, this, &VersionSelectWidget::onTaskSucceeded);
connect(loadTask, &Task::failed, this, &VersionSelectWidget::onTaskFailed);
connect(loadTask, &Task::progress, this, &VersionSelectWidget::changeProgress);
if(!loadTask->isRunning())
{
loadTask->start();
}
sneakyProgressBar->setHidden(false);
}
void VersionSelectWidget::onTaskSucceeded()
{
if (m_proxyModel->rowCount() == 0)
{
listView->setEmptyMode(VersionListView::String);
}
sneakyProgressBar->setHidden(true);
preselect();
loadTask = nullptr;
}
void VersionSelectWidget::onTaskFailed(const QString& reason)
{
CustomMessageBox::selectable(this, tr("Error"), tr("List update failed:\n%1").arg(reason), QMessageBox::Warning)->show();
onTaskSucceeded();
}
void VersionSelectWidget::changeProgress(qint64 current, qint64 total)
{
sneakyProgressBar->setMaximum(total);
sneakyProgressBar->setValue(current);
}
void VersionSelectWidget::currentRowChanged(const QModelIndex& current, const QModelIndex&)
{
auto variant = m_proxyModel->data(current, BaseVersionList::VersionPointerRole);
emit selectedVersionChanged(variant.value<BaseVersionPtr>());
}
void VersionSelectWidget::preselect()
{
if(preselectedAlready)
return;
selectCurrent();
if(preselectedAlready)
return;
selectRecommended();
}
void VersionSelectWidget::selectCurrent()
{
if(m_currentVersion.isEmpty())
{
return;
}
auto idx = m_proxyModel->getVersion(m_currentVersion);
if(idx.isValid())
{
preselectedAlready = true;
listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
listView->scrollTo(idx, QAbstractItemView::PositionAtCenter);
}
}
void VersionSelectWidget::selectRecommended()
{
auto idx = m_proxyModel->getRecommended();
if(idx.isValid())
{
preselectedAlready = true;
listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
listView->scrollTo(idx, QAbstractItemView::PositionAtCenter);
}
}
bool VersionSelectWidget::hasVersions() const
{
return m_proxyModel->rowCount(QModelIndex()) != 0;
}
BaseVersionPtr VersionSelectWidget::selectedVersion() const
{
auto currentIndex = listView->selectionModel()->currentIndex();
auto variant = m_proxyModel->data(currentIndex, BaseVersionList::VersionPointerRole);
return variant.value<BaseVersionPtr>();
}
void VersionSelectWidget::setExactFilter(BaseVersionList::ModelRoles role, QString filter)
{
m_proxyModel->setFilter(role, new ExactFilter(filter));
}
void VersionSelectWidget::setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter)
{
m_proxyModel->setFilter(role, new ContainsFilter(filter));
}
void VersionSelectWidget::setFilter(BaseVersionList::ModelRoles role, Filter *filter)
{
m_proxyModel->setFilter(role, filter);
}

View File

@ -0,0 +1,81 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QWidget>
#include <QSortFilterProxyModel>
#include "BaseVersionList.h"
class VersionProxyModel;
class VersionListView;
class QVBoxLayout;
class QProgressBar;
class Filter;
class VersionSelectWidget: public QWidget
{
Q_OBJECT
public:
explicit VersionSelectWidget(QWidget *parent = 0);
~VersionSelectWidget();
//! loads the list if needed.
void initialize(BaseVersionList *vlist);
//! Starts a task that loads the list.
void loadList();
bool hasVersions() const;
BaseVersionPtr selectedVersion() const;
void selectRecommended();
void selectCurrent();
void setCurrentVersion(const QString & version);
void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter);
void setExactFilter(BaseVersionList::ModelRoles role, QString filter);
void setFilter(BaseVersionList::ModelRoles role, Filter *filter);
void setEmptyString(QString emptyString);
void setEmptyErrorString(QString emptyErrorString);
void setResizeOn(int column);
signals:
void selectedVersionChanged(BaseVersionPtr version);
protected:
virtual void closeEvent ( QCloseEvent* );
private slots:
void onTaskSucceeded();
void onTaskFailed(const QString &reason);
void changeProgress(qint64 current, qint64 total);
void currentRowChanged(const QModelIndex &current, const QModelIndex &);
private:
void preselect();
private:
QString m_currentVersion;
BaseVersionList *m_vlist = nullptr;
VersionProxyModel *m_proxyModel = nullptr;
int resizeOnColumn = 0;
Task * loadTask;
bool preselectedAlready = false;
private:
QVBoxLayout *verticalLayout = nullptr;
VersionListView *listView = nullptr;
QProgressBar *sneakyProgressBar = nullptr;
};

View File

@ -0,0 +1,116 @@
#include "WideBar.h"
#include <QToolButton>
#include <QMenu>
class ActionButton : public QToolButton
{
Q_OBJECT
public:
ActionButton(QAction * action, QWidget * parent = 0) : QToolButton(parent), m_action(action) {
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
connect(action, &QAction::changed, this, &ActionButton::actionChanged);
connect(this, &ActionButton::clicked, action, &QAction::trigger);
actionChanged();
};
private slots:
void actionChanged() {
setEnabled(m_action->isEnabled());
setChecked(m_action->isChecked());
setCheckable(m_action->isCheckable());
setText(m_action->text());
setIcon(m_action->icon());
setToolTip(m_action->toolTip());
setHidden(!m_action->isVisible());
setFocusPolicy(Qt::NoFocus);
}
private:
QAction * m_action;
};
WideBar::WideBar(const QString& title, QWidget* parent) : QToolBar(title, parent)
{
setFloatable(false);
setMovable(false);
}
WideBar::WideBar(QWidget* parent) : QToolBar(parent)
{
setFloatable(false);
setMovable(false);
}
struct WideBar::BarEntry {
enum Type {
None,
Action,
Separator,
Spacer
} type = None;
QAction *qAction = nullptr;
QAction *wideAction = nullptr;
};
WideBar::~WideBar()
{
for(auto *iter: m_entries) {
delete iter;
}
}
void WideBar::addAction(QAction* action)
{
auto entry = new BarEntry();
entry->qAction = addWidget(new ActionButton(action, this));
entry->wideAction = action;
entry->type = BarEntry::Action;
m_entries.push_back(entry);
}
void WideBar::addSeparator()
{
auto entry = new BarEntry();
entry->qAction = QToolBar::addSeparator();
entry->type = BarEntry::Separator;
m_entries.push_back(entry);
}
void WideBar::insertSpacer(QAction* action)
{
auto iter = std::find_if(m_entries.begin(), m_entries.end(), [action](BarEntry * entry) {
return entry->wideAction == action;
});
if(iter == m_entries.end()) {
return;
}
QWidget* spacer = new QWidget();
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
auto entry = new BarEntry();
entry->qAction = insertWidget((*iter)->qAction, spacer);
entry->type = BarEntry::Spacer;
m_entries.insert(iter, entry);
}
QMenu * WideBar::createContextMenu(QWidget *parent, const QString & title)
{
QMenu *contextMenu = new QMenu(title, parent);
for(auto & item: m_entries) {
switch(item->type) {
default:
case BarEntry::None:
break;
case BarEntry::Separator:
case BarEntry::Spacer:
contextMenu->addSeparator();
break;
case BarEntry::Action:
contextMenu->addAction(item->wideAction);
break;
}
}
return contextMenu;
}
#include "WideBar.moc"

View File

@ -0,0 +1,26 @@
#pragma once
#include <QToolBar>
#include <QAction>
#include <QMap>
class QMenu;
class WideBar : public QToolBar
{
Q_OBJECT
public:
explicit WideBar(const QString &title, QWidget * parent = nullptr);
explicit WideBar(QWidget * parent = nullptr);
virtual ~WideBar();
void addAction(QAction *action);
void addSeparator();
void insertSpacer(QAction *action);
QMenu *createContextMenu(QWidget *parent = nullptr, const QString & title = QString());
private:
struct BarEntry;
QList<BarEntry *> m_entries;
};