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,149 @@
/* 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 "AboutDialog.h"
#include "ui_AboutDialog.h"
#include <QIcon>
#include "Application.h"
#include "BuildConfig.h"
#include <net/NetJob.h>
#include "HoeDown.h"
namespace {
// Credits
// This is a hack, but I can't think of a better way to do this easily without screwing with QTextDocument...
QString getCreditsHtml(QStringList patrons)
{
QString patronsHeading = QObject::tr("Patrons", "About Credits");
QString output;
QTextStream stream(&output);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
stream << "<center>\n";
// TODO: possibly retrieve from git history at build time?
stream << "<h3>" << QObject::tr("Developers", "About Credits") << "</h3>\n";
stream << "<p>Andrew Okin &lt;<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>&gt;</p>\n";
stream << "<p>Petr Mrázek &lt;<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>&gt;</p>\n";
stream << "<p>Sky Welch &lt;<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>&gt;</p>\n";
stream << "<p>Jan (02JanDal) &lt;<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>&gt;</p>\n";
stream << "<p>RoboSky &lt;<a href='https://twitter.com/RoboSky_'>@RoboSky_</a>&gt;</p>\n";
stream << "<br />\n";
stream << "<h3>" << QObject::tr("With thanks to", "About Credits") << "</h3>\n";
stream << "<p>Orochimarufan &lt;<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>&gt;</p>\n";
stream << "<p>TakSuyu &lt;<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>&gt;</p>\n";
stream << "<p>Kilobyte &lt;<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>&gt;</p>\n";
stream << "<p>Rootbear75 &lt;<a href='https://twitter.com/rootbear75'>@rootbear75</a>&gt;</p>\n";
stream << "<p>Zeker Zhayard &lt;<a href='https://twitter.com/zeker_zhayard'>@Zeker_Zhayard</a>&gt;</p>\n";
stream << "<br />\n";
if(!patrons.isEmpty()) {
stream << "<h3>" << QObject::tr("Patrons", "About Credits") << "</h3>\n";
for (QString patron : patrons)
{
stream << "<p>" << patron << "</p>\n";
}
}
stream << "</center>\n";
return output;
}
QString getLicenseHtml()
{
HoeDown hoedown;
QFile dataFile(":/documents/COPYING.md");
dataFile.open(QIODevice::ReadOnly);
QString output = hoedown.process(dataFile.readAll());
return output;
}
}
AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog)
{
ui->setupUi(this);
QString launcherName = BuildConfig.LAUNCHER_NAME;
setWindowTitle(tr("About %1").arg(launcherName));
QString chtml = getCreditsHtml(QStringList());
ui->creditsText->setHtml(chtml);
QString lhtml = getLicenseHtml();
ui->licenseText->setHtml(lhtml);
ui->urlLabel->setOpenExternalLinks(true);
ui->icon->setPixmap(APPLICATION->getThemedIcon("logo").pixmap(64));
ui->title->setText(launcherName);
ui->versionLabel->setText(tr("Version") +": " + BuildConfig.printableVersionString());
ui->platformLabel->setText(tr("Platform") +": " + BuildConfig.BUILD_PLATFORM);
if (BuildConfig.VERSION_BUILD >= 0)
ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(BuildConfig.VERSION_BUILD));
else
ui->buildNumLabel->setVisible(false);
if (!BuildConfig.VERSION_CHANNEL.isEmpty())
ui->channelLabel->setText(tr("Channel") +": " + BuildConfig.VERSION_CHANNEL);
else
ui->channelLabel->setVisible(false);
ui->redistributionText->setHtml(tr(
"<p>We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.</p>\n"
"<p>Part of the reason for using the Apache license is we don't want people using the &quot;MultiMC&quot; name when redistributing the project. "
"This means people must take the time to go through the source code and remove all references to &quot;MultiMC&quot;, including but not limited to the project "
"icon and the title of windows, (no <b>MultiMC-fork</b> in the title).</p>\n"
"<p>The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. "
"However, it should be abundantly clear that the project is a fork <b>without</b> implying that you have our blessing.</p>"
));
QString urlText("<html><head/><body><p><a href=\"%1\">%1</a></p></body></html>");
ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT));
QString copyText("© 2012-2021 %1");
ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT));
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));
connect(ui->aboutQt, &QPushButton::clicked, &QApplication::aboutQt);
loadPatronList();
}
AboutDialog::~AboutDialog()
{
delete ui;
}
void AboutDialog::loadPatronList()
{
netJob = new NetJob("Patreon Patron List");
netJob->addNetAction(Net::Download::makeByteArray(QUrl("https://files.multimc.org/patrons.txt"), &dataSink));
connect(netJob.get(), &NetJob::succeeded, this, &AboutDialog::patronListLoaded);
netJob->start(APPLICATION->network());
}
void AboutDialog::patronListLoaded()
{
QString patronListStr(dataSink);
dataSink.clear();
QString html = getCreditsHtml(patronListStr.split("\n", QString::SkipEmptyParts));
ui->creditsText->setHtml(html);
}

View File

@ -0,0 +1,47 @@
/* 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 <QDialog>
#include <net/NetJob.h>
namespace Ui
{
class AboutDialog;
}
class AboutDialog : public QDialog
{
Q_OBJECT
public:
explicit AboutDialog(QWidget *parent = 0);
~AboutDialog();
public
slots:
/// Starts loading a list of Patreon patrons.
void loadPatronList();
/// Slot for when the patron list loads successfully.
void patronListLoaded();
private:
Ui::AboutDialog *ui;
NetJob::Ptr netJob;
QByteArray dataSink;
};

View File

@ -0,0 +1,316 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutDialog</class>
<widget class="QDialog" name="AboutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>783</width>
<height>843</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>450</width>
<height>400</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="baseSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="title">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string notr="true">MultiMC 5</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="aboutTab">
<attribute name="title">
<string>About</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="aboutLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;A custom launcher that makes managing Minecraft easier by allowing you to have multiple instances of Minecraft at once.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="urlLabel">
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string notr="true">GIT URL</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="copyLabel">
<property name="font">
<font>
<pointsize>8</pointsize>
<kerning>true</kerning>
</font>
</property>
<property name="text">
<string notr="true">COPYRIGHT</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="versionLabel">
<property name="text">
<string>Version:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="platformLabel">
<property name="text">
<string>Platform:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="buildNumLabel">
<property name="text">
<string>Build Number:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelLabel">
<property name="text">
<string>Channel:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</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>212</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="creditsTab">
<attribute name="title">
<string>Credits</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTextEdit" name="creditsText">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="licenseTab">
<attribute name="title">
<string>License</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTextEdit" name="licenseText">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>DejaVu Sans Mono</family>
</font>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="forkingTab">
<attribute name="title">
<string>Forking/Redistribution</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QTextEdit" name="redistributionText">
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="aboutQt">
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string>About Qt</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="closeButton">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>creditsText</tabstop>
<tabstop>licenseText</tabstop>
<tabstop>redistributionText</tabstop>
<tabstop>aboutQt</tabstop>
<tabstop>closeButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,144 @@
/* 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 <QLayout>
#include <QPushButton>
#include "Application.h"
#include "CopyInstanceDialog.h"
#include "ui_CopyInstanceDialog.h"
#include "ui/dialogs/IconPickerDialog.h"
#include "BaseVersion.h"
#include "icons/IconList.h"
#include "tasks/Task.h"
#include "BaseInstance.h"
#include "InstanceList.h"
CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
:QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original)
{
ui->setupUi(this);
resize(minimumSizeHint());
layout()->setSizeConstraint(QLayout::SetFixedSize);
InstIconKey = original->iconKey();
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
ui->instNameTextBox->setText(original->name());
ui->instNameTextBox->setFocus();
auto groups = APPLICATION->instances()->getGroups().toSet();
auto groupList = QStringList(groups.toList());
groupList.sort(Qt::CaseInsensitive);
groupList.removeOne("");
groupList.push_front("");
ui->groupBox->addItems(groupList);
int index = groupList.indexOf(APPLICATION->instances()->getInstanceGroup(m_original->id()));
if(index == -1)
{
index = 0;
}
ui->groupBox->setCurrentIndex(index);
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
ui->copySavesCheckbox->setChecked(m_copySaves);
ui->keepPlaytimeCheckbox->setChecked(m_keepPlaytime);
}
CopyInstanceDialog::~CopyInstanceDialog()
{
delete ui;
}
void CopyInstanceDialog::updateDialogState()
{
auto allowOK = !instName().isEmpty();
auto OkButton = ui->buttonBox->button(QDialogButtonBox::Ok);
if(OkButton->isEnabled() != allowOK)
{
OkButton->setEnabled(allowOK);
}
}
QString CopyInstanceDialog::instName() const
{
auto result = ui->instNameTextBox->text().trimmed();
if(result.size())
{
return result;
}
return QString();
}
QString CopyInstanceDialog::iconKey() const
{
return InstIconKey;
}
QString CopyInstanceDialog::instGroup() const
{
return ui->groupBox->currentText();
}
void CopyInstanceDialog::on_iconButton_clicked()
{
IconPickerDialog dlg(this);
dlg.execWithSelection(InstIconKey);
if (dlg.result() == QDialog::Accepted)
{
InstIconKey = dlg.selectedIconKey;
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
}
}
void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1)
{
updateDialogState();
}
bool CopyInstanceDialog::shouldCopySaves() const
{
return m_copySaves;
}
void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state)
{
if(state == Qt::Unchecked)
{
m_copySaves = false;
}
else if(state == Qt::Checked)
{
m_copySaves = true;
}
}
bool CopyInstanceDialog::shouldKeepPlaytime() const
{
return m_keepPlaytime;
}
void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state)
{
if(state == Qt::Unchecked)
{
m_keepPlaytime = false;
}
else if(state == Qt::Checked)
{
m_keepPlaytime = true;
}
}

View File

@ -0,0 +1,58 @@
/* 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 <QDialog>
#include "BaseVersion.h"
#include <BaseInstance.h>
class BaseInstance;
namespace Ui
{
class CopyInstanceDialog;
}
class CopyInstanceDialog : public QDialog
{
Q_OBJECT
public:
explicit CopyInstanceDialog(InstancePtr original, QWidget *parent = 0);
~CopyInstanceDialog();
void updateDialogState();
QString instName() const;
QString instGroup() const;
QString iconKey() const;
bool shouldCopySaves() const;
bool shouldKeepPlaytime() const;
private
slots:
void on_iconButton_clicked();
void on_instNameTextBox_textChanged(const QString &arg1);
void on_copySavesCheckbox_stateChanged(int state);
void on_keepPlaytimeCheckbox_stateChanged(int state);
private:
Ui::CopyInstanceDialog *ui;
QString InstIconKey;
InstancePtr m_original;
bool m_copySaves = true;
bool m_keepPlaytime = true;
};

View File

@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CopyInstanceDialog</class>
<widget class="QDialog" name="CopyInstanceDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>345</width>
<height>323</height>
</rect>
</property>
<property name="windowTitle">
<string>Copy Instance</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>:/icons/toolbar/copy</normaloff>:/icons/toolbar/copy</iconset>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="iconBtnLayout">
<item>
<spacer name="iconBtnLeftSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="iconButton">
<property name="icon">
<iconset>
<normaloff>:/icons/instances/grass</normaloff>:/icons/instances/grass</iconset>
</property>
<property name="iconSize">
<size>
<width>80</width>
<height>80</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="iconBtnRightSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLineEdit" name="instNameTextBox">
<property name="placeholderText">
<string>Name</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="labelVersion_3">
<property name="text">
<string>&amp;Group</string>
</property>
<property name="buddy">
<cstring>groupBox</cstring>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QComboBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="copySavesCheckbox">
<property name="text">
<string>Copy saves</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="keepPlaytimeCheckbox">
<property name="text">
<string>Keep play time</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>iconButton</tabstop>
<tabstop>instNameTextBox</tabstop>
<tabstop>groupBox</tabstop>
<tabstop>copySavesCheckbox</tabstop>
<tabstop>keepPlaytimeCheckbox</tabstop>
</tabstops>
<resources>
<include location="../../graphics.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>CopyInstanceDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>CopyInstanceDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,35 @@
/* 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 "CustomMessageBox.h"
namespace CustomMessageBox
{
QMessageBox *selectable(QWidget *parent, const QString &title, const QString &text,
QMessageBox::Icon icon, QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton)
{
QMessageBox *messageBox = new QMessageBox(parent);
messageBox->setWindowTitle(title);
messageBox->setText(text);
messageBox->setStandardButtons(buttons);
messageBox->setDefaultButton(defaultButton);
messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse);
messageBox->setIcon(icon);
messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction);
return messageBox;
}
}

View File

@ -0,0 +1,26 @@
/* 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 <QMessageBox>
namespace CustomMessageBox
{
QMessageBox *selectable(QWidget *parent, const QString &title, const QString &text,
QMessageBox::Icon icon = QMessageBox::NoIcon,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
}

View File

@ -0,0 +1,61 @@
/* 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 "EditAccountDialog.h"
#include "ui_EditAccountDialog.h"
#include <DesktopServices.h>
#include <QUrl>
EditAccountDialog::EditAccountDialog(const QString &text, QWidget *parent, int flags)
: QDialog(parent), ui(new Ui::EditAccountDialog)
{
ui->setupUi(this);
ui->label->setText(text);
ui->label->setVisible(!text.isEmpty());
ui->userTextBox->setEnabled(flags & UsernameField);
ui->passTextBox->setEnabled(flags & PasswordField);
}
EditAccountDialog::~EditAccountDialog()
{
delete ui;
}
void EditAccountDialog::on_label_linkActivated(const QString &link)
{
DesktopServices::openUrl(QUrl(link));
}
void EditAccountDialog::setUsername(const QString & user) const
{
ui->userTextBox->setText(user);
}
QString EditAccountDialog::username() const
{
return ui->userTextBox->text();
}
void EditAccountDialog::setPassword(const QString & pass) const
{
ui->passTextBox->setText(pass);
}
QString EditAccountDialog::password() const
{
return ui->passTextBox->text();
}

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 <QDialog>
namespace Ui
{
class EditAccountDialog;
}
class EditAccountDialog : public QDialog
{
Q_OBJECT
public:
explicit EditAccountDialog(const QString &text = "", QWidget *parent = 0,
int flags = UsernameField | PasswordField);
~EditAccountDialog();
void setUsername(const QString & user) const;
void setPassword(const QString & pass) const;
QString username() const;
QString password() const;
enum Flags
{
NoFlags = 0,
//! Specifies that the dialog should have a username field.
UsernameField,
//! Specifies that the dialog should have a password field.
PasswordField,
};
private slots:
void on_label_linkActivated(const QString &link);
private:
Ui::EditAccountDialog *ui;
};

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EditAccountDialog</class>
<widget class="QDialog" name="EditAccountDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>148</height>
</rect>
</property>
<property name="windowTitle">
<string>Login</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string notr="true">Message label placeholder.</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="userTextBox">
<property name="placeholderText">
<string>Email</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="passTextBox">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="placeholderText">
<string>Password</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>EditAccountDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>EditAccountDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,482 @@
/* 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 "ExportInstanceDialog.h"
#include "ui_ExportInstanceDialog.h"
#include <BaseInstance.h>
#include <MMCZip.h>
#include <QFileDialog>
#include <QMessageBox>
#include <qfilesystemmodel.h>
#include <QSortFilterProxyModel>
#include <QDebug>
#include <qstack.h>
#include <QSaveFile>
#include "MMCStrings.h"
#include "SeparatorPrefixTree.h"
#include "Application.h"
#include <icons/IconList.h>
#include <FileSystem.h>
class PackIgnoreProxy : public QSortFilterProxyModel
{
Q_OBJECT
public:
PackIgnoreProxy(InstancePtr instance, QObject *parent) : QSortFilterProxyModel(parent)
{
m_instance = instance;
}
// NOTE: Sadly, we have to do sorting ourselves.
bool lessThan(const QModelIndex &left, const QModelIndex &right) const
{
QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel());
if (!fsm)
{
return QSortFilterProxyModel::lessThan(left, right);
}
bool asc = sortOrder() == Qt::AscendingOrder ? true : false;
QFileInfo leftFileInfo = fsm->fileInfo(left);
QFileInfo rightFileInfo = fsm->fileInfo(right);
if (!leftFileInfo.isDir() && rightFileInfo.isDir())
{
return !asc;
}
if (leftFileInfo.isDir() && !rightFileInfo.isDir())
{
return asc;
}
// sort and proxy model breaks the original model...
if (sortColumn() == 0)
{
return Strings::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(),
Qt::CaseInsensitive) < 0;
}
if (sortColumn() == 1)
{
auto leftSize = leftFileInfo.size();
auto rightSize = rightFileInfo.size();
if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir()))
{
return Strings::naturalCompare(leftFileInfo.fileName(),
rightFileInfo.fileName(),
Qt::CaseInsensitive) < 0
? asc
: !asc;
}
return leftSize < rightSize;
}
return QSortFilterProxyModel::lessThan(left, right);
}
virtual Qt::ItemFlags flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
auto sourceIndex = mapToSource(index);
Qt::ItemFlags flags = sourceIndex.flags();
if (index.column() == 0)
{
flags |= Qt::ItemIsUserCheckable;
if (sourceIndex.model()->hasChildren(sourceIndex))
{
flags |= Qt::ItemIsTristate;
}
}
return flags;
}
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
{
QModelIndex sourceIndex = mapToSource(index);
if (index.column() == 0 && role == Qt::CheckStateRole)
{
QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel());
auto blockedPath = relPath(fsm->filePath(sourceIndex));
auto cover = blocked.cover(blockedPath);
if (!cover.isNull())
{
return QVariant(Qt::Unchecked);
}
else if (blocked.exists(blockedPath))
{
return QVariant(Qt::PartiallyChecked);
}
else
{
return QVariant(Qt::Checked);
}
}
return sourceIndex.data(role);
}
virtual bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole)
{
if (index.column() == 0 && role == Qt::CheckStateRole)
{
Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
return setFilterState(index, state);
}
QModelIndex sourceIndex = mapToSource(index);
return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role);
}
QString relPath(const QString &path) const
{
QString prefix = QDir().absoluteFilePath(m_instance->instanceRoot());
prefix += '/';
if (!path.startsWith(prefix))
{
return QString();
}
return path.mid(prefix.size());
}
bool setFilterState(QModelIndex index, Qt::CheckState state)
{
QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel());
if (!fsm)
{
return false;
}
QModelIndex sourceIndex = mapToSource(index);
auto blockedPath = relPath(fsm->filePath(sourceIndex));
bool changed = false;
if (state == Qt::Unchecked)
{
// blocking a path
auto &node = blocked.insert(blockedPath);
// get rid of all blocked nodes below
node.clear();
changed = true;
}
else if (state == Qt::Checked || state == Qt::PartiallyChecked)
{
if (!blocked.remove(blockedPath))
{
auto cover = blocked.cover(blockedPath);
qDebug() << "Blocked by cover" << cover;
// uncover
blocked.remove(cover);
// block all contents, except for any cover
QModelIndex rootIndex =
fsm->index(FS::PathCombine(m_instance->instanceRoot(), cover));
QModelIndex doing = rootIndex;
int row = 0;
QStack<QModelIndex> todo;
while (1)
{
auto node = doing.child(row, 0);
if (!node.isValid())
{
if (!todo.size())
{
break;
}
else
{
doing = todo.pop();
row = 0;
continue;
}
}
auto relpath = relPath(fsm->filePath(node));
if (blockedPath.startsWith(relpath)) // cover found?
{
// continue processing cover later
todo.push(node);
}
else
{
// or just block this one.
blocked.insert(relpath);
}
row++;
}
}
changed = true;
}
if (changed)
{
// update the thing
emit dataChanged(index, index, {Qt::CheckStateRole});
// update everything above index
QModelIndex up = index.parent();
while (1)
{
if (!up.isValid())
break;
emit dataChanged(up, up, {Qt::CheckStateRole});
up = up.parent();
}
// and everything below the index
QModelIndex doing = index;
int row = 0;
QStack<QModelIndex> todo;
while (1)
{
auto node = doing.child(row, 0);
if (!node.isValid())
{
if (!todo.size())
{
break;
}
else
{
doing = todo.pop();
row = 0;
continue;
}
}
emit dataChanged(node, node, {Qt::CheckStateRole});
todo.push(node);
row++;
}
// siblings and unrelated nodes are ignored
}
return true;
}
bool shouldExpand(QModelIndex index)
{
QModelIndex sourceIndex = mapToSource(index);
QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel());
if (!fsm)
{
return false;
}
auto blockedPath = relPath(fsm->filePath(sourceIndex));
auto found = blocked.find(blockedPath);
if(found)
{
return !found->leaf();
}
return false;
}
void setBlockedPaths(QStringList paths)
{
beginResetModel();
blocked.clear();
blocked.insert(paths);
endResetModel();
}
const SeparatorPrefixTree<'/'> & blockedPaths() const
{
return blocked;
}
protected:
bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent)
// adjust the columns you want to filter out here
// return false for those that will be hidden
if (source_column == 2 || source_column == 3)
return false;
return true;
}
private:
InstancePtr m_instance;
SeparatorPrefixTree<'/'> blocked;
};
ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent)
: QDialog(parent), ui(new Ui::ExportInstanceDialog), m_instance(instance)
{
ui->setupUi(this);
auto model = new QFileSystemModel(this);
proxyModel = new PackIgnoreProxy(m_instance, this);
loadPackIgnore();
proxyModel->setSourceModel(model);
auto root = instance->instanceRoot();
ui->treeView->setModel(proxyModel);
ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root)));
ui->treeView->sortByColumn(0, Qt::AscendingOrder);
connect(proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int)));
model->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden);
model->setRootPath(root);
auto headerView = ui->treeView->header();
headerView->setSectionResizeMode(QHeaderView::ResizeToContents);
headerView->setSectionResizeMode(0, QHeaderView::Stretch);
}
ExportInstanceDialog::~ExportInstanceDialog()
{
delete ui;
}
/// Save icon to instance's folder is needed
void SaveIcon(InstancePtr m_instance)
{
auto iconKey = m_instance->iconKey();
auto iconList = APPLICATION->icons();
auto mmcIcon = iconList->icon(iconKey);
if(!mmcIcon || mmcIcon->isBuiltIn()) {
return;
}
auto path = mmcIcon->getFilePath();
if(!path.isNull()) {
QFileInfo inInfo (path);
FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName())) ();
return;
}
auto & image = mmcIcon->m_images[mmcIcon->type()];
auto & icon = image.icon;
auto sizes = icon.availableSizes();
if(sizes.size() == 0)
{
return;
}
auto areaOf = [](QSize size)
{
return size.width() * size.height();
};
QSize largest = sizes[0];
// find variant with largest area
for(auto size: sizes)
{
if(areaOf(largest) < areaOf(size))
{
largest = size;
}
}
auto pixmap = icon.pixmap(largest);
pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png"));
}
bool ExportInstanceDialog::doExport()
{
auto name = FS::RemoveInvalidFilenameChars(m_instance->name());
const QString output = QFileDialog::getSaveFileName(
this, tr("Export %1").arg(m_instance->name()),
FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr, QFileDialog::DontConfirmOverwrite);
if (output.isEmpty())
{
return false;
}
if (QFile::exists(output))
{
int ret =
QMessageBox::question(this, tr("Overwrite?"),
tr("This file already exists. Do you want to overwrite it?"),
QMessageBox::No, QMessageBox::Yes);
if (ret == QMessageBox::No)
{
return false;
}
}
SaveIcon(m_instance);
auto & blocked = proxyModel->blockedPaths();
using std::placeholders::_1;
if (!JlCompress::compressDir(output, m_instance->instanceRoot(), name, std::bind(&SeparatorPrefixTree<'/'>::covers, blocked, _1)))
{
QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
return false;
}
return true;
}
void ExportInstanceDialog::done(int result)
{
savePackIgnore();
if (result == QDialog::Accepted)
{
if (doExport())
{
QDialog::done(QDialog::Accepted);
return;
}
else
{
return;
}
}
QDialog::done(result);
}
void ExportInstanceDialog::rowsInserted(QModelIndex parent, int top, int bottom)
{
//WARNING: possible off-by-one?
for(int i = top; i < bottom; i++)
{
auto node = parent.child(i, 0);
if(proxyModel->shouldExpand(node))
{
auto expNode = node.parent();
if(!expNode.isValid())
{
continue;
}
ui->treeView->expand(node);
}
}
}
QString ExportInstanceDialog::ignoreFileName()
{
return FS::PathCombine(m_instance->instanceRoot(), ".packignore");
}
void ExportInstanceDialog::loadPackIgnore()
{
auto filename = ignoreFileName();
QFile ignoreFile(filename);
if(!ignoreFile.open(QIODevice::ReadOnly))
{
return;
}
auto data = ignoreFile.readAll();
auto string = QString::fromUtf8(data);
proxyModel->setBlockedPaths(string.split('\n', QString::SkipEmptyParts));
}
void ExportInstanceDialog::savePackIgnore()
{
auto data = proxyModel->blockedPaths().toStringList().join('\n').toUtf8();
auto filename = ignoreFileName();
try
{
FS::write(filename, data);
}
catch (const Exception &e)
{
qWarning() << e.cause();
}
}
#include "ExportInstanceDialog.moc"

View File

@ -0,0 +1,54 @@
/* 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 <QDialog>
#include <QModelIndex>
#include <memory>
class BaseInstance;
class PackIgnoreProxy;
typedef std::shared_ptr<BaseInstance> InstancePtr;
namespace Ui
{
class ExportInstanceDialog;
}
class ExportInstanceDialog : public QDialog
{
Q_OBJECT
public:
explicit ExportInstanceDialog(InstancePtr instance, QWidget *parent = 0);
~ExportInstanceDialog();
virtual void done(int result);
private:
bool doExport();
void loadPackIgnore();
void savePackIgnore();
QString ignoreFileName();
private:
Ui::ExportInstanceDialog *ui;
InstancePtr m_instance;
PackIgnoreProxy * proxyModel;
private slots:
void rowsInserted(QModelIndex parent, int top, int bottom);
};

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ExportInstanceDialog</class>
<widget class="QDialog" name="ExportInstanceDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>720</width>
<height>625</height>
</rect>
</property>
<property name="windowTitle">
<string>Export Instance</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeView" name="treeView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>treeView</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ExportInstanceDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ExportInstanceDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

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 <QKeyEvent>
#include <QPushButton>
#include <QFileDialog>
#include "Application.h"
#include "IconPickerDialog.h"
#include "ui_IconPickerDialog.h"
#include "ui/instanceview/InstanceDelegate.h"
#include "icons/IconList.h"
#include "icons/IconUtils.h"
#include <DesktopServices.h>
IconPickerDialog::IconPickerDialog(QWidget *parent)
: QDialog(parent), ui(new Ui::IconPickerDialog)
{
ui->setupUi(this);
setWindowModality(Qt::WindowModal);
auto contentsWidget = ui->iconView;
contentsWidget->setViewMode(QListView::IconMode);
contentsWidget->setFlow(QListView::LeftToRight);
contentsWidget->setIconSize(QSize(48, 48));
contentsWidget->setMovement(QListView::Static);
contentsWidget->setResizeMode(QListView::Adjust);
contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection);
contentsWidget->setSpacing(5);
contentsWidget->setWordWrap(false);
contentsWidget->setWrapping(true);
contentsWidget->setUniformItemSizes(true);
contentsWidget->setTextElideMode(Qt::ElideRight);
contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
contentsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
contentsWidget->setItemDelegate(new ListViewDelegate());
// contentsWidget->setAcceptDrops(true);
contentsWidget->setDropIndicatorShown(true);
contentsWidget->viewport()->setAcceptDrops(true);
contentsWidget->setDragDropMode(QAbstractItemView::DropOnly);
contentsWidget->setDefaultDropAction(Qt::CopyAction);
contentsWidget->installEventFilter(this);
contentsWidget->setModel(APPLICATION->icons().get());
// NOTE: ResetRole forces the button to be on the left, while the OK/Cancel ones are on the right. We win.
auto buttonAdd = ui->buttonBox->addButton(tr("Add Icon"), QDialogButtonBox::ResetRole);
auto buttonRemove = ui->buttonBox->addButton(tr("Remove Icon"), QDialogButtonBox::ResetRole);
connect(buttonAdd, SIGNAL(clicked(bool)), SLOT(addNewIcon()));
connect(buttonRemove, SIGNAL(clicked(bool)), SLOT(removeSelectedIcon()));
connect(contentsWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(activated(QModelIndex)));
connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(selectionChanged(QItemSelection, QItemSelection)));
auto buttonFolder = ui->buttonBox->addButton(tr("Open Folder"), QDialogButtonBox::ResetRole);
connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder);
}
bool IconPickerDialog::eventFilter(QObject *obj, QEvent *evt)
{
if (obj != ui->iconView)
return QDialog::eventFilter(obj, evt);
if (evt->type() != QEvent::KeyPress)
{
return QDialog::eventFilter(obj, evt);
}
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(evt);
switch (keyEvent->key())
{
case Qt::Key_Delete:
removeSelectedIcon();
return true;
case Qt::Key_Plus:
addNewIcon();
return true;
default:
break;
}
return QDialog::eventFilter(obj, evt);
}
void IconPickerDialog::addNewIcon()
{
//: The title of the select icons open file dialog
QString selectIcons = tr("Select Icons");
//: The type of icon files
auto filter = IconUtils::getIconFilter();
QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(), tr("Icons %1").arg(filter));
APPLICATION->icons()->installIcons(fileNames);
}
void IconPickerDialog::removeSelectedIcon()
{
APPLICATION->icons()->deleteIcon(selectedIconKey);
}
void IconPickerDialog::activated(QModelIndex index)
{
selectedIconKey = index.data(Qt::UserRole).toString();
accept();
}
void IconPickerDialog::selectionChanged(QItemSelection selected, QItemSelection deselected)
{
if (selected.empty())
return;
QString key = selected.first().indexes().first().data(Qt::UserRole).toString();
if (!key.isEmpty()) {
selectedIconKey = key;
}
}
int IconPickerDialog::execWithSelection(QString selection)
{
auto list = APPLICATION->icons();
auto contentsWidget = ui->iconView;
selectedIconKey = selection;
int index_nr = list->getIconIndex(selection);
auto model_index = list->index(index_nr);
contentsWidget->selectionModel()->select(
model_index, QItemSelectionModel::Current | QItemSelectionModel::Select);
QMetaObject::invokeMethod(this, "delayed_scroll", Qt::QueuedConnection, Q_ARG(QModelIndex, model_index));
return QDialog::exec();
}
void IconPickerDialog::delayed_scroll(QModelIndex model_index)
{
auto contentsWidget = ui->iconView;
contentsWidget->scrollTo(model_index);
}
IconPickerDialog::~IconPickerDialog()
{
delete ui;
}
void IconPickerDialog::openFolder()
{
DesktopServices::openDirectory(APPLICATION->icons()->getDirectory(), true);
}

View File

@ -0,0 +1,49 @@
/* 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 <QDialog>
#include <QItemSelection>
namespace Ui
{
class IconPickerDialog;
}
class IconPickerDialog : public QDialog
{
Q_OBJECT
public:
explicit IconPickerDialog(QWidget *parent = 0);
~IconPickerDialog();
int execWithSelection(QString selection);
QString selectedIconKey;
protected:
virtual bool eventFilter(QObject *, QEvent *);
private:
Ui::IconPickerDialog *ui;
private
slots:
void selectionChanged(QItemSelection, QItemSelection);
void activated(QModelIndex);
void delayed_scroll(QModelIndex);
void addNewIcon();
void removeSelectedIcon();
void openFolder();
};

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>IconPickerDialog</class>
<widget class="QDialog" name="IconPickerDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>676</width>
<height>555</height>
</rect>
</property>
<property name="windowTitle">
<string>Pick icon</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListView" name="iconView"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>IconPickerDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>IconPickerDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,119 @@
/* 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 "LoginDialog.h"
#include "ui_LoginDialog.h"
#include "minecraft/auth/AccountTask.h"
#include <QtWidgets/QPushButton>
LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LoginDialog)
{
ui->setupUi(this);
ui->progressBar->setVisible(false);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
LoginDialog::~LoginDialog()
{
delete ui;
}
// Stage 1: User interaction
void LoginDialog::accept()
{
setUserInputsEnabled(false);
ui->progressBar->setVisible(true);
// Setup the login task and start it
m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text());
m_loginTask = m_account->login(nullptr, ui->passTextBox->text());
connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed);
connect(m_loginTask.get(), &Task::succeeded, this, &LoginDialog::onTaskSucceeded);
connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus);
connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress);
m_loginTask->start();
}
void LoginDialog::setUserInputsEnabled(bool enable)
{
ui->userTextBox->setEnabled(enable);
ui->passTextBox->setEnabled(enable);
ui->buttonBox->setEnabled(enable);
}
// Enable the OK button only when both textboxes contain something.
void LoginDialog::on_userTextBox_textEdited(const QString &newText)
{
ui->buttonBox->button(QDialogButtonBox::Ok)
->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty());
}
void LoginDialog::on_passTextBox_textEdited(const QString &newText)
{
ui->buttonBox->button(QDialogButtonBox::Ok)
->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty());
}
void LoginDialog::onTaskFailed(const QString &reason)
{
// Set message
auto lines = reason.split('\n');
QString processed;
for(auto line: lines) {
if(line.size()) {
processed += "<font color='red'>" + line + "</font><br />";
}
else {
processed += "<br />";
}
}
ui->label->setText(processed);
// Re-enable user-interaction
setUserInputsEnabled(true);
ui->progressBar->setVisible(false);
}
void LoginDialog::onTaskSucceeded()
{
QDialog::accept();
}
void LoginDialog::onTaskStatus(const QString &status)
{
ui->label->setText(status);
}
void LoginDialog::onTaskProgress(qint64 current, qint64 total)
{
ui->progressBar->setMaximum(total);
ui->progressBar->setValue(current);
}
// Public interface
MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg)
{
LoginDialog dlg(parent);
dlg.ui->label->setText(msg);
if (dlg.exec() == QDialog::Accepted)
{
return dlg.m_account;
}
return 0;
}

View File

@ -0,0 +1,59 @@
/* 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 <QtWidgets/QDialog>
#include <QtCore/QEventLoop>
#include "minecraft/auth/MinecraftAccount.h"
#include "tasks/Task.h"
namespace Ui
{
class LoginDialog;
}
class LoginDialog : public QDialog
{
Q_OBJECT
public:
~LoginDialog();
static MinecraftAccountPtr newAccount(QWidget *parent, QString message);
private:
explicit LoginDialog(QWidget *parent = 0);
void setUserInputsEnabled(bool enable);
protected
slots:
void accept();
void onTaskFailed(const QString &reason);
void onTaskSucceeded();
void onTaskStatus(const QString &status);
void onTaskProgress(qint64 current, qint64 total);
void on_userTextBox_textEdited(const QString &newText);
void on_passTextBox_textEdited(const QString &newText);
private:
Ui::LoginDialog *ui;
MinecraftAccountPtr m_account;
Task::Ptr m_loginTask;
};

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LoginDialog</class>
<widget class="QDialog" name="LoginDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>421</width>
<height>198</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Add Account</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string notr="true">Message label placeholder.</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="userTextBox">
<property name="placeholderText">
<string>Email</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="passTextBox">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="placeholderText">
<string>Password</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>24</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,141 @@
/* 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 "MSALoginDialog.h"
#include "ui_MSALoginDialog.h"
#include "minecraft/auth/AccountTask.h"
#include <QtWidgets/QPushButton>
#include <QUrl>
MSALoginDialog::MSALoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MSALoginDialog)
{
ui->setupUi(this);
ui->progressBar->setVisible(false);
// ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
int MSALoginDialog::exec() {
setUserInputsEnabled(false);
ui->progressBar->setVisible(true);
// Setup the login task and start it
m_account = MinecraftAccount::createBlankMSA();
m_loginTask = m_account->loginMSA(nullptr);
connect(m_loginTask.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
connect(m_loginTask.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
connect(m_loginTask.get(), &Task::progress, this, &MSALoginDialog::onTaskProgress);
connect(m_loginTask.get(), &AccountTask::showVerificationUriAndCode, this, &MSALoginDialog::showVerificationUriAndCode);
connect(m_loginTask.get(), &AccountTask::hideVerificationUriAndCode, this, &MSALoginDialog::hideVerificationUriAndCode);
connect(&m_externalLoginTimer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick);
m_loginTask->start();
return QDialog::exec();
}
MSALoginDialog::~MSALoginDialog()
{
delete ui;
}
void MSALoginDialog::externalLoginTick() {
m_externalLoginElapsed++;
ui->progressBar->setValue(m_externalLoginElapsed);
ui->progressBar->repaint();
if(m_externalLoginElapsed >= m_externalLoginTimeout) {
m_externalLoginTimer.stop();
}
}
void MSALoginDialog::showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn) {
m_externalLoginElapsed = 0;
m_externalLoginTimeout = expiresIn;
m_externalLoginTimer.setInterval(1000);
m_externalLoginTimer.setSingleShot(false);
m_externalLoginTimer.start();
ui->progressBar->setMaximum(expiresIn);
ui->progressBar->setValue(m_externalLoginElapsed);
QString urlString = uri.toString();
QString linkString = QString("<a href=\"%1\">%2</a>").arg(urlString, urlString);
ui->label->setText(tr("<p>Please open up %1 in a browser and put in the code <b>%2</b> to proceed with login.</p>").arg(linkString, code));
}
void MSALoginDialog::hideVerificationUriAndCode() {
m_externalLoginTimer.stop();
}
void MSALoginDialog::setUserInputsEnabled(bool enable)
{
ui->buttonBox->setEnabled(enable);
}
void MSALoginDialog::onTaskFailed(const QString &reason)
{
// Set message
auto lines = reason.split('\n');
QString processed;
for(auto line: lines) {
if(line.size()) {
processed += "<font color='red'>" + line + "</font><br />";
}
else {
processed += "<br />";
}
}
ui->label->setText(processed);
// Re-enable user-interaction
setUserInputsEnabled(true);
ui->progressBar->setVisible(false);
}
void MSALoginDialog::onTaskSucceeded()
{
QDialog::accept();
}
void MSALoginDialog::onTaskStatus(const QString &status)
{
ui->label->setText(status);
}
void MSALoginDialog::onTaskProgress(qint64 current, qint64 total)
{
ui->progressBar->setMaximum(total);
ui->progressBar->setValue(current);
}
// Public interface
MinecraftAccountPtr MSALoginDialog::newAccount(QWidget *parent, QString msg)
{
MSALoginDialog dlg(parent);
dlg.ui->label->setText(msg);
if (dlg.exec() == QDialog::Accepted)
{
return dlg.m_account;
}
return 0;
}

View File

@ -0,0 +1,63 @@
/* 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 <QtWidgets/QDialog>
#include <QtCore/QEventLoop>
#include <QTimer>
#include "minecraft/auth/MinecraftAccount.h"
namespace Ui
{
class MSALoginDialog;
}
class MSALoginDialog : public QDialog
{
Q_OBJECT
public:
~MSALoginDialog();
static MinecraftAccountPtr newAccount(QWidget *parent, QString message);
int exec() override;
private:
explicit MSALoginDialog(QWidget *parent = 0);
void setUserInputsEnabled(bool enable);
protected
slots:
void onTaskFailed(const QString &reason);
void onTaskSucceeded();
void onTaskStatus(const QString &status);
void onTaskProgress(qint64 current, qint64 total);
void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn);
void hideVerificationUriAndCode();
void externalLoginTick();
private:
Ui::MSALoginDialog *ui;
MinecraftAccountPtr m_account;
shared_qobject_ptr<AccountTask> m_loginTask;
QTimer m_externalLoginTimer;
int m_externalLoginElapsed = 0;
int m_externalLoginTimeout = 0;
};

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MSALoginDialog</class>
<widget class="QDialog" name="MSALoginDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>491</width>
<height>143</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Add Microsoft Account</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string notr="true">Message label placeholder.
aaaaa</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</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="QProgressBar" name="progressBar">
<property name="value">
<number>24</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,106 @@
/* 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 "Application.h"
#include "NewComponentDialog.h"
#include "ui_NewComponentDialog.h"
#include <BaseVersion.h>
#include <icons/IconList.h>
#include <tasks/Task.h>
#include <InstanceList.h>
#include "VersionSelectDialog.h"
#include "ProgressDialog.h"
#include "IconPickerDialog.h"
#include <QLayout>
#include <QPushButton>
#include <QFileDialog>
#include <QValidator>
#include <meta/Index.h>
#include <meta/VersionList.h>
NewComponentDialog::NewComponentDialog(const QString & initialName, const QString & initialUid, QWidget *parent)
: QDialog(parent), ui(new Ui::NewComponentDialog)
{
ui->setupUi(this);
resize(minimumSizeHint());
ui->nameTextBox->setText(initialName);
ui->uidTextBox->setText(initialUid);
connect(ui->nameTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState);
connect(ui->uidTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState);
auto groups = APPLICATION->instances()->getGroups().toSet();
ui->nameTextBox->setFocus();
originalPlaceholderText = ui->uidTextBox->placeholderText();
updateDialogState();
}
NewComponentDialog::~NewComponentDialog()
{
delete ui;
}
void NewComponentDialog::updateDialogState()
{
auto protoUid = ui->nameTextBox->text().toLower();
protoUid.remove(QRegularExpression("[^a-z]"));
if(protoUid.isEmpty())
{
ui->uidTextBox->setPlaceholderText(originalPlaceholderText);
}
else
{
QString suggestedUid = "org.multimc.custom." + protoUid;
ui->uidTextBox->setPlaceholderText(suggestedUid);
}
bool allowOK = !name().isEmpty() && !uid().isEmpty() && !uidBlacklist.contains(uid());
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(allowOK);
}
QString NewComponentDialog::name() const
{
auto result = ui->nameTextBox->text();
if(result.size())
{
return result.trimmed();
}
return QString();
}
QString NewComponentDialog::uid() const
{
auto result = ui->uidTextBox->text();
if(result.size())
{
return result.trimmed();
}
result = ui->uidTextBox->placeholderText();
if(result.size() && result != originalPlaceholderText)
{
return result.trimmed();
}
return QString();
}
void NewComponentDialog::setBlacklist(QStringList badUids)
{
uidBlacklist = badUids;
}

View File

@ -0,0 +1,48 @@
/* 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 <QDialog>
#include <QString>
#include <QStringList>
namespace Ui
{
class NewComponentDialog;
}
class NewComponentDialog : public QDialog
{
Q_OBJECT
public:
explicit NewComponentDialog(const QString & initialName = QString(), const QString & initialUid = QString(), QWidget *parent = 0);
virtual ~NewComponentDialog();
void setBlacklist(QStringList badUids);
QString name() const;
QString uid() const;
private slots:
void updateDialogState();
private:
Ui::NewComponentDialog *ui;
QString originalPlaceholderText;
QStringList uidBlacklist;
};

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewComponentDialog</class>
<widget class="QDialog" name="NewComponentDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>345</width>
<height>146</height>
</rect>
</property>
<property name="windowTitle">
<string>Add Empty Component</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>:/icons/toolbar/copy</normaloff>:/icons/toolbar/copy</iconset>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLineEdit" name="nameTextBox">
<property name="placeholderText">
<string>Name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="uidTextBox">
<property name="placeholderText">
<string>uid</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>nameTextBox</tabstop>
<tabstop>uidTextBox</tabstop>
</tabstops>
<resources>
<include location="../../graphics.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>NewComponentDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>NewComponentDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,255 @@
/* 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 "Application.h"
#include "NewInstanceDialog.h"
#include "ui_NewInstanceDialog.h"
#include <BaseVersion.h>
#include <icons/IconList.h>
#include <tasks/Task.h>
#include <InstanceList.h>
#include "VersionSelectDialog.h"
#include "ProgressDialog.h"
#include "IconPickerDialog.h"
#include <QLayout>
#include <QPushButton>
#include <QFileDialog>
#include <QValidator>
#include <QDialogButtonBox>
#include "ui/widgets/PageContainer.h"
#include "ui/pages/modplatform/VanillaPage.h"
#include "ui/pages/modplatform/atlauncher/AtlPage.h"
#include "ui/pages/modplatform/ftb/FtbPage.h"
#include "ui/pages/modplatform/legacy_ftb/Page.h"
#include "ui/pages/modplatform/flame/FlamePage.h"
#include "ui/pages/modplatform/ImportPage.h"
#include "ui/pages/modplatform/technic/TechnicPage.h"
NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent)
: QDialog(parent), ui(new Ui::NewInstanceDialog)
{
ui->setupUi(this);
setWindowIcon(APPLICATION->getThemedIcon("new"));
InstIconKey = "default";
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
auto groups = APPLICATION->instances()->getGroups().toSet();
auto groupList = QStringList(groups.toList());
groupList.sort(Qt::CaseInsensitive);
groupList.removeOne("");
groupList.push_front(initialGroup);
groupList.push_front("");
ui->groupBox->addItems(groupList);
int index = groupList.indexOf(initialGroup);
if(index == -1)
{
index = 0;
}
ui->groupBox->setCurrentIndex(index);
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
// NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not move this below.
m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
m_container = new PageContainer(this);
m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding);
m_container->layout()->setContentsMargins(0, 0, 0, 0);
ui->verticalLayout->insertWidget(2, m_container);
m_container->addButtons(m_buttons);
// Bonk Qt over its stupid head and make sure it understands which button is the default one...
// See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button
auto OkButton = m_buttons->button(QDialogButtonBox::Ok);
OkButton->setDefault(true);
OkButton->setAutoDefault(true);
connect(OkButton, &QPushButton::clicked, this, &NewInstanceDialog::accept);
auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel);
CancelButton->setDefault(false);
CancelButton->setAutoDefault(false);
connect(CancelButton, &QPushButton::clicked, this, &NewInstanceDialog::reject);
auto HelpButton = m_buttons->button(QDialogButtonBox::Help);
HelpButton->setDefault(false);
HelpButton->setAutoDefault(false);
connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help);
if(!url.isEmpty())
{
QUrl actualUrl(url);
m_container->selectPage("import");
importPage->setUrl(url);
}
updateDialogState();
restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("NewInstanceGeometry").toByteArray()));
}
void NewInstanceDialog::reject()
{
APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64());
QDialog::reject();
}
void NewInstanceDialog::accept()
{
APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64());
importIconNow();
QDialog::accept();
}
QList<BasePage *> NewInstanceDialog::getPages()
{
importPage = new ImportPage(this);
flamePage = new FlamePage(this);
auto technicPage = new TechnicPage(this);
return
{
new VanillaPage(this),
importPage,
new AtlPage(this),
flamePage,
new FtbPage(this),
new LegacyFTB::Page(this),
technicPage
};
}
QString NewInstanceDialog::dialogTitle()
{
return tr("New Instance");
}
NewInstanceDialog::~NewInstanceDialog()
{
delete ui;
}
void NewInstanceDialog::setSuggestedPack(const QString& name, InstanceTask* task)
{
creationTask.reset(task);
ui->instNameTextBox->setPlaceholderText(name);
if(!task)
{
ui->iconButton->setIcon(APPLICATION->icons()->getIcon("default"));
importIcon = false;
}
auto allowOK = task && !instName().isEmpty();
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK);
}
void NewInstanceDialog::setSuggestedIconFromFile(const QString &path, const QString &name)
{
importIcon = true;
importIconPath = path;
importIconName = name;
//Hmm, for some reason they can be to small
ui->iconButton->setIcon(QIcon(path));
}
void NewInstanceDialog::setSuggestedIcon(const QString &key)
{
auto icon = APPLICATION->icons()->getIcon(key);
importIcon = false;
ui->iconButton->setIcon(icon);
}
InstanceTask * NewInstanceDialog::extractTask()
{
InstanceTask * extracted = creationTask.get();
creationTask.release();
extracted->setName(instName());
extracted->setGroup(instGroup());
extracted->setIcon(iconKey());
return extracted;
}
void NewInstanceDialog::updateDialogState()
{
auto allowOK = creationTask && !instName().isEmpty();
auto OkButton = m_buttons->button(QDialogButtonBox::Ok);
if(OkButton->isEnabled() != allowOK)
{
OkButton->setEnabled(allowOK);
}
}
QString NewInstanceDialog::instName() const
{
auto result = ui->instNameTextBox->text().trimmed();
if(result.size())
{
return result;
}
result = ui->instNameTextBox->placeholderText().trimmed();
if(result.size())
{
return result;
}
return QString();
}
QString NewInstanceDialog::instGroup() const
{
return ui->groupBox->currentText();
}
QString NewInstanceDialog::iconKey() const
{
return InstIconKey;
}
void NewInstanceDialog::on_iconButton_clicked()
{
importIconNow(); //so the user can switch back
IconPickerDialog dlg(this);
dlg.execWithSelection(InstIconKey);
if (dlg.result() == QDialog::Accepted)
{
InstIconKey = dlg.selectedIconKey;
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
importIcon = false;
}
}
void NewInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1)
{
updateDialogState();
}
void NewInstanceDialog::importIconNow()
{
if(importIcon) {
APPLICATION->icons()->installIcon(importIconPath, importIconName);
InstIconKey = importIconName;
importIcon = false;
}
APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64());
}

View File

@ -0,0 +1,80 @@
/* 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 <QDialog>
#include "BaseVersion.h"
#include "ui/pages/BasePageProvider.h"
#include "InstanceTask.h"
namespace Ui
{
class NewInstanceDialog;
}
class PageContainer;
class QDialogButtonBox;
class ImportPage;
class FlamePage;
class NewInstanceDialog : public QDialog, public BasePageProvider
{
Q_OBJECT
public:
explicit NewInstanceDialog(const QString & initialGroup, const QString & url = QString(), QWidget *parent = 0);
~NewInstanceDialog();
void updateDialogState();
void setSuggestedPack(const QString & name = QString(), InstanceTask * task = nullptr);
void setSuggestedIconFromFile(const QString &path, const QString &name);
void setSuggestedIcon(const QString &key);
InstanceTask * extractTask();
QString dialogTitle() override;
QList<BasePage *> getPages() override;
QString instName() const;
QString instGroup() const;
QString iconKey() const;
public slots:
void accept() override;
void reject() override;
private slots:
void on_iconButton_clicked();
void on_instNameTextBox_textChanged(const QString &arg1);
private:
Ui::NewInstanceDialog *ui = nullptr;
PageContainer * m_container = nullptr;
QDialogButtonBox * m_buttons = nullptr;
QString InstIconKey;
ImportPage *importPage = nullptr;
FlamePage *flamePage = nullptr;
std::unique_ptr<InstanceTask> creationTask;
bool importIcon = false;
QString importIconPath;
QString importIconName;
void importIconNow();
};

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewInstanceDialog</class>
<widget class="QDialog" name="NewInstanceDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>730</width>
<height>127</height>
</rect>
</property>
<property name="windowTitle">
<string>New Instance</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>:/icons/toolbar/new</normaloff>:/icons/toolbar/new</iconset>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="2">
<widget class="QComboBox" name="groupBox">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</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="0" column="2">
<widget class="QLineEdit" name="instNameTextBox"/>
</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="0" rowspan="2">
<widget class="QToolButton" name="iconButton">
<property name="iconSize">
<size>
<width>80</width>
<height>80</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>iconButton</tabstop>
<tabstop>instNameTextBox</tabstop>
<tabstop>groupBox</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,86 @@
#include "NotificationDialog.h"
#include "ui_NotificationDialog.h"
#include <QTimerEvent>
#include <QStyle>
NotificationDialog::NotificationDialog(const NotificationChecker::NotificationEntry &entry, QWidget *parent) :
QDialog(parent, Qt::MSWindowsFixedSizeDialogHint | Qt::WindowTitleHint | Qt::CustomizeWindowHint),
ui(new Ui::NotificationDialog)
{
ui->setupUi(this);
QStyle::StandardPixmap icon;
switch (entry.type)
{
case NotificationChecker::NotificationEntry::Critical:
icon = QStyle::SP_MessageBoxCritical;
break;
case NotificationChecker::NotificationEntry::Warning:
icon = QStyle::SP_MessageBoxWarning;
break;
default:
case NotificationChecker::NotificationEntry::Information:
icon = QStyle::SP_MessageBoxInformation;
break;
}
ui->iconLabel->setPixmap(style()->standardPixmap(icon, 0, this));
ui->messageLabel->setText(entry.message);
m_dontShowAgainText = tr("Don't show again");
m_closeText = tr("Close");
ui->dontShowAgainBtn->setText(m_dontShowAgainText + QString(" (%1)").arg(m_dontShowAgainTime));
ui->closeBtn->setText(m_closeText + QString(" (%1)").arg(m_closeTime));
startTimer(1000);
}
NotificationDialog::~NotificationDialog()
{
delete ui;
}
void NotificationDialog::timerEvent(QTimerEvent *event)
{
if (m_dontShowAgainTime > 0)
{
m_dontShowAgainTime--;
if (m_dontShowAgainTime == 0)
{
ui->dontShowAgainBtn->setText(m_dontShowAgainText);
ui->dontShowAgainBtn->setEnabled(true);
}
else
{
ui->dontShowAgainBtn->setText(m_dontShowAgainText + QString(" (%1)").arg(m_dontShowAgainTime));
}
}
if (m_closeTime > 0)
{
m_closeTime--;
if (m_closeTime == 0)
{
ui->closeBtn->setText(m_closeText);
ui->closeBtn->setEnabled(true);
}
else
{
ui->closeBtn->setText(m_closeText + QString(" (%1)").arg(m_closeTime));
}
}
if (m_closeTime == 0 && m_dontShowAgainTime == 0)
{
killTimer(event->timerId());
}
}
void NotificationDialog::on_dontShowAgainBtn_clicked()
{
done(DontShowAgain);
}
void NotificationDialog::on_closeBtn_clicked()
{
done(Normal);
}

View File

@ -0,0 +1,44 @@
#ifndef NOTIFICATIONDIALOG_H
#define NOTIFICATIONDIALOG_H
#include <QDialog>
#include "notifications/NotificationChecker.h"
namespace Ui {
class NotificationDialog;
}
class NotificationDialog : public QDialog
{
Q_OBJECT
public:
explicit NotificationDialog(const NotificationChecker::NotificationEntry &entry, QWidget *parent = 0);
~NotificationDialog();
enum ExitCode
{
Normal,
DontShowAgain
};
protected:
void timerEvent(QTimerEvent *event);
private:
Ui::NotificationDialog *ui;
int m_dontShowAgainTime = 10;
int m_closeTime = 5;
QString m_dontShowAgainText;
QString m_closeText;
private
slots:
void on_dontShowAgainBtn_clicked();
void on_closeBtn_clicked();
};
#endif // NOTIFICATIONDIALOG_H

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NotificationDialog</class>
<widget class="QDialog" name="NotificationDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>320</width>
<height>240</height>
</rect>
</property>
<property name="windowTitle">
<string>Notification</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,1">
<item>
<widget class="QLabel" name="iconLabel">
<property name="text">
<string notr="true">TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="messageLabel">
<property name="text">
<string notr="true">TextLabel</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="dontShowAgainBtn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Don't show again</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="closeBtn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

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 "ProfileSelectDialog.h"
#include "ui_ProfileSelectDialog.h"
#include <QItemSelectionModel>
#include <QDebug>
#include "SkinUtils.h"
#include "Application.h"
#include "ui/dialogs/ProgressDialog.h"
ProfileSelectDialog::ProfileSelectDialog(const QString &message, int flags, QWidget *parent)
: QDialog(parent), ui(new Ui::ProfileSelectDialog)
{
ui->setupUi(this);
m_accounts = APPLICATION->accounts();
auto view = ui->listView;
//view->setModel(m_accounts.get());
//view->hideColumn(AccountList::ActiveColumn);
view->setColumnCount(1);
view->setRootIsDecorated(false);
// FIXME: use a real model, not this
if(QTreeWidgetItem* header = view->headerItem())
{
header->setText(0, tr("Name"));
}
else
{
view->setHeaderLabel(tr("Name"));
}
QList <QTreeWidgetItem *> items;
for (int i = 0; i < m_accounts->count(); i++)
{
MinecraftAccountPtr account = m_accounts->at(i);
QString profileLabel;
if(account->isInUse()) {
profileLabel = tr("%1 (in use)").arg(account->profileName());
}
else {
profileLabel = account->profileName();
}
auto item = new QTreeWidgetItem(view);
item->setText(0, profileLabel);
item->setIcon(0, account->getFace());
item->setData(0, AccountList::PointerRole, QVariant::fromValue(account));
items.append(item);
}
view->addTopLevelItems(items);
// Set the message label.
ui->msgLabel->setVisible(!message.isEmpty());
ui->msgLabel->setText(message);
// Flags...
ui->globalDefaultCheck->setVisible(flags & GlobalDefaultCheckbox);
ui->instDefaultCheck->setVisible(flags & InstanceDefaultCheckbox);
qDebug() << flags;
// Select the first entry in the list.
ui->listView->setCurrentIndex(ui->listView->model()->index(0, 0));
connect(ui->listView, SIGNAL(doubleClicked(QModelIndex)), SLOT(on_buttonBox_accepted()));
}
ProfileSelectDialog::~ProfileSelectDialog()
{
delete ui;
}
MinecraftAccountPtr ProfileSelectDialog::selectedAccount() const
{
return m_selected;
}
bool ProfileSelectDialog::useAsGlobalDefault() const
{
return ui->globalDefaultCheck->isChecked();
}
bool ProfileSelectDialog::useAsInstDefaullt() const
{
return ui->instDefaultCheck->isChecked();
}
void ProfileSelectDialog::on_buttonBox_accepted()
{
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
if (selection.size() > 0)
{
QModelIndex selected = selection.first();
m_selected = selected.data(AccountList::PointerRole).value<MinecraftAccountPtr>();
}
close();
}
void ProfileSelectDialog::on_buttonBox_rejected()
{
close();
}

View File

@ -0,0 +1,90 @@
/* 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 <QDialog>
#include <memory>
#include "minecraft/auth/AccountList.h"
namespace Ui
{
class ProfileSelectDialog;
}
class ProfileSelectDialog : public QDialog
{
Q_OBJECT
public:
enum Flags
{
NoFlags = 0,
/*!
* Shows a check box on the dialog that allows the user to specify that the account
* they've selected should be used as the global default for all instances.
*/
GlobalDefaultCheckbox,
/*!
* Shows a check box on the dialog that allows the user to specify that the account
* they've selected should be used as the default for the instance they are currently launching.
* This is not currently implemented.
*/
InstanceDefaultCheckbox,
};
/*!
* Constructs a new account select dialog with the given parent and message.
* The message will be shown at the top of the dialog. It is an empty string by default.
*/
explicit ProfileSelectDialog(const QString& message="", int flags=0, QWidget *parent = 0);
~ProfileSelectDialog();
/*!
* Gets a pointer to the account that the user selected.
* This is null if the user clicked cancel or hasn't clicked OK yet.
*/
MinecraftAccountPtr selectedAccount() const;
/*!
* Returns true if the user checked the "use as global default" checkbox.
* If the checkbox wasn't shown, this function returns false.
*/
bool useAsGlobalDefault() const;
/*!
* Returns true if the user checked the "use as instance default" checkbox.
* If the checkbox wasn't shown, this function returns false.
*/
bool useAsInstDefaullt() const;
public
slots:
void on_buttonBox_accepted();
void on_buttonBox_rejected();
protected:
shared_qobject_ptr<AccountList> m_accounts;
//! The account that was selected when the user clicked OK.
MinecraftAccountPtr m_selected;
private:
Ui::ProfileSelectDialog *ui;
};

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProfileSelectDialog</class>
<widget class="QDialog" name="ProfileSelectDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>465</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Select an Account</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="msgLabel">
<property name="text">
<string>Select a profile.</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="listView">
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="globalDefaultCheck">
<property name="text">
<string>Use as default?</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="instDefaultCheck">
<property name="text">
<string>Use as default for this instance only?</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,250 @@
/* 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 "ProfileSetupDialog.h"
#include "ui_ProfileSetupDialog.h"
#include <QPushButton>
#include <QAction>
#include <QRegExpValidator>
#include <QJsonDocument>
#include <QDebug>
#include "ui/dialogs/ProgressDialog.h"
#include <Application.h>
#include "minecraft/auth/flows/AuthRequest.h"
#include "minecraft/auth/flows/Parsers.h"
ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget *parent)
: QDialog(parent), m_accountToSetup(accountToSetup), ui(new Ui::ProfileSetupDialog)
{
ui->setupUi(this);
ui->errorLabel->setVisible(false);
goodIcon = APPLICATION->getThemedIcon("status-good");
yellowIcon = APPLICATION->getThemedIcon("status-yellow");
badIcon = APPLICATION->getThemedIcon("status-bad");
QRegExp permittedNames("[a-zA-Z0-9_]{3,16}");
auto nameEdit = ui->nameEdit;
nameEdit->setValidator(new QRegExpValidator(permittedNames));
nameEdit->setClearButtonEnabled(true);
validityAction = nameEdit->addAction(yellowIcon, QLineEdit::LeadingPosition);
connect(nameEdit, &QLineEdit::textEdited, this, &ProfileSetupDialog::nameEdited);
checkStartTimer.setSingleShot(true);
connect(&checkStartTimer, &QTimer::timeout, this, &ProfileSetupDialog::startCheck);
setNameStatus(NameStatus::NotSet, QString());
}
ProfileSetupDialog::~ProfileSetupDialog()
{
delete ui;
}
void ProfileSetupDialog::on_buttonBox_accepted()
{
setupProfile(currentCheck);
}
void ProfileSetupDialog::on_buttonBox_rejected()
{
reject();
}
void ProfileSetupDialog::setNameStatus(ProfileSetupDialog::NameStatus status, QString errorString = QString())
{
nameStatus = status;
auto okButton = ui->buttonBox->button(QDialogButtonBox::Ok);
switch(nameStatus)
{
case NameStatus::Available: {
validityAction->setIcon(goodIcon);
okButton->setEnabled(true);
}
break;
case NameStatus::NotSet:
case NameStatus::Pending:
validityAction->setIcon(yellowIcon);
okButton->setEnabled(false);
break;
case NameStatus::Exists:
case NameStatus::Error:
validityAction->setIcon(badIcon);
okButton->setEnabled(false);
break;
}
if(!errorString.isEmpty()) {
ui->errorLabel->setText(errorString);
ui->errorLabel->setVisible(true);
}
else {
ui->errorLabel->setVisible(false);
}
}
void ProfileSetupDialog::nameEdited(const QString& name)
{
if(!ui->nameEdit->hasAcceptableInput()) {
setNameStatus(NameStatus::NotSet, tr("Name is too short - must be between 3 and 16 characters long."));
return;
}
scheduleCheck(name);
}
void ProfileSetupDialog::scheduleCheck(const QString& name) {
queuedCheck = name;
setNameStatus(NameStatus::Pending);
checkStartTimer.start(1000);
}
void ProfileSetupDialog::startCheck() {
if(isChecking) {
return;
}
if(queuedCheck.isNull()) {
return;
}
checkName(queuedCheck);
}
void ProfileSetupDialog::checkName(const QString &name) {
if(isChecking) {
return;
}
currentCheck = name;
isChecking = true;
auto token = m_accountToSetup->accessToken();
auto url = QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name);
QNetworkRequest request = QNetworkRequest(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
AuthRequest *requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::checkFinished);
requestor->get(request);
}
void ProfileSetupDialog::checkFinished(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
if(error == QNetworkReply::NoError) {
auto doc = QJsonDocument::fromJson(data);
auto root = doc.object();
auto statusValue = root.value("status").toString("INVALID");
if(statusValue == "AVAILABLE") {
setNameStatus(NameStatus::Available);
}
else if (statusValue == "DUPLICATE") {
setNameStatus(NameStatus::Exists, tr("Minecraft profile with name %1 already exists.").arg(currentCheck));
}
else if (statusValue == "NOT_ALLOWED") {
setNameStatus(NameStatus::Exists, tr("The name %1 is not allowed.").arg(currentCheck));
}
else {
setNameStatus(NameStatus::Error, tr("Unhandled profile name status: %1").arg(statusValue));
}
}
else {
setNameStatus(NameStatus::Error, tr("Failed to check name availability."));
}
isChecking = false;
}
void ProfileSetupDialog::setupProfile(const QString &profileName) {
if(isWorking) {
return;
}
auto token = m_accountToSetup->accessToken();
auto url = QString("https://api.minecraftservices.com/minecraft/profile");
QNetworkRequest request = QNetworkRequest(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
QString payloadTemplate("{\"profileName\":\"%1\"}");
auto data = payloadTemplate.arg(profileName).toUtf8();
AuthRequest *requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::setupProfileFinished);
requestor->post(request, data);
isWorking = true;
auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
button->setEnabled(false);
}
namespace {
struct MojangError{
static MojangError fromJSON(QByteArray data) {
MojangError out;
out.error = QString::fromUtf8(data);
auto doc = QJsonDocument::fromJson(data, &out.parseError);
auto object = doc.object();
out.fullyParsed = true;
out.fullyParsed &= Parsers::getString(object.value("path"), out.path);
out.fullyParsed &= Parsers::getString(object.value("error"), out.error);
out.fullyParsed &= Parsers::getString(object.value("errorMessage"), out.errorMessage);
return out;
}
QString rawError;
QJsonParseError parseError;
bool fullyParsed;
QString path;
QString error;
QString errorMessage;
};
}
void ProfileSetupDialog::setupProfileFinished(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
isWorking = false;
if(error == QNetworkReply::NoError) {
/*
* data contains the profile in the response
* ... we could parse it and update the account, but let's just return back to the normal login flow instead...
*/
accept();
}
else {
auto parsedError = MojangError::fromJSON(data);
ui->errorLabel->setVisible(true);
ui->errorLabel->setText(tr("The server returned the following error:") + "\n\n" + parsedError.errorMessage);
qDebug() << parsedError.rawError;
auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
button->setEnabled(true);
}
}

View File

@ -0,0 +1,88 @@
/* 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 <QDialog>
#include <QIcon>
#include <QTimer>
#include <QNetworkReply>
#include <memory>
#include <minecraft/auth/MinecraftAccount.h>
namespace Ui
{
class ProfileSetupDialog;
}
class ProfileSetupDialog : public QDialog
{
Q_OBJECT
public:
explicit ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget *parent = 0);
~ProfileSetupDialog();
enum class NameStatus
{
NotSet,
Pending,
Available,
Exists,
Error
} nameStatus = NameStatus::NotSet;
private slots:
void on_buttonBox_accepted();
void on_buttonBox_rejected();
void nameEdited(const QString &name);
void checkFinished(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
);
void startCheck();
void setupProfileFinished(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
);
protected:
void scheduleCheck(const QString &name);
void checkName(const QString &name);
void setNameStatus(NameStatus status, QString errorString);
void setupProfile(const QString & profileName);
private:
MinecraftAccountPtr m_accountToSetup;
Ui::ProfileSetupDialog *ui;
QIcon goodIcon;
QIcon yellowIcon;
QIcon badIcon;
QAction * validityAction = nullptr;
QString queuedCheck;
bool isChecking = false;
bool isWorking = false;
QString currentCheck;
QTimer checkStartTimer;
};

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProfileSetupDialog</class>
<widget class="QDialog" name="ProfileSetupDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>615</width>
<height>208</height>
</rect>
</property>
<property name="windowTitle">
<string>Choose Minecraft name</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="descriptionLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>You just need to take one more step to be able to play Minecraft on this account.
Choose your name carefully:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>nameEdit</cstring>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLineEdit" name="nameEdit"/>
</item>
<item row="4" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="errorLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string notr="true">Errors go here</string>
</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>
<tabstops>
<tabstop>nameEdit</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,196 @@
/* 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 "ProgressDialog.h"
#include "ui_ProgressDialog.h"
#include <QKeyEvent>
#include <QDebug>
#include "tasks/Task.h"
ProgressDialog::ProgressDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ProgressDialog)
{
ui->setupUi(this);
this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
setSkipButton(false);
changeProgress(0, 100);
}
void ProgressDialog::setSkipButton(bool present, QString label)
{
ui->skipButton->setAutoDefault(false);
ui->skipButton->setDefault(false);
ui->skipButton->setFocusPolicy(Qt::ClickFocus);
ui->skipButton->setEnabled(present);
ui->skipButton->setVisible(present);
ui->skipButton->setText(label);
updateSize();
}
void ProgressDialog::on_skipButton_clicked(bool checked)
{
Q_UNUSED(checked);
task->abort();
}
ProgressDialog::~ProgressDialog()
{
delete ui;
}
void ProgressDialog::updateSize()
{
QSize qSize = QSize(480, minimumSizeHint().height());
resize(qSize);
setFixedSize(qSize);
}
int ProgressDialog::execWithTask(Task *task)
{
this->task = task;
QDialog::DialogCode result;
if(!task)
{
qDebug() << "Programmer error: progress dialog created with null task.";
return Accepted;
}
if(handleImmediateResult(result))
{
return result;
}
// Connect signals.
connect(task, SIGNAL(started()), SLOT(onTaskStarted()));
connect(task, SIGNAL(failed(QString)), SLOT(onTaskFailed(QString)));
connect(task, SIGNAL(succeeded()), SLOT(onTaskSucceeded()));
connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString &)));
connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64)));
// if this didn't connect to an already running task, invoke start
if(!task->isRunning())
{
task->start();
}
if(task->isRunning())
{
changeProgress(task->getProgress(), task->getTotalProgress());
changeStatus(task->getStatus());
return QDialog::exec();
}
else if(handleImmediateResult(result))
{
return result;
}
else
{
return QDialog::Rejected;
}
}
// TODO: only provide the unique_ptr overloads
int ProgressDialog::execWithTask(std::unique_ptr<Task> &&task)
{
connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater);
return execWithTask(task.release());
}
int ProgressDialog::execWithTask(std::unique_ptr<Task> &task)
{
connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater);
return execWithTask(task.release());
}
bool ProgressDialog::handleImmediateResult(QDialog::DialogCode &result)
{
if(task->isFinished())
{
if(task->wasSuccessful())
{
result = QDialog::Accepted;
}
else
{
result = QDialog::Rejected;
}
return true;
}
return false;
}
Task *ProgressDialog::getTask()
{
return task;
}
void ProgressDialog::onTaskStarted()
{
}
void ProgressDialog::onTaskFailed(QString failure)
{
reject();
}
void ProgressDialog::onTaskSucceeded()
{
accept();
}
void ProgressDialog::changeStatus(const QString &status)
{
ui->statusLabel->setText(status);
updateSize();
}
void ProgressDialog::changeProgress(qint64 current, qint64 total)
{
ui->taskProgressBar->setMaximum(total);
ui->taskProgressBar->setValue(current);
}
void ProgressDialog::keyPressEvent(QKeyEvent *e)
{
if(ui->skipButton->isVisible())
{
if (e->key() == Qt::Key_Escape)
{
on_skipButton_clicked(true);
return;
}
else if(e->key() == Qt::Key_Tab)
{
ui->skipButton->setFocusPolicy(Qt::StrongFocus);
ui->skipButton->setFocus();
ui->skipButton->setAutoDefault(true);
ui->skipButton->setDefault(true);
return;
}
}
QDialog::keyPressEvent(e);
}
void ProgressDialog::closeEvent(QCloseEvent *e)
{
if (task && task->isRunning())
{
e->ignore();
}
else
{
QDialog::closeEvent(e);
}
}

View File

@ -0,0 +1,71 @@
/* 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 <QDialog>
#include <memory>
class Task;
namespace Ui
{
class ProgressDialog;
}
class ProgressDialog : public QDialog
{
Q_OBJECT
public:
explicit ProgressDialog(QWidget *parent = 0);
~ProgressDialog();
void updateSize();
int execWithTask(Task *task);
int execWithTask(std::unique_ptr<Task> &&task);
int execWithTask(std::unique_ptr<Task> &task);
void setSkipButton(bool present, QString label = QString());
Task *getTask();
public
slots:
void onTaskStarted();
void onTaskFailed(QString failure);
void onTaskSucceeded();
void changeStatus(const QString &status);
void changeProgress(qint64 current, qint64 total);
private
slots:
void on_skipButton_clicked(bool checked);
protected:
virtual void keyPressEvent(QKeyEvent *e);
virtual void closeEvent(QCloseEvent *e);
private:
bool handleImmediateResult(QDialog::DialogCode &result);
private:
Ui::ProgressDialog *ui;
Task *task;
};

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProgressDialog</class>
<widget class="QDialog" name="ProgressDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>100</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>16777215</height>
</size>
</property>
<property name="windowTitle">
<string>Please wait...</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="statusLabel">
<property name="text">
<string>Task Status...</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QProgressBar" name="taskProgressBar">
<property name="value">
<number>24</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="skipButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Skip</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,155 @@
#include <QFileInfo>
#include <QFileDialog>
#include <QPainter>
#include <FileSystem.h>
#include <minecraft/services/SkinUpload.h>
#include <minecraft/services/CapeChange.h>
#include <tasks/SequentialTask.h>
#include "SkinUploadDialog.h"
#include "ui_SkinUploadDialog.h"
#include "ProgressDialog.h"
#include "CustomMessageBox.h"
void SkinUploadDialog::on_buttonBox_rejected()
{
close();
}
void SkinUploadDialog::on_buttonBox_accepted()
{
AuthSessionPtr session = std::make_shared<AuthSession>();
auto login = m_acct->refresh(session);
ProgressDialog prog(this);
if (prog.execWithTask((Task*)login.get()) != QDialog::Accepted)
{
//FIXME: recover with password prompt
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to login!"), QMessageBox::Warning)->exec();
close();
return;
}
QString fileName;
QString input = ui->skinPathTextBox->text();
QRegExp urlPrefixMatcher("^([a-z]+)://.+$");
bool isLocalFile = false;
// it has an URL prefix -> it is an URL
if(urlPrefixMatcher.exactMatch(input))
{
QUrl fileURL = input;
if(fileURL.isValid())
{
// local?
if(fileURL.isLocalFile())
{
isLocalFile = true;
fileName = fileURL.toLocalFile();
}
else
{
CustomMessageBox::selectable(
this,
tr("Skin Upload"),
tr("Using remote URLs for setting skins is not implemented yet."),
QMessageBox::Warning
)->exec();
close();
return;
}
}
else
{
CustomMessageBox::selectable(
this,
tr("Skin Upload"),
tr("You cannot use an invalid URL for uploading skins."),
QMessageBox::Warning
)->exec();
close();
return;
}
}
else
{
// just assume it's a path then
isLocalFile = true;
fileName = ui->skinPathTextBox->text();
}
if (isLocalFile && !QFile::exists(fileName))
{
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec();
close();
return;
}
SkinUpload::Model model = SkinUpload::STEVE;
if (ui->steveBtn->isChecked())
{
model = SkinUpload::STEVE;
}
else if (ui->alexBtn->isChecked())
{
model = SkinUpload::ALEX;
}
SequentialTask skinUpload;
skinUpload.addTask(shared_qobject_ptr<SkinUpload>(new SkinUpload(this, session, FS::read(fileName), model)));
auto selectedCape = ui->capeCombo->currentData().toString();
if(selectedCape != m_acct->accountData()->minecraftProfile.currentCape) {
skinUpload.addTask(shared_qobject_ptr<CapeChange>(new CapeChange(this, session, selectedCape)));
}
if (prog.execWithTask(&skinUpload) != QDialog::Accepted)
{
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec();
close();
return;
}
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Success"), QMessageBox::Information)->exec();
close();
}
void SkinUploadDialog::on_skinBrowseBtn_clicked()
{
QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), "*.png");
if (raw_path.isEmpty() || !QFileInfo::exists(raw_path))
{
return;
}
QString cooked_path = FS::NormalizePath(raw_path);
ui->skinPathTextBox->setText(cooked_path);
}
SkinUploadDialog::SkinUploadDialog(MinecraftAccountPtr acct, QWidget *parent)
:QDialog(parent), m_acct(acct), ui(new Ui::SkinUploadDialog)
{
ui->setupUi(this);
// FIXME: add a model for this, download/refresh the capes on demand
auto &data = *acct->accountData();
int index = 0;
ui->capeCombo->addItem(tr("No Cape"), QVariant());
auto currentCape = data.minecraftProfile.currentCape;
if(currentCape.isEmpty()) {
ui->capeCombo->setCurrentIndex(index);
}
for(auto & cape: data.minecraftProfile.capes) {
index++;
if(cape.data.size()) {
QPixmap capeImage;
if(capeImage.loadFromData(cape.data, "PNG")) {
QPixmap preview = QPixmap(10, 16);
QPainter painter(&preview);
painter.drawPixmap(0, 0, capeImage.copy(1, 1, 10, 16));
ui->capeCombo->addItem(capeImage, cape.alias, cape.id);
if(currentCape == cape.id) {
ui->capeCombo->setCurrentIndex(index);
}
continue;
}
}
ui->capeCombo->addItem(cape.alias, cape.id);
if(currentCape == cape.id) {
ui->capeCombo->setCurrentIndex(index);
}
}
}

View File

@ -0,0 +1,29 @@
#pragma once
#include <QDialog>
#include <minecraft/auth/MinecraftAccount.h>
namespace Ui
{
class SkinUploadDialog;
}
class SkinUploadDialog : public QDialog {
Q_OBJECT
public:
explicit SkinUploadDialog(MinecraftAccountPtr acct, QWidget *parent = 0);
virtual ~SkinUploadDialog() {};
public slots:
void on_buttonBox_accepted();
void on_buttonBox_rejected();
void on_skinBrowseBtn_clicked();
protected:
MinecraftAccountPtr m_acct;
private:
Ui::SkinUploadDialog *ui;
};

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SkinUploadDialog</class>
<widget class="QDialog" name="SkinUploadDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>394</width>
<height>360</height>
</rect>
</property>
<property name="windowTitle">
<string>Skin Upload</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="fileBox">
<property name="title">
<string>Skin File</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="skinPathTextBox"/>
</item>
<item>
<widget class="QPushButton" name="skinBrowseBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>28</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="modelBox">
<property name="title">
<string>Player Model</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
<widget class="QRadioButton" name="steveBtn">
<property name="text">
<string>Steve Model</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="alexBtn">
<property name="text">
<string>Alex Model</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="capeBox">
<property name="title">
<string>Cape</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QComboBox" name="capeCombo"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,182 @@
#include "UpdateDialog.h"
#include "ui_UpdateDialog.h"
#include <QDebug>
#include "Application.h"
#include <settings/SettingsObject.h>
#include <Json.h>
#include "BuildConfig.h"
#include "HoeDown.h"
UpdateDialog::UpdateDialog(bool hasUpdate, QWidget *parent) : QDialog(parent), ui(new Ui::UpdateDialog)
{
ui->setupUi(this);
auto channel = APPLICATION->settings()->get("UpdateChannel").toString();
if(hasUpdate)
{
ui->label->setText(tr("A new %1 update is available!").arg(channel));
}
else
{
ui->label->setText(tr("No %1 updates found. You are running the latest version.").arg(channel));
ui->btnUpdateNow->setHidden(true);
ui->btnUpdateLater->setText(tr("Close"));
}
ui->changelogBrowser->setHtml(tr("<center><h1>Loading changelog...</h1></center>"));
loadChangelog();
restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("UpdateDialogGeometry").toByteArray()));
}
UpdateDialog::~UpdateDialog()
{
}
void UpdateDialog::loadChangelog()
{
auto channel = APPLICATION->settings()->get("UpdateChannel").toString();
dljob.reset(new NetJob("Changelog"));
QString url;
if(channel == "stable")
{
url = QString("https://raw.githubusercontent.com/MultiMC/Launcher/%1/changelog.md").arg(channel);
m_changelogType = CHANGELOG_MARKDOWN;
}
else
{
url = QString("https://api.github.com/repos/MultiMC/Launcher/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel);
m_changelogType = CHANGELOG_COMMITS;
}
dljob->addNetAction(Net::Download::makeByteArray(QUrl(url), &changelogData));
connect(dljob.get(), &NetJob::succeeded, this, &UpdateDialog::changelogLoaded);
connect(dljob.get(), &NetJob::failed, this, &UpdateDialog::changelogFailed);
dljob->start(APPLICATION->network());
}
QString reprocessMarkdown(QByteArray markdown)
{
HoeDown hoedown;
QString output = hoedown.process(markdown);
// HACK: easier than customizing hoedown
output.replace(QRegExp("GH-([0-9]+)"), "<a href=\"https://github.com/MultiMC/Launcher/issues/\\1\">GH-\\1</a>");
qDebug() << output;
return output;
}
QString reprocessCommits(QByteArray json)
{
auto channel = APPLICATION->settings()->get("UpdateChannel").toString();
try
{
QString result;
auto document = Json::requireDocument(json);
auto rootobject = Json::requireObject(document);
auto status = Json::requireString(rootobject, "status");
auto diff_url = Json::requireString(rootobject, "html_url");
auto print_commits = [&]()
{
result += "<table cellspacing=0 cellpadding=2 style='border-width: 1px; border-style: solid'>";
auto commitarray = Json::requireArray(rootobject, "commits");
for(int i = commitarray.size() - 1; i >= 0; i--)
{
const auto & commitval = commitarray[i];
auto commitobj = Json::requireObject(commitval);
auto parents_info = Json::ensureArray(commitobj, "parents");
// NOTE: this ignores merge commits, because they have more than one parent
if(parents_info.size() > 1)
{
continue;
}
auto commit_url = Json::requireString(commitobj, "html_url");
auto commit_info = Json::requireObject(commitobj, "commit");
auto commit_message = Json::requireString(commit_info, "message");
auto lines = commit_message.split('\n');
QRegularExpression regexp("(?<prefix>(GH-(?<issuenr>[0-9]+))|(NOISSUE)|(SCRATCH))? *(?<rest>.*) *");
auto match = regexp.match(lines.takeFirst(), 0, QRegularExpression::NormalMatch);
auto issuenr = match.captured("issuenr");
auto prefix = match.captured("prefix");
auto rest = match.captured("rest");
result += "<tr><td>";
if(issuenr.length())
{
result += QString("<a href=\"https://github.com/MultiMC/Launcher/issues/%1\">GH-%2</a>").arg(issuenr, issuenr);
}
else if(prefix.length())
{
result += QString("<a href=\"%1\">%2</a>").arg(commit_url, prefix);
}
else
{
result += QString("<a href=\"%1\">NOISSUE</a>").arg(commit_url);
}
result += "</td>";
lines.prepend(rest);
result += "<td><p>" + lines.join("<br />") + "</p></td></tr>";
}
result += "</table>";
};
if(status == "identical")
{
return QObject::tr("<p>There are no code changes between your current version and latest %1.</p>").arg(channel);
}
else if(status == "ahead")
{
result += QObject::tr("<p>Following commits were added since last update:</p>");
print_commits();
}
else if(status == "diverged")
{
auto commit_ahead = Json::requireInteger(rootobject, "ahead_by");
auto commit_behind = Json::requireInteger(rootobject, "behind_by");
result += QObject::tr("<p>The update removes %1 commits and adds the following %2:</p>").arg(commit_behind).arg(commit_ahead);
print_commits();
}
result += QObject::tr("<p>You can <a href=\"%1\">look at the changes on github</a>.</p>").arg(diff_url);
return result;
}
catch (const JSONValidationError &e)
{
qWarning() << "Got an unparseable commit log from github:" << e.what();
qDebug() << json;
}
return QString();
}
void UpdateDialog::changelogLoaded()
{
QString result;
switch(m_changelogType)
{
case CHANGELOG_COMMITS:
result = reprocessCommits(changelogData);
break;
case CHANGELOG_MARKDOWN:
result = reprocessMarkdown(changelogData);
break;
}
changelogData.clear();
ui->changelogBrowser->setHtml(result);
}
void UpdateDialog::changelogFailed(QString reason)
{
ui->changelogBrowser->setHtml(tr("<p align=\"center\" <span style=\"font-size:22pt;\">Failed to fetch changelog... Error: %1</span></p>").arg(reason));
}
void UpdateDialog::on_btnUpdateLater_clicked()
{
reject();
}
void UpdateDialog::on_btnUpdateNow_clicked()
{
done(UPDATE_NOW);
}
void UpdateDialog::closeEvent(QCloseEvent* evt)
{
APPLICATION->settings()->set("UpdateDialogGeometry", saveGeometry().toBase64());
QDialog::closeEvent(evt);
}

View File

@ -0,0 +1,67 @@
/* 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 <QDialog>
#include "net/NetJob.h"
namespace Ui
{
class UpdateDialog;
}
enum UpdateAction
{
UPDATE_LATER = QDialog::Rejected,
UPDATE_NOW = QDialog::Accepted,
};
enum ChangelogType
{
CHANGELOG_MARKDOWN,
CHANGELOG_COMMITS
};
class UpdateDialog : public QDialog
{
Q_OBJECT
public:
explicit UpdateDialog(bool hasUpdate = true, QWidget *parent = 0);
~UpdateDialog();
public slots:
void on_btnUpdateNow_clicked();
void on_btnUpdateLater_clicked();
/// Starts loading the changelog
void loadChangelog();
/// Slot for when the chengelog loads successfully.
void changelogLoaded();
/// Slot for when the chengelog fails to load...
void changelogFailed(QString reason);
protected:
void closeEvent(QCloseEvent * ) override;
private:
Ui::UpdateDialog *ui;
QByteArray changelogData;
NetJob::Ptr dljob;
ChangelogType m_changelogType = CHANGELOG_MARKDOWN;
};

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UpdateDialog</class>
<widget class="QDialog" name="UpdateDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>657</width>
<height>673</height>
</rect>
</property>
<property name="windowTitle">
<string>MultiMC Update</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>:/icons/toolbar/checkupdate</normaloff>:/icons/toolbar/checkupdate</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>changelogBrowser</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTextBrowser" name="changelogBrowser">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPushButton" name="btnUpdateNow">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Update now</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="btnUpdateLater">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Don't update yet</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>changelogBrowser</tabstop>
<tabstop>btnUpdateNow</tabstop>
<tabstop>btnUpdateLater</tabstop>
</tabstops>
<resources>
<include location="../../resources/multimc/multimc.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,141 @@
/* 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 "VersionSelectDialog.h"
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QVBoxLayout>
#include <QDebug>
#include "ui/dialogs/ProgressDialog.h"
#include "ui/widgets/VersionSelectWidget.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "BaseVersion.h"
#include "BaseVersionList.h"
#include "tasks/Task.h"
#include "Application.h"
#include "VersionProxyModel.h"
VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent, bool cancelable)
: QDialog(parent)
{
setObjectName(QStringLiteral("VersionSelectDialog"));
resize(400, 347);
m_verticalLayout = new QVBoxLayout(this);
m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
m_versionWidget = new VersionSelectWidget(parent);
m_verticalLayout->addWidget(m_versionWidget);
m_horizontalLayout = new QHBoxLayout();
m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
m_refreshButton = new QPushButton(this);
m_refreshButton->setObjectName(QStringLiteral("refreshButton"));
m_horizontalLayout->addWidget(m_refreshButton);
m_buttonBox = new QDialogButtonBox(this);
m_buttonBox->setObjectName(QStringLiteral("buttonBox"));
m_buttonBox->setOrientation(Qt::Horizontal);
m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
m_horizontalLayout->addWidget(m_buttonBox);
m_verticalLayout->addLayout(m_horizontalLayout);
retranslate();
QObject::connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
QObject::connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
QMetaObject::connectSlotsByName(this);
setWindowModality(Qt::WindowModal);
setWindowTitle(title);
m_vlist = vlist;
if (!cancelable)
{
m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
}
}
void VersionSelectDialog::retranslate()
{
// FIXME: overrides custom title given in constructor!
setWindowTitle(tr("Choose Version"));
m_refreshButton->setToolTip(tr("Reloads the version list."));
m_refreshButton->setText(tr("&Refresh"));
}
void VersionSelectDialog::setCurrentVersion(const QString& version)
{
m_currentVersion = version;
m_versionWidget->setCurrentVersion(version);
}
void VersionSelectDialog::setEmptyString(QString emptyString)
{
m_versionWidget->setEmptyString(emptyString);
}
void VersionSelectDialog::setEmptyErrorString(QString emptyErrorString)
{
m_versionWidget->setEmptyErrorString(emptyErrorString);
}
void VersionSelectDialog::setResizeOn(int column)
{
resizeOnColumn = column;
}
int VersionSelectDialog::exec()
{
QDialog::open();
m_versionWidget->initialize(m_vlist);
if(resizeOnColumn != -1)
{
m_versionWidget->setResizeOn(resizeOnColumn);
}
return QDialog::exec();
}
void VersionSelectDialog::selectRecommended()
{
m_versionWidget->selectRecommended();
}
BaseVersionPtr VersionSelectDialog::selectedVersion() const
{
return m_versionWidget->selectedVersion();
}
void VersionSelectDialog::on_refreshButton_clicked()
{
m_versionWidget->loadList();
}
void VersionSelectDialog::setExactFilter(BaseVersionList::ModelRoles role, QString filter)
{
m_versionWidget->setExactFilter(role, filter);
}
void VersionSelectDialog::setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter)
{
m_versionWidget->setFuzzyFilter(role, filter);
}

View File

@ -0,0 +1,78 @@
/* 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 <QDialog>
#include <QSortFilterProxyModel>
#include "BaseVersionList.h"
class QVBoxLayout;
class QHBoxLayout;
class QDialogButtonBox;
class VersionSelectWidget;
class QPushButton;
namespace Ui
{
class VersionSelectDialog;
}
class VersionProxyModel;
class VersionSelectDialog : public QDialog
{
Q_OBJECT
public:
explicit VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent = 0, bool cancelable = true);
virtual ~VersionSelectDialog() {};
int exec() override;
BaseVersionPtr selectedVersion() const;
void setCurrentVersion(const QString & version);
void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter);
void setExactFilter(BaseVersionList::ModelRoles role, QString filter);
void setEmptyString(QString emptyString);
void setEmptyErrorString(QString emptyErrorString);
void setResizeOn(int column);
private slots:
void on_refreshButton_clicked();
private:
void retranslate();
void selectRecommended();
private:
QString m_currentVersion;
VersionSelectWidget *m_versionWidget = nullptr;
QVBoxLayout *m_verticalLayout = nullptr;
QHBoxLayout *m_horizontalLayout = nullptr;
QPushButton *m_refreshButton = nullptr;
QDialogButtonBox *m_buttonBox = nullptr;
BaseVersionList *m_vlist = nullptr;
VersionProxyModel *m_proxyModel = nullptr;
int resizeOnColumn = -1;
Task * loadTask = nullptr;
};