NOISSUE continue reshuffling the codebase

This commit is contained in:
Petr Mrázek
2021-11-22 03:55:16 +01:00
parent 5040231f8d
commit b258eac215
244 changed files with 516 additions and 768 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 the launcher'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,432 @@
#include "JavaSettingsWidget.h"
#include <QVBoxLayout>
#include <QGroupBox>
#include <QSpinBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QToolButton>
#include <QFileDialog>
#include <sys.h>
#include "java/JavaInstall.h"
#include "java/JavaUtils.h"
#include "FileSystem.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/widgets/VersionSelectWidget.h"
#include "Application.h"
#include "BuildConfig.h"
JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent)
{
m_availableMemory = Sys::getSystemRam() / Sys::mebibyte;
goodIcon = APPLICATION->getThemedIcon("status-good");
yellowIcon = APPLICATION->getThemedIcon("status-yellow");
badIcon = APPLICATION->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(" MiB"));
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(" MiB"));
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(" MiB"));
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(APPLICATION->javalist().get());
m_versionWidget->setResizeOn(2);
auto s = APPLICATION->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"
"%1 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"
).arg(BuildConfig.LAUNCHER_NAME),
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 "Application.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 = APPLICATION->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 = APPLICATION->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/Launcher/wiki/Translating-MultiMC");
helpUsLabel->setText(text);
}
void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, const QModelIndex& previous)
{
if (current == previous)
{
return;
}
auto translations = APPLICATION->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,168 @@
/* 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 "ui/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,240 @@
/* 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 "PageContainer_p.h"
#include <QStackedLayout>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QUrl>
#include <QStyledItemDelegate>
#include <QListView>
#include <QLineEdit>
#include <QLabel>
#include <QDialogButtonBox>
#include <QGridLayout>
#include "settings/SettingsObject.h"
#include "ui/widgets/IconLabel.h"
#include "DesktopServices.h"
#include "Application.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 = APPLICATION->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 = APPLICATION->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(APPLICATION->getThemedIcon("bug"));
}
}
void PageContainer::help()
{
if (m_currentPage)
{
QString pageId = m_currentPage->helpPage();
if (pageId.isEmpty())
return;
DesktopServices::openUrl(QUrl("https://github.com/MultiMC/Launcher/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 "ui/pages/BasePageProvider.h"
#include "ui/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,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,205 @@
#include "VersionSelectWidget.h"
#include <QProgressBar>
#include <QVBoxLayout>
#include <QHeaderView>
#include "VersionListView.h"
#include "VersionProxyModel.h"
#include "ui/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;
};