aboutsummaryrefslogtreecommitdiff
path: root/gui
diff options
context:
space:
mode:
Diffstat (limited to 'gui')
-rw-r--r--gui/ConsoleWindow.cpp280
-rw-r--r--gui/ConsoleWindow.h94
-rw-r--r--gui/ConsoleWindow.ui86
-rw-r--r--gui/MainWindow.cpp1501
-rw-r--r--gui/MainWindow.h211
-rw-r--r--gui/MainWindow.ui539
-rw-r--r--gui/Platform.h32
-rw-r--r--gui/Platform_Other.cpp27
-rw-r--r--gui/Platform_X11.cpp62
-rw-r--r--gui/dialogs/AboutDialog.cpp54
-rw-r--r--gui/dialogs/AboutDialog.h35
-rw-r--r--gui/dialogs/AboutDialog.ui543
-rw-r--r--gui/dialogs/AccountListDialog.cpp160
-rw-r--r--gui/dialogs/AccountListDialog.h65
-rw-r--r--gui/dialogs/AccountListDialog.ui96
-rw-r--r--gui/dialogs/AccountSelectDialog.cpp84
-rw-r--r--gui/dialogs/AccountSelectDialog.h90
-rw-r--r--gui/dialogs/AccountSelectDialog.ui56
-rw-r--r--gui/dialogs/CopyInstanceDialog.cpp84
-rw-r--r--gui/dialogs/CopyInstanceDialog.h50
-rw-r--r--gui/dialogs/CopyInstanceDialog.ui134
-rw-r--r--gui/dialogs/CustomMessageBox.cpp34
-rw-r--r--gui/dialogs/CustomMessageBox.h26
-rw-r--r--gui/dialogs/EditAccountDialog.cpp51
-rw-r--r--gui/dialogs/EditAccountDialog.h60
-rw-r--r--gui/dialogs/EditAccountDialog.ui94
-rw-r--r--gui/dialogs/EditNotesDialog.cpp42
-rw-r--r--gui/dialogs/EditNotesDialog.h38
-rw-r--r--gui/dialogs/EditNotesDialog.ui77
-rw-r--r--gui/dialogs/IconPickerDialog.cpp156
-rw-r--r--gui/dialogs/IconPickerDialog.h48
-rw-r--r--gui/dialogs/IconPickerDialog.ui67
-rw-r--r--gui/dialogs/InstanceSettings.cpp243
-rw-r--r--gui/dialogs/InstanceSettings.h60
-rw-r--r--gui/dialogs/InstanceSettings.ui419
-rw-r--r--gui/dialogs/LegacyModEditDialog.cpp393
-rw-r--r--gui/dialogs/LegacyModEditDialog.h78
-rw-r--r--gui/dialogs/LegacyModEditDialog.ui321
-rw-r--r--gui/dialogs/LwjglSelectDialog.cpp72
-rw-r--r--gui/dialogs/LwjglSelectDialog.h44
-rw-r--r--gui/dialogs/LwjglSelectDialog.ui85
-rw-r--r--gui/dialogs/ModEditDialogCommon.cpp57
-rw-r--r--gui/dialogs/ModEditDialogCommon.h22
-rw-r--r--gui/dialogs/NewInstanceDialog.cpp125
-rw-r--r--gui/dialogs/NewInstanceDialog.h55
-rw-r--r--gui/dialogs/NewInstanceDialog.ui179
-rw-r--r--gui/dialogs/OneSixModEditDialog.cpp367
-rw-r--r--gui/dialogs/OneSixModEditDialog.h69
-rw-r--r--gui/dialogs/OneSixModEditDialog.ui340
-rw-r--r--gui/dialogs/ProgressDialog.cpp125
-rw-r--r--gui/dialogs/ProgressDialog.h64
-rw-r--r--gui/dialogs/ProgressDialog.ui66
-rw-r--r--gui/dialogs/SettingsDialog.cpp505
-rw-r--r--gui/dialogs/SettingsDialog.h99
-rw-r--r--gui/dialogs/SettingsDialog.ui944
-rw-r--r--gui/dialogs/UpdateDialog.cpp28
-rw-r--r--gui/dialogs/UpdateDialog.h46
-rw-r--r--gui/dialogs/UpdateDialog.ui70
-rw-r--r--gui/dialogs/VersionSelectDialog.cpp116
-rw-r--r--gui/dialogs/VersionSelectDialog.h62
-rw-r--r--gui/dialogs/VersionSelectDialog.ui110
-rw-r--r--gui/widgets/Common.cpp27
-rw-r--r--gui/widgets/Common.h6
-rw-r--r--gui/widgets/InstanceDelegate.cpp231
-rw-r--r--gui/widgets/InstanceDelegate.h27
-rw-r--r--gui/widgets/LabeledToolButton.cpp86
-rw-r--r--gui/widgets/LabeledToolButton.h37
-rw-r--r--gui/widgets/MCModInfoFrame.cpp111
-rw-r--r--gui/widgets/MCModInfoFrame.h46
-rw-r--r--gui/widgets/MCModInfoFrame.ui68
-rw-r--r--gui/widgets/ModListView.cpp62
-rw-r--r--gui/widgets/ModListView.h27
-rw-r--r--gui/widgets/VersionListView.cpp150
-rw-r--r--gui/widgets/VersionListView.h43
74 files changed, 11061 insertions, 0 deletions
diff --git a/gui/ConsoleWindow.cpp b/gui/ConsoleWindow.cpp
new file mode 100644
index 00000000..dc36a8ff
--- /dev/null
+++ b/gui/ConsoleWindow.cpp
@@ -0,0 +1,280 @@
+/* Copyright 2013 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 "ConsoleWindow.h"
+#include "ui_ConsoleWindow.h"
+#include "MultiMC.h"
+
+#include <QScrollBar>
+#include <QMessageBox>
+#include <QSystemTrayIcon>
+
+#include <gui/Platform.h>
+#include <gui/dialogs/CustomMessageBox.h>
+#include <gui/dialogs/ProgressDialog.h>
+
+#include "logic/net/PasteUpload.h"
+#include "logic/icons/IconList.h"
+
+ConsoleWindow::ConsoleWindow(MinecraftProcess *mcproc, QWidget *parent)
+ : QMainWindow(parent), ui(new Ui::ConsoleWindow), proc(mcproc)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+ connect(mcproc, SIGNAL(log(QString, MessageLevel::Enum)), this,
+ SLOT(write(QString, MessageLevel::Enum)));
+ connect(mcproc, SIGNAL(ended(BaseInstance *, int, QProcess::ExitStatus)), this,
+ SLOT(onEnded(BaseInstance *, int, QProcess::ExitStatus)));
+ connect(mcproc, SIGNAL(prelaunch_failed(BaseInstance *, int, QProcess::ExitStatus)), this,
+ SLOT(onEnded(BaseInstance *, int, QProcess::ExitStatus)));
+ connect(mcproc, SIGNAL(launch_failed(BaseInstance *)), this,
+ SLOT(onLaunchFailed(BaseInstance *)));
+
+ restoreState(
+ QByteArray::fromBase64(MMC->settings()->get("ConsoleWindowState").toByteArray()));
+ restoreGeometry(
+ QByteArray::fromBase64(MMC->settings()->get("ConsoleWindowGeometry").toByteArray()));
+
+ QString iconKey = proc->instance()->iconKey();
+ QString name = proc->instance()->name();
+ auto icon = MMC->icons()->getIcon(iconKey);
+ setWindowIcon(icon);
+ m_trayIcon = new QSystemTrayIcon(icon, this);
+ QString consoleTitle = tr("Console window for ") + name;
+ m_trayIcon->setToolTip(consoleTitle);
+ setWindowTitle(consoleTitle);
+
+ connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
+ SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
+ m_trayIcon->show();
+ if (mcproc->instance()->settings().get("ShowConsole").toBool())
+ {
+ show();
+ }
+ setMayClose(false);
+}
+
+ConsoleWindow::~ConsoleWindow()
+{
+ delete ui;
+}
+
+void ConsoleWindow::iconActivated(QSystemTrayIcon::ActivationReason reason)
+{
+ switch (reason)
+ {
+ case QSystemTrayIcon::Trigger:
+ {
+ toggleConsole();
+ }
+ default:
+ return;
+ }
+}
+
+void ConsoleWindow::writeColor(QString text, const char *color)
+{
+ // append a paragraph
+ QString newtext;
+ newtext += "<span style=\"";
+ {
+ if (color)
+ newtext += QString("color:") + color + ";";
+ newtext += "font-family: monospace;";
+ }
+ newtext += "\">";
+ newtext += text.toHtmlEscaped();
+ newtext += "</span>";
+ ui->text->appendHtml(newtext);
+}
+
+void ConsoleWindow::write(QString data, MessageLevel::Enum mode)
+{
+ QScrollBar *bar = ui->text->verticalScrollBar();
+ int max_bar = bar->maximum();
+ int val_bar = bar->value();
+ if(isVisible())
+ {
+ if (m_scroll_active)
+ {
+ m_scroll_active = (max_bar - val_bar) <= 1;
+ }
+ else
+ {
+ m_scroll_active = val_bar == max_bar;
+ }
+ }
+ if (data.endsWith('\n'))
+ data = data.left(data.length() - 1);
+ QStringList paragraphs = data.split('\n');
+ for (QString &paragraph : paragraphs)
+ {
+ paragraph = paragraph.trimmed();
+ }
+
+ QListIterator<QString> iter(paragraphs);
+ if (mode == MessageLevel::MultiMC)
+ while (iter.hasNext())
+ writeColor(iter.next(), "blue");
+ else if (mode == MessageLevel::Error)
+ while (iter.hasNext())
+ writeColor(iter.next(), "red");
+ else if (mode == MessageLevel::Warning)
+ while (iter.hasNext())
+ writeColor(iter.next(), "orange");
+ else if (mode == MessageLevel::Fatal)
+ while (iter.hasNext())
+ writeColor(iter.next(), "pink");
+ else if (mode == MessageLevel::Debug)
+ while (iter.hasNext())
+ writeColor(iter.next(), "green");
+ else if (mode == MessageLevel::PrePost)
+ while (iter.hasNext())
+ writeColor(iter.next(), "grey");
+ // TODO: implement other MessageLevels
+ else
+ while (iter.hasNext())
+ writeColor(iter.next());
+ if(isVisible())
+ {
+ if (m_scroll_active)
+ {
+ bar->setValue(bar->maximum());
+ }
+ m_last_scroll_value = bar->value();
+ }
+}
+
+void ConsoleWindow::clear()
+{
+ ui->text->clear();
+}
+
+void ConsoleWindow::on_closeButton_clicked()
+{
+ close();
+}
+
+void ConsoleWindow::setMayClose(bool mayclose)
+{
+ if(mayclose)
+ ui->closeButton->setText(tr("Close"));
+ else
+ ui->closeButton->setText(tr("Hide"));
+ m_mayclose = mayclose;
+}
+
+void ConsoleWindow::toggleConsole()
+{
+ QScrollBar *bar = ui->text->verticalScrollBar();
+ if (isVisible())
+ {
+ int max_bar = bar->maximum();
+ int val_bar = m_last_scroll_value = bar->value();
+ m_scroll_active = (max_bar - val_bar) <= 1;
+ hide();
+ }
+ else
+ {
+ show();
+ if (m_scroll_active)
+ {
+ bar->setValue(bar->maximum());
+ }
+ else
+ {
+ bar->setValue(m_last_scroll_value);
+ }
+ }
+}
+
+void ConsoleWindow::closeEvent(QCloseEvent *event)
+{
+ if (!m_mayclose)
+ {
+ toggleConsole();
+ }
+ else
+ {
+ MMC->settings()->set("ConsoleWindowState", saveState().toBase64());
+ MMC->settings()->set("ConsoleWindowGeometry", saveGeometry().toBase64());
+
+ emit isClosing();
+ m_trayIcon->hide();
+ QMainWindow::closeEvent(event);
+ }
+}
+
+void ConsoleWindow::on_btnKillMinecraft_clicked()
+{
+ ui->btnKillMinecraft->setEnabled(false);
+ auto response = CustomMessageBox::selectable(
+ this, tr("Kill Minecraft?"),
+ tr("This can cause the instance to get corrupted and should only be used if Minecraft "
+ "is frozen for some reason"),
+ QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec();
+ if (response == QMessageBox::Yes)
+ proc->killMinecraft();
+ else
+ ui->btnKillMinecraft->setEnabled(true);
+}
+
+void ConsoleWindow::onEnded(BaseInstance *instance, int code, QProcess::ExitStatus status)
+{
+ bool peacefulExit = code == 0 && status != QProcess::CrashExit;
+ ui->btnKillMinecraft->setEnabled(false);
+
+ setMayClose(true);
+
+ if (instance->settings().get("AutoCloseConsole").toBool())
+ {
+ if (peacefulExit)
+ {
+ this->close();
+ return;
+ }
+ }
+ /*
+ if(!peacefulExit)
+ {
+ m_trayIcon->showMessage(tr("Oh no!"), tr("Minecraft crashed!"), QSystemTrayIcon::Critical);
+ }
+ */
+ if (!isVisible())
+ show();
+}
+
+void ConsoleWindow::onLaunchFailed(BaseInstance *instance)
+{
+ ui->btnKillMinecraft->setEnabled(false);
+
+ setMayClose(true);
+
+ if (!isVisible())
+ show();
+}
+
+void ConsoleWindow::on_btnPaste_clicked()
+{
+ auto text = ui->text->toPlainText();
+ ProgressDialog dialog(this);
+ PasteUpload *paste = new PasteUpload(this, text);
+ dialog.exec(paste);
+ if (!paste->successful())
+ {
+ CustomMessageBox::selectable(this, "Upload failed", paste->failReason(),
+ QMessageBox::Critical)->exec();
+ }
+}
diff --git a/gui/ConsoleWindow.h b/gui/ConsoleWindow.h
new file mode 100644
index 00000000..9291320e
--- /dev/null
+++ b/gui/ConsoleWindow.h
@@ -0,0 +1,94 @@
+/* Copyright 2013 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 <QMainWindow>
+#include <QSystemTrayIcon>
+#include "logic/MinecraftProcess.h"
+
+namespace Ui
+{
+class ConsoleWindow;
+}
+
+class ConsoleWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit ConsoleWindow(MinecraftProcess *proc, QWidget *parent = 0);
+ ~ConsoleWindow();
+
+ /**
+ * @brief specify if the window is allowed to close
+ * @param mayclose
+ * used to keep it alive while MC runs
+ */
+ void setMayClose(bool mayclose);
+
+private:
+ /**
+ * @brief write a colored paragraph
+ * @param data the string
+ * @param color the css color name
+ * this will only insert a single paragraph.
+ * \n are ignored. a real \n is always appended.
+ */
+ void writeColor(QString data, const char *color = nullptr);
+
+signals:
+ void isClosing();
+
+public
+slots:
+ /**
+ * @brief write a string
+ * @param data the string
+ * @param mode the WriteMode
+ * lines have to be put through this as a whole!
+ */
+ void write(QString data, MessageLevel::Enum level = MessageLevel::MultiMC);
+
+ /**
+ * @brief clear the text widget
+ */
+ void clear();
+
+private
+slots:
+ void on_closeButton_clicked();
+ void on_btnKillMinecraft_clicked();
+ void onEnded(BaseInstance *instance, int code, QProcess::ExitStatus status);
+ void onLaunchFailed(BaseInstance *instance);
+
+ // FIXME: add handlers for the other MinecraftProcess signals (pre/post launch command
+ // failures)
+
+ void on_btnPaste_clicked();
+ void iconActivated(QSystemTrayIcon::ActivationReason);
+ void toggleConsole();
+protected:
+ void closeEvent(QCloseEvent *);
+
+private:
+ Ui::ConsoleWindow *ui = nullptr;
+ MinecraftProcess *proc = nullptr;
+ bool m_mayclose = true;
+ int m_last_scroll_value = 0;
+ bool m_scroll_active = true;
+ QSystemTrayIcon *m_trayIcon = nullptr;
+ int m_saved_offset = 0;
+};
diff --git a/gui/ConsoleWindow.ui b/gui/ConsoleWindow.ui
new file mode 100644
index 00000000..c2307ecc
--- /dev/null
+++ b/gui/ConsoleWindow.ui
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConsoleWindow</class>
+ <widget class="QMainWindow" name="ConsoleWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>640</width>
+ <height>440</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MultiMC Console</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QPlainTextEdit" name="text">
+ <property name="undoRedoEnabled">
+ <bool>false</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="plainText">
+ <string notr="true"/>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </property>
+ <property name="centerOnScroll">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="btnPaste">
+ <property name="text">
+ <string>Upload Log</string>
+ </property>
+ </widget>
+ </item>
+ <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="btnKillMinecraft">
+ <property name="text">
+ <string>&amp;Kill Minecraft</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="closeButton">
+ <property name="text">
+ <string>&amp;Close</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp
new file mode 100644
index 00000000..9977dc75
--- /dev/null
+++ b/gui/MainWindow.cpp
@@ -0,0 +1,1501 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Authors: Andrew Okin
+ * Peterix
+ * Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * 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 "MultiMC.h"
+
+#include "MainWindow.h"
+#include "ui_MainWindow.h"
+
+#include <QMenu>
+#include <QMessageBox>
+#include <QInputDialog>
+
+#include <QDesktopServices>
+#include <QUrl>
+#include <QDir>
+#include <QFileInfo>
+#include <QLabel>
+#include <QToolButton>
+#include <QWidgetAction>
+
+#include "osutils.h"
+#include "userutils.h"
+#include "pathutils.h"
+
+#include "categorizedview.h"
+#include "categorydrawer.h"
+
+#include "gui/Platform.h"
+
+#include "gui/widgets/InstanceDelegate.h"
+#include "gui/widgets/LabeledToolButton.h"
+
+#include "gui/dialogs/SettingsDialog.h"
+#include "gui/dialogs/NewInstanceDialog.h"
+#include "gui/dialogs/ProgressDialog.h"
+#include "gui/dialogs/AboutDialog.h"
+#include "gui/dialogs/VersionSelectDialog.h"
+#include "gui/dialogs/CustomMessageBox.h"
+#include "gui/dialogs/LwjglSelectDialog.h"
+#include "gui/dialogs/InstanceSettings.h"
+#include "gui/dialogs/IconPickerDialog.h"
+#include "gui/dialogs/EditNotesDialog.h"
+#include "gui/dialogs/CopyInstanceDialog.h"
+#include "gui/dialogs/AccountListDialog.h"
+#include "gui/dialogs/AccountSelectDialog.h"
+#include "gui/dialogs/UpdateDialog.h"
+#include "gui/dialogs/EditAccountDialog.h"
+
+#include "gui/ConsoleWindow.h"
+
+#include "logic/lists/InstanceList.h"
+#include "logic/lists/MinecraftVersionList.h"
+#include "logic/lists/LwjglVersionList.h"
+#include "logic/icons/IconList.h"
+#include "logic/lists/JavaVersionList.h"
+
+#include "logic/auth/flows/AuthenticateTask.h"
+#include "logic/auth/flows/RefreshTask.h"
+
+#include "logic/updater/DownloadUpdateTask.h"
+
+#include "logic/news/NewsChecker.h"
+
+#include "logic/status/StatusChecker.h"
+
+#include "logic/net/URLConstants.h"
+
+#include "logic/BaseInstance.h"
+#include "logic/InstanceFactory.h"
+#include "logic/MinecraftProcess.h"
+#include "logic/OneSixUpdate.h"
+#include "logic/JavaUtils.h"
+#include "logic/NagUtils.h"
+#include "logic/SkinUtils.h"
+
+#include "logic/LegacyInstance.h"
+
+#include "logic/assets/AssetsUtils.h"
+#include "logic/assets/AssetsMigrateTask.h"
+#include <logic/updater/UpdateChecker.h>
+#include <logic/updater/NotificationChecker.h>
+#include <logic/tasks/ThreadTask.h>
+
+MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+
+ QString winTitle = QString("MultiMC 5 - Version %1").arg(MMC->version().toString());
+ if (!MMC->version().platform.isEmpty())
+ winTitle += " on " + MMC->version().platform;
+ setWindowTitle(winTitle);
+
+ // OSX magic.
+ // setUnifiedTitleAndToolBarOnMac(true);
+
+ // The instance action toolbar customizations
+ {
+ // disabled until we have an instance selected
+ ui->instanceToolBar->setEnabled(false);
+
+ // the rename label is inside the rename tool button
+ renameButton = new LabeledToolButton();
+ renameButton->setText("Instance Name");
+ renameButton->setToolTip(ui->actionRenameInstance->toolTip());
+ connect(renameButton, SIGNAL(clicked(bool)), SLOT(on_actionRenameInstance_triggered()));
+ ui->instanceToolBar->insertWidget(ui->actionLaunchInstance, renameButton);
+ ui->instanceToolBar->insertSeparator(ui->actionLaunchInstance);
+ renameButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ }
+
+ // Add the news label to the news toolbar.
+ {
+ newsLabel = new QToolButton();
+ newsLabel->setIcon(QIcon::fromTheme("news"));
+ newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel);
+ QObject::connect(newsLabel, &QAbstractButton::clicked, this,
+ &MainWindow::newsButtonClicked);
+ QObject::connect(MMC->newsChecker().get(), &NewsChecker::newsLoaded, this,
+ &MainWindow::updateNewsLabel);
+ updateNewsLabel();
+ }
+
+ // Create the instance list widget
+ {
+ view = new KCategorizedView(ui->centralWidget);
+ drawer = new KCategoryDrawer(view);
+
+ view->setSelectionMode(QAbstractItemView::SingleSelection);
+ view->setCategoryDrawer(drawer);
+ view->setCollapsibleBlocks(true);
+ view->setViewMode(QListView::IconMode);
+ view->setFlow(QListView::LeftToRight);
+ view->setWordWrap(true);
+ view->setMouseTracking(true);
+ view->viewport()->setAttribute(Qt::WA_Hover);
+ auto delegate = new ListViewDelegate();
+ view->setItemDelegate(delegate);
+ view->setSpacing(10);
+ view->setUniformItemWidths(true);
+
+ // do not show ugly blue border on the mac
+ view->setAttribute(Qt::WA_MacShowFocusRect, false);
+
+ view->installEventFilter(this);
+
+ proxymodel = new InstanceProxyModel(this);
+ // proxymodel->setSortRole(KCategorizedSortFilterProxyModel::CategorySortRole);
+ // proxymodel->setFilterRole(KCategorizedSortFilterProxyModel::CategorySortRole);
+ // proxymodel->setDynamicSortFilter ( true );
+
+ // FIXME: instList should be global-ish, or at least not tied to the main window...
+ // maybe the application itself?
+ proxymodel->setSourceModel(MMC->instances().get());
+ proxymodel->sort(0);
+ view->setFrameShape(QFrame::NoFrame);
+ view->setModel(proxymodel);
+
+ view->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(view, SIGNAL(customContextMenuRequested(const QPoint &)), this,
+ SLOT(showInstanceContextMenu(const QPoint &)));
+
+ ui->horizontalLayout->addWidget(view);
+ }
+ // The cat background
+ {
+ bool cat_enable = MMC->settings()->get("TheCat").toBool();
+ ui->actionCAT->setChecked(cat_enable);
+ connect(ui->actionCAT, SIGNAL(toggled(bool)), SLOT(onCatToggled(bool)));
+ setCatBackground(cat_enable);
+ }
+ // start instance when double-clicked
+ connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this,
+ SLOT(instanceActivated(const QModelIndex &)));
+ // track the selection -- update the instance toolbar
+ connect(view->selectionModel(),
+ SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this,
+ SLOT(instanceChanged(const QModelIndex &, const QModelIndex &)));
+
+ // track icon changes and update the toolbar!
+ connect(MMC->icons().get(), SIGNAL(iconUpdated(QString)), SLOT(iconUpdated(QString)));
+
+ // model reset -> selection is invalid. All the instance pointers are wrong.
+ // FIXME: stop using POINTERS everywhere
+ connect(MMC->instances().get(), SIGNAL(dataIsInvalid()), SLOT(selectionBad()));
+
+ m_statusLeft = new QLabel(tr("No instance selected"), this);
+ m_statusRight = new QLabel(tr("No status available"), this);
+ m_statusRefresh = new QToolButton(this);
+ m_statusRefresh->setCheckable(true);
+ m_statusRefresh->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ m_statusRefresh->setIcon(QIcon::fromTheme("refresh"));
+
+ statusBar()->addPermanentWidget(m_statusLeft, 1);
+ statusBar()->addPermanentWidget(m_statusRight, 0);
+ statusBar()->addPermanentWidget(m_statusRefresh, 0);
+
+ // Start status checker
+ {
+ connect(MMC->statusChecker().get(), &StatusChecker::statusLoaded, this,
+ &MainWindow::updateStatusUI);
+ connect(MMC->statusChecker().get(), &StatusChecker::statusLoadingFailed, this,
+ &MainWindow::updateStatusFailedUI);
+
+ connect(m_statusRefresh, &QAbstractButton::clicked, this, &MainWindow::reloadStatus);
+ connect(&statusTimer, &QTimer::timeout, this, &MainWindow::reloadStatus);
+ statusTimer.setSingleShot(true);
+
+ reloadStatus();
+ }
+
+ // Add "manage accounts" button, right align
+ QWidget *spacer = new QWidget();
+ spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ ui->mainToolBar->addWidget(spacer);
+
+ accountMenu = new QMenu(this);
+ manageAccountsAction = new QAction(tr("Manage Accounts"), this);
+ manageAccountsAction->setCheckable(false);
+ connect(manageAccountsAction, SIGNAL(triggered(bool)), this,
+ SLOT(on_actionManageAccounts_triggered()));
+
+ repopulateAccountsMenu();
+
+ accountMenuButton = new QToolButton(this);
+ accountMenuButton->setText(tr("Accounts"));
+ accountMenuButton->setMenu(accountMenu);
+ accountMenuButton->setPopupMode(QToolButton::InstantPopup);
+ accountMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ accountMenuButton->setIcon(QIcon::fromTheme("noaccount"));
+
+ QWidgetAction *accountMenuButtonAction = new QWidgetAction(this);
+ accountMenuButtonAction->setDefaultWidget(accountMenuButton);
+
+ ui->mainToolBar->addAction(accountMenuButtonAction);
+
+ // Update the menu when the active account changes.
+ // Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit.
+ // Template hell sucks...
+ connect(MMC->accounts().get(), &MojangAccountList::activeAccountChanged, [this]
+ { activeAccountChanged(); });
+ connect(MMC->accounts().get(), &MojangAccountList::listChanged, [this]
+ { repopulateAccountsMenu(); });
+
+ // Show initial account
+ activeAccountChanged();
+
+ auto accounts = MMC->accounts();
+
+ // TODO: Nicer way to iterate?
+ for (int i = 0; i < accounts->count(); i++)
+ {
+ auto account = accounts->at(i);
+ if (account != nullptr)
+ {
+ auto job = new NetJob("Startup player skins: " + account->username());
+
+ for (auto profile : account->profiles())
+ {
+ auto meta = MMC->metacache()->resolveEntry("skins", profile.name + ".png");
+ auto action = CacheDownload::make(
+ QUrl("http://" + URLConstants::SKINS_BASE + profile.name + ".png"), meta);
+ job->addNetAction(action);
+ meta->stale = true;
+ }
+
+ connect(job, SIGNAL(succeeded()), SLOT(activeAccountChanged()));
+ job->start();
+ }
+ }
+
+ // run the things that load and download other things... FIXME: this is NOT the place
+ // FIXME: invisible actions in the background = NOPE.
+ {
+ if (!MMC->minecraftlist()->isLoaded())
+ {
+ m_versionLoadTask = MMC->minecraftlist()->getLoadTask();
+ startTask(m_versionLoadTask);
+ }
+ if (!MMC->lwjgllist()->isLoaded())
+ {
+ MMC->lwjgllist()->loadList();
+ }
+
+ MMC->newsChecker()->reloadNews();
+ updateNewsLabel();
+
+ // set up the updater object.
+ auto updater = MMC->updateChecker();
+ connect(updater.get(), &UpdateChecker::updateAvailable, this,
+ &MainWindow::updateAvailable);
+ connect(updater.get(), &UpdateChecker::noUpdateFound, [this]()
+ {
+ CustomMessageBox::selectable(
+ this, tr("No update found."),
+ tr("No MultiMC update was found!\nYou are using the latest version."))->exec();
+ });
+ // if automatic update checks are allowed, start one.
+ if (MMC->settings()->get("AutoUpdate").toBool())
+ on_actionCheckUpdate_triggered();
+
+ connect(MMC->notificationChecker().get(),
+ &NotificationChecker::notificationCheckFinished, this,
+ &MainWindow::notificationsChanged);
+ }
+
+ setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString());
+
+ // removing this looks stupid
+ view->setFocus();
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+ delete proxymodel;
+ delete drawer;
+}
+
+void MainWindow::showInstanceContextMenu(const QPoint &pos)
+{
+ if (!view->indexAt(pos).isValid())
+ {
+ return;
+ }
+
+ QList<QAction *> actions = ui->instanceToolBar->actions();
+
+ // HACK: Filthy rename button hack because the instance view is getting rewritten anyway
+ QAction *actionRename;
+ actionRename = new QAction(tr("Rename"), this);
+ actionRename->setToolTip(ui->actionRenameInstance->toolTip());
+
+ connect(actionRename, SIGNAL(triggered(bool)), SLOT(on_actionRenameInstance_triggered()));
+
+ actions.replace(1, actionRename);
+
+ QMenu myMenu;
+ myMenu.addActions(actions);
+ myMenu.exec(view->mapToGlobal(pos));
+}
+
+void MainWindow::repopulateAccountsMenu()
+{
+ accountMenu->clear();
+
+ std::shared_ptr<MojangAccountList> accounts = MMC->accounts();
+ MojangAccountPtr active_account = accounts->activeAccount();
+
+ QString active_username = "";
+ if (active_account != nullptr)
+ {
+ active_username = accounts->activeAccount()->username();
+ }
+
+ if (accounts->count() <= 0)
+ {
+ QAction *action = new QAction(tr("No accounts added!"), this);
+ action->setEnabled(false);
+ accountMenu->addAction(action);
+
+ accountMenu->addSeparator();
+ }
+ else
+ {
+ // TODO: Nicer way to iterate?
+ for (int i = 0; i < accounts->count(); i++)
+ {
+ MojangAccountPtr account = accounts->at(i);
+
+ // Styling hack
+ QAction *section = new QAction(account->username(), this);
+ section->setEnabled(false);
+ accountMenu->addAction(section);
+
+ for (auto profile : account->profiles())
+ {
+ QAction *action = new QAction(profile.name, this);
+ action->setData(account->username());
+ action->setCheckable(true);
+ if (active_username == account->username())
+ {
+ action->setChecked(true);
+ }
+
+ action->setIcon(SkinUtils::getFaceFromCache(profile.name));
+ accountMenu->addAction(action);
+ connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount()));
+ }
+
+ accountMenu->addSeparator();
+ }
+ }
+
+ QAction *action = new QAction(tr("No Default Account"), this);
+ action->setCheckable(true);
+ action->setIcon(QIcon::fromTheme("noaccount"));
+ action->setData("");
+ if (active_username.isEmpty())
+ {
+ action->setChecked(true);
+ }
+
+ accountMenu->addAction(action);
+ connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount()));
+
+ accountMenu->addSeparator();
+ accountMenu->addAction(manageAccountsAction);
+}
+
+/*
+ * Assumes the sender is a QAction
+ */
+void MainWindow::changeActiveAccount()
+{
+ QAction *sAction = (QAction *)sender();
+ // Profile's associated Mojang username
+ // Will need to change when profiles are properly implemented
+ if (sAction->data().type() != QVariant::Type::String)
+ return;
+
+ QVariant data = sAction->data();
+ QString id = "";
+ if (!data.isNull())
+ {
+ id = data.toString();
+ }
+
+ MMC->accounts()->setActiveAccount(id);
+
+ activeAccountChanged();
+}
+
+void MainWindow::activeAccountChanged()
+{
+ repopulateAccountsMenu();
+
+ MojangAccountPtr account = MMC->accounts()->activeAccount();
+
+ if (account != nullptr && account->username() != "")
+ {
+ const AccountProfile *profile = account->currentProfile();
+ if (profile != nullptr)
+ {
+ accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->name));
+ return;
+ }
+ }
+
+ // Set the icon to the "no account" icon.
+ accountMenuButton->setIcon(QIcon::fromTheme("noaccount"));
+}
+
+bool MainWindow::eventFilter(QObject *obj, QEvent *ev)
+{
+ if (obj == view)
+ {
+ if (ev->type() == QEvent::KeyPress)
+ {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
+ switch (keyEvent->key())
+ {
+ case Qt::Key_Enter:
+ case Qt::Key_Return:
+ on_actionLaunchInstance_triggered();
+ return true;
+ case Qt::Key_Delete:
+ on_actionDeleteInstance_triggered();
+ return true;
+ case Qt::Key_F5:
+ on_actionRefresh_triggered();
+ return true;
+ case Qt::Key_F2:
+ on_actionRenameInstance_triggered();
+ return true;
+ default:
+ break;
+ }
+ }
+ }
+ return QMainWindow::eventFilter(obj, ev);
+}
+
+void MainWindow::updateNewsLabel()
+{
+ auto newsChecker = MMC->newsChecker();
+ if (newsChecker->isLoadingNews())
+ {
+ newsLabel->setText(tr("Loading news..."));
+ newsLabel->setEnabled(false);
+ }
+ else
+ {
+ QList<NewsEntryPtr> entries = newsChecker->getNewsEntries();
+ if (entries.length() > 0)
+ {
+ newsLabel->setText(entries[0]->title);
+ newsLabel->setEnabled(true);
+ }
+ else
+ {
+ newsLabel->setText(tr("No news available."));
+ newsLabel->setEnabled(false);
+ }
+ }
+}
+
+static QString convertStatus(const QString &status)
+{
+ QString ret = "?";
+
+ if (status == "green")
+ ret = "↑";
+ else if (status == "yellow")
+ ret = "-";
+ else if (status == "red")
+ ret = "↓";
+
+ return "<span style=\"font-size:11pt; font-weight:600;\">" + ret + "</span>";
+}
+
+void MainWindow::reloadStatus()
+{
+ m_statusRefresh->setChecked(true);
+ MMC->statusChecker()->reloadStatus();
+ // updateStatusUI();
+}
+
+static QString makeStatusString(const QMap<QString, QString> statuses)
+{
+ QString status = "";
+ status += "Web: " + convertStatus(statuses["minecraft.net"]);
+ status += " Account: " + convertStatus(statuses["account.mojang.com"]);
+ status += " Skins: " + convertStatus(statuses["skins.minecraft.net"]);
+ status += " Auth: " + convertStatus(statuses["authserver.mojang.com"]);
+ status += " Session: " + convertStatus(statuses["sessionserver.mojang.com"]);
+
+ return status;
+}
+
+void MainWindow::updateStatusUI()
+{
+ auto statusChecker = MMC->statusChecker();
+ auto statuses = statusChecker->getStatusEntries();
+
+ QString status = makeStatusString(statuses);
+ m_statusRefresh->setChecked(false);
+
+ m_statusRight->setText(status);
+
+ statusTimer.start(60 * 1000);
+}
+
+void MainWindow::updateStatusFailedUI()
+{
+ m_statusRight->setText(makeStatusString(QMap<QString, QString>()));
+ m_statusRefresh->setChecked(false);
+
+ statusTimer.start(60 * 1000);
+}
+
+void MainWindow::updateAvailable(QString repo, QString versionName, int versionId)
+{
+ UpdateDialog dlg;
+ UpdateAction action = (UpdateAction)dlg.exec();
+ switch (action)
+ {
+ case UPDATE_LATER:
+ QLOG_INFO() << "Update will be installed later.";
+ break;
+ case UPDATE_NOW:
+ downloadUpdates(repo, versionId);
+ break;
+ case UPDATE_ONEXIT:
+ downloadUpdates(repo, versionId, true);
+ break;
+ }
+}
+
+QList<int> stringToIntList(const QString &string)
+{
+ QStringList split = string.split(',', QString::SkipEmptyParts);
+ QList<int> out;
+ for (int i = 0; i < split.size(); ++i)
+ {
+ out.append(split.at(i).toInt());
+ }
+ return out;
+}
+QString intListToString(const QList<int> &list)
+{
+ QStringList slist;
+ for (int i = 0; i < list.size(); ++i)
+ {
+ slist.append(QString::number(list.at(i)));
+ }
+ return slist.join(',');
+}
+void MainWindow::notificationsChanged()
+{
+ QList<NotificationChecker::NotificationEntry> entries =
+ MMC->notificationChecker()->notificationEntries();
+ QList<int> shownNotifications =
+ stringToIntList(MMC->settings()->get("ShownNotifications").toString());
+ for (auto it = entries.begin(); it != entries.end(); ++it)
+ {
+ NotificationChecker::NotificationEntry entry = *it;
+ if (!shownNotifications.contains(entry.id) && entry.applies())
+ {
+ QMessageBox::Icon icon;
+ switch (entry.type)
+ {
+ case NotificationChecker::NotificationEntry::Critical:
+ icon = QMessageBox::Critical;
+ break;
+ case NotificationChecker::NotificationEntry::Warning:
+ icon = QMessageBox::Warning;
+ break;
+ case NotificationChecker::NotificationEntry::Information:
+ icon = QMessageBox::Information;
+ break;
+ }
+
+ QMessageBox box(icon, tr("Notification"), entry.message, QMessageBox::Close, this);
+ QPushButton *dontShowAgainButton =
+ box.addButton(tr("Don't show again"), QMessageBox::AcceptRole);
+ box.setDefaultButton(QMessageBox::Close);
+ box.exec();
+ if (box.clickedButton() == dontShowAgainButton)
+ {
+ shownNotifications.append(entry.id);
+ }
+ }
+ }
+ MMC->settings()->set("ShownNotifications", intListToString(shownNotifications));
+}
+
+void MainWindow::downloadUpdates(QString repo, int versionId, bool installOnExit)
+{
+ QLOG_INFO() << "Downloading updates.";
+ // TODO: If the user chooses to update on exit, we should download updates in the
+ // background.
+ // Doing so is a bit complicated, because we'd have to make sure it finished downloading
+ // before actually exiting MultiMC.
+ ProgressDialog updateDlg(this);
+ DownloadUpdateTask updateTask(repo, versionId, &updateDlg);
+ // If the task succeeds, install the updates.
+ if (updateDlg.exec(&updateTask))
+ {
+ UpdateFlags baseFlags = None;
+#ifdef MultiMC_UPDATER_DRY_RUN
+ baseFlags |= DryRun;
+#endif
+ if (installOnExit)
+ MMC->installUpdates(updateTask.updateFilesDir(), baseFlags | OnExit);
+ else
+ MMC->installUpdates(updateTask.updateFilesDir(), baseFlags | RestartOnFinish);
+ }
+}
+
+void MainWindow::onCatToggled(bool state)
+{
+ setCatBackground(state);
+ MMC->settings()->set("TheCat", state);
+}
+
+void MainWindow::setCatBackground(bool enabled)
+{
+ if (enabled)
+ {
+ view->setStyleSheet("QListView"
+ "{"
+ "background-image: url(:/backgrounds/kitteh);"
+ "background-attachment: fixed;"
+ "background-clip: padding;"
+ "background-position: top right;"
+ "background-repeat: none;"
+ "background-color:palette(base);"
+ "}");
+ }
+ else
+ {
+ view->setStyleSheet(QString());
+ }
+}
+
+void MainWindow::on_actionAddInstance_triggered()
+{
+ if (!MMC->minecraftlist()->isLoaded() && m_versionLoadTask &&
+ m_versionLoadTask->isRunning())
+ {
+ QEventLoop waitLoop;
+ waitLoop.connect(m_versionLoadTask, SIGNAL(failed(QString)), SLOT(quit()));
+ waitLoop.connect(m_versionLoadTask, SIGNAL(succeeded()), SLOT(quit()));
+ waitLoop.exec();
+ }
+
+ NewInstanceDialog newInstDlg(this);
+ if (!newInstDlg.exec())
+ return;
+
+ BaseInstance *newInstance = NULL;
+
+ QString instancesDir = MMC->settings()->get("InstanceDir").toString();
+ QString instDirName = DirNameFromString(newInstDlg.instName(), instancesDir);
+ QString instDir = PathCombine(instancesDir, instDirName);
+
+ auto &loader = InstanceFactory::get();
+
+ auto error = loader.createInstance(newInstance, newInstDlg.selectedVersion(), instDir);
+ QString errorMsg = QString("Failed to create instance %1: ").arg(instDirName);
+ switch (error)
+ {
+ case InstanceFactory::NoCreateError:
+ newInstance->setName(newInstDlg.instName());
+ newInstance->setIconKey(newInstDlg.iconKey());
+ MMC->instances()->add(InstancePtr(newInstance));
+ break;
+
+ case InstanceFactory::InstExists:
+ {
+ errorMsg += "An instance with the given directory name already exists.";
+ CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
+ return;
+ }
+
+ case InstanceFactory::CantCreateDir:
+ {
+ errorMsg += "Failed to create the instance directory.";
+ CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
+ return;
+ }
+
+ default:
+ {
+ errorMsg += QString("Unknown instance loader error %1").arg(error);
+ CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
+ return;
+ }
+ }
+
+ if (MMC->accounts()->anyAccountIsValid())
+ {
+ ProgressDialog loadDialog(this);
+ auto update = newInstance->doUpdate();
+ connect(update.get(), &Task::failed, [this](QString reason)
+ {
+ QString error = QString("Instance load failed: %1").arg(reason);
+ CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)
+ ->show();
+ });
+ loadDialog.exec(update.get());
+ }
+ else
+ {
+ CustomMessageBox::selectable(
+ this, tr("Error"),
+ tr("MultiMC cannot download Minecraft or update instances unless you have at least "
+ "one account added.\nPlease add your Mojang or Minecraft account."),
+ QMessageBox::Warning)->show();
+ }
+}
+
+void MainWindow::on_actionCopyInstance_triggered()
+{
+ if (!m_selectedInstance)
+ return;
+
+ CopyInstanceDialog copyInstDlg(m_selectedInstance, this);
+ if (!copyInstDlg.exec())
+ return;
+
+ QString instancesDir = MMC->settings()->get("InstanceDir").toString();
+ QString instDirName = DirNameFromString(copyInstDlg.instName(), instancesDir);
+ QString instDir = PathCombine(instancesDir, instDirName);
+
+ auto &loader = InstanceFactory::get();
+
+ BaseInstance *newInstance = NULL;
+ auto error = loader.copyInstance(newInstance, m_selectedInstance, instDir);
+
+ QString errorMsg = QString("Failed to create instance %1: ").arg(instDirName);
+ switch (error)
+ {
+ case InstanceFactory::NoCreateError:
+ newInstance->setName(copyInstDlg.instName());
+ newInstance->setIconKey(copyInstDlg.iconKey());
+ MMC->instances()->add(InstancePtr(newInstance));
+ return;
+
+ case InstanceFactory::InstExists:
+ {
+ errorMsg += "An instance with the given directory name already exists.";
+ CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
+ break;
+ }
+
+ case InstanceFactory::CantCreateDir:
+ {
+ errorMsg += "Failed to create the instance directory.";
+ CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
+ break;
+ }
+
+ default:
+ {
+ errorMsg += QString("Unknown instance loader error %1").arg(error);
+ CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
+ break;
+ }
+ }
+}
+
+void MainWindow::on_actionChangeInstIcon_triggered()
+{
+ if (!m_selectedInstance)
+ return;
+
+ IconPickerDialog dlg(this);
+ dlg.exec(m_selectedInstance->iconKey());
+ if (dlg.result() == QDialog::Accepted)
+ {
+ m_selectedInstance->setIconKey(dlg.selectedIconKey);
+ auto ico = MMC->icons()->getBigIcon(dlg.selectedIconKey);
+ ui->actionChangeInstIcon->setIcon(ico);
+ }
+}
+
+void MainWindow::iconUpdated(QString icon)
+{
+ if (icon == m_currentInstIcon)
+ {
+ ui->actionChangeInstIcon->setIcon(MMC->icons()->getBigIcon(m_currentInstIcon));
+ }
+}
+
+void MainWindow::updateInstanceToolIcon(QString new_icon)
+{
+ m_currentInstIcon = new_icon;
+ ui->actionChangeInstIcon->setIcon(MMC->icons()->getBigIcon(m_currentInstIcon));
+}
+
+void MainWindow::setSelectedInstanceById(const QString &id)
+{
+ QModelIndex selectionIndex = proxymodel->index(0, 0);
+ if (!id.isNull())
+ {
+ const QModelIndex index = MMC->instances()->getInstanceIndexById(id);
+ if (index.isValid())
+ {
+ selectionIndex = proxymodel->mapFromSource(index);
+ }
+ }
+ view->selectionModel()->setCurrentIndex(selectionIndex,
+ QItemSelectionModel::ClearAndSelect);
+}
+
+void MainWindow::on_actionChangeInstGroup_triggered()
+{
+ if (!m_selectedInstance)
+ return;
+
+ bool ok = false;
+ QString name(m_selectedInstance->group());
+ auto groups = MMC->instances()->getGroups();
+ groups.insert(0, "");
+ groups.sort(Qt::CaseInsensitive);
+ int foo = groups.indexOf(name);
+
+ name = QInputDialog::getItem(this, tr("Group name"), tr("Enter a new group name."), groups,
+ foo, true, &ok);
+ name = name.simplified();
+ if (ok)
+ m_selectedInstance->setGroupPost(name);
+}
+
+void MainWindow::on_actionViewInstanceFolder_triggered()
+{
+ QString str = MMC->settings()->get("InstanceDir").toString();
+ openDirInDefaultProgram(str);
+}
+
+void MainWindow::on_actionRefresh_triggered()
+{
+ MMC->instances()->loadList();
+}
+
+void MainWindow::on_actionViewCentralModsFolder_triggered()
+{
+ openDirInDefaultProgram(MMC->settings()->get("CentralModsDir").toString(), true);
+}
+
+void MainWindow::on_actionConfig_Folder_triggered()
+{
+ if (m_selectedInstance)
+ {
+ QString str = m_selectedInstance->instanceConfigFolder();
+ openDirInDefaultProgram(QDir(str).absolutePath());
+ }
+}
+
+void MainWindow::on_actionCheckUpdate_triggered()
+{
+ auto updater = MMC->updateChecker();
+
+ updater->checkForUpdate(true);
+}
+
+void MainWindow::on_actionSettings_triggered()
+{
+ SettingsDialog dialog(this);
+ dialog.exec();
+ // FIXME: quick HACK to make this work. improve, optimize.
+ proxymodel->invalidate();
+ proxymodel->sort(0);
+}
+
+void MainWindow::on_actionManageAccounts_triggered()
+{
+ AccountListDialog dialog(this);
+ dialog.exec();
+}
+
+void MainWindow::on_actionReportBug_triggered()
+{
+ openWebPage(QUrl("https://github.com/MultiMC/MultiMC5/issues"));
+}
+
+void MainWindow::on_actionMoreNews_triggered()
+{
+ openWebPage(QUrl("http://multimc.org/posts.html"));
+}
+
+void MainWindow::newsButtonClicked()
+{
+ QList<NewsEntryPtr> entries = MMC->newsChecker()->getNewsEntries();
+ if (entries.count() > 0)
+ openWebPage(QUrl(entries[0]->link));
+ else
+ openWebPage(QUrl("http://multimc.org/posts.html"));
+}
+
+void MainWindow::on_actionAbout_triggered()
+{
+ AboutDialog dialog(this);
+ dialog.exec();
+}
+
+void MainWindow::on_mainToolBar_visibilityChanged(bool)
+{
+ // Don't allow hiding the main toolbar.
+ // This is the only way I could find to prevent it... :/
+ ui->mainToolBar->setVisible(true);
+}
+
+void MainWindow::on_actionDeleteInstance_triggered()
+{
+ if (m_selectedInstance)
+ {
+ auto response = CustomMessageBox::selectable(
+ this, tr("CAREFUL"), tr("This is permanent! Are you sure?\nAbout to delete: ") +
+ m_selectedInstance->name(),
+ QMessageBox::Question, QMessageBox::Yes | QMessageBox::No)->exec();
+ if (response == QMessageBox::Yes)
+ {
+ m_selectedInstance->nuke();
+ }
+ }
+}
+
+void MainWindow::on_actionRenameInstance_triggered()
+{
+ if (m_selectedInstance)
+ {
+ bool ok = false;
+ QString name(m_selectedInstance->name());
+ name =
+ QInputDialog::getText(this, tr("Instance name"), tr("Enter a new instance name."),
+ QLineEdit::Normal, name, &ok);
+
+ if (name.length() > 0)
+ {
+ if (ok && name.length())
+ {
+ m_selectedInstance->setName(name);
+ renameButton->setText(name);
+ }
+ }
+ }
+}
+
+void MainWindow::on_actionViewSelectedInstFolder_triggered()
+{
+ if (m_selectedInstance)
+ {
+ QString str = m_selectedInstance->instanceRoot();
+ openDirInDefaultProgram(QDir(str).absolutePath());
+ }
+}
+
+void MainWindow::on_actionEditInstMods_triggered()
+{
+ if (m_selectedInstance)
+ {
+ auto dialog = m_selectedInstance->createModEditDialog(this);
+ if (dialog)
+ dialog->exec();
+ dialog->deleteLater();
+ }
+}
+
+void MainWindow::closeEvent(QCloseEvent *event)
+{
+ // Save the window state and geometry.
+
+ MMC->settings()->set("MainWindowState", saveState().toBase64());
+ MMC->settings()->set("MainWindowGeometry", saveGeometry().toBase64());
+
+ QMainWindow::closeEvent(event);
+ QApplication::exit();
+}
+/*
+void MainWindow::on_instanceView_customContextMenuRequested(const QPoint &pos)
+{
+ QMenu *instContextMenu = new QMenu("Instance", this);
+
+ // Add the actions from the toolbar to the context menu.
+ instContextMenu->addActions(ui->instanceToolBar->actions());
+
+ instContextMenu->exec(view->mapToGlobal(pos));
+}
+*/
+void MainWindow::instanceActivated(QModelIndex index)
+{
+ if (!index.isValid())
+ return;
+
+ BaseInstance *inst =
+ (BaseInstance *)index.data(InstanceList::InstancePointerRole).value<void *>();
+
+ NagUtils::checkJVMArgs(inst->settings().get("JvmArgs").toString(), this);
+
+ doLaunch();
+}
+
+void MainWindow::on_actionLaunchInstance_triggered()
+{
+ if (m_selectedInstance)
+ {
+ NagUtils::checkJVMArgs(m_selectedInstance->settings().get("JvmArgs").toString(), this);
+ doLaunch();
+ }
+}
+
+void MainWindow::on_actionLaunchInstanceOffline_triggered()
+{
+ if (m_selectedInstance)
+ {
+ NagUtils::checkJVMArgs(m_selectedInstance->settings().get("JvmArgs").toString(), this);
+ doLaunch(false);
+ }
+}
+
+void MainWindow::doLaunch(bool online)
+{
+ if (!m_selectedInstance)
+ return;
+
+ // Find an account to use.
+ std::shared_ptr<MojangAccountList> accounts = MMC->accounts();
+ MojangAccountPtr account = accounts->activeAccount();
+ if (accounts->count() <= 0)
+ {
+ // Tell the user they need to log in at least one account in order to play.
+ auto reply = CustomMessageBox::selectable(
+ this, tr("No Accounts"),
+ tr("In order to play Minecraft, you must have at least one Mojang or Minecraft "
+ "account logged in to MultiMC."
+ "Would you like to open the account manager to add an account now?"),
+ QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)->exec();
+
+ if (reply == QMessageBox::Yes)
+ {
+ // Open the account manager.
+ on_actionManageAccounts_triggered();
+ }
+ }
+ else if (account.get() == nullptr)
+ {
+ // If no default account is set, ask the user which one to use.
+ AccountSelectDialog selectDialog(tr("Which account would you like to use?"),
+ AccountSelectDialog::GlobalDefaultCheckbox, this);
+
+ selectDialog.exec();
+
+ // Launch the instance with the selected account.
+ account = selectDialog.selectedAccount();
+
+ // If the user said to use the account as default, do that.
+ if (selectDialog.useAsGlobalDefault() && account.get() != nullptr)
+ accounts->setActiveAccount(account->username());
+ }
+
+ // if no account is selected, we bail
+ if (!account.get())
+ return;
+
+ // we try empty password first :)
+ QString password;
+ // we loop until the user succeeds in logging in or gives up
+ bool tryagain = true;
+ // the failure. the default failure.
+ QString failReason = tr("Your account is currently not logged in. Please enter "
+ "your password to log in again.");
+
+ while (tryagain)
+ {
+ AuthSessionPtr session(new AuthSession());
+ session->wants_online = online;
+ auto task = account->login(session, password);
+ if (task)
+ {
+ // We'll need to validate the access token to make sure the account
+ // is still logged in.
+ ProgressDialog progDialog(this);
+ if (online)
+ progDialog.setSkipButton(true, tr("Play Offline"));
+ progDialog.exec(task.get());
+ if (!task->successful())
+ {
+ failReason = task->failReason();
+ }
+ }
+ switch (session->status)
+ {
+ case AuthSession::Undetermined:
+ {
+ QLOG_ERROR() << "Received undetermined session status during login. Bye.";
+ tryagain = false;
+ break;
+ }
+ case AuthSession::RequiresPassword:
+ {
+ EditAccountDialog passDialog(failReason, this, EditAccountDialog::PasswordField);
+ if (passDialog.exec() == QDialog::Accepted)
+ {
+ password = passDialog.password();
+ }
+ else
+ {
+ tryagain = false;
+ }
+ break;
+ }
+ case AuthSession::PlayableOffline:
+ {
+ // we ask the user for a player name
+ bool ok = false;
+ QString usedname = session->player_name;
+ QString name = QInputDialog::getText(this, tr("Player name"),
+ tr("Choose your offline mode player name."),
+ QLineEdit::Normal, session->player_name, &ok);
+ if (!ok)
+ {
+ tryagain = false;
+ break;
+ }
+ if (name.length())
+ {
+ usedname = name;
+ }
+ session->MakeOffline(usedname);
+ // offline flavored game from here :3
+ }
+ case AuthSession::PlayableOnline:
+ {
+ // update first if the server actually responded
+ if (session->auth_server_online)
+ {
+ updateInstance(m_selectedInstance, session);
+ }
+ else
+ {
+ launchInstance(m_selectedInstance, session);
+ }
+ tryagain = false;
+ }
+ }
+ }
+}
+
+void MainWindow::updateInstance(BaseInstance *instance, AuthSessionPtr session)
+{
+ auto updateTask = instance->doUpdate();
+ if (!updateTask)
+ {
+ launchInstance(instance, session);
+ return;
+ }
+ ProgressDialog tDialog(this);
+ connect(updateTask.get(), &Task::succeeded, [this, instance, session]
+ { launchInstance(instance, session); });
+ connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
+ tDialog.exec(updateTask.get());
+}
+
+void MainWindow::launchInstance(BaseInstance *instance, AuthSessionPtr session)
+{
+ Q_ASSERT_X(instance != NULL, "launchInstance", "instance is NULL");
+ Q_ASSERT_X(session.get() != nullptr, "launchInstance", "session is NULL");
+
+ proc = instance->prepareForLaunch(session);
+ if (!proc)
+ return;
+
+ this->hide();
+
+ console = new ConsoleWindow(proc);
+ connect(console, SIGNAL(isClosing()), this, SLOT(instanceEnded()));
+
+ proc->setLogin(session);
+ proc->launch();
+}
+
+void MainWindow::onGameUpdateError(QString error)
+{
+ CustomMessageBox::selectable(this, tr("Error updating instance"), error,
+ QMessageBox::Warning)->show();
+}
+
+void MainWindow::taskStart()
+{
+ // Nothing to do here yet.
+}
+
+void MainWindow::taskEnd()
+{
+ QObject *sender = QObject::sender();
+ if (sender == m_versionLoadTask)
+ m_versionLoadTask = NULL;
+
+ sender->deleteLater();
+}
+
+void MainWindow::startTask(Task *task)
+{
+ connect(task, SIGNAL(started()), SLOT(taskStart()));
+ connect(task, SIGNAL(succeeded()), SLOT(taskEnd()));
+ connect(task, SIGNAL(failed(QString)), SLOT(taskEnd()));
+ task->start();
+}
+
+// Create A Desktop Shortcut
+void MainWindow::on_actionMakeDesktopShortcut_triggered()
+{
+ QString name("Test");
+ name = QInputDialog::getText(this, tr("MultiMC Shortcut"), tr("Enter a Shortcut Name."),
+ QLineEdit::Normal, name);
+
+ Util::createShortCut(Util::getDesktopDir(), QApplication::instance()->applicationFilePath(),
+ QStringList() << "-dl" << QDir::currentPath() << "test", name,
+ "application-x-octet-stream");
+
+ CustomMessageBox::selectable(
+ this, tr("Not useful"),
+ tr("A Dummy Shortcut was created. it will not do anything productive"),
+ QMessageBox::Warning)->show();
+}
+
+// BrowserDialog
+void MainWindow::openWebPage(QUrl url)
+{
+ QDesktopServices::openUrl(url);
+}
+
+void MainWindow::on_actionChangeInstMCVersion_triggered()
+{
+ if (view->selectionModel()->selectedIndexes().count() < 1)
+ return;
+
+ VersionSelectDialog vselect(m_selectedInstance->versionList().get(),
+ tr("Change Minecraft version"), this);
+ vselect.setFilter(1, "OneSix");
+ if (!vselect.exec() || !vselect.selectedVersion())
+ return;
+
+ if (!MMC->accounts()->anyAccountIsValid())
+ {
+ CustomMessageBox::selectable(
+ this, tr("Error"),
+ tr("MultiMC cannot download Minecraft or update instances unless you have at least "
+ "one account added.\nPlease add your Mojang or Minecraft account."),
+ QMessageBox::Warning)->show();
+ return;
+ }
+
+ if (m_selectedInstance->versionIsCustom())
+ {
+ auto result = CustomMessageBox::selectable(
+ this, tr("Are you sure?"),
+ tr("This will remove any library/version customization you did previously. "
+ "This includes things like Forge install and similar."),
+ QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Abort,
+ QMessageBox::Abort)->exec();
+
+ if (result != QMessageBox::Ok)
+ return;
+ }
+ m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor());
+
+ auto updateTask = m_selectedInstance->doUpdate();
+ if (!updateTask)
+ {
+ return;
+ }
+ ProgressDialog tDialog(this);
+ connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
+ tDialog.exec(updateTask.get());
+}
+
+void MainWindow::on_actionChangeInstLWJGLVersion_triggered()
+{
+ if (!m_selectedInstance)
+ return;
+
+ LWJGLSelectDialog lselect(this);
+ lselect.exec();
+ if (lselect.result() == QDialog::Accepted)
+ {
+ LegacyInstance *linst = (LegacyInstance *)m_selectedInstance;
+ linst->setLWJGLVersion(lselect.selectedVersion());
+ }
+}
+
+void MainWindow::on_actionInstanceSettings_triggered()
+{
+ if (view->selectionModel()->selectedIndexes().count() < 1)
+ return;
+
+ InstanceSettings settings(&m_selectedInstance->settings(), this);
+ settings.setWindowTitle(tr("Instance settings"));
+ settings.exec();
+}
+
+void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &previous)
+{
+ if (current.isValid() &&
+ nullptr != (m_selectedInstance =
+ (BaseInstance *)current.data(InstanceList::InstancePointerRole)
+ .value<void *>()))
+ {
+ ui->instanceToolBar->setEnabled(true);
+ renameButton->setText(m_selectedInstance->name());
+ ui->actionChangeInstLWJGLVersion->setEnabled(
+ m_selectedInstance->menuActionEnabled("actionChangeInstLWJGLVersion"));
+ ui->actionEditInstMods->setEnabled(
+ m_selectedInstance->menuActionEnabled("actionEditInstMods"));
+ ui->actionChangeInstMCVersion->setEnabled(
+ m_selectedInstance->menuActionEnabled("actionChangeInstMCVersion"));
+ m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
+ updateInstanceToolIcon(m_selectedInstance->iconKey());
+
+ MMC->settings()->set("SelectedInstance", m_selectedInstance->id());
+ }
+ else
+ {
+ selectionBad();
+
+ MMC->settings()->set("SelectedInstance", QString());
+ }
+}
+
+void MainWindow::selectionBad()
+{
+ // start by reseting everything...
+ m_selectedInstance = nullptr;
+
+ statusBar()->clearMessage();
+ ui->instanceToolBar->setEnabled(false);
+ renameButton->setText(tr("Rename Instance"));
+ updateInstanceToolIcon("infinity");
+
+ // ...and then see if we can enable the previously selected instance
+ setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString());
+}
+
+void MainWindow::on_actionEditInstNotes_triggered()
+{
+ if (!m_selectedInstance)
+ return;
+ LegacyInstance *linst = (LegacyInstance *)m_selectedInstance;
+
+ EditNotesDialog noteedit(linst->notes(), linst->name(), this);
+ noteedit.exec();
+ if (noteedit.result() == QDialog::Accepted)
+ {
+
+ linst->setNotes(noteedit.getText());
+ }
+}
+
+void MainWindow::instanceEnded()
+{
+ this->show();
+}
+
+void MainWindow::checkMigrateLegacyAssets()
+{
+ int legacyAssets = AssetsUtils::findLegacyAssets();
+ if (legacyAssets > 0)
+ {
+ ProgressDialog migrateDlg(this);
+ AssetsMigrateTask migrateTask(legacyAssets, &migrateDlg);
+ {
+ ThreadTask threadTask(&migrateTask);
+
+ if (migrateDlg.exec(&threadTask))
+ {
+ QLOG_INFO() << "Assets migration task completed successfully";
+ }
+ else
+ {
+ QLOG_INFO() << "Assets migration task reported failure";
+ }
+ }
+ }
+ else
+ {
+ QLOG_INFO() << "Didn't find any legacy assets to migrate";
+ }
+}
+
+void MainWindow::checkSetDefaultJava()
+{
+ bool askForJava = false;
+ {
+ QString currentHostName = QHostInfo::localHostName();
+ QString oldHostName = MMC->settings()->get("LastHostname").toString();
+ if (currentHostName != oldHostName)
+ {
+ MMC->settings()->set("LastHostname", currentHostName);
+ askForJava = true;
+ }
+ }
+
+ {
+ QString currentJavaPath = MMC->settings()->get("JavaPath").toString();
+ if (currentJavaPath.isEmpty())
+ {
+ askForJava = true;
+ }
+ }
+
+ if (askForJava)
+ {
+ QLOG_DEBUG() << "Java path needs resetting, showing Java selection dialog...";
+
+ JavaVersionPtr java;
+
+ VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this,
+ false);
+ vselect.setResizeOn(2);
+ vselect.exec();
+
+ if (vselect.selectedVersion())
+ java = std::dynamic_pointer_cast<JavaVersion>(vselect.selectedVersion());
+ else
+ {
+ CustomMessageBox::selectable(
+ this, tr("Invalid version selected"),
+ tr("You didn't select a valid Java version, so MultiMC will "
+ "select the default. "
+ "You can change this in the settings dialog."),
+ QMessageBox::Warning)->show();
+
+ JavaUtils ju;
+ java = ju.GetDefaultJava();
+ }
+ if (java)
+ MMC->settings()->set("JavaPath", java->path);
+ else
+ MMC->settings()->set("JavaPath", QString("java"));
+ }
+}
diff --git a/gui/MainWindow.h b/gui/MainWindow.h
new file mode 100644
index 00000000..eeba2c26
--- /dev/null
+++ b/gui/MainWindow.h
@@ -0,0 +1,211 @@
+/* Copyright 2013 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 <QMainWindow>
+#include <QProcess>
+#include <QTimer>
+
+#include "logic/lists/InstanceList.h"
+#include "logic/BaseInstance.h"
+
+#include "logic/auth/MojangAccount.h"
+
+class QToolButton;
+class LabeledToolButton;
+class QLabel;
+class InstanceProxyModel;
+class KCategorizedView;
+class KCategoryDrawer;
+class MinecraftProcess;
+class ConsoleWindow;
+
+namespace Ui
+{
+class MainWindow;
+}
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+ void closeEvent(QCloseEvent *event);
+
+ // Browser Dialog
+ void openWebPage(QUrl url);
+
+ void checkSetDefaultJava();
+ void checkMigrateLegacyAssets();
+
+private
+slots:
+ void onCatToggled(bool);
+
+ void on_actionAbout_triggered();
+
+ void on_actionAddInstance_triggered();
+
+ void on_actionCopyInstance_triggered();
+
+ void on_actionChangeInstGroup_triggered();
+
+ void on_actionChangeInstIcon_triggered();
+
+ void on_actionViewInstanceFolder_triggered();
+
+ void on_actionConfig_Folder_triggered();
+
+ void on_actionViewSelectedInstFolder_triggered();
+
+ void on_actionRefresh_triggered();
+
+ void on_actionViewCentralModsFolder_triggered();
+
+ void on_actionCheckUpdate_triggered();
+
+ void on_actionSettings_triggered();
+
+ void on_actionManageAccounts_triggered();
+
+ void on_actionReportBug_triggered();
+
+ void on_actionMoreNews_triggered();
+
+ void newsButtonClicked();
+
+ void on_mainToolBar_visibilityChanged(bool);
+
+ // void on_instanceView_customContextMenuRequested(const QPoint &pos);
+
+ void on_actionLaunchInstance_triggered();
+
+ void on_actionLaunchInstanceOffline_triggered();
+
+ void on_actionDeleteInstance_triggered();
+
+ void on_actionRenameInstance_triggered();
+
+ void on_actionMakeDesktopShortcut_triggered();
+
+ void on_actionChangeInstMCVersion_triggered();
+
+ void on_actionEditInstMods_triggered();
+
+ void on_actionEditInstNotes_triggered();
+
+ /*!
+ * Launches the currently selected instance with the default account.
+ * If no default account is selected, prompts the user to pick an account.
+ */
+ void doLaunch(bool online = true);
+
+ /*!
+ * Launches the given instance with the given account.
+ * This function assumes that the given account has a valid, usable access token.
+ */
+ void launchInstance(BaseInstance *instance, AuthSessionPtr session);
+
+ /*!
+ * Prepares the given instance for launch with the given account.
+ */
+ void updateInstance(BaseInstance *instance, AuthSessionPtr account);
+
+ void onGameUpdateError(QString error);
+
+ void taskStart();
+ void taskEnd();
+
+ void on_actionChangeInstLWJGLVersion_triggered();
+
+ void instanceEnded();
+
+ void on_actionInstanceSettings_triggered();
+
+ // called when an icon is changed in the icon model.
+ void iconUpdated(QString);
+
+ void showInstanceContextMenu(const QPoint&);
+
+public
+slots:
+ void instanceActivated(QModelIndex);
+
+ void instanceChanged(const QModelIndex &current, const QModelIndex &previous);
+
+ void selectionBad();
+
+ void startTask(Task *task);
+
+ void updateAvailable(QString repo, QString versionName, int versionId);
+
+ void notificationsChanged();
+
+ void activeAccountChanged();
+
+ void changeActiveAccount();
+
+ void repopulateAccountsMenu();
+
+ void updateNewsLabel();
+
+ void updateStatusUI();
+
+ void updateStatusFailedUI();
+
+ void reloadStatus();
+
+ /*!
+ * Runs the DownloadUpdateTask and installs updates.
+ */
+ void downloadUpdates(QString repo, int versionId, bool installOnExit=false);
+
+protected:
+ bool eventFilter(QObject *obj, QEvent *ev);
+ void setCatBackground(bool enabled);
+ void updateInstanceToolIcon(QString new_icon);
+
+ void setSelectedInstanceById(const QString &id);
+
+private:
+ Ui::MainWindow *ui;
+ KCategoryDrawer *drawer;
+ KCategorizedView *view;
+ InstanceProxyModel *proxymodel;
+ MinecraftProcess *proc;
+ ConsoleWindow *console;
+ LabeledToolButton *renameButton;
+ QToolButton *changeIconButton;
+ QToolButton* newsLabel;
+
+ BaseInstance *m_selectedInstance;
+ QString m_currentInstIcon;
+
+ Task *m_versionLoadTask;
+
+ QLabel *m_statusLeft;
+ QLabel *m_statusRight;
+ QToolButton *m_statusRefresh;
+
+ QMenu *accountMenu;
+ QToolButton *accountMenuButton;
+ QAction *manageAccountsAction;
+
+ QTimer statusTimer;
+};
diff --git a/gui/MainWindow.ui b/gui/MainWindow.ui
new file mode 100644
index 00000000..8cf26d18
--- /dev/null
+++ b/gui/MainWindow.ui
@@ -0,0 +1,539 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>694</width>
+ <height>563</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MultiMC 5</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../resources/multimc/multimc.qrc">
+ <normaloff>:/icons/multimc/scalable/apps/multimc.svg</normaloff>:/icons/multimc/scalable/apps/multimc.svg</iconset>
+ </property>
+ <widget class="QWidget" name="centralWidget">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ <widget class="QToolBar" name="mainToolBar">
+ <property name="windowTitle">
+ <string>Main Toolbar</string>
+ </property>
+ <property name="movable">
+ <bool>false</bool>
+ </property>
+ <property name="allowedAreas">
+ <set>Qt::TopToolBarArea</set>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonIconOnly</enum>
+ </property>
+ <property name="floatable">
+ <bool>false</bool>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionAddInstance"/>
+ <addaction name="actionCopyInstance"/>
+ <addaction name="separator"/>
+ <addaction name="actionViewInstanceFolder"/>
+ <addaction name="actionViewCentralModsFolder"/>
+ <addaction name="actionRefresh"/>
+ <addaction name="separator"/>
+ <addaction name="actionCheckUpdate"/>
+ <addaction name="actionSettings"/>
+ <addaction name="separator"/>
+ <addaction name="actionReportBug"/>
+ <addaction name="actionAbout"/>
+ <addaction name="separator"/>
+ <addaction name="actionCAT"/>
+ </widget>
+ <widget class="QStatusBar" name="statusBar"/>
+ <widget class="QToolBar" name="instanceToolBar">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="windowTitle">
+ <string>Instance Toolbar</string>
+ </property>
+ <property name="allowedAreas">
+ <set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>80</width>
+ <height>80</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonIconOnly</enum>
+ </property>
+ <property name="floatable">
+ <bool>false</bool>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>RightToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionChangeInstIcon"/>
+ <addaction name="actionLaunchInstance"/>
+ <addaction name="actionLaunchInstanceOffline"/>
+ <addaction name="separator"/>
+ <addaction name="actionEditInstNotes"/>
+ <addaction name="actionChangeInstGroup"/>
+ <addaction name="separator"/>
+ <addaction name="actionInstanceSettings"/>
+ <addaction name="actionChangeInstMCVersion"/>
+ <addaction name="actionChangeInstLWJGLVersion"/>
+ <addaction name="actionEditInstMods"/>
+ <addaction name="actionViewSelectedInstFolder"/>
+ <addaction name="actionConfig_Folder"/>
+ <addaction name="separator"/>
+ <addaction name="actionDeleteInstance"/>
+ </widget>
+ <widget class="QToolBar" name="newsToolBar">
+ <property name="windowTitle">
+ <string>News Toolbar</string>
+ </property>
+ <property name="movable">
+ <bool>false</bool>
+ </property>
+ <property name="allowedAreas">
+ <set>Qt::BottomToolBarArea</set>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="floatable">
+ <bool>false</bool>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>BottomToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionMoreNews"/>
+ </widget>
+ <action name="actionAddInstance">
+ <property name="icon">
+ <iconset theme="new">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Add Instance</string>
+ </property>
+ <property name="toolTip">
+ <string>Add a new instance.</string>
+ </property>
+ <property name="statusTip">
+ <string>Add a new instance.</string>
+ </property>
+ </action>
+ <action name="actionViewInstanceFolder">
+ <property name="icon">
+ <iconset theme="viewfolder">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>View Instance Folder</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the instance folder in a file browser.</string>
+ </property>
+ <property name="statusTip">
+ <string>Open the instance folder in a file browser.</string>
+ </property>
+ </action>
+ <action name="actionRefresh">
+ <property name="icon">
+ <iconset theme="refresh">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Refresh</string>
+ </property>
+ <property name="toolTip">
+ <string>Reload the instance list.</string>
+ </property>
+ <property name="statusTip">
+ <string>Reload the instance list.</string>
+ </property>
+ </action>
+ <action name="actionViewCentralModsFolder">
+ <property name="icon">
+ <iconset theme="centralmods">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>View Central Mods Folder</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the central mods folder in a file browser.</string>
+ </property>
+ <property name="statusTip">
+ <string>Open the central mods folder in a file browser.</string>
+ </property>
+ </action>
+ <action name="actionCheckUpdate">
+ <property name="icon">
+ <iconset theme="checkupdate">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Check for Updates</string>
+ </property>
+ <property name="toolTip">
+ <string>Check for new updates for MultiMC</string>
+ </property>
+ <property name="statusTip">
+ <string>Check for new updates for MultiMC</string>
+ </property>
+ </action>
+ <action name="actionSettings">
+ <property name="icon">
+ <iconset theme="settings">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Settings</string>
+ </property>
+ <property name="toolTip">
+ <string>Change settings.</string>
+ </property>
+ <property name="statusTip">
+ <string>Change settings.</string>
+ </property>
+ <property name="menuRole">
+ <enum>QAction::PreferencesRole</enum>
+ </property>
+ </action>
+ <action name="actionReportBug">
+ <property name="icon">
+ <iconset theme="bug">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Report a Bug</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the bug tracker to report a bug with MultiMC.</string>
+ </property>
+ <property name="statusTip">
+ <string>Open the bug tracker to report a bug with MultiMC.</string>
+ </property>
+ </action>
+ <action name="actionMoreNews">
+ <property name="icon">
+ <iconset theme="news">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>More News</string>
+ </property>
+ <property name="iconText">
+ <string>More news...</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the MultiMC development blog to read more news about MultiMC.</string>
+ </property>
+ <property name="statusTip">
+ <string>Open the MultiMC development blog to read more news about MultiMC.</string>
+ </property>
+ </action>
+ <action name="actionAbout">
+ <property name="icon">
+ <iconset theme="about">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>About MultiMC</string>
+ </property>
+ <property name="toolTip">
+ <string>View information about MultiMC.</string>
+ </property>
+ <property name="statusTip">
+ <string>About MultiMC</string>
+ </property>
+ <property name="menuRole">
+ <enum>QAction::AboutRole</enum>
+ </property>
+ </action>
+ <action name="actionLaunchInstance">
+ <property name="text">
+ <string>Play</string>
+ </property>
+ <property name="toolTip">
+ <string>Launch the selected instance.</string>
+ </property>
+ <property name="statusTip">
+ <string>Launch the selected instance.</string>
+ </property>
+ </action>
+ <action name="actionRenameInstance">
+ <property name="text">
+ <string>Instance Name</string>
+ </property>
+ <property name="toolTip">
+ <string>Rename the selected instance.</string>
+ </property>
+ <property name="statusTip">
+ <string>Rename the selected instance.</string>
+ </property>
+ </action>
+ <action name="actionChangeInstGroup">
+ <property name="text">
+ <string>Change Group</string>
+ </property>
+ <property name="toolTip">
+ <string>Change the selected instance's group.</string>
+ </property>
+ <property name="statusTip">
+ <string>Change the selected instance's group.</string>
+ </property>
+ </action>
+ <action name="actionChangeInstIcon">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="../resources/instances/instances.qrc">
+ <normaloff>:/icons/instances/infinity</normaloff>:/icons/instances/infinity</iconset>
+ </property>
+ <property name="text">
+ <string>Change Icon</string>
+ </property>
+ <property name="toolTip">
+ <string>Change the selected instance's icon.</string>
+ </property>
+ <property name="statusTip">
+ <string>Change the selected instance's icon.</string>
+ </property>
+ <property name="iconVisibleInMenu">
+ <bool>true</bool>
+ </property>
+ </action>
+ <action name="actionEditInstNotes">
+ <property name="text">
+ <string>Edit Notes</string>
+ </property>
+ <property name="toolTip">
+ <string>Edit the notes for the selected instance.</string>
+ </property>
+ <property name="statusTip">
+ <string>Edit the notes for the selected instance.</string>
+ </property>
+ </action>
+ <action name="actionInstanceSettings">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Settings</string>
+ </property>
+ <property name="toolTip">
+ <string>Change settings for the selected instance.</string>
+ </property>
+ <property name="statusTip">
+ <string>Change settings for the selected instance.</string>
+ </property>
+ </action>
+ <action name="actionMakeDesktopShortcut">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Make Shortcut</string>
+ </property>
+ <property name="toolTip">
+ <string>Make a shortcut on the desktop for the selected instance.</string>
+ </property>
+ <property name="statusTip">
+ <string>Make a shortcut on the desktop for the selected instance.</string>
+ </property>
+ </action>
+ <action name="actionManageInstSaves">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Manage Saves</string>
+ </property>
+ <property name="toolTip">
+ <string>Manage saves for the selected instance.</string>
+ </property>
+ <property name="statusTip">
+ <string>Manage saves for the selected instance.</string>
+ </property>
+ </action>
+ <action name="actionEditInstMods">
+ <property name="text">
+ <string>Edit Mods</string>
+ </property>
+ <property name="toolTip">
+ <string>Edit the mods for the selected instance.</string>
+ </property>
+ <property name="statusTip">
+ <string>Edit the mods for the selected instance.</string>
+ </property>
+ </action>
+ <action name="actionChangeInstMCVersion">
+ <property name="text">
+ <string>Change Version</string>
+ </property>
+ <property name="toolTip">
+ <string>Change the selected instance's Minecraft version.</string>
+ </property>
+ <property name="statusTip">
+ <string>Change the selected instance's Minecraft version.</string>
+ </property>
+ </action>
+ <action name="actionChangeInstLWJGLVersion">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Change LWJGL</string>
+ </property>
+ <property name="toolTip">
+ <string>Change the version of LWJGL for the selected instance to use.</string>
+ </property>
+ <property name="statusTip">
+ <string>Change the version of LWJGL for the selected instance to use.</string>
+ </property>
+ </action>
+ <action name="actionViewSelectedInstFolder">
+ <property name="text">
+ <string>Instance Folder</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the selected instance's root folder in a file browser.</string>
+ </property>
+ <property name="statusTip">
+ <string>Open the selected instance's root folder in a file browser.</string>
+ </property>
+ </action>
+ <action name="actionDeleteInstance">
+ <property name="text">
+ <string>Delete</string>
+ </property>
+ <property name="toolTip">
+ <string>Delete the selected instance.</string>
+ </property>
+ <property name="statusTip">
+ <string>Delete the selected instance.</string>
+ </property>
+ </action>
+ <action name="actionConfig_Folder">
+ <property name="text">
+ <string>Config Folder</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the instance's config folder</string>
+ </property>
+ </action>
+ <action name="actionCAT">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="cat">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Meow</string>
+ </property>
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;It's a fluffy kitty :3&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </action>
+ <action name="actionCopyInstance">
+ <property name="icon">
+ <iconset theme="copy">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Copy Instance</string>
+ </property>
+ <property name="toolTip">
+ <string>Copy the selected instance.</string>
+ </property>
+ <property name="statusTip">
+ <string>Add a new instance.</string>
+ </property>
+ </action>
+ <action name="actionManageAccounts">
+ <property name="text">
+ <string>Manage Accounts</string>
+ </property>
+ <property name="toolTip">
+ <string>Manage your Mojang or Minecraft accounts.</string>
+ </property>
+ </action>
+ <action name="actionLaunchInstanceOffline">
+ <property name="text">
+ <string>Play Offline</string>
+ </property>
+ <property name="toolTip">
+ <string>Launch the selected instance in offline mode.</string>
+ </property>
+ <property name="statusTip">
+ <string>Launch the selected instance.</string>
+ </property>
+ </action>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources>
+ <include location="../resources/instances/instances.qrc"/>
+ <include location="../resources/multimc/multimc.qrc"/>
+ <include location="../resources/backgrounds/backgrounds.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/gui/Platform.h b/gui/Platform.h
new file mode 100644
index 00000000..5cde9505
--- /dev/null
+++ b/gui/Platform.h
@@ -0,0 +1,32 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * 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
+
+/**
+ * @file Platform.h
+ * This file contains platform-specific functions, tweaks and fixes.
+ */
+
+#include <QWidget>
+
+class MultiMCPlatform
+{
+public:
+ // X11 WM_CLASS
+ static void fixWM_CLASS(QWidget *widget);
+};
diff --git a/gui/Platform_Other.cpp b/gui/Platform_Other.cpp
new file mode 100644
index 00000000..e02bd921
--- /dev/null
+++ b/gui/Platform_Other.cpp
@@ -0,0 +1,27 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * 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 <gui/Platform.h>
+/**
+ * Stub for non-X11 platforms
+ * @brief MultiMCPlatform::fixWM_CLASS
+ * @param widget
+ */
+void MultiMCPlatform::fixWM_CLASS(QWidget *widget)
+{
+ Q_UNUSED(widget);
+}
diff --git a/gui/Platform_X11.cpp b/gui/Platform_X11.cpp
new file mode 100644
index 00000000..bcd26f53
--- /dev/null
+++ b/gui/Platform_X11.cpp
@@ -0,0 +1,62 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * 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 <gui/Platform.h>
+#include <QtX11Extras/QX11Info>
+#include <xcb/xcb.h>
+
+static QByteArray WM_CLASS = "MultiMC5\0MultiMC5";
+
+template <typename... ArgTypes, typename... ArgTypes2>
+static inline unsigned int XcbCallVoid(xcb_void_cookie_t (*func)(xcb_connection_t *, ArgTypes...), ArgTypes2... args...)
+{
+ return func(QX11Info::connection(), args...).sequence;
+}
+
+static void getAtoms(size_t n, xcb_atom_t *atoms, const char *const names[], bool create)
+{
+ xcb_connection_t *conn = QX11Info::connection();
+ xcb_intern_atom_cookie_t *cookies = (xcb_intern_atom_cookie_t *)malloc(sizeof(xcb_intern_atom_cookie_t) * 2);
+ for (size_t i = 0; i < n; ++i)
+ cookies[i] = xcb_intern_atom(conn, create, strlen(names[i]), names[i]);
+ memset(atoms, 0, sizeof(xcb_atom_t) * n);
+ for (size_t i = 0; i < n; ++i)
+ {
+ xcb_intern_atom_reply_t *r = xcb_intern_atom_reply(conn, cookies[i], 0);
+ if (r)
+ {
+ atoms[i] = r->atom;
+ free(r);
+ }
+ }
+ free(cookies);
+}
+
+static inline xcb_atom_t getAtom(const char *name, bool create=false)
+{
+ xcb_atom_t atom;
+ getAtoms(1, &atom, &name, create);
+ return atom;
+}
+
+void MultiMCPlatform::fixWM_CLASS(QWidget *widget)
+{
+ static const xcb_atom_t atom = getAtom("WM_CLASS");
+ XcbCallVoid(xcb_change_property, XCB_PROP_MODE_REPLACE,
+ widget->winId(), atom, XCB_ATOM_STRING, 8, WM_CLASS.count(),
+ WM_CLASS.constData());
+}
diff --git a/gui/dialogs/AboutDialog.cpp b/gui/dialogs/AboutDialog.cpp
new file mode 100644
index 00000000..35c85815
--- /dev/null
+++ b/gui/dialogs/AboutDialog.cpp
@@ -0,0 +1,54 @@
+/* Copyright 2013 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 "MultiMC.h"
+#include "gui/Platform.h"
+
+AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+
+ ui->urlLabel->setOpenExternalLinks(true);
+
+ ui->icon->setPixmap(QIcon(":/icons/multimc/scalable/apps/multimc.svg").pixmap(64));
+ ui->title->setText("MultiMC 5 " + MMC->version().toString());
+
+ ui->versionLabel->setText(tr("Version") +": " + MMC->version().toString());
+ ui->vtypeLabel->setText(tr("Version Type") +": " + MMC->version().typeName());
+ ui->platformLabel->setText(tr("Platform") +": " + MMC->version().platform);
+
+ if (MMC->version().build >= 0)
+ ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(MMC->version().build));
+ else
+ ui->buildNumLabel->setVisible(false);
+
+ if (!MMC->version().channel.isEmpty())
+ ui->channelLabel->setText(tr("Channel") +": " + MMC->version().channel);
+ else
+ ui->channelLabel->setVisible(false);
+
+ connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));
+
+ MMC->connect(ui->aboutQt, SIGNAL(clicked()), SLOT(aboutQt()));
+}
+
+AboutDialog::~AboutDialog()
+{
+ delete ui;
+}
diff --git a/gui/dialogs/AboutDialog.h b/gui/dialogs/AboutDialog.h
new file mode 100644
index 00000000..9d747bac
--- /dev/null
+++ b/gui/dialogs/AboutDialog.h
@@ -0,0 +1,35 @@
+/* Copyright 2013 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 AboutDialog;
+}
+
+class AboutDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit AboutDialog(QWidget *parent = 0);
+ ~AboutDialog();
+
+private:
+ Ui::AboutDialog *ui;
+};
diff --git a/gui/dialogs/AboutDialog.ui b/gui/dialogs/AboutDialog.ui
new file mode 100644
index 00000000..64a355d3
--- /dev/null
+++ b/gui/dialogs/AboutDialog.ui
@@ -0,0 +1,543 @@
+<?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>707</width>
+ <height>593</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>450</width>
+ <height>400</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>About MultiMC</string>
+ </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>
+ <property name="pixmap">
+ <pixmap resource="../../graphics.qrc">:/icons/multimc/scalable/apps/multimc.svg</pixmap>
+ </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>MultiMC 5</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolBox" name="toolBox">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="aboutPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>689</width>
+ <height>311</height>
+ </rect>
+ </property>
+ <attribute name="label">
+ <string>About</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <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="vtypeLabel">
+ <property name="text">
+ <string>Version Type:</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>
+ <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;MultiMC is 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="copyLabel">
+ <property name="font">
+ <font>
+ <pointsize>8</pointsize>
+ <kerning>true</kerning>
+ </font>
+ </property>
+ <property name="text">
+ <string>© 2013 MultiMC Contributors</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="urlLabel">
+ <property name="font">
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://github.com/Forkk/MultiMC5&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://github.com/MultiMC/MultiMC5&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="creditsPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>689</width>
+ <height>311</height>
+ </rect>
+ </property>
+ <attribute name="label">
+ <string>Credits</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QTextEdit" name="creditsText">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="html">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;&quot;&gt;MultiMC&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;Andrew Okin &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:forkk@forkk.net&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;forkk@forkk.net&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;Petr Mrázek &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:peterix@gmail.com&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;peterix@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;Sky &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://www.twitter.com/drayshak&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@drayshak&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:10pt; font-weight:600;&quot;&gt;With thanks to&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;Orochimarufan &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:orochimarufan.x3@gmail.com&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;orochimarufan.x3@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;TakSuyu &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:taksuyu@gmail.com&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;taksuyu@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;Kilobyte &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:stiepen22@gmx.de&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;stiepen22@gmx.de&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;Jan (02JanDal) &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:02jandal@gmail.com&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;02jandal@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;Robotbrain &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://twitter.com/skylordelros&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@skylordelros&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;Rootbear75 &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://twitter.com/rootbear75&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@rootbear75&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;&amp;gt; (build server)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="translationInfo">
+ <property name="text">
+ <string extracomment="Hey, Translator, feel free to put credit to you here">No Language file loaded.</string>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="licensePage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>689</width>
+ <height>311</height>
+ </rect>
+ </property>
+ <attribute name="label">
+ <string>License</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <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="html">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;&quot;&gt;MultiMC&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Copyright 2012-2014 MultiMC Contributors&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Licensed under the Apache License, Version 2.0 (the &amp;quot;License&amp;quot;);&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;you may not use this file except in compliance with the License.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;You may obtain a copy of the License at&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt; http://www.apache.org/licenses/LICENSE-2.0&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Unless required by applicable law or agreed to in writing, software&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;distributed under the License is distributed on an &amp;quot;AS IS&amp;quot; BASIS,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;See the License for the specific language governing permissions and&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;limitations under the License.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;&quot;&gt;QSLog&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Copyright (c) 2010, Razvan Petru&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;All rights reserved.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Redistribution and use in source and binary forms, with or without modification,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;are permitted provided that the following conditions are met:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;* Redistributions of source code must retain the above copyright notice, this&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; list of conditions and the following disclaimer.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;* Redistributions in binary form must reproduce the above copyright notice, this&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; list of conditions and the following disclaimer in the documentation and/or other&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; materials provided with the distribution.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;* The name of the contributors may not be used to endorse or promote products&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; derived from this software without specific prior written permission.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &amp;quot;AS IS&amp;quot; AND&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;OF THE POSSIBILITY OF SUCH DAMAGE.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;&quot;&gt;Group View (instance view)&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; /*&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Copyright (C) 2007 Rafael Fernández López &amp;lt;ereslibre@kde.org&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Copyright (C) 2007 John Tapsell &amp;lt;tapsell@kde.org&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; *&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * This library is free software; you can redistribute it and/or&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * modify it under the terms of the GNU Library General Public&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * License as published by the Free Software Foundation; either&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * version 2 of the License, or (at your option) any later version.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; *&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * This library is distributed in the hope that it will be useful,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Library General Public License for more details.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; *&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * You should have received a copy of the GNU Library General Public License&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * along with this library; see the file COPYING.LIB. If not, write to&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Boston, MA 02110-1301, USA.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; */&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;&quot;&gt;Pack200&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;The GNU General Public License (GPL)&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Version 2, June 1991&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;+ &amp;quot;CLASSPATH&amp;quot; EXCEPTION TO THE GPL&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Certain source files distributed by Oracle America and/or its affiliates are&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;subject to the following clarification and special exception to the GPL, but&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;only where Oracle has expressly included in the particular source file's header&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;the words &amp;quot;Oracle designates this particular file as subject to the &amp;quot;Classpath&amp;quot;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;exception as provided by Oracle in the LICENSE file that accompanied this code.&amp;quot;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; Linking this library statically or dynamically with other modules is making&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; a combined work based on this library. Thus, the terms and conditions of&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; the GNU General Public License cover the whole combination.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; As a special exception, the copyright holders of this library give you&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; permission to link this library with independent modules to produce an&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; executable, regardless of the license terms of these independent modules,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; and to copy and distribute the resulting executable under terms of your&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; choice, provided that you also meet, for each linked independent module,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; the terms and conditions of the license of that module. An independent&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; module is a module which is not derived from or based on this library. If&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; you modify this library, you may extend this exception to your version of&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; the library, but you are not obligated to do so. If you do not wish to do&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; so, delete this exception statement from your version.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;&quot;&gt;Quazip&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Copyright (C) 2005-2011 Sergey A. Tachenov&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This program is free software; you can redistribute it and/or modify it&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;under the terms of the GNU Lesser General Public License as published by&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;the Free Software Foundation; either version 2 of the License, or (at&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;your option) any later version.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This program is distributed in the hope that it will be useful, but&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;WITHOUT ANY WARRANTY; without even the implied warranty of&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;General Public License for more details.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;You should have received a copy of the GNU Lesser General Public License&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;along with this program; if not, write to the Free Software Foundation,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;See COPYING file for the full LGPL text.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Original ZIP package is copyrighted by Gilles Vollant, see&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;quazip/(un)zip.h files for details, basically it's zlib license.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;&quot;&gt;xz-minidec&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;/*&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * XZ decompressor&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; *&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Authors: Lasse Collin &amp;lt;lasse.collin@tukaani.org&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Igor Pavlov &amp;lt;http://7-zip.org/&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; *&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * This file has been put into the public domain.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * You can do whatever you want with this file.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; */&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;&quot;&gt;Java IconLoader class&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Copyright (c) 2011, Chris Molini&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;All rights reserved.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Redistribution and use in source and binary forms, with or without&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;modification, are permitted provided that the following conditions are met:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Redistributions of source code must retain the above copyright&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; notice, this list of conditions and the following disclaimer.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Redistributions in binary form must reproduce the above copyright&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; notice, this list of conditions and the following disclaimer in the&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; documentation and/or other materials provided with the distribution.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Neither the name of the &amp;lt;organization&amp;gt; nor the&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; names of its contributors may be used to endorse or promote products&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; derived from this software without specific prior written permission.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &amp;quot;AS IS&amp;quot; AND&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;DISCLAIMED. IN NO EVENT SHALL &amp;lt;COPYRIGHT HOLDER&amp;gt; BE LIABLE FOR ANY&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="forkPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>689</width>
+ <height>311</height>
+ </rect>
+ </property>
+ <attribute name="label">
+ <string>Forking/Redistribution</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_33">
+ <item>
+ <widget class="QTextEdit" name="textEdit">
+ <property name="html">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;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.&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Part of the reason for using the Apache license is we don't want people using the &amp;quot;MultiMC&amp;quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &amp;quot;MultiMC&amp;quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;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 &lt;span style=&quot; font-weight:600;&quot;&gt;without&lt;/span&gt; implying that you have our blessing.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <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>
+ <resources>
+ <include location="../../graphics.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/gui/dialogs/AccountListDialog.cpp b/gui/dialogs/AccountListDialog.cpp
new file mode 100644
index 00000000..a38035a6
--- /dev/null
+++ b/gui/dialogs/AccountListDialog.cpp
@@ -0,0 +1,160 @@
+/* Copyright 2013 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 "AccountListDialog.h"
+#include "ui_AccountListDialog.h"
+
+#include <QItemSelectionModel>
+
+#include <logger/QsLog.h>
+
+#include <logic/net/NetJob.h>
+#include <logic/net/URLConstants.h>
+
+#include <gui/dialogs/EditAccountDialog.h>
+#include <gui/dialogs/ProgressDialog.h>
+#include <gui/dialogs/AccountSelectDialog.h>
+#include "CustomMessageBox.h"
+#include <logic/tasks/Task.h>
+#include <logic/auth/YggdrasilTask.h>
+
+#include <MultiMC.h>
+
+AccountListDialog::AccountListDialog(QWidget *parent)
+ : QDialog(parent), ui(new Ui::AccountListDialog)
+{
+ ui->setupUi(this);
+
+ m_accounts = MMC->accounts();
+
+ ui->listView->setModel(m_accounts.get());
+ ui->listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+
+ // Expand the account column
+ ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
+
+ QItemSelectionModel* selectionModel = ui->listView->selectionModel();
+
+ connect(selectionModel, &QItemSelectionModel::selectionChanged,
+ [this] (const QItemSelection& sel, const QItemSelection& dsel) { updateButtonStates(); });
+
+ connect(m_accounts.get(), SIGNAL(listChanged()), SLOT(listChanged()));
+ connect(m_accounts.get(), SIGNAL(activeAccountChanged()), SLOT(listChanged()));
+
+ updateButtonStates();
+}
+
+AccountListDialog::~AccountListDialog()
+{
+ delete ui;
+}
+
+void AccountListDialog::listChanged()
+{
+ updateButtonStates();
+}
+
+void AccountListDialog::on_addAccountBtn_clicked()
+{
+ addAccount(tr("Please enter your Mojang or Minecraft account username and password to add your account."));
+}
+
+void AccountListDialog::on_rmAccountBtn_clicked()
+{
+ QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
+ if (selection.size() > 0)
+ {
+ QModelIndex selected = selection.first();
+ m_accounts->removeAccount(selected);
+ }
+}
+
+void AccountListDialog::on_setDefaultBtn_clicked()
+{
+ QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
+ if (selection.size() > 0)
+ {
+ QModelIndex selected = selection.first();
+ MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>();
+ m_accounts->setActiveAccount(account->username());
+ }
+}
+
+void AccountListDialog::on_noDefaultBtn_clicked()
+{
+ m_accounts->setActiveAccount("");
+}
+
+void AccountListDialog::on_closeBtnBox_rejected()
+{
+ close();
+}
+
+void AccountListDialog::updateButtonStates()
+{
+ // If there is no selection, disable buttons that require something selected.
+ QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
+
+ ui->rmAccountBtn->setEnabled(selection.size() > 0);
+ ui->setDefaultBtn->setEnabled(selection.size() > 0);
+
+ ui->noDefaultBtn->setDown(m_accounts->activeAccount().get() == nullptr);
+}
+
+void AccountListDialog::addAccount(const QString& errMsg)
+{
+ // TODO: We can use the login dialog for this for now, but we'll have to make something better for it eventually.
+ EditAccountDialog loginDialog(errMsg, this, EditAccountDialog::UsernameField | EditAccountDialog::PasswordField);
+ loginDialog.exec();
+
+ if (loginDialog.result() == QDialog::Accepted)
+ {
+ QString username(loginDialog.username());
+ QString password(loginDialog.password());
+
+ MojangAccountPtr account = MojangAccount::createFromUsername(username);
+ ProgressDialog progDialog(this);
+ auto task = account->login(nullptr, password);
+ progDialog.exec(task.get());
+ if(task->successful())
+ {
+ m_accounts->addAccount(account);
+ if (m_accounts->count() == 1)
+ m_accounts->setActiveAccount(account->username());
+
+ // Grab associated player skins
+ auto job = new NetJob("Player skins: " + account->username());
+
+ for(AccountProfile profile : account->profiles())
+ {
+ auto meta = MMC->metacache()->resolveEntry("skins", profile.name + ".png");
+ auto action = CacheDownload::make(
+ QUrl("http://" + URLConstants::SKINS_BASE + profile.name + ".png"),
+ meta);
+ job->addNetAction(action);
+ meta->stale = true;
+ }
+
+ job->start();
+ }
+ else
+ {
+ auto reason = task->failReason();
+ auto dlg = CustomMessageBox::selectable(this, tr("Login error."), reason, QMessageBox::Critical);
+ dlg->exec();
+ delete dlg;
+ }
+ }
+}
diff --git a/gui/dialogs/AccountListDialog.h b/gui/dialogs/AccountListDialog.h
new file mode 100644
index 00000000..fe0c8773
--- /dev/null
+++ b/gui/dialogs/AccountListDialog.h
@@ -0,0 +1,65 @@
+/* Copyright 2013 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 "logic/auth/MojangAccountList.h"
+
+namespace Ui
+{
+class AccountListDialog;
+}
+
+class AuthenticateTask;
+
+class AccountListDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit AccountListDialog(QWidget *parent = 0);
+ ~AccountListDialog();
+
+public
+slots:
+ void on_addAccountBtn_clicked();
+
+ void on_rmAccountBtn_clicked();
+
+ void on_setDefaultBtn_clicked();
+
+ void on_noDefaultBtn_clicked();
+
+ // This will be sent when the "close" button is clicked.
+ void on_closeBtnBox_rejected();
+
+ void listChanged();
+
+ //! Updates the states of the dialog's buttons.
+ void updateButtonStates();
+
+protected:
+ std::shared_ptr<MojangAccountList> m_accounts;
+
+protected
+slots:
+ void addAccount(const QString& errMsg="");
+
+private:
+ Ui::AccountListDialog *ui;
+};
diff --git a/gui/dialogs/AccountListDialog.ui b/gui/dialogs/AccountListDialog.ui
new file mode 100644
index 00000000..72682163
--- /dev/null
+++ b/gui/dialogs/AccountListDialog.ui
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AccountListDialog</class>
+ <widget class="QDialog" name="AccountListDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Manage Accounts</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="welcomeLabel">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Welcome! If you're new here, you can click the &amp;quot;Add&amp;quot; button to add your Mojang or Minecraft account.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QTreeView" name="listView"/>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="manageAcctsBtnBox">
+ <item>
+ <widget class="QPushButton" name="addAccountBtn">
+ <property name="text">
+ <string>&amp;Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="rmAccountBtn">
+ <property name="text">
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="buttonSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="setDefaultBtn">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set the currently selected account as the active account. The active account is the account that is used to log in (unless it is overridden in an instance-specific setting).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>&amp;Set Default</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="noDefaultBtn">
+ <property name="toolTip">
+ <string>Set no default account. This will cause MultiMC to prompt you to select an account every time you launch an instance that doesn't have its own default set.</string>
+ </property>
+ <property name="text">
+ <string>&amp;No Default</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="closeBtnBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/gui/dialogs/AccountSelectDialog.cpp b/gui/dialogs/AccountSelectDialog.cpp
new file mode 100644
index 00000000..ec2f09be
--- /dev/null
+++ b/gui/dialogs/AccountSelectDialog.cpp
@@ -0,0 +1,84 @@
+/* Copyright 2013 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 "AccountSelectDialog.h"
+#include "ui_AccountSelectDialog.h"
+
+#include <QItemSelectionModel>
+
+#include <logger/QsLog.h>
+
+#include <gui/dialogs/ProgressDialog.h>
+
+#include <MultiMC.h>
+
+AccountSelectDialog::AccountSelectDialog(const QString &message, int flags, QWidget *parent)
+ : QDialog(parent), ui(new Ui::AccountSelectDialog)
+{
+ ui->setupUi(this);
+
+ m_accounts = MMC->accounts();
+ ui->listView->setModel(m_accounts.get());
+ ui->listView->hideColumn(MojangAccountList::ActiveColumn);
+
+ // Set the message label.
+ ui->msgLabel->setVisible(!message.isEmpty());
+ ui->msgLabel->setText(message);
+
+ // Flags...
+ ui->globalDefaultCheck->setVisible(flags & GlobalDefaultCheckbox);
+ ui->instDefaultCheck->setVisible(flags & InstanceDefaultCheckbox);
+ QLOG_DEBUG() << flags;
+
+ // Select the first entry in the list.
+ ui->listView->setCurrentIndex(ui->listView->model()->index(0, 0));
+}
+
+AccountSelectDialog::~AccountSelectDialog()
+{
+ delete ui;
+}
+
+MojangAccountPtr AccountSelectDialog::selectedAccount() const
+{
+ return m_selected;
+}
+
+bool AccountSelectDialog::useAsGlobalDefault() const
+{
+ return ui->globalDefaultCheck->isChecked();
+}
+
+bool AccountSelectDialog::useAsInstDefaullt() const
+{
+ return ui->instDefaultCheck->isChecked();
+}
+
+void AccountSelectDialog::on_buttonBox_accepted()
+{
+ QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
+ if (selection.size() > 0)
+ {
+ QModelIndex selected = selection.first();
+ MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>();
+ m_selected = account;
+ }
+ close();
+}
+
+void AccountSelectDialog::on_buttonBox_rejected()
+{
+ close();
+}
diff --git a/gui/dialogs/AccountSelectDialog.h b/gui/dialogs/AccountSelectDialog.h
new file mode 100644
index 00000000..6007253d
--- /dev/null
+++ b/gui/dialogs/AccountSelectDialog.h
@@ -0,0 +1,90 @@
+/* Copyright 2013 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 "logic/auth/MojangAccountList.h"
+
+namespace Ui
+{
+class AccountSelectDialog;
+}
+
+class AccountSelectDialog : 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 AccountSelectDialog(const QString& message="", int flags=0, QWidget *parent = 0);
+ ~AccountSelectDialog();
+
+ /*!
+ * Gets a pointer to the account that the user selected.
+ * This is null if the user clicked cancel or hasn't clicked OK yet.
+ */
+ MojangAccountPtr 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:
+ std::shared_ptr<MojangAccountList> m_accounts;
+
+ //! The account that was selected when the user clicked OK.
+ MojangAccountPtr m_selected;
+
+private:
+ Ui::AccountSelectDialog *ui;
+};
diff --git a/gui/dialogs/AccountSelectDialog.ui b/gui/dialogs/AccountSelectDialog.ui
new file mode 100644
index 00000000..7af1512a
--- /dev/null
+++ b/gui/dialogs/AccountSelectDialog.ui
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AccountSelectDialog</class>
+ <widget class="QDialog" name="AccountSelectDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>413</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 an account.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="listView"/>
+ </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>
diff --git a/gui/dialogs/CopyInstanceDialog.cpp b/gui/dialogs/CopyInstanceDialog.cpp
new file mode 100644
index 00000000..4095408b
--- /dev/null
+++ b/gui/dialogs/CopyInstanceDialog.cpp
@@ -0,0 +1,84 @@
+/* Copyright 2013 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 "MultiMC.h"
+#include "CopyInstanceDialog.h"
+#include "ui_CopyInstanceDialog.h"
+
+#include "gui/Platform.h"
+#include "gui/dialogs/VersionSelectDialog.h"
+#include "gui/dialogs/ProgressDialog.h"
+#include "gui/dialogs/IconPickerDialog.h"
+
+#include "logic/InstanceFactory.h"
+#include "logic/BaseVersion.h"
+#include "logic/icons/IconList.h"
+#include "logic/lists/MinecraftVersionList.h"
+#include "logic/tasks/Task.h"
+#include "logic/BaseInstance.h"
+
+CopyInstanceDialog::CopyInstanceDialog(BaseInstance *original, QWidget *parent)
+ :QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+ resize(minimumSizeHint());
+ layout()->setSizeConstraint(QLayout::SetFixedSize);
+
+ InstIconKey = original->iconKey();
+ ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey));
+ ui->instNameTextBox->setText(original->name());
+ ui->instNameTextBox->setFocus();
+}
+
+CopyInstanceDialog::~CopyInstanceDialog()
+{
+ delete ui;
+}
+
+void CopyInstanceDialog::updateDialogState()
+{
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!instName().isEmpty());
+}
+
+QString CopyInstanceDialog::instName() const
+{
+ return ui->instNameTextBox->text();
+}
+
+QString CopyInstanceDialog::iconKey() const
+{
+ return InstIconKey;
+}
+
+void CopyInstanceDialog::on_iconButton_clicked()
+{
+ IconPickerDialog dlg(this);
+ dlg.exec(InstIconKey);
+
+ if (dlg.result() == QDialog::Accepted)
+ {
+ InstIconKey = dlg.selectedIconKey;
+ ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey));
+ }
+}
+
+void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1)
+{
+ updateDialogState();
+}
diff --git a/gui/dialogs/CopyInstanceDialog.h b/gui/dialogs/CopyInstanceDialog.h
new file mode 100644
index 00000000..7ab366e2
--- /dev/null
+++ b/gui/dialogs/CopyInstanceDialog.h
@@ -0,0 +1,50 @@
+/* Copyright 2013 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 "logic/BaseVersion.h"
+
+class BaseInstance;
+
+namespace Ui
+{
+class CopyInstanceDialog;
+}
+
+class CopyInstanceDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit CopyInstanceDialog(BaseInstance *original, QWidget *parent = 0);
+ ~CopyInstanceDialog();
+
+ void updateDialogState();
+
+ QString instName() const;
+ QString iconKey() const;
+
+private
+slots:
+ void on_iconButton_clicked();
+ void on_instNameTextBox_textChanged(const QString &arg1);
+
+private:
+ Ui::CopyInstanceDialog *ui;
+ QString InstIconKey;
+ BaseInstance *m_original;
+};
diff --git a/gui/dialogs/CopyInstanceDialog.ui b/gui/dialogs/CopyInstanceDialog.ui
new file mode 100644
index 00000000..4aa1cb27
--- /dev/null
+++ b/gui/dialogs/CopyInstanceDialog.ui
@@ -0,0 +1,134 @@
+<?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>205</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Copy Instance</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../../graphics.qrc">
+ <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 resource="../../graphics.qrc">
+ <normaloff>:/icons/instances/infinity</normaloff>:/icons/instances/infinity</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>
+ <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>
+ <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>
diff --git a/gui/dialogs/CustomMessageBox.cpp b/gui/dialogs/CustomMessageBox.cpp
new file mode 100644
index 00000000..1d2ab58a
--- /dev/null
+++ b/gui/dialogs/CustomMessageBox.cpp
@@ -0,0 +1,34 @@
+/* Copyright 2013 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);
+
+ return messageBox;
+}
+}
diff --git a/gui/dialogs/CustomMessageBox.h b/gui/dialogs/CustomMessageBox.h
new file mode 100644
index 00000000..b08b9f57
--- /dev/null
+++ b/gui/dialogs/CustomMessageBox.h
@@ -0,0 +1,26 @@
+/* Copyright 2013 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);
+}
diff --git a/gui/dialogs/EditAccountDialog.cpp b/gui/dialogs/EditAccountDialog.cpp
new file mode 100644
index 00000000..a1bd5591
--- /dev/null
+++ b/gui/dialogs/EditAccountDialog.cpp
@@ -0,0 +1,51 @@
+/* Copyright 2013 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 <QDesktopServices>
+#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->setVisible(flags & UsernameField);
+ ui->passTextBox->setVisible(flags & PasswordField);
+}
+
+EditAccountDialog::~EditAccountDialog()
+{
+ delete ui;
+}
+
+void EditAccountDialog::on_label_linkActivated(const QString &link)
+{
+ QDesktopServices::openUrl(QUrl(link));
+}
+
+QString EditAccountDialog::username() const
+{
+ return ui->userTextBox->text();
+}
+
+QString EditAccountDialog::password() const
+{
+ return ui->passTextBox->text();
+}
diff --git a/gui/dialogs/EditAccountDialog.h b/gui/dialogs/EditAccountDialog.h
new file mode 100644
index 00000000..83f25124
--- /dev/null
+++ b/gui/dialogs/EditAccountDialog.h
@@ -0,0 +1,60 @@
+/* Copyright 2013 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();
+
+ /*!
+ * Gets the text entered in the dialog's username field.
+ */
+ QString username() const;
+
+ /*!
+ * Gets the text entered in the dialog's password field.
+ */
+ 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;
+};
diff --git a/gui/dialogs/EditAccountDialog.ui b/gui/dialogs/EditAccountDialog.ui
new file mode 100644
index 00000000..5f727bd4
--- /dev/null
+++ b/gui/dialogs/EditAccountDialog.ui
@@ -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>Edit Account</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>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 / Username</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>
diff --git a/gui/dialogs/EditNotesDialog.cpp b/gui/dialogs/EditNotesDialog.cpp
new file mode 100644
index 00000000..a265a4d0
--- /dev/null
+++ b/gui/dialogs/EditNotesDialog.cpp
@@ -0,0 +1,42 @@
+/* Copyright 2013 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 "EditNotesDialog.h"
+#include "ui_EditNotesDialog.h"
+#include "gui/Platform.h"
+
+#include <QIcon>
+#include <QApplication>
+
+EditNotesDialog::EditNotesDialog(QString notes, QString name, QWidget *parent)
+ : QDialog(parent), ui(new Ui::EditNotesDialog), m_instance_name(name),
+ m_instance_notes(notes)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+ ui->noteEditor->setText(notes);
+ setWindowTitle(tr("Edit notes of %1").arg(m_instance_name));
+ // connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));
+}
+
+EditNotesDialog::~EditNotesDialog()
+{
+ delete ui;
+}
+
+QString EditNotesDialog::getText()
+{
+ return ui->noteEditor->toPlainText();
+}
diff --git a/gui/dialogs/EditNotesDialog.h b/gui/dialogs/EditNotesDialog.h
new file mode 100644
index 00000000..b74558c4
--- /dev/null
+++ b/gui/dialogs/EditNotesDialog.h
@@ -0,0 +1,38 @@
+/* Copyright 2013 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 EditNotesDialog;
+}
+
+class EditNotesDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit EditNotesDialog(QString notes, QString name, QWidget *parent = 0);
+ ~EditNotesDialog();
+ QString getText();
+
+private:
+ Ui::EditNotesDialog *ui;
+ QString m_instance_name;
+ QString m_instance_notes;
+};
diff --git a/gui/dialogs/EditNotesDialog.ui b/gui/dialogs/EditNotesDialog.ui
new file mode 100644
index 00000000..487dfb84
--- /dev/null
+++ b/gui/dialogs/EditNotesDialog.ui
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EditNotesDialog</class>
+ <widget class="QDialog" name="EditNotesDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>459</width>
+ <height>399</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Edit Notes</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTextEdit" name="noteEditor">
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOn</enum>
+ </property>
+ <property name="acceptRichText">
+ <bool>false</bool>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </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>EditNotesDialog</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>EditNotesDialog</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>
diff --git a/gui/dialogs/IconPickerDialog.cpp b/gui/dialogs/IconPickerDialog.cpp
new file mode 100644
index 00000000..f7970b37
--- /dev/null
+++ b/gui/dialogs/IconPickerDialog.cpp
@@ -0,0 +1,156 @@
+/* Copyright 2013 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 "MultiMC.h"
+
+#include "IconPickerDialog.h"
+#include "ui_IconPickerDialog.h"
+
+#include "gui/Platform.h"
+#include "gui/widgets/InstanceDelegate.h"
+
+#include "logic/icons/IconList.h"
+
+IconPickerDialog::IconPickerDialog(QWidget *parent)
+ : QDialog(parent), ui(new Ui::IconPickerDialog)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ 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(MMC->icons().get());
+
+ 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)));
+}
+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
+ QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(),
+ tr("Icons") + "(*.png *.jpg *.jpeg *.ico)");
+ MMC->icons()->installIcons(fileNames);
+}
+
+void IconPickerDialog::removeSelectedIcon()
+{
+ MMC->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::exec(QString selection)
+{
+ auto list = MMC->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;
+}
diff --git a/gui/dialogs/IconPickerDialog.h b/gui/dialogs/IconPickerDialog.h
new file mode 100644
index 00000000..f00c2388
--- /dev/null
+++ b/gui/dialogs/IconPickerDialog.h
@@ -0,0 +1,48 @@
+/* Copyright 2013 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 exec(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();
+};
diff --git a/gui/dialogs/IconPickerDialog.ui b/gui/dialogs/IconPickerDialog.ui
new file mode 100644
index 00000000..c548edfb
--- /dev/null
+++ b/gui/dialogs/IconPickerDialog.ui
@@ -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>
diff --git a/gui/dialogs/InstanceSettings.cpp b/gui/dialogs/InstanceSettings.cpp
new file mode 100644
index 00000000..edb4a921
--- /dev/null
+++ b/gui/dialogs/InstanceSettings.cpp
@@ -0,0 +1,243 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Authors: Andrew Okin
+ * Peterix
+ * Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * 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 "MultiMC.h"
+#include "InstanceSettings.h"
+#include "ui_InstanceSettings.h"
+#include "gui/Platform.h"
+#include "gui/dialogs/VersionSelectDialog.h"
+
+#include "logic/JavaUtils.h"
+#include "logic/NagUtils.h"
+#include "logic/lists/JavaVersionList.h"
+#include "logic/JavaChecker.h"
+
+#include <QFileDialog>
+#include <QMessageBox>
+
+InstanceSettings::InstanceSettings(SettingsObject *obj, QWidget *parent)
+ : QDialog(parent), ui(new Ui::InstanceSettings), m_obj(obj)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+
+ restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("SettingsGeometry").toByteArray()));
+
+ loadSettings();
+}
+
+InstanceSettings::~InstanceSettings()
+{
+ delete ui;
+}
+
+void InstanceSettings::showEvent(QShowEvent *ev)
+{
+ QDialog::showEvent(ev);
+}
+
+void InstanceSettings::closeEvent(QCloseEvent *ev)
+{
+ MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
+
+ QDialog::closeEvent(ev);
+}
+
+void InstanceSettings::on_customCommandsGroupBox_toggled(bool state)
+{
+ ui->labelCustomCmdsDescription->setEnabled(state);
+}
+
+void InstanceSettings::on_buttonBox_accepted()
+{
+ MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
+
+ applySettings();
+ accept();
+}
+
+void InstanceSettings::on_buttonBox_rejected()
+{
+ MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
+
+ reject();
+}
+
+void InstanceSettings::applySettings()
+{
+ // Console
+ bool console = ui->consoleSettingsBox->isChecked();
+ m_obj->set("OverrideConsole", console);
+ if (console)
+ {
+ m_obj->set("ShowConsole", ui->showConsoleCheck->isChecked());
+ m_obj->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked());
+ }
+ else
+ {
+ m_obj->reset("ShowConsole");
+ m_obj->reset("AutoCloseConsole");
+ }
+
+ // Window Size
+ bool window = ui->windowSizeGroupBox->isChecked();
+ m_obj->set("OverrideWindow", window);
+ if (window)
+ {
+ m_obj->set("LaunchMaximized", ui->maximizedCheckBox->isChecked());
+ m_obj->set("MinecraftWinWidth", ui->windowWidthSpinBox->value());
+ m_obj->set("MinecraftWinHeight", ui->windowHeightSpinBox->value());
+ }
+ else
+ {
+ m_obj->reset("LaunchMaximized");
+ m_obj->reset("MinecraftWinWidth");
+ m_obj->reset("MinecraftWinHeight");
+ }
+
+ // Memory
+ bool memory = ui->memoryGroupBox->isChecked();
+ m_obj->set("OverrideMemory", memory);
+ if (memory)
+ {
+ m_obj->set("MinMemAlloc", ui->minMemSpinBox->value());
+ m_obj->set("MaxMemAlloc", ui->maxMemSpinBox->value());
+ m_obj->set("PermGen", ui->permGenSpinBox->value());
+ }
+ else
+ {
+ m_obj->reset("MinMemAlloc");
+ m_obj->reset("MaxMemAlloc");
+ m_obj->reset("PermGen");
+ }
+
+ // Java Settings
+ bool java = ui->javaSettingsGroupBox->isChecked();
+ m_obj->set("OverrideJava", java);
+ if (java)
+ {
+ m_obj->set("JavaPath", ui->javaPathTextBox->text());
+ m_obj->set("JvmArgs", ui->jvmArgsTextBox->text());
+
+ NagUtils::checkJVMArgs(m_obj->get("JvmArgs").toString(), this->parentWidget());
+ }
+ else
+ {
+ m_obj->reset("JavaPath");
+ m_obj->reset("JvmArgs");
+ }
+
+ // Custom Commands
+ bool custcmd = ui->customCommandsGroupBox->isChecked();
+ m_obj->set("OverrideCommands", custcmd);
+ if (custcmd)
+ {
+ m_obj->set("PreLaunchCommand", ui->preLaunchCmdTextBox->text());
+ m_obj->set("PostExitCommand", ui->postExitCmdTextBox->text());
+ }
+ else
+ {
+ m_obj->reset("PreLaunchCommand");
+ m_obj->reset("PostExitCommand");
+ }
+}
+
+void InstanceSettings::loadSettings()
+{
+ // Console
+ ui->consoleSettingsBox->setChecked(m_obj->get("OverrideConsole").toBool());
+ ui->showConsoleCheck->setChecked(m_obj->get("ShowConsole").toBool());
+ ui->autoCloseConsoleCheck->setChecked(m_obj->get("AutoCloseConsole").toBool());
+
+ // Window Size
+ ui->windowSizeGroupBox->setChecked(m_obj->get("OverrideWindow").toBool());
+ ui->maximizedCheckBox->setChecked(m_obj->get("LaunchMaximized").toBool());
+ ui->windowWidthSpinBox->setValue(m_obj->get("MinecraftWinWidth").toInt());
+ ui->windowHeightSpinBox->setValue(m_obj->get("MinecraftWinHeight").toInt());
+
+ // Memory
+ ui->memoryGroupBox->setChecked(m_obj->get("OverrideMemory").toBool());
+ ui->minMemSpinBox->setValue(m_obj->get("MinMemAlloc").toInt());
+ ui->maxMemSpinBox->setValue(m_obj->get("MaxMemAlloc").toInt());
+ ui->permGenSpinBox->setValue(m_obj->get("PermGen").toInt());
+
+ // Java Settings
+ ui->javaSettingsGroupBox->setChecked(m_obj->get("OverrideJava").toBool());
+ ui->javaPathTextBox->setText(m_obj->get("JavaPath").toString());
+ ui->jvmArgsTextBox->setText(m_obj->get("JvmArgs").toString());
+
+ // Custom Commands
+ ui->customCommandsGroupBox->setChecked(m_obj->get("OverrideCommands").toBool());
+ ui->preLaunchCmdTextBox->setText(m_obj->get("PreLaunchCommand").toString());
+ ui->postExitCmdTextBox->setText(m_obj->get("PostExitCommand").toString());
+}
+
+void InstanceSettings::on_javaDetectBtn_clicked()
+{
+ JavaVersionPtr java;
+
+ VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true);
+ vselect.setResizeOn(2);
+ vselect.exec();
+
+ if (vselect.result() == QDialog::Accepted && vselect.selectedVersion())
+ {
+ java = std::dynamic_pointer_cast<JavaVersion>(vselect.selectedVersion());
+ ui->javaPathTextBox->setText(java->path);
+ }
+}
+
+void InstanceSettings::on_javaBrowseBtn_clicked()
+{
+ QString dir = QFileDialog::getOpenFileName(this, tr("Find Java executable"));
+ if (!dir.isNull())
+ {
+ ui->javaPathTextBox->setText(dir);
+ }
+}
+
+void InstanceSettings::on_javaTestBtn_clicked()
+{
+ checker.reset(new JavaChecker());
+ connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
+ SLOT(checkFinished(JavaCheckResult)));
+ checker->path = ui->javaPathTextBox->text();
+ checker->performCheck();
+}
+
+void InstanceSettings::checkFinished(JavaCheckResult result)
+{
+ if (result.valid)
+ {
+ QString text;
+ text += "Java test succeeded!\n";
+ if (result.is_64bit)
+ text += "Using 64bit java.\n";
+ text += "\n";
+ text += "Platform reported: " + result.realPlatform;
+ QMessageBox::information(this, tr("Java test success"), text);
+ }
+ else
+ {
+ QMessageBox::warning(
+ this, tr("Java test failure"),
+ tr("The specified java binary didn't work. You should use the auto-detect feature, "
+ "or set the path to the java executable."));
+ }
+}
diff --git a/gui/dialogs/InstanceSettings.h b/gui/dialogs/InstanceSettings.h
new file mode 100644
index 00000000..e296db4c
--- /dev/null
+++ b/gui/dialogs/InstanceSettings.h
@@ -0,0 +1,60 @@
+/* Copyright 2013 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 "settingsobject.h"
+#include "logic/JavaChecker.h"
+
+namespace Ui
+{
+class InstanceSettings;
+}
+
+class InstanceSettings : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit InstanceSettings(SettingsObject *s, QWidget *parent = 0);
+ ~InstanceSettings();
+
+ void updateCheckboxStuff();
+
+ void applySettings();
+ void loadSettings();
+
+protected:
+ virtual void showEvent(QShowEvent *);
+ virtual void closeEvent(QCloseEvent *);
+private
+slots:
+ void on_customCommandsGroupBox_toggled(bool arg1);
+ void on_buttonBox_accepted();
+ void on_buttonBox_rejected();
+
+ void on_javaDetectBtn_clicked();
+
+ void on_javaTestBtn_clicked();
+
+ void on_javaBrowseBtn_clicked();
+
+ void checkFinished(JavaCheckResult result);
+private:
+ Ui::InstanceSettings *ui;
+ SettingsObject *m_obj;
+ std::shared_ptr<JavaChecker> checker;
+};
diff --git a/gui/dialogs/InstanceSettings.ui b/gui/dialogs/InstanceSettings.ui
new file mode 100644
index 00000000..9c7e1757
--- /dev/null
+++ b/gui/dialogs/InstanceSettings.ui
@@ -0,0 +1,419 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>InstanceSettings</class>
+ <widget class="QDialog" name="InstanceSettings">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>526</width>
+ <height>637</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Instance Settings</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTabWidget" name="settingsTabs">
+ <property name="tabShape">
+ <enum>QTabWidget::Rounded</enum>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="minecraftTab">
+ <attribute name="title">
+ <string>Minecraft</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QGroupBox" name="windowSizeGroupBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Window Size</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QCheckBox" name="maximizedCheckBox">
+ <property name="text">
+ <string>Start Minecraft maximized?</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayoutWindowSize">
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelWindowHeight">
+ <property name="text">
+ <string>Window height:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelWindowWidth">
+ <property name="text">
+ <string>Window width:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="windowWidthSpinBox">
+ <property name="minimum">
+ <number>854</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>854</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="windowHeightSpinBox">
+ <property name="minimum">
+ <number>480</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ <property name="value">
+ <number>480</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="consoleSettingsBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Console Settings</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="showConsoleCheck">
+ <property name="text">
+ <string>Show console while the game is running?</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="autoCloseConsoleCheck">
+ <property name="text">
+ <string>Automatically close console when the game quits?</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacerMinecraft">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="javaTab">
+ <attribute name="title">
+ <string>Java</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QGroupBox" name="memoryGroupBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Memory</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="maxMemSpinBox">
+ <property name="toolTip">
+ <string>The maximum amount of memory Minecraft is allowed to use.</string>
+ </property>
+ <property name="suffix">
+ <string> MB</string>
+ </property>
+ <property name="minimum">
+ <number>512</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ <property name="singleStep">
+ <number>128</number>
+ </property>
+ <property name="value">
+ <number>1024</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelMinMem">
+ <property name="text">
+ <string>Minimum memory allocation:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelMaxMem">
+ <property name="text">
+ <string>Maximum memory allocation:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="minMemSpinBox">
+ <property name="toolTip">
+ <string>The amount of memory Minecraft is started with.</string>
+ </property>
+ <property name="suffix">
+ <string> MB</string>
+ </property>
+ <property name="minimum">
+ <number>256</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ <property name="singleStep">
+ <number>128</number>
+ </property>
+ <property name="value">
+ <number>256</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSpinBox" name="permGenSpinBox">
+ <property name="toolTip">
+ <string>The amount of memory available to store loaded Java classes.</string>
+ </property>
+ <property name="suffix">
+ <string> MB</string>
+ </property>
+ <property name="minimum">
+ <number>64</number>
+ </property>
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ <property name="singleStep">
+ <number>8</number>
+ </property>
+ <property name="value">
+ <number>64</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="labelPermGen">
+ <property name="text">
+ <string>PermGen:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="javaSettingsGroupBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Java Settings</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="2" column="4">
+ <widget class="QPushButton" name="javaTestBtn">
+ <property name="text">
+ <string>Test</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelJavaPath">
+ <property name="text">
+ <string>Java path:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="labelJVMArgs">
+ <property name="text">
+ <string>JVM arguments:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2" colspan="3">
+ <widget class="QLineEdit" name="jvmArgsTextBox"/>
+ </item>
+ <item row="0" column="2" colspan="3">
+ <widget class="QLineEdit" name="javaPathTextBox"/>
+ </item>
+ <item row="2" column="3">
+ <widget class="QPushButton" name="javaBrowseBtn">
+ <property name="text">
+ <string>Browse...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="javaDetectBtn">
+ <property name="text">
+ <string>Auto-detect...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="customCommandsGroupBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Custom Commands</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="preLaunchCmdTextBox"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelPostExitCmd">
+ <property name="text">
+ <string>Post-exit command:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelPreLaunchCmd">
+ <property name="text">
+ <string>Pre-launch command:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="postExitCmdTextBox"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="labelCustomCmdsDescription">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Pre-launch command runs before the instance launches and post-exit command runs after it exits. Both will be run in MultiMC's working directory with INST_ID, INST_DIR, and INST_NAME as environment variables.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </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>settingsTabs</tabstop>
+ <tabstop>buttonBox</tabstop>
+ <tabstop>windowSizeGroupBox</tabstop>
+ <tabstop>maximizedCheckBox</tabstop>
+ <tabstop>windowWidthSpinBox</tabstop>
+ <tabstop>windowHeightSpinBox</tabstop>
+ <tabstop>consoleSettingsBox</tabstop>
+ <tabstop>showConsoleCheck</tabstop>
+ <tabstop>autoCloseConsoleCheck</tabstop>
+ <tabstop>memoryGroupBox</tabstop>
+ <tabstop>minMemSpinBox</tabstop>
+ <tabstop>maxMemSpinBox</tabstop>
+ <tabstop>permGenSpinBox</tabstop>
+ <tabstop>javaSettingsGroupBox</tabstop>
+ <tabstop>jvmArgsTextBox</tabstop>
+ <tabstop>customCommandsGroupBox</tabstop>
+ <tabstop>preLaunchCmdTextBox</tabstop>
+ <tabstop>postExitCmdTextBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/gui/dialogs/LegacyModEditDialog.cpp b/gui/dialogs/LegacyModEditDialog.cpp
new file mode 100644
index 00000000..66d53ee1
--- /dev/null
+++ b/gui/dialogs/LegacyModEditDialog.cpp
@@ -0,0 +1,393 @@
+/* Copyright 2013 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 "MultiMC.h"
+#include "LegacyModEditDialog.h"
+#include "ModEditDialogCommon.h"
+#include "VersionSelectDialog.h"
+#include "ProgressDialog.h"
+#include "ui_LegacyModEditDialog.h"
+#include "logic/ModList.h"
+#include "logic/lists/ForgeVersionList.h"
+#include "gui/Platform.h"
+
+#include <pathutils.h>
+#include <QFileDialog>
+//#include <QMessageBox>
+#include <QDebug>
+#include <QEvent>
+#include <QKeyEvent>
+
+LegacyModEditDialog::LegacyModEditDialog(LegacyInstance *inst, QWidget *parent)
+ : QDialog(parent), ui(new Ui::LegacyModEditDialog), m_inst(inst)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+
+ // Jar mods
+ {
+ ensureFolderPathExists(m_inst->jarModsDir());
+ m_jarmods = m_inst->jarModList();
+ ui->jarModsTreeView->setModel(m_jarmods.get());
+#ifndef Q_OS_LINUX
+ // FIXME: internal DnD causes segfaults later
+ ui->jarModsTreeView->setDragDropMode(QAbstractItemView::DragDrop);
+ // FIXME: DnD is glitched with contiguous (we move only first item in selection)
+ ui->jarModsTreeView->setSelectionMode(QAbstractItemView::SingleSelection);
+#endif
+ ui->jarModsTreeView->installEventFilter(this);
+ m_jarmods->startWatching();
+ auto smodel = ui->jarModsTreeView->selectionModel();
+ connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)),
+ SLOT(jarCurrent(QModelIndex, QModelIndex)));
+ }
+ // Core mods
+ {
+ ensureFolderPathExists(m_inst->coreModsDir());
+ m_coremods = m_inst->coreModList();
+ ui->coreModsTreeView->setModel(m_coremods.get());
+ ui->coreModsTreeView->installEventFilter(this);
+ m_coremods->startWatching();
+ auto smodel = ui->coreModsTreeView->selectionModel();
+ connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)),
+ SLOT(coreCurrent(QModelIndex, QModelIndex)));
+ }
+ // Loader mods
+ {
+ ensureFolderPathExists(m_inst->loaderModsDir());
+ m_mods = m_inst->loaderModList();
+ ui->loaderModTreeView->setModel(m_mods.get());
+ ui->loaderModTreeView->installEventFilter(this);
+ m_mods->startWatching();
+ auto smodel = ui->loaderModTreeView->selectionModel();
+ connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)),
+ SLOT(loaderCurrent(QModelIndex, QModelIndex)));
+ }
+ // texture packs
+ {
+ ensureFolderPathExists(m_inst->texturePacksDir());
+ m_texturepacks = m_inst->texturePackList();
+ ui->texPackTreeView->setModel(m_texturepacks.get());
+ ui->texPackTreeView->installEventFilter(this);
+ m_texturepacks->startWatching();
+ }
+}
+
+LegacyModEditDialog::~LegacyModEditDialog()
+{
+ m_mods->stopWatching();
+ m_coremods->stopWatching();
+ m_jarmods->stopWatching();
+ m_texturepacks->stopWatching();
+ delete ui;
+}
+
+bool LegacyModEditDialog::coreListFilter(QKeyEvent *keyEvent)
+{
+ switch (keyEvent->key())
+ {
+ case Qt::Key_Delete:
+ on_rmCoreBtn_clicked();
+ return true;
+ case Qt::Key_Plus:
+ on_addCoreBtn_clicked();
+ return true;
+ default:
+ break;
+ }
+ return QDialog::eventFilter(ui->coreModsTreeView, keyEvent);
+}
+
+bool LegacyModEditDialog::jarListFilter(QKeyEvent *keyEvent)
+{
+ switch (keyEvent->key())
+ {
+ case Qt::Key_Up:
+ {
+ if (keyEvent->modifiers() & Qt::ControlModifier)
+ {
+ on_moveJarUpBtn_clicked();
+ return true;
+ }
+ break;
+ }
+ case Qt::Key_Down:
+ {
+ if (keyEvent->modifiers() & Qt::ControlModifier)
+ {
+ on_moveJarDownBtn_clicked();
+ return true;
+ }
+ break;
+ }
+ case Qt::Key_Delete:
+ on_rmJarBtn_clicked();
+ return true;
+ case Qt::Key_Plus:
+ on_addJarBtn_clicked();
+ return true;
+ default:
+ break;
+ }
+ return QDialog::eventFilter(ui->jarModsTreeView, keyEvent);
+}
+
+bool LegacyModEditDialog::loaderListFilter(QKeyEvent *keyEvent)
+{
+ switch (keyEvent->key())
+ {
+ case Qt::Key_Delete:
+ on_rmModBtn_clicked();
+ return true;
+ case Qt::Key_Plus:
+ on_addModBtn_clicked();
+ return true;
+ default:
+ break;
+ }
+ return QDialog::eventFilter(ui->loaderModTreeView, keyEvent);
+}
+
+bool LegacyModEditDialog::texturePackListFilter(QKeyEvent *keyEvent)
+{
+ switch (keyEvent->key())
+ {
+ case Qt::Key_Delete:
+ on_rmTexPackBtn_clicked();
+ return true;
+ case Qt::Key_Plus:
+ on_addTexPackBtn_clicked();
+ return true;
+ default:
+ break;
+ }
+ return QDialog::eventFilter(ui->texPackTreeView, keyEvent);
+}
+
+bool LegacyModEditDialog::eventFilter(QObject *obj, QEvent *ev)
+{
+ if (ev->type() != QEvent::KeyPress)
+ {
+ return QDialog::eventFilter(obj, ev);
+ }
+ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
+ if (obj == ui->jarModsTreeView)
+ return jarListFilter(keyEvent);
+ if (obj == ui->coreModsTreeView)
+ return coreListFilter(keyEvent);
+ if (obj == ui->loaderModTreeView)
+ return loaderListFilter(keyEvent);
+ if (obj == ui->texPackTreeView)
+ return texturePackListFilter(keyEvent);
+ return QDialog::eventFilter(obj, ev);
+}
+
+void LegacyModEditDialog::on_addCoreBtn_clicked()
+{
+ //: Title of core mod selection dialog
+ QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Core Mods"));
+ for (auto filename : fileNames)
+ {
+ m_coremods->stopWatching();
+ m_coremods->installMod(QFileInfo(filename));
+ m_coremods->startWatching();
+ }
+}
+void LegacyModEditDialog::on_addForgeBtn_clicked()
+{
+ VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this);
+ vselect.setFilter(1, m_inst->intendedVersionId());
+ if (vselect.exec() && vselect.selectedVersion())
+ {
+ ForgeVersionPtr forge =
+ std::dynamic_pointer_cast<ForgeVersion>(vselect.selectedVersion());
+ if (!forge)
+ return;
+ auto entry = MMC->metacache()->resolveEntry("minecraftforge", forge->filename);
+ if (entry->stale)
+ {
+ NetJob *fjob = new NetJob("Forge download");
+ fjob->addNetAction(CacheDownload::make(forge->universal_url, entry));
+ ProgressDialog dlg(this);
+ dlg.exec(fjob);
+ if (dlg.result() == QDialog::Accepted)
+ {
+ m_jarmods->stopWatching();
+ m_jarmods->installMod(QFileInfo(entry->getFullPath()));
+ m_jarmods->startWatching();
+ }
+ else
+ {
+ // failed to download forge :/
+ }
+ }
+ else
+ {
+ m_jarmods->stopWatching();
+ m_jarmods->installMod(QFileInfo(entry->getFullPath()));
+ m_jarmods->startWatching();
+ }
+ }
+}
+void LegacyModEditDialog::on_addJarBtn_clicked()
+{
+ //: Title of jar mod selection dialog
+ QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Jar Mods"));
+ for (auto filename : fileNames)
+ {
+ m_jarmods->stopWatching();
+ m_jarmods->installMod(QFileInfo(filename));
+ m_jarmods->startWatching();
+ }
+}
+void LegacyModEditDialog::on_addModBtn_clicked()
+{
+ //: Title of regular mod selection dialog
+ QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Loader Mods"));
+ for (auto filename : fileNames)
+ {
+ m_mods->stopWatching();
+ m_mods->installMod(QFileInfo(filename));
+ m_mods->startWatching();
+ }
+}
+void LegacyModEditDialog::on_addTexPackBtn_clicked()
+{
+ //: Title of texture pack selection dialog
+ QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Texture Packs"));
+ for (auto filename : fileNames)
+ {
+ m_texturepacks->stopWatching();
+ m_texturepacks->installMod(QFileInfo(filename));
+ m_texturepacks->startWatching();
+ }
+}
+
+void LegacyModEditDialog::on_moveJarDownBtn_clicked()
+{
+ int first, last;
+ auto list = ui->jarModsTreeView->selectionModel()->selectedRows();
+
+ if (!lastfirst(list, first, last))
+ return;
+
+ m_jarmods->moveModsDown(first, last);
+}
+void LegacyModEditDialog::on_moveJarUpBtn_clicked()
+{
+ int first, last;
+ auto list = ui->jarModsTreeView->selectionModel()->selectedRows();
+
+ if (!lastfirst(list, first, last))
+ return;
+ m_jarmods->moveModsUp(first, last);
+}
+void LegacyModEditDialog::on_rmCoreBtn_clicked()
+{
+ int first, last;
+ auto list = ui->coreModsTreeView->selectionModel()->selectedRows();
+
+ if (!lastfirst(list, first, last))
+ return;
+ m_coremods->stopWatching();
+ m_coremods->deleteMods(first, last);
+ m_coremods->startWatching();
+}
+void LegacyModEditDialog::on_rmJarBtn_clicked()
+{
+ int first, last;
+ auto list = ui->jarModsTreeView->selectionModel()->selectedRows();
+
+ if (!lastfirst(list, first, last))
+ return;
+ m_jarmods->stopWatching();
+ m_jarmods->deleteMods(first, last);
+ m_jarmods->startWatching();
+}
+void LegacyModEditDialog::on_rmModBtn_clicked()
+{
+ int first, last;
+ auto list = ui->loaderModTreeView->selectionModel()->selectedRows();
+
+ if (!lastfirst(list, first, last))
+ return;
+ m_mods->stopWatching();
+ m_mods->deleteMods(first, last);
+ m_mods->startWatching();
+}
+void LegacyModEditDialog::on_rmTexPackBtn_clicked()
+{
+ int first, last;
+ auto list = ui->texPackTreeView->selectionModel()->selectedRows();
+
+ if (!lastfirst(list, first, last))
+ return;
+ m_texturepacks->stopWatching();
+ m_texturepacks->deleteMods(first, last);
+ m_texturepacks->startWatching();
+}
+void LegacyModEditDialog::on_viewCoreBtn_clicked()
+{
+ openDirInDefaultProgram(m_inst->coreModsDir(), true);
+}
+void LegacyModEditDialog::on_viewModBtn_clicked()
+{
+ openDirInDefaultProgram(m_inst->loaderModsDir(), true);
+}
+void LegacyModEditDialog::on_viewTexPackBtn_clicked()
+{
+ openDirInDefaultProgram(m_inst->texturePacksDir(), true);
+}
+
+void LegacyModEditDialog::on_buttonBox_rejected()
+{
+ close();
+}
+
+void LegacyModEditDialog::jarCurrent(QModelIndex current, QModelIndex previous)
+{
+ if (!current.isValid())
+ {
+ ui->jarMIFrame->clear();
+ return;
+ }
+ int row = current.row();
+ Mod &m = m_jarmods->operator[](row);
+ ui->jarMIFrame->updateWithMod(m);
+}
+
+void LegacyModEditDialog::coreCurrent(QModelIndex current, QModelIndex previous)
+{
+ if (!current.isValid())
+ {
+ ui->coreMIFrame->clear();
+ return;
+ }
+ int row = current.row();
+ Mod &m = m_coremods->operator[](row);
+ ui->coreMIFrame->updateWithMod(m);
+}
+
+void LegacyModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previous)
+{
+ if (!current.isValid())
+ {
+ ui->loaderMIFrame->clear();
+ return;
+ }
+ int row = current.row();
+ Mod &m = m_mods->operator[](row);
+ ui->loaderMIFrame->updateWithMod(m);
+}
diff --git a/gui/dialogs/LegacyModEditDialog.h b/gui/dialogs/LegacyModEditDialog.h
new file mode 100644
index 00000000..d5582aef
--- /dev/null
+++ b/gui/dialogs/LegacyModEditDialog.h
@@ -0,0 +1,78 @@
+/* Copyright 2013 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 "logic/LegacyInstance.h"
+#include <logic/net/NetJob.h>
+
+namespace Ui
+{
+class LegacyModEditDialog;
+}
+
+class LegacyModEditDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit LegacyModEditDialog(LegacyInstance *inst, QWidget *parent = 0);
+ ~LegacyModEditDialog();
+
+private
+slots:
+
+ void on_addJarBtn_clicked();
+ void on_rmJarBtn_clicked();
+ void on_addForgeBtn_clicked();
+ void on_moveJarUpBtn_clicked();
+ void on_moveJarDownBtn_clicked();
+
+ void on_addCoreBtn_clicked();
+ void on_rmCoreBtn_clicked();
+ void on_viewCoreBtn_clicked();
+
+ void on_addModBtn_clicked();
+ void on_rmModBtn_clicked();
+ void on_viewModBtn_clicked();
+
+ void on_addTexPackBtn_clicked();
+ void on_rmTexPackBtn_clicked();
+ void on_viewTexPackBtn_clicked();
+
+ // Questionable: SettingsDialog doesn't need this for some reason?
+ void on_buttonBox_rejected();
+
+ void jarCurrent(QModelIndex current, QModelIndex previous);
+ void coreCurrent(QModelIndex current, QModelIndex previous);
+ void loaderCurrent(QModelIndex current, QModelIndex previous);
+
+protected:
+ bool eventFilter(QObject *obj, QEvent *ev);
+ bool jarListFilter(QKeyEvent *ev);
+ bool coreListFilter(QKeyEvent *ev);
+ bool loaderListFilter(QKeyEvent *ev);
+ bool texturePackListFilter(QKeyEvent *ev);
+
+private:
+ Ui::LegacyModEditDialog *ui;
+ std::shared_ptr<ModList> m_mods;
+ std::shared_ptr<ModList> m_coremods;
+ std::shared_ptr<ModList> m_jarmods;
+ std::shared_ptr<ModList> m_texturepacks;
+ LegacyInstance *m_inst;
+ NetJobPtr forgeJob;
+};
diff --git a/gui/dialogs/LegacyModEditDialog.ui b/gui/dialogs/LegacyModEditDialog.ui
new file mode 100644
index 00000000..0662c712
--- /dev/null
+++ b/gui/dialogs/LegacyModEditDialog.ui
@@ -0,0 +1,321 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LegacyModEditDialog</class>
+ <widget class="QDialog" name="LegacyModEditDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>540</width>
+ <height>420</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Edit Mods</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="jarTab">
+ <attribute name="title">
+ <string>Jar Mods</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="ModListView" name="jarModsTreeView">
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOn</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="jarModsButtonBox">
+ <item>
+ <widget class="QPushButton" name="addJarBtn">
+ <property name="text">
+ <string>&amp;Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="rmJarBtn">
+ <property name="text">
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="addForgeBtn">
+ <property name="text">
+ <string>MCForge</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="jarModsButtonSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="moveJarUpBtn">
+ <property name="text">
+ <string>Move &amp;Up</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="moveJarDownBtn">
+ <property name="text">
+ <string>Move &amp;Down</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="MCModInfoFrame" name="jarMIFrame">
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="coreTab">
+ <attribute name="title">
+ <string>Core Mods</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="ModListView" name="coreModsTreeView">
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::DropOnly</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="coreModsButtonBox">
+ <item>
+ <widget class="QPushButton" name="addCoreBtn">
+ <property name="text">
+ <string>&amp;Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="rmCoreBtn">
+ <property name="text">
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="coreModsButtonSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="viewCoreBtn">
+ <property name="text">
+ <string>&amp;View Folder</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="MCModInfoFrame" name="coreMIFrame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="modTab">
+ <attribute name="title">
+ <string>Loader Mods</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="ModListView" name="loaderModTreeView">
+ <property name="acceptDrops">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::DropOnly</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="mlModsButtonBox">
+ <item>
+ <widget class="QPushButton" name="addModBtn">
+ <property name="text">
+ <string>&amp;Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="rmModBtn">
+ <property name="text">
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="mlModsButtonSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="viewModBtn">
+ <property name="text">
+ <string>&amp;View Folder</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="MCModInfoFrame" name="loaderMIFrame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="texPackTab">
+ <property name="acceptDrops">
+ <bool>false</bool>
+ </property>
+ <attribute name="title">
+ <string>Texture Packs</string>
+ </attribute>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="ModListView" name="texPackTreeView">
+ <property name="acceptDrops">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::DropOnly</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="texturePacksButtonBox">
+ <item>
+ <widget class="QPushButton" name="addTexPackBtn">
+ <property name="text">
+ <string>&amp;Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="rmTexPackBtn">
+ <property name="text">
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="texturePacksButtonSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="viewTexPackBtn">
+ <property name="text">
+ <string>&amp;View Folder</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ModListView</class>
+ <extends>QTreeView</extends>
+ <header>gui/widgets/ModListView.h</header>
+ </customwidget>
+ <customwidget>
+ <class>MCModInfoFrame</class>
+ <extends>QFrame</extends>
+ <header>gui/widgets/MCModInfoFrame.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/gui/dialogs/LwjglSelectDialog.cpp b/gui/dialogs/LwjglSelectDialog.cpp
new file mode 100644
index 00000000..046a4e2e
--- /dev/null
+++ b/gui/dialogs/LwjglSelectDialog.cpp
@@ -0,0 +1,72 @@
+/* Copyright 2013 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 "MultiMC.h"
+#include "LwjglSelectDialog.h"
+#include "ui_LwjglSelectDialog.h"
+#include "gui/Platform.h"
+
+#include "logic/lists/LwjglVersionList.h"
+
+LWJGLSelectDialog::LWJGLSelectDialog(QWidget *parent)
+ : QDialog(parent), ui(new Ui::LWJGLSelectDialog)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+ ui->labelStatus->setVisible(false);
+ auto lwjgllist = MMC->lwjgllist();
+ ui->lwjglListView->setModel(lwjgllist.get());
+
+ connect(lwjgllist.get(), SIGNAL(loadingStateUpdated(bool)),
+ SLOT(loadingStateUpdated(bool)));
+ connect(lwjgllist.get(), SIGNAL(loadListFailed(QString)), SLOT(loadingFailed(QString)));
+ loadingStateUpdated(lwjgllist->isLoading());
+}
+
+LWJGLSelectDialog::~LWJGLSelectDialog()
+{
+ delete ui;
+}
+
+QString LWJGLSelectDialog::selectedVersion() const
+{
+ return MMC->lwjgllist()
+ ->data(ui->lwjglListView->selectionModel()->currentIndex(), Qt::DisplayRole)
+ .toString();
+}
+
+void LWJGLSelectDialog::on_refreshButton_clicked()
+{
+ if (!MMC->lwjgllist()->isLoading())
+ MMC->lwjgllist()->loadList();
+}
+
+void LWJGLSelectDialog::loadingStateUpdated(bool loading)
+{
+ setEnabled(!loading);
+ if (loading)
+ {
+ ui->labelStatus->setText(tr("Loading LWJGL version list..."));
+ ui->labelStatus->setStyleSheet("QLabel { color: black; }");
+ }
+ ui->labelStatus->setVisible(loading);
+}
+
+void LWJGLSelectDialog::loadingFailed(QString error)
+{
+ ui->labelStatus->setText(error);
+ ui->labelStatus->setStyleSheet("QLabel { color: red; }");
+ ui->labelStatus->setVisible(true);
+}
diff --git a/gui/dialogs/LwjglSelectDialog.h b/gui/dialogs/LwjglSelectDialog.h
new file mode 100644
index 00000000..2724cbe8
--- /dev/null
+++ b/gui/dialogs/LwjglSelectDialog.h
@@ -0,0 +1,44 @@
+/* Copyright 2013 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 LWJGLSelectDialog;
+}
+
+class LWJGLSelectDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit LWJGLSelectDialog(QWidget *parent = 0);
+ ~LWJGLSelectDialog();
+
+ QString selectedVersion() const;
+
+private
+slots:
+ void on_refreshButton_clicked();
+
+ void loadingStateUpdated(bool loading);
+ void loadingFailed(QString error);
+
+private:
+ Ui::LWJGLSelectDialog *ui;
+};
diff --git a/gui/dialogs/LwjglSelectDialog.ui b/gui/dialogs/LwjglSelectDialog.ui
new file mode 100644
index 00000000..0287ec8f
--- /dev/null
+++ b/gui/dialogs/LwjglSelectDialog.ui
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LWJGLSelectDialog</class>
+ <widget class="QDialog" name="LWJGLSelectDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Manage Lwjgl Versions</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="labelStatus">
+ <property name="text">
+ <string>Status label...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListView" name="lwjglListView"/>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="refreshButton">
+ <property name="text">
+ <string>&amp;Refresh</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>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>LWJGLSelectDialog</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>LWJGLSelectDialog</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>
diff --git a/gui/dialogs/ModEditDialogCommon.cpp b/gui/dialogs/ModEditDialogCommon.cpp
new file mode 100644
index 00000000..eee42e5e
--- /dev/null
+++ b/gui/dialogs/ModEditDialogCommon.cpp
@@ -0,0 +1,57 @@
+/* Copyright 2013 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 "ModEditDialogCommon.h"
+#include "CustomMessageBox.h"
+#include <QDesktopServices>
+#include <QMessageBox>
+#include <QString>
+#include <QUrl>
+bool lastfirst(QModelIndexList &list, int &first, int &last)
+{
+ if (!list.size())
+ return false;
+ first = last = list[0].row();
+ for (auto item : list)
+ {
+ int row = item.row();
+ if (row < first)
+ first = row;
+ if (row > last)
+ last = row;
+ }
+ return true;
+}
+
+void showWebsiteForMod(QWidget *parentDlg, Mod &m)
+{
+ QString url = m.homeurl();
+ if (url.size())
+ {
+ // catch the cases where the protocol is missing
+ if (!url.startsWith("http"))
+ {
+ url = "http://" + url;
+ }
+ QDesktopServices::openUrl(url);
+ }
+ else
+ {
+ CustomMessageBox::selectable(
+ parentDlg, QObject::tr("How sad!"),
+ QObject::tr("The mod author didn't provide a website link for this mod."),
+ QMessageBox::Warning);
+ }
+}
diff --git a/gui/dialogs/ModEditDialogCommon.h b/gui/dialogs/ModEditDialogCommon.h
new file mode 100644
index 00000000..a226d5a9
--- /dev/null
+++ b/gui/dialogs/ModEditDialogCommon.h
@@ -0,0 +1,22 @@
+/* Copyright 2013 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 <QAbstractItemModel>
+#include <logic/Mod.h>
+
+bool lastfirst(QModelIndexList &list, int &first, int &last);
+
+void showWebsiteForMod(QWidget *parentDlg, Mod &m); \ No newline at end of file
diff --git a/gui/dialogs/NewInstanceDialog.cpp b/gui/dialogs/NewInstanceDialog.cpp
new file mode 100644
index 00000000..c7b273af
--- /dev/null
+++ b/gui/dialogs/NewInstanceDialog.cpp
@@ -0,0 +1,125 @@
+/* Copyright 2013 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 "MultiMC.h"
+#include "NewInstanceDialog.h"
+#include "ui_NewInstanceDialog.h"
+
+#include "logic/InstanceFactory.h"
+#include "logic/BaseVersion.h"
+#include "logic/icons/IconList.h"
+#include "logic/lists/MinecraftVersionList.h"
+#include "logic/tasks/Task.h"
+
+#include "gui/Platform.h"
+#include "VersionSelectDialog.h"
+#include "ProgressDialog.h"
+#include "IconPickerDialog.h"
+
+#include <QLayout>
+#include <QPushButton>
+
+NewInstanceDialog::NewInstanceDialog(QWidget *parent)
+ : QDialog(parent), ui(new Ui::NewInstanceDialog)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+ resize(minimumSizeHint());
+ layout()->setSizeConstraint(QLayout::SetFixedSize);
+ /*
+ if (!MinecraftVersionList::getMainList().isLoaded())
+ {
+ TaskDialog *taskDlg = new TaskDialog(this);
+ Task *loadTask = MinecraftVersionList::getMainList().getLoadTask();
+ loadTask->setParent(taskDlg);
+ taskDlg->exec(loadTask);
+ }
+ */
+ setSelectedVersion(MMC->minecraftlist()->getLatestStable());
+ InstIconKey = "infinity";
+ ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey));
+}
+
+NewInstanceDialog::~NewInstanceDialog()
+{
+ delete ui;
+}
+
+void NewInstanceDialog::updateDialogState()
+{
+ ui->buttonBox->button(QDialogButtonBox::Ok)
+ ->setEnabled(!instName().isEmpty() && m_selectedVersion);
+}
+
+void NewInstanceDialog::setSelectedVersion(BaseVersionPtr version)
+{
+ m_selectedVersion = version;
+
+ if (m_selectedVersion)
+ {
+ ui->versionTextBox->setText(version->name());
+ }
+ else
+ {
+ ui->versionTextBox->setText("");
+ }
+
+ updateDialogState();
+}
+
+QString NewInstanceDialog::instName() const
+{
+ return ui->instNameTextBox->text();
+}
+
+QString NewInstanceDialog::iconKey() const
+{
+ return InstIconKey;
+}
+
+BaseVersionPtr NewInstanceDialog::selectedVersion() const
+{
+ return m_selectedVersion;
+}
+
+void NewInstanceDialog::on_btnChangeVersion_clicked()
+{
+ VersionSelectDialog vselect(MMC->minecraftlist().get(), tr("Change Minecraft version"),
+ this);
+ vselect.exec();
+ if (vselect.result() == QDialog::Accepted)
+ {
+ BaseVersionPtr version = vselect.selectedVersion();
+ if (version)
+ setSelectedVersion(version);
+ }
+}
+
+void NewInstanceDialog::on_iconButton_clicked()
+{
+ IconPickerDialog dlg(this);
+ dlg.exec(InstIconKey);
+
+ if (dlg.result() == QDialog::Accepted)
+ {
+ InstIconKey = dlg.selectedIconKey;
+ ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey));
+ }
+}
+
+void NewInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1)
+{
+ updateDialogState();
+}
diff --git a/gui/dialogs/NewInstanceDialog.h b/gui/dialogs/NewInstanceDialog.h
new file mode 100644
index 00000000..4357c28d
--- /dev/null
+++ b/gui/dialogs/NewInstanceDialog.h
@@ -0,0 +1,55 @@
+/* Copyright 2013 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 "logic/BaseVersion.h"
+
+namespace Ui
+{
+class NewInstanceDialog;
+}
+
+class NewInstanceDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit NewInstanceDialog(QWidget *parent = 0);
+ ~NewInstanceDialog();
+
+ void updateDialogState();
+
+ void setSelectedVersion(BaseVersionPtr version);
+
+ void loadVersionList();
+
+ QString instName() const;
+ QString iconKey() const;
+ BaseVersionPtr selectedVersion() const;
+
+private
+slots:
+ void on_btnChangeVersion_clicked();
+ void on_iconButton_clicked();
+ void on_instNameTextBox_textChanged(const QString &arg1);
+
+private:
+ Ui::NewInstanceDialog *ui;
+
+ BaseVersionPtr m_selectedVersion;
+ QString InstIconKey;
+};
diff --git a/gui/dialogs/NewInstanceDialog.ui b/gui/dialogs/NewInstanceDialog.ui
new file mode 100644
index 00000000..00544463
--- /dev/null
+++ b/gui/dialogs/NewInstanceDialog.ui
@@ -0,0 +1,179 @@
+<?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>220</width>
+ <height>234</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>New Instance</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../../graphics.qrc">
+ <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="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 resource="../../graphics.qrc">
+ <normaloff>:/icons/instances/infinity</normaloff>:/icons/instances/infinity</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="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="labelVersion">
+ <property name="text">
+ <string>Version:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="versionTextBox">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="btnChangeVersion">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="Line" name="line_2">
+ <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>
+ <resources>
+ <include location="../../graphics.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>NewInstanceDialog</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>NewInstanceDialog</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>
diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp
new file mode 100644
index 00000000..27315c69
--- /dev/null
+++ b/gui/dialogs/OneSixModEditDialog.cpp
@@ -0,0 +1,367 @@
+/* Copyright 2013 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 "MultiMC.h"
+
+#include <pathutils.h>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QDebug>
+#include <QEvent>
+#include <QKeyEvent>
+#include <QDesktopServices>
+
+#include "OneSixModEditDialog.h"
+#include "ModEditDialogCommon.h"
+#include "ui_OneSixModEditDialog.h"
+
+#include "gui/Platform.h"
+#include "gui/dialogs/CustomMessageBox.h"
+#include "gui/dialogs/VersionSelectDialog.h"
+
+#include "gui/dialogs/ProgressDialog.h"
+
+#include "logic/ModList.h"
+#include "logic/OneSixVersion.h"
+#include "logic/EnabledItemFilter.h"
+#include "logic/lists/ForgeVersionList.h"
+#include "logic/ForgeInstaller.h"
+#include "logic/LiteLoaderInstaller.h"
+
+OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent)
+ : QDialog(parent), ui(new Ui::OneSixModEditDialog), m_inst(inst)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+ // libraries!
+
+ m_version = m_inst->getFullVersion();
+ if (m_version)
+ {
+ main_model = new EnabledItemFilter(this);
+ main_model->setActive(true);
+ main_model->setSourceModel(m_version.get());
+ ui->libraryTreeView->setModel(main_model);
+ ui->libraryTreeView->installEventFilter(this);
+ ui->mainClassEdit->setText(m_version->mainClass);
+ updateVersionControls();
+ }
+ else
+ {
+ disableVersionControls();
+ }
+ // Loader mods
+ {
+ ensureFolderPathExists(m_inst->loaderModsDir());
+ m_mods = m_inst->loaderModList();
+ ui->loaderModTreeView->setModel(m_mods.get());
+ ui->loaderModTreeView->installEventFilter(this);
+ m_mods->startWatching();
+ auto smodel = ui->loaderModTreeView->selectionModel();
+ connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)),
+ SLOT(loaderCurrent(QModelIndex, QModelIndex)));
+ }
+ // resource packs
+ {
+ ensureFolderPathExists(m_inst->resourcePacksDir());
+ m_resourcepacks = m_inst->resourcePackList();
+ ui->resPackTreeView->setModel(m_resourcepacks.get());
+ ui->resPackTreeView->installEventFilter(this);
+ m_resourcepacks->startWatching();
+ }
+}
+
+OneSixModEditDialog::~OneSixModEditDialog()
+{
+ m_mods->stopWatching();
+ m_resourcepacks->stopWatching();
+ delete ui;
+}
+
+void OneSixModEditDialog::updateVersionControls()
+{
+ bool customVersion = m_inst->versionIsCustom();
+ ui->customizeBtn->setEnabled(!customVersion);
+ ui->revertBtn->setEnabled(customVersion);
+ ui->forgeBtn->setEnabled(true);
+ ui->liteloaderBtn->setEnabled(LiteLoaderInstaller(m_inst->intendedVersionId()).canApply());
+ ui->customEditorBtn->setEnabled(customVersion);
+}
+
+void OneSixModEditDialog::disableVersionControls()
+{
+ ui->customizeBtn->setEnabled(false);
+ ui->revertBtn->setEnabled(false);
+ ui->forgeBtn->setEnabled(false);
+ ui->liteloaderBtn->setEnabled(false);
+ ui->customEditorBtn->setEnabled(false);
+}
+
+void OneSixModEditDialog::on_customizeBtn_clicked()
+{
+ if (m_inst->customizeVersion())
+ {
+ m_version = m_inst->getFullVersion();
+ main_model->setSourceModel(m_version.get());
+ updateVersionControls();
+ }
+}
+
+void OneSixModEditDialog::on_revertBtn_clicked()
+{
+ auto response = CustomMessageBox::selectable(
+ this, tr("Revert?"), tr("Do you want to revert the "
+ "version of this instance to its original configuration?"),
+ QMessageBox::Question, QMessageBox::Yes | QMessageBox::No)->exec();
+ if (response == QMessageBox::Yes)
+ {
+ if (m_inst->revertCustomVersion())
+ {
+ m_version = m_inst->getFullVersion();
+ main_model->setSourceModel(m_version.get());
+ updateVersionControls();
+ }
+ }
+}
+
+void OneSixModEditDialog::on_customEditorBtn_clicked()
+{
+ if (m_inst->versionIsCustom())
+ {
+ if (!MMC->openJsonEditor(m_inst->instanceRoot() + "/custom.json"))
+ {
+ QMessageBox::warning(this, tr("Error"),
+ tr("Unable to open custom.json, check the settings"));
+ }
+ }
+}
+
+void OneSixModEditDialog::on_forgeBtn_clicked()
+{
+ VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this);
+ vselect.setFilter(1, m_inst->currentVersionId());
+ vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") +
+ m_inst->currentVersionId());
+ if (vselect.exec() && vselect.selectedVersion())
+ {
+ if (m_inst->versionIsCustom())
+ {
+ auto reply = QMessageBox::question(
+ this, tr("Revert?"),
+ tr("This will revert any "
+ "changes you did to the version up to this point. Is that "
+ "OK?"),
+ QMessageBox::Yes | QMessageBox::No);
+ if (reply == QMessageBox::Yes)
+ {
+ m_inst->revertCustomVersion();
+ m_inst->customizeVersion();
+ {
+ m_version = m_inst->getFullVersion();
+ main_model->setSourceModel(m_version.get());
+ updateVersionControls();
+ }
+ }
+ else
+ return;
+ }
+ else
+ {
+ m_inst->customizeVersion();
+ m_version = m_inst->getFullVersion();
+ main_model->setSourceModel(m_version.get());
+ updateVersionControls();
+ }
+ ForgeVersionPtr forgeVersion =
+ std::dynamic_pointer_cast<ForgeVersion>(vselect.selectedVersion());
+ if (!forgeVersion)
+ return;
+ auto entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename);
+ if (entry->stale)
+ {
+ NetJob *fjob = new NetJob("Forge download");
+ fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry));
+ ProgressDialog dlg(this);
+ dlg.exec(fjob);
+ if (dlg.result() == QDialog::Accepted)
+ {
+ // install
+ QString forgePath = entry->getFullPath();
+ ForgeInstaller forge(forgePath, forgeVersion->universal_url);
+ if (!forge.apply(m_version))
+ {
+ // failure notice
+ }
+ }
+ else
+ {
+ // failed to download forge :/
+ }
+ }
+ else
+ {
+ // install
+ QString forgePath = entry->getFullPath();
+ ForgeInstaller forge(forgePath, forgeVersion->universal_url);
+ if (!forge.apply(m_version))
+ {
+ // failure notice
+ }
+ }
+ }
+}
+
+void OneSixModEditDialog::on_liteloaderBtn_clicked()
+{
+ LiteLoaderInstaller liteloader(m_inst->intendedVersionId());
+ if (!liteloader.canApply())
+ {
+ QMessageBox::critical(
+ this, tr("LiteLoader"),
+ tr("There is no information available on how to install LiteLoader "
+ "into this version of Minecraft"));
+ return;
+ }
+ if (!m_inst->versionIsCustom())
+ {
+ m_inst->customizeVersion();
+ m_version = m_inst->getFullVersion();
+ main_model->setSourceModel(m_version.get());
+ updateVersionControls();
+ }
+ if (!liteloader.apply(m_version))
+ {
+ QMessageBox::critical(this, tr("LiteLoader"),
+ tr("For reasons unknown, the LiteLoader installation failed. "
+ "Check your MultiMC log files for details."));
+ }
+}
+
+bool OneSixModEditDialog::loaderListFilter(QKeyEvent *keyEvent)
+{
+ switch (keyEvent->key())
+ {
+ case Qt::Key_Delete:
+ on_rmModBtn_clicked();
+ return true;
+ case Qt::Key_Plus:
+ on_addModBtn_clicked();
+ return true;
+ default:
+ break;
+ }
+ return QDialog::eventFilter(ui->loaderModTreeView, keyEvent);
+}
+
+bool OneSixModEditDialog::resourcePackListFilter(QKeyEvent *keyEvent)
+{
+ switch (keyEvent->key())
+ {
+ case Qt::Key_Delete:
+ on_rmResPackBtn_clicked();
+ return true;
+ case Qt::Key_Plus:
+ on_addResPackBtn_clicked();
+ return true;
+ default:
+ break;
+ }
+ return QDialog::eventFilter(ui->resPackTreeView, keyEvent);
+}
+
+bool OneSixModEditDialog::eventFilter(QObject *obj, QEvent *ev)
+{
+ if (ev->type() != QEvent::KeyPress)
+ {
+ return QDialog::eventFilter(obj, ev);
+ }
+ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
+ if (obj == ui->loaderModTreeView)
+ return loaderListFilter(keyEvent);
+ if (obj == ui->resPackTreeView)
+ return resourcePackListFilter(keyEvent);
+ return QDialog::eventFilter(obj, ev);
+}
+
+void OneSixModEditDialog::on_buttonBox_rejected()
+{
+ close();
+}
+
+void OneSixModEditDialog::on_addModBtn_clicked()
+{
+ QStringList fileNames = QFileDialog::getOpenFileNames(
+ this, QApplication::translate("LegacyModEditDialog", "Select Loader Mods"));
+ for (auto filename : fileNames)
+ {
+ m_mods->stopWatching();
+ m_mods->installMod(QFileInfo(filename));
+ m_mods->startWatching();
+ }
+}
+void OneSixModEditDialog::on_rmModBtn_clicked()
+{
+ int first, last;
+ auto list = ui->loaderModTreeView->selectionModel()->selectedRows();
+
+ if (!lastfirst(list, first, last))
+ return;
+ m_mods->stopWatching();
+ m_mods->deleteMods(first, last);
+ m_mods->startWatching();
+}
+void OneSixModEditDialog::on_viewModBtn_clicked()
+{
+ openDirInDefaultProgram(m_inst->loaderModsDir(), true);
+}
+
+void OneSixModEditDialog::on_addResPackBtn_clicked()
+{
+ QStringList fileNames = QFileDialog::getOpenFileNames(
+ this, QApplication::translate("LegacyModEditDialog", "Select Resource Packs"));
+ for (auto filename : fileNames)
+ {
+ m_resourcepacks->stopWatching();
+ m_resourcepacks->installMod(QFileInfo(filename));
+ m_resourcepacks->startWatching();
+ }
+}
+void OneSixModEditDialog::on_rmResPackBtn_clicked()
+{
+ int first, last;
+ auto list = ui->resPackTreeView->selectionModel()->selectedRows();
+
+ if (!lastfirst(list, first, last))
+ return;
+ m_resourcepacks->stopWatching();
+ m_resourcepacks->deleteMods(first, last);
+ m_resourcepacks->startWatching();
+}
+void OneSixModEditDialog::on_viewResPackBtn_clicked()
+{
+ openDirInDefaultProgram(m_inst->resourcePacksDir(), true);
+}
+
+void OneSixModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previous)
+{
+ if (!current.isValid())
+ {
+ ui->frame->clear();
+ return;
+ }
+ int row = current.row();
+ Mod &m = m_mods->operator[](row);
+ ui->frame->updateWithMod(m);
+}
diff --git a/gui/dialogs/OneSixModEditDialog.h b/gui/dialogs/OneSixModEditDialog.h
new file mode 100644
index 00000000..2510c59c
--- /dev/null
+++ b/gui/dialogs/OneSixModEditDialog.h
@@ -0,0 +1,69 @@
+/* Copyright 2013 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 <logic/OneSixInstance.h>
+
+class EnabledItemFilter;
+namespace Ui
+{
+class OneSixModEditDialog;
+}
+
+class OneSixModEditDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit OneSixModEditDialog(OneSixInstance *inst, QWidget *parent = 0);
+ virtual ~OneSixModEditDialog();
+
+private
+slots:
+ void on_addModBtn_clicked();
+ void on_rmModBtn_clicked();
+ void on_viewModBtn_clicked();
+
+ void on_addResPackBtn_clicked();
+ void on_rmResPackBtn_clicked();
+ void on_viewResPackBtn_clicked();
+ // Questionable: SettingsDialog doesn't need this for some reason?
+ void on_buttonBox_rejected();
+ void on_forgeBtn_clicked();
+ void on_liteloaderBtn_clicked();
+ void on_customizeBtn_clicked();
+ void on_revertBtn_clicked();
+ void on_customEditorBtn_clicked();
+ void updateVersionControls();
+ void disableVersionControls();
+
+protected:
+ bool eventFilter(QObject *obj, QEvent *ev);
+ bool loaderListFilter(QKeyEvent *ev);
+ bool resourcePackListFilter(QKeyEvent *ev);
+
+private:
+ Ui::OneSixModEditDialog *ui;
+ std::shared_ptr<OneSixVersion> m_version;
+ std::shared_ptr<ModList> m_mods;
+ std::shared_ptr<ModList> m_resourcepacks;
+ EnabledItemFilter *main_model;
+ OneSixInstance *m_inst;
+public
+slots:
+ void loaderCurrent(QModelIndex current, QModelIndex previous);
+};
diff --git a/gui/dialogs/OneSixModEditDialog.ui b/gui/dialogs/OneSixModEditDialog.ui
new file mode 100644
index 00000000..899e0cbf
--- /dev/null
+++ b/gui/dialogs/OneSixModEditDialog.ui
@@ -0,0 +1,340 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>OneSixModEditDialog</class>
+ <widget class="QDialog" name="OneSixModEditDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>555</width>
+ <height>463</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Manage Mods</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <widget class="QWidget" name="libTab">
+ <attribute name="title">
+ <string>Version</string>
+ </attribute>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_10">
+ <item>
+ <widget class="ModListView" name="libraryTreeView">
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOn</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Main Class:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="mainClassEdit">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QPushButton" name="forgeBtn">
+ <property name="toolTip">
+ <string>Replace any current custom version with Minecraft Forge</string>
+ </property>
+ <property name="text">
+ <string>Install Forge</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="liteloaderBtn">
+ <property name="text">
+ <string>Install LiteLoader</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="customizeBtn">
+ <property name="toolTip">
+ <string>Create an customized copy of the base version</string>
+ </property>
+ <property name="text">
+ <string>Customize</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="revertBtn">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Revert to original base version</string>
+ </property>
+ <property name="text">
+ <string>Revert</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="addLibraryBtn">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Add new libraries</string>
+ </property>
+ <property name="text">
+ <string>&amp;Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="removeLibraryBtn">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Remove selected libraries</string>
+ </property>
+ <property name="text">
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="customEditorBtn">
+ <property name="text">
+ <string>Open custom.json</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_7">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="modTab">
+ <attribute name="title">
+ <string>Loader Mods</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="ModListView" name="loaderModTreeView">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="acceptDrops">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::DropOnly</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QPushButton" name="addModBtn">
+ <property name="text">
+ <string>&amp;Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="rmModBtn">
+ <property name="text">
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="viewModBtn">
+ <property name="text">
+ <string>&amp;View Folder</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="MCModInfoFrame" name="frame">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="resPackTab">
+ <attribute name="title">
+ <string>Resource Packs</string>
+ </attribute>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="ModListView" name="resPackTreeView">
+ <property name="acceptDrops">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::DropOnly</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QPushButton" name="addResPackBtn">
+ <property name="text">
+ <string>&amp;Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="rmResPackBtn">
+ <property name="text">
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="viewResPackBtn">
+ <property name="text">
+ <string>&amp;View Folder</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ModListView</class>
+ <extends>QTreeView</extends>
+ <header>gui/widgets/ModListView.h</header>
+ </customwidget>
+ <customwidget>
+ <class>MCModInfoFrame</class>
+ <extends>QFrame</extends>
+ <header>gui/widgets/MCModInfoFrame.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/gui/dialogs/ProgressDialog.cpp b/gui/dialogs/ProgressDialog.cpp
new file mode 100644
index 00000000..ba14cca2
--- /dev/null
+++ b/gui/dialogs/ProgressDialog.cpp
@@ -0,0 +1,125 @@
+/* Copyright 2013 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 "logic/tasks/Task.h"
+#include "gui/Platform.h"
+
+ProgressDialog::ProgressDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ProgressDialog)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+ this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
+ setSkipButton(false);
+ changeProgress(0, 100);
+}
+
+void ProgressDialog::setSkipButton(bool present, QString label)
+{
+ 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()
+{
+ resize(QSize(480, minimumSizeHint().height()));
+}
+
+int ProgressDialog::exec(ProgressProvider *task)
+{
+ this->task = task;
+
+ // 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())
+ return QDialog::exec();
+ else
+ return 0;
+}
+
+ProgressProvider *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 (e->key() == Qt::Key_Escape)
+ return;
+ QDialog::keyPressEvent(e);
+}
+
+void ProgressDialog::closeEvent(QCloseEvent *e)
+{
+ if (task && task->isRunning())
+ {
+ e->ignore();
+ }
+ else
+ {
+ QDialog::closeEvent(e);
+ }
+}
diff --git a/gui/dialogs/ProgressDialog.h b/gui/dialogs/ProgressDialog.h
new file mode 100644
index 00000000..fe63a826
--- /dev/null
+++ b/gui/dialogs/ProgressDialog.h
@@ -0,0 +1,64 @@
+/* Copyright 2013 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>
+
+class ProgressProvider;
+
+namespace Ui
+{
+class ProgressDialog;
+}
+
+class ProgressDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit ProgressDialog(QWidget *parent = 0);
+ ~ProgressDialog();
+
+ void updateSize();
+
+ int exec(ProgressProvider *task);
+ void setSkipButton(bool present, QString label = QString());
+
+ ProgressProvider *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:
+ Ui::ProgressDialog *ui;
+
+ ProgressProvider *task;
+};
diff --git a/gui/dialogs/ProgressDialog.ui b/gui/dialogs/ProgressDialog.ui
new file mode 100644
index 00000000..04b8fef3
--- /dev/null
+++ b/gui/dialogs/ProgressDialog.ui
@@ -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>
diff --git a/gui/dialogs/SettingsDialog.cpp b/gui/dialogs/SettingsDialog.cpp
new file mode 100644
index 00000000..ef363f02
--- /dev/null
+++ b/gui/dialogs/SettingsDialog.cpp
@@ -0,0 +1,505 @@
+/* Copyright 2013 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 "MultiMC.h"
+
+#include "gui/dialogs/SettingsDialog.h"
+#include "ui_SettingsDialog.h"
+
+#include "gui/Platform.h"
+#include "gui/dialogs/VersionSelectDialog.h"
+#include "gui/dialogs/CustomMessageBox.h"
+
+#include "logic/JavaUtils.h"
+#include "logic/NagUtils.h"
+#include "logic/lists/JavaVersionList.h"
+#include <logic/JavaChecker.h>
+
+#include "logic/updater/UpdateChecker.h"
+
+#include <settingsobject.h>
+#include <pathutils.h>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QDir>
+
+SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::SettingsDialog)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+ ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name);
+ ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch);
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
+ ui->jsonEditorTextBox->setClearButtonEnabled(true);
+#endif
+
+ restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("SettingsGeometry").toByteArray()));
+
+ loadSettings(MMC->settings().get());
+ updateCheckboxStuff();
+
+ QObject::connect(MMC->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &SettingsDialog::refreshUpdateChannelList);
+
+ if (MMC->updateChecker()->hasChannels())
+ {
+ refreshUpdateChannelList();
+ }
+ else
+ {
+ MMC->updateChecker()->updateChanList();
+ }
+ connect(ui->proxyGroup, SIGNAL(buttonClicked(int)), SLOT(proxyChanged(int)));
+}
+
+SettingsDialog::~SettingsDialog()
+{
+ delete ui;
+}
+void SettingsDialog::showEvent(QShowEvent *ev)
+{
+ QDialog::showEvent(ev);
+}
+
+void SettingsDialog::closeEvent(QCloseEvent *ev)
+{
+ MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
+
+ QDialog::closeEvent(ev);
+}
+
+void SettingsDialog::updateCheckboxStuff()
+{
+ ui->windowWidthSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked());
+ ui->windowHeightSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked());
+ ui->proxyAddrBox->setEnabled(!ui->proxyNoneBtn->isChecked() && !ui->proxyDefaultBtn->isChecked());
+ ui->proxyAuthBox->setEnabled(!ui->proxyNoneBtn->isChecked() && !ui->proxyDefaultBtn->isChecked());
+}
+
+void SettingsDialog::on_ftbLauncherBrowseBtn_clicked()
+{
+ QString raw_dir = QFileDialog::getExistingDirectory(this, tr("FTB Launcher Directory"),
+ ui->ftbLauncherBox->text());
+ QString cooked_dir = NormalizePath(raw_dir);
+
+ // do not allow current dir - it's dirty. Do not allow dirs that don't exist
+ if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
+ {
+ ui->ftbLauncherBox->setText(cooked_dir);
+ }
+}
+
+void SettingsDialog::on_ftbBrowseBtn_clicked()
+{
+ QString raw_dir = QFileDialog::getExistingDirectory(this, tr("FTB Directory"),
+ ui->ftbBox->text());
+ QString cooked_dir = NormalizePath(raw_dir);
+
+ // do not allow current dir - it's dirty. Do not allow dirs that don't exist
+ if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
+ {
+ ui->ftbBox->setText(cooked_dir);
+ }
+}
+
+void SettingsDialog::on_instDirBrowseBtn_clicked()
+{
+ QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Directory"),
+ ui->instDirTextBox->text());
+ QString cooked_dir = NormalizePath(raw_dir);
+
+ // do not allow current dir - it's dirty. Do not allow dirs that don't exist
+ if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
+ {
+ ui->instDirTextBox->setText(cooked_dir);
+ }
+}
+void SettingsDialog::on_iconsDirBrowseBtn_clicked()
+{
+ QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Icons Directory"),
+ ui->iconsDirTextBox->text());
+ QString cooked_dir = NormalizePath(raw_dir);
+
+ // do not allow current dir - it's dirty. Do not allow dirs that don't exist
+ if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
+ {
+ ui->iconsDirTextBox->setText(cooked_dir);
+ }
+}
+
+void SettingsDialog::on_modsDirBrowseBtn_clicked()
+{
+ QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Directory"),
+ ui->modsDirTextBox->text());
+ QString cooked_dir = NormalizePath(raw_dir);
+
+ // do not allow current dir - it's dirty. Do not allow dirs that don't exist
+ if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
+ {
+ ui->modsDirTextBox->setText(cooked_dir);
+ }
+}
+
+void SettingsDialog::on_lwjglDirBrowseBtn_clicked()
+{
+ QString raw_dir = QFileDialog::getExistingDirectory(this, tr("LWJGL Directory"),
+ ui->lwjglDirTextBox->text());
+ QString cooked_dir = NormalizePath(raw_dir);
+
+ // do not allow current dir - it's dirty. Do not allow dirs that don't exist
+ if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
+ {
+ ui->lwjglDirTextBox->setText(cooked_dir);
+ }
+}
+
+void SettingsDialog::on_jsonEditorBrowseBtn_clicked()
+{
+ QString raw_file = QFileDialog::getOpenFileName(
+ this, tr("JSON Editor"),
+ ui->jsonEditorTextBox->text().isEmpty()
+ #if defined(Q_OS_LINUX)
+ ? QString("/usr/bin")
+ #else
+ ? QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).first()
+ #endif
+ : ui->jsonEditorTextBox->text());
+ QString cooked_file = NormalizePath(raw_file);
+
+ if (cooked_file.isEmpty())
+ {
+ return;
+ }
+
+ // it has to exist and be an executable
+ if (QFileInfo(cooked_file).exists() &&
+ QFileInfo(cooked_file).isExecutable())
+ {
+ ui->jsonEditorTextBox->setText(cooked_file);
+ }
+ else
+ {
+ QMessageBox::warning(this, tr("Invalid"), tr("The file chosen does not seem to be an executable"));
+ }
+}
+
+void SettingsDialog::on_maximizedCheckBox_clicked(bool checked)
+{
+ Q_UNUSED(checked);
+ updateCheckboxStuff();
+}
+
+void SettingsDialog::on_buttonBox_accepted()
+{
+ applySettings(MMC->settings().get());
+
+ // Apply proxy settings
+ MMC->updateProxySettings();
+
+ MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
+}
+
+void SettingsDialog::on_buttonBox_rejected()
+{
+ MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
+}
+
+void SettingsDialog::proxyChanged(int)
+{
+ updateCheckboxStuff();
+}
+
+void SettingsDialog::refreshUpdateChannelList()
+{
+ // Stop listening for selection changes. It's going to change a lot while we update it and we don't need to update the
+ // description label constantly.
+ QObject::disconnect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateChannelSelectionChanged(int)));
+
+ QList<UpdateChecker::ChannelListEntry> channelList = MMC->updateChecker()->getChannelList();
+ ui->updateChannelComboBox->clear();
+ int selection = -1;
+ for (int i = 0; i < channelList.count(); i++)
+ {
+ UpdateChecker::ChannelListEntry entry = channelList.at(i);
+
+ // When it comes to selection, we'll rely on the indexes of a channel entry being the same in the
+ // combo box as it is in the update checker's channel list.
+ // This probably isn't very safe, but the channel list doesn't change often enough (or at all) for
+ // this to be a big deal. Hope it doesn't break...
+ ui->updateChannelComboBox->addItem(entry.name);
+
+ // If the update channel we just added was the selected one, set the current index in the combo box to it.
+ if (entry.id == m_currentUpdateChannel)
+ {
+ QLOG_DEBUG() << "Selected index" << i << "channel id" << m_currentUpdateChannel;
+ selection = i;
+ }
+ }
+
+ ui->updateChannelComboBox->setCurrentIndex(selection);
+
+ // Start listening for selection changes again and update the description label.
+ QObject::connect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateChannelSelectionChanged(int)));
+ refreshUpdateChannelDesc();
+
+ // Now that we've updated the channel list, we can enable the combo box.
+ // It starts off disabled so that if the channel list hasn't been loaded, it will be disabled.
+ ui->updateChannelComboBox->setEnabled(true);
+}
+
+void SettingsDialog::updateChannelSelectionChanged(int index)
+{
+ refreshUpdateChannelDesc();
+}
+
+void SettingsDialog::refreshUpdateChannelDesc()
+{
+ // Get the channel list.
+ QList<UpdateChecker::ChannelListEntry> channelList = MMC->updateChecker()->getChannelList();
+ int selectedIndex = ui->updateChannelComboBox->currentIndex();
+ if(selectedIndex < 0)
+ {
+ return;
+ }
+ if (selectedIndex < channelList.count())
+ {
+ // Find the channel list entry with the given index.
+ UpdateChecker::ChannelListEntry selected = channelList.at(selectedIndex);
+
+ // Set the description text.
+ ui->updateChannelDescLabel->setText(selected.description);
+
+ // Set the currently selected channel ID.
+ m_currentUpdateChannel = selected.id;
+ }
+}
+
+void SettingsDialog::applySettings(SettingsObject *s)
+{
+ // Language
+ s->set("Language", ui->languageBox->itemData(ui->languageBox->currentIndex()).toLocale().bcp47Name());
+
+ // Updates
+ s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked());
+ s->set("UpdateChannel", m_currentUpdateChannel);
+
+ // FTB
+ s->set("TrackFTBInstances", ui->trackFtbBox->isChecked());
+ s->set("FTBLauncherRoot", ui->ftbLauncherBox->text());
+ s->set("FTBRoot", ui->ftbBox->text());
+
+ // Folders
+ // TODO: Offer to move instances to new instance folder.
+ s->set("InstanceDir", ui->instDirTextBox->text());
+ s->set("CentralModsDir", ui->modsDirTextBox->text());
+ s->set("LWJGLDir", ui->lwjglDirTextBox->text());
+ s->set("IconsDir", ui->iconsDirTextBox->text());
+
+ // Editors
+ QString jsonEditor = ui->jsonEditorTextBox->text();
+ if (!jsonEditor.isEmpty() && (!QFileInfo(jsonEditor).exists() || !QFileInfo(jsonEditor).isExecutable()))
+ {
+ QString found = QStandardPaths::findExecutable(jsonEditor);
+ if (!found.isEmpty())
+ {
+ jsonEditor = found;
+ }
+ }
+ s->set("JsonEditor", jsonEditor);
+
+ // Console
+ s->set("ShowConsole", ui->showConsoleCheck->isChecked());
+ s->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked());
+
+ // Window Size
+ s->set("LaunchMaximized", ui->maximizedCheckBox->isChecked());
+ s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value());
+ s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value());
+
+ // Proxy
+ QString proxyType = "None";
+ if (ui->proxyDefaultBtn->isChecked()) proxyType = "Default";
+ else if (ui->proxyNoneBtn->isChecked()) proxyType = "None";
+ else if (ui->proxySOCKS5Btn->isChecked()) proxyType = "SOCKS5";
+ else if (ui->proxyHTTPBtn->isChecked()) proxyType = "HTTP";
+
+ s->set("ProxyType", proxyType);
+ s->set("ProxyAddr", ui->proxyAddrEdit->text());
+ s->set("ProxyPort", ui->proxyPortEdit->value());
+ s->set("ProxyUser", ui->proxyUserEdit->text());
+ s->set("ProxyPass", ui->proxyPassEdit->text());
+
+ // Memory
+ s->set("MinMemAlloc", ui->minMemSpinBox->value());
+ s->set("MaxMemAlloc", ui->maxMemSpinBox->value());
+ s->set("PermGen", ui->permGenSpinBox->value());
+
+ // Java Settings
+ s->set("JavaPath", ui->javaPathTextBox->text());
+ s->set("JvmArgs", ui->jvmArgsTextBox->text());
+ NagUtils::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget());
+
+ // Custom Commands
+ s->set("PreLaunchCommand", ui->preLaunchCmdTextBox->text());
+ s->set("PostExitCommand", ui->postExitCmdTextBox->text());
+
+ auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId();
+ switch (sortMode)
+ {
+ case Sort_LastLaunch:
+ s->set("InstSortMode", "LastLaunch");
+ break;
+ case Sort_Name:
+ default:
+ s->set("InstSortMode", "Name");
+ break;
+ }
+
+ s->set("PostExitCommand", ui->postExitCmdTextBox->text());
+}
+
+void SettingsDialog::loadSettings(SettingsObject *s)
+{
+ // Language
+ ui->languageBox->clear();
+ ui->languageBox->addItem(tr("English"), QLocale(QLocale::English));
+ foreach(const QString & lang,
+ QDir(MMC->root() + "/translations").entryList(QStringList() << "*.qm", QDir::Files))
+ {
+ QLocale locale(lang.section(QRegExp("[_\.]"), 1));
+ ui->languageBox->addItem(
+ QLocale::languageToString(locale.language()),
+ locale);
+ }
+ ui->languageBox->setCurrentIndex(ui->languageBox->findData(QLocale(s->get("Language").toString())));
+
+ // Updates
+ ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
+ m_currentUpdateChannel = s->get("UpdateChannel").toString();
+
+ // FTB
+ ui->trackFtbBox->setChecked(s->get("TrackFTBInstances").toBool());
+ ui->ftbLauncherBox->setText(s->get("FTBLauncherRoot").toString());
+ ui->ftbBox->setText(s->get("FTBRoot").toString());
+
+ // Folders
+ ui->instDirTextBox->setText(s->get("InstanceDir").toString());
+ ui->modsDirTextBox->setText(s->get("CentralModsDir").toString());
+ ui->lwjglDirTextBox->setText(s->get("LWJGLDir").toString());
+ ui->iconsDirTextBox->setText(s->get("IconsDir").toString());
+
+ // Editors
+ ui->jsonEditorTextBox->setText(s->get("JsonEditor").toString());
+
+ // Console
+ ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool());
+ ui->autoCloseConsoleCheck->setChecked(s->get("AutoCloseConsole").toBool());
+
+ // Window Size
+ ui->maximizedCheckBox->setChecked(s->get("LaunchMaximized").toBool());
+ ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt());
+ ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt());
+
+ // Memory
+ ui->minMemSpinBox->setValue(s->get("MinMemAlloc").toInt());
+ ui->maxMemSpinBox->setValue(s->get("MaxMemAlloc").toInt());
+ ui->permGenSpinBox->setValue(s->get("PermGen").toInt());
+
+ QString sortMode = s->get("InstSortMode").toString();
+
+ if (sortMode == "LastLaunch")
+ {
+ ui->sortLastLaunchedBtn->setChecked(true);
+ }
+ else
+ {
+ ui->sortByNameBtn->setChecked(true);
+ }
+
+ // Proxy
+ QString proxyType = s->get("ProxyType").toString();
+ if (proxyType == "Default") ui->proxyDefaultBtn->setChecked(true);
+ else if (proxyType == "None") ui->proxyNoneBtn->setChecked(true);
+ else if (proxyType == "SOCKS5") ui->proxySOCKS5Btn->setChecked(true);
+ else if (proxyType == "HTTP") ui->proxyHTTPBtn->setChecked(true);
+
+ ui->proxyAddrEdit->setText(s->get("ProxyAddr").toString());
+ ui->proxyPortEdit->setValue(s->get("ProxyPort").value<qint16>());
+ ui->proxyUserEdit->setText(s->get("ProxyUser").toString());
+ ui->proxyPassEdit->setText(s->get("ProxyPass").toString());
+
+ // Java Settings
+ ui->javaPathTextBox->setText(s->get("JavaPath").toString());
+ ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString());
+
+ // Custom Commands
+ ui->preLaunchCmdTextBox->setText(s->get("PreLaunchCommand").toString());
+ ui->postExitCmdTextBox->setText(s->get("PostExitCommand").toString());
+}
+
+void SettingsDialog::on_javaDetectBtn_clicked()
+{
+ JavaVersionPtr java;
+
+ VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true);
+ vselect.setResizeOn(2);
+ vselect.exec();
+
+ if (vselect.result() == QDialog::Accepted && vselect.selectedVersion())
+ {
+ java = std::dynamic_pointer_cast<JavaVersion>(vselect.selectedVersion());
+ ui->javaPathTextBox->setText(java->path);
+ }
+}
+
+void SettingsDialog::on_javaBrowseBtn_clicked()
+{
+ QString dir = QFileDialog::getOpenFileName(this, tr("Find Java executable"));
+ if (!dir.isNull())
+ {
+ ui->javaPathTextBox->setText(dir);
+ }
+}
+
+void SettingsDialog::on_javaTestBtn_clicked()
+{
+ checker.reset(new JavaChecker());
+ connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
+ SLOT(checkFinished(JavaCheckResult)));
+ checker->path = ui->javaPathTextBox->text();
+ checker->performCheck();
+}
+
+void SettingsDialog::checkFinished(JavaCheckResult result)
+{
+ if (result.valid)
+ {
+ QString text;
+ text += "Java test succeeded!\n";
+ if (result.is_64bit)
+ text += "Using 64bit java.\n";
+ text += "\n";
+ text += "Platform reported: " + result.realPlatform + "\n";
+ text += "Java version reported: " + result.javaVersion;
+ QMessageBox::information(this, tr("Java test success"), text);
+ }
+ else
+ {
+ QMessageBox::warning(
+ this, tr("Java test failure"),
+ tr("The specified java binary didn't work. You should use the auto-detect feature, "
+ "or set the path to the java executable."));
+ }
+}
diff --git a/gui/dialogs/SettingsDialog.h b/gui/dialogs/SettingsDialog.h
new file mode 100644
index 00000000..d7bbbeb3
--- /dev/null
+++ b/gui/dialogs/SettingsDialog.h
@@ -0,0 +1,99 @@
+/* Copyright 2013 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 <memory>
+#include <QDialog>
+
+#include "logic/JavaChecker.h"
+
+class SettingsObject;
+
+namespace Ui
+{
+class SettingsDialog;
+}
+
+class SettingsDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit SettingsDialog(QWidget *parent = 0);
+ ~SettingsDialog();
+
+ void updateCheckboxStuff();
+
+ void applySettings(SettingsObject *s);
+ void loadSettings(SettingsObject *s);
+
+protected:
+ virtual void showEvent(QShowEvent *ev);
+ virtual void closeEvent(QCloseEvent *ev);
+
+private
+slots:
+ void on_ftbLauncherBrowseBtn_clicked();
+
+ void on_ftbBrowseBtn_clicked();
+
+ void on_instDirBrowseBtn_clicked();
+
+ void on_modsDirBrowseBtn_clicked();
+
+ void on_lwjglDirBrowseBtn_clicked();
+
+
+ void on_jsonEditorBrowseBtn_clicked();
+
+ void on_iconsDirBrowseBtn_clicked();
+
+ void on_maximizedCheckBox_clicked(bool checked);
+
+ void on_buttonBox_accepted();
+
+ void on_buttonBox_rejected();
+
+ void on_javaDetectBtn_clicked();
+
+ void on_javaTestBtn_clicked();
+
+ void on_javaBrowseBtn_clicked();
+
+ void checkFinished(JavaCheckResult result);
+
+ /*!
+ * Updates the list of update channels in the combo box.
+ */
+ void refreshUpdateChannelList();
+
+ /*!
+ * Updates the channel description label.
+ */
+ void refreshUpdateChannelDesc();
+
+ void updateChannelSelectionChanged(int index);
+ void proxyChanged(int);
+
+private:
+ Ui::SettingsDialog *ui;
+ std::shared_ptr<JavaChecker> checker;
+
+ /*!
+ * Stores the currently selected update channel.
+ */
+ QString m_currentUpdateChannel;
+};
diff --git a/gui/dialogs/SettingsDialog.ui b/gui/dialogs/SettingsDialog.ui
new file mode 100644
index 00000000..54e7db7a
--- /dev/null
+++ b/gui/dialogs/SettingsDialog.ui
@@ -0,0 +1,944 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SettingsDialog</class>
+ <widget class="QDialog" name="SettingsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>545</width>
+ <height>609</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Settings</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../../graphics.qrc">
+ <normaloff>:/icons/toolbar/settings</normaloff>:/icons/toolbar/settings</iconset>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="mainLayout">
+ <item>
+ <widget class="QTabWidget" name="settingsTabs">
+ <property name="tabShape">
+ <enum>QTabWidget::Rounded</enum>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="featuresTab">
+ <attribute name="title">
+ <string>Features</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_9">
+ <item>
+ <widget class="QGroupBox" name="updateSettingsBox">
+ <property name="title">
+ <string>Update Settings</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <widget class="QCheckBox" name="autoUpdateCheckBox">
+ <property name="text">
+ <string>Check for updates when MultiMC starts?</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="updateChannelLabel">
+ <property name="text">
+ <string>Update Channel:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="updateChannelComboBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="updateChannelDescLabel">
+ <property name="text">
+ <string>No channel selected.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>FTB</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="2">
+ <widget class="QPushButton" name="ftbLauncherBrowseBtn">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <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="focusPolicy">
+ <enum>Qt::TabFocus</enum>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Launcher:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="ftbLauncherBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QCheckBox" name="trackFtbBox">
+ <property name="text">
+ <string>Track FTB instances</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="ftbBox"/>
+ </item>
+ <item row="3" column="2">
+ <widget class="QPushButton" name="ftbBrowseBtn">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <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>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Files:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="foldersBox">
+ <property name="title">
+ <string>Folders</string>
+ </property>
+ <layout class="QGridLayout" name="foldersBoxLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelInstDir">
+ <property name="text">
+ <string>Instances:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="instDirTextBox"/>
+ </item>
+ <item row="0" column="2">
+ <widget class="QToolButton" name="instDirBrowseBtn">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelModsDir">
+ <property name="text">
+ <string>Mods:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="modsDirTextBox"/>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="lwjglDirTextBox"/>
+ </item>
+ <item row="1" column="2">
+ <widget class="QToolButton" name="modsDirBrowseBtn">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="labelLWJGLDir">
+ <property name="text">
+ <string>LWJGL:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QToolButton" name="lwjglDirBrowseBtn">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="iconsDirTextBox"/>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="labelIconsDir">
+ <property name="text">
+ <string>Icons:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QToolButton" name="iconsDirBrowseBtn">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="generalTab">
+ <attribute name="title">
+ <string>User Interface</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Language (needs restart):</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="languageBox"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="sortingModeBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Sorting Mode</string>
+ </property>
+ <layout class="QHBoxLayout" name="sortingModeBoxLayout">
+ <item>
+ <widget class="QRadioButton" name="sortLastLaunchedBtn">
+ <property name="text">
+ <string>By last launched</string>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">sortingModeGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="sortByNameBtn">
+ <property name="text">
+ <string>By name</string>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">sortingModeGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="editorsBox">
+ <property name="title">
+ <string>External Editors (leave empty for system default)</string>
+ </property>
+ <layout class="QGridLayout" name="foldersBoxLayout_2">
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="jsonEditorTextBox"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelJsonEditor">
+ <property name="text">
+ <string>JSON Editor:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QToolButton" name="jsonEditorBrowseBtn">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="generalTabSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="minecraftTab">
+ <attribute name="title">
+ <string>Minecraft</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QGroupBox" name="windowSizeGroupBox">
+ <property name="title">
+ <string>Window Size</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QCheckBox" name="maximizedCheckBox">
+ <property name="text">
+ <string>Start Minecraft maximized?</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayoutWindowSize">
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelWindowHeight">
+ <property name="text">
+ <string>Window height:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelWindowWidth">
+ <property name="text">
+ <string>Window width:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="windowWidthSpinBox">
+ <property name="minimum">
+ <number>854</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>854</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="windowHeightSpinBox">
+ <property name="minimum">
+ <number>480</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ <property name="value">
+ <number>480</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="consoleSettingsBox">
+ <property name="title">
+ <string>Console Settings</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="showConsoleCheck">
+ <property name="text">
+ <string>Show console while the game is running?</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="autoCloseConsoleCheck">
+ <property name="text">
+ <string>Automatically close console when the game quits?</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacerMinecraft">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="javaTab">
+ <attribute name="title">
+ <string>Java</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QGroupBox" name="memoryGroupBox">
+ <property name="title">
+ <string>Memory</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="maxMemSpinBox">
+ <property name="toolTip">
+ <string>The maximum amount of memory Minecraft is allowed to use.</string>
+ </property>
+ <property name="suffix">
+ <string> MB</string>
+ </property>
+ <property name="minimum">
+ <number>512</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ <property name="singleStep">
+ <number>128</number>
+ </property>
+ <property name="value">
+ <number>1024</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelMinMem">
+ <property name="text">
+ <string>Minimum memory allocation:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelMaxMem">
+ <property name="text">
+ <string>Maximum memory allocation:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="minMemSpinBox">
+ <property name="toolTip">
+ <string>The amount of memory Minecraft is started with.</string>
+ </property>
+ <property name="suffix">
+ <string> MB</string>
+ </property>
+ <property name="minimum">
+ <number>256</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ <property name="singleStep">
+ <number>128</number>
+ </property>
+ <property name="value">
+ <number>256</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="labelPermGen">
+ <property name="text">
+ <string>PermGen:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSpinBox" name="permGenSpinBox">
+ <property name="toolTip">
+ <string>The amount of memory available to store loaded Java classes.</string>
+ </property>
+ <property name="suffix">
+ <string> MB</string>
+ </property>
+ <property name="minimum">
+ <number>64</number>
+ </property>
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ <property name="singleStep">
+ <number>8</number>
+ </property>
+ <property name="value">
+ <number>64</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="javaSettingsGroupBox">
+ <property name="title">
+ <string>Java Settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelJavaPath">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Java path:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="javaDetectBtn">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Auto-detect...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="javaTestBtn">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Test</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="labelJVMArgs">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>JVM arguments:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="javaPathTextBox"/>
+ </item>
+ <item>
+ <widget class="QPushButton" name="javaBrowseBtn">
+ <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>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="1" colspan="2">
+ <widget class="QLineEdit" name="jvmArgsTextBox"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="customCommandsGroupBox">
+ <property name="title">
+ <string>Custom Commands</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelPostExitCmd">
+ <property name="text">
+ <string>Post-exit command:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelPreLaunchCmd">
+ <property name="text">
+ <string>Pre-launch command:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="preLaunchCmdTextBox"/>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="postExitCmdTextBox"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="labelCustomCmdsDescription">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Pre-launch command runs before the instance launches and post-exit command runs after it exits. Both will be run in MultiMC's working directory with INST_ID, INST_DIR, and INST_NAME as environment variables.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="networkTab">
+ <property name="toolTip">
+ <string>Network settings.</string>
+ </property>
+ <attribute name="title">
+ <string>Network</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QGroupBox" name="proxySettingsBox">
+ <property name="title">
+ <string>Proxy</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <item>
+ <widget class="QGroupBox" name="proxyTypeBox">
+ <property name="title">
+ <string>Type</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QRadioButton" name="proxyDefaultBtn">
+ <property name="toolTip">
+ <string>Uses your system's default proxy settings.</string>
+ </property>
+ <property name="text">
+ <string>Default</string>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">proxyGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="proxyNoneBtn">
+ <property name="text">
+ <string>None</string>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">proxyGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="proxySOCKS5Btn">
+ <property name="text">
+ <string>SOCKS5</string>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">proxyGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="proxyHTTPBtn">
+ <property name="text">
+ <string>HTTP</string>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">proxyGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="proxyAddrBox">
+ <property name="title">
+ <string>Address and Port</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLineEdit" name="proxyAddrEdit">
+ <property name="placeholderText">
+ <string>127.0.0.1</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="proxyPortEdit">
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::PlusMinus</enum>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ <property name="value">
+ <number>8080</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="proxyAuthBox">
+ <property name="title">
+ <string>Authentication</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="proxyUserEdit"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="proxyUsernameLabel">
+ <property name="text">
+ <string>Username:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="proxyPasswordLabel">
+ <property name="text">
+ <string>Password:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="proxyPassEdit">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QLabel" name="proxyPlainTextWarningLabel">
+ <property name="text">
+ <string>Note: Proxy username and password are stored in plain text inside MultiMC's configuration file!</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </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>buttonBox</tabstop>
+ <tabstop>sortLastLaunchedBtn</tabstop>
+ <tabstop>sortByNameBtn</tabstop>
+ <tabstop>jsonEditorTextBox</tabstop>
+ <tabstop>jsonEditorBrowseBtn</tabstop>
+ <tabstop>maximizedCheckBox</tabstop>
+ <tabstop>windowWidthSpinBox</tabstop>
+ <tabstop>windowHeightSpinBox</tabstop>
+ <tabstop>showConsoleCheck</tabstop>
+ <tabstop>autoCloseConsoleCheck</tabstop>
+ <tabstop>minMemSpinBox</tabstop>
+ <tabstop>maxMemSpinBox</tabstop>
+ <tabstop>permGenSpinBox</tabstop>
+ <tabstop>javaPathTextBox</tabstop>
+ <tabstop>javaBrowseBtn</tabstop>
+ <tabstop>javaDetectBtn</tabstop>
+ <tabstop>javaTestBtn</tabstop>
+ <tabstop>jvmArgsTextBox</tabstop>
+ <tabstop>preLaunchCmdTextBox</tabstop>
+ <tabstop>postExitCmdTextBox</tabstop>
+ <tabstop>settingsTabs</tabstop>
+ </tabstops>
+ <resources>
+ <include location="../../graphics.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>257</x>
+ <y>410</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>325</x>
+ <y>410</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+ <buttongroups>
+ <buttongroup name="proxyGroup"/>
+ <buttongroup name="sortingModeGroup"/>
+ </buttongroups>
+</ui>
diff --git a/gui/dialogs/UpdateDialog.cpp b/gui/dialogs/UpdateDialog.cpp
new file mode 100644
index 00000000..c56798b4
--- /dev/null
+++ b/gui/dialogs/UpdateDialog.cpp
@@ -0,0 +1,28 @@
+#include "UpdateDialog.h"
+#include "ui_UpdateDialog.h"
+#include "gui/Platform.h"
+
+UpdateDialog::UpdateDialog(QWidget *parent) : QDialog(parent), ui(new Ui::UpdateDialog)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+}
+
+UpdateDialog::~UpdateDialog()
+{
+}
+
+void UpdateDialog::on_btnUpdateLater_clicked()
+{
+ reject();
+}
+
+void UpdateDialog::on_btnUpdateNow_clicked()
+{
+ done(UPDATE_NOW);
+}
+
+void UpdateDialog::on_btnUpdateOnExit_clicked()
+{
+ done(UPDATE_ONEXIT);
+}
diff --git a/gui/dialogs/UpdateDialog.h b/gui/dialogs/UpdateDialog.h
new file mode 100644
index 00000000..c13eb6bf
--- /dev/null
+++ b/gui/dialogs/UpdateDialog.h
@@ -0,0 +1,46 @@
+/* Copyright 2013 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 UpdateDialog;
+}
+
+enum UpdateAction
+{
+ UPDATE_LATER = QDialog::Rejected,
+ UPDATE_NOW = QDialog::Accepted,
+ UPDATE_ONEXIT = 2
+};
+
+class UpdateDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit UpdateDialog(QWidget *parent = 0);
+ ~UpdateDialog();
+
+private:
+ Ui::UpdateDialog *ui;
+public slots:
+ void on_btnUpdateNow_clicked();
+ void on_btnUpdateOnExit_clicked();
+ void on_btnUpdateLater_clicked();
+};
diff --git a/gui/dialogs/UpdateDialog.ui b/gui/dialogs/UpdateDialog.ui
new file mode 100644
index 00000000..1fe65e62
--- /dev/null
+++ b/gui/dialogs/UpdateDialog.ui
@@ -0,0 +1,70 @@
+<?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>350</width>
+ <height>260</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MultiMC Update</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../../graphics.qrc">
+ <normaloff>:/icons/toolbar/checkupdate</normaloff>:/icons/toolbar/checkupdate</iconset>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>A new MultiMC update is available!</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <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>
+ <widget class="QPushButton" name="btnUpdateOnExit">
+ <property name="text">
+ <string>Update after MultiMC closes</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <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>
+ </widget>
+ <resources>
+ <include location="../../graphics.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/gui/dialogs/VersionSelectDialog.cpp b/gui/dialogs/VersionSelectDialog.cpp
new file mode 100644
index 00000000..0f379f56
--- /dev/null
+++ b/gui/dialogs/VersionSelectDialog.cpp
@@ -0,0 +1,116 @@
+/* Copyright 2013 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 "ui_VersionSelectDialog.h"
+
+#include <QHeaderView>
+
+#include <QDebug>
+
+#include <gui/dialogs/ProgressDialog.h>
+#include "gui/Platform.h"
+
+#include <logic/BaseVersion.h>
+#include <logic/lists/BaseVersionList.h>
+#include <logic/tasks/Task.h>
+
+VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent,
+ bool cancelable)
+ : QDialog(parent), ui(new Ui::VersionSelectDialog)
+{
+ MultiMCPlatform::fixWM_CLASS(this);
+ ui->setupUi(this);
+ setWindowModality(Qt::WindowModal);
+ setWindowTitle(title);
+
+ m_vlist = vlist;
+
+ m_proxyModel = new QSortFilterProxyModel(this);
+ m_proxyModel->setSourceModel(vlist);
+
+ ui->listView->setModel(m_proxyModel);
+ ui->listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+ ui->listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch);
+
+ if (!cancelable)
+ {
+ ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
+ }
+}
+
+void VersionSelectDialog::setEmptyString(QString emptyString)
+{
+ ui->listView->setEmptyString(emptyString);
+}
+
+VersionSelectDialog::~VersionSelectDialog()
+{
+ delete ui;
+}
+
+void VersionSelectDialog::setResizeOn(int column)
+{
+ ui->listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::ResizeToContents);
+ resizeOnColumn = column;
+ ui->listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch);
+}
+
+int VersionSelectDialog::exec()
+{
+ QDialog::open();
+ if (!m_vlist->isLoaded())
+ loadList();
+ return QDialog::exec();
+}
+
+void VersionSelectDialog::loadList()
+{
+ ProgressDialog *taskDlg = new ProgressDialog(this);
+ Task *loadTask = m_vlist->getLoadTask();
+ loadTask->setParent(taskDlg);
+ taskDlg->exec(loadTask);
+}
+
+BaseVersionPtr VersionSelectDialog::selectedVersion() const
+{
+ auto currentIndex = ui->listView->selectionModel()->currentIndex();
+ auto variant = m_proxyModel->data(currentIndex, BaseVersionList::VersionPointerRole);
+ return variant.value<BaseVersionPtr>();
+}
+
+void VersionSelectDialog::on_refreshButton_clicked()
+{
+ loadList();
+}
+
+void VersionSelectDialog::setFilter(int column, QString filter)
+{
+ m_proxyModel->setFilterKeyColumn(column);
+ m_proxyModel->setFilterFixedString(filter);
+ /*
+ QStringList filteredTypes;
+ if (!ui->filterSnapshotsCheckbox->isChecked())
+ filteredTypes += "Snapshot";
+ if (!ui->filterMCNostalgiaCheckbox->isChecked())
+ filteredTypes += "Nostalgia";
+
+ QString regexStr = "^.*$";
+ if (filteredTypes.length() > 0)
+ regexStr = QString("^((?!%1).)*$").arg(filteredTypes.join('|'));
+
+ QLOG_DEBUG() << "Filter:" << regexStr;
+ */
+}
diff --git a/gui/dialogs/VersionSelectDialog.h b/gui/dialogs/VersionSelectDialog.h
new file mode 100644
index 00000000..61fa8ab6
--- /dev/null
+++ b/gui/dialogs/VersionSelectDialog.h
@@ -0,0 +1,62 @@
+/* Copyright 2013 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 "logic/BaseVersion.h"
+
+class BaseVersionList;
+
+namespace Ui
+{
+class VersionSelectDialog;
+}
+
+class VersionSelectDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent = 0,
+ bool cancelable = true);
+ ~VersionSelectDialog();
+
+ virtual int exec();
+
+ //! Starts a task that loads the list.
+ void loadList();
+
+ BaseVersionPtr selectedVersion() const;
+
+ void setFilter(int column, QString filter);
+ void setEmptyString(QString emptyString);
+ void setResizeOn(int column);
+
+private
+slots:
+ void on_refreshButton_clicked();
+
+private:
+ Ui::VersionSelectDialog *ui;
+
+ BaseVersionList *m_vlist;
+
+ QSortFilterProxyModel *m_proxyModel;
+
+ int resizeOnColumn = 0;
+};
diff --git a/gui/dialogs/VersionSelectDialog.ui b/gui/dialogs/VersionSelectDialog.ui
new file mode 100644
index 00000000..07e9e73e
--- /dev/null
+++ b/gui/dialogs/VersionSelectDialog.ui
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VersionSelectDialog</class>
+ <widget class="QDialog" name="VersionSelectDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>347</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Choose Version</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="VersionListView" name="listView">
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <attribute name="headerCascadingSectionResizes">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="refreshButton">
+ <property name="toolTip">
+ <string>Reloads the version list.</string>
+ </property>
+ <property name="text">
+ <string>&amp;Refresh</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>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>VersionListView</class>
+ <extends>QTreeView</extends>
+ <header>gui/widgets/VersionListView.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>VersionSelectDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>257</x>
+ <y>290</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>VersionSelectDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>325</x>
+ <y>290</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/gui/widgets/Common.cpp b/gui/widgets/Common.cpp
new file mode 100644
index 00000000..9b730d6c
--- /dev/null
+++ b/gui/widgets/Common.cpp
@@ -0,0 +1,27 @@
+#include "Common.h"
+
+// Origin: Qt
+QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
+ qreal &widthUsed)
+{
+ QStringList lines;
+ height = 0;
+ widthUsed = 0;
+ textLayout.beginLayout();
+ QString str = textLayout.text();
+ while (true)
+ {
+ QTextLine line = textLayout.createLine();
+ if (!line.isValid())
+ break;
+ if (line.textLength() == 0)
+ break;
+ line.setLineWidth(lineWidth);
+ line.setPosition(QPointF(0, height));
+ height += line.height();
+ lines.append(str.mid(line.textStart(), line.textLength()));
+ widthUsed = qMax(widthUsed, line.naturalTextWidth());
+ }
+ textLayout.endLayout();
+ return lines;
+}
diff --git a/gui/widgets/Common.h b/gui/widgets/Common.h
new file mode 100644
index 00000000..fc46e08f
--- /dev/null
+++ b/gui/widgets/Common.h
@@ -0,0 +1,6 @@
+#pragma once
+#include <QStringList>
+#include <QTextLayout>
+
+QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
+ qreal &widthUsed); \ No newline at end of file
diff --git a/gui/widgets/InstanceDelegate.cpp b/gui/widgets/InstanceDelegate.cpp
new file mode 100644
index 00000000..33da7130
--- /dev/null
+++ b/gui/widgets/InstanceDelegate.cpp
@@ -0,0 +1,231 @@
+/* Copyright 2013 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 "InstanceDelegate.h"
+#include <QPainter>
+#include <QTextOption>
+#include <QTextLayout>
+#include <QApplication>
+#include <QtCore/qmath.h>
+#include "Common.h"
+#define QFIXED_MAX (INT_MAX / 256)
+
+ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent)
+{
+}
+
+void drawSelectionRect(QPainter *painter, const QStyleOptionViewItemV4 &option,
+ const QRect &rect)
+{
+ if ((option.state & QStyle::State_Selected))
+ painter->fillRect(rect, option.palette.brush(QPalette::Highlight));
+ else
+ {
+ QColor backgroundColor = option.palette.color(QPalette::Background);
+ backgroundColor.setAlpha(160);
+ painter->fillRect(rect, QBrush(backgroundColor));
+ }
+}
+
+void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, const QRect &rect)
+{
+ if (!(option.state & QStyle::State_HasFocus))
+ return;
+ QStyleOptionFocusRect opt;
+ opt.direction = option.direction;
+ opt.fontMetrics = option.fontMetrics;
+ opt.palette = option.palette;
+ opt.rect = rect;
+ // opt.state = option.state | QStyle::State_KeyboardFocusChange |
+ // QStyle::State_Item;
+ auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base;
+ opt.backgroundColor = option.palette.color(col);
+ // Apparently some widget styles expect this hint to not be set
+ painter->setRenderHint(QPainter::Antialiasing, false);
+
+ QStyle *style = option.widget ? option.widget->style() : QApplication::style();
+
+ style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget);
+
+ painter->setRenderHint(QPainter::Antialiasing);
+}
+
+static QSize viewItemTextSize(const QStyleOptionViewItemV4 *option)
+{
+ QStyle *style = option->widget ? option->widget->style() : QApplication::style();
+ QTextOption textOption;
+ textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
+ QTextLayout textLayout;
+ textLayout.setTextOption(textOption);
+ textLayout.setFont(option->font);
+ textLayout.setText(option->text);
+ const int textMargin =
+ style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1;
+ QRect bounds(0, 0, 100 - 2 * textMargin, 600);
+ qreal height = 0, widthUsed = 0;
+ viewItemTextLayout(textLayout, bounds.width(), height, widthUsed);
+ const QSize size(qCeil(widthUsed), qCeil(height));
+ return QSize(size.width() + 2 * textMargin, size.height());
+}
+
+void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ QStyleOptionViewItemV4 opt = option;
+ initStyleOption(&opt, index);
+ painter->save();
+ painter->setClipRect(opt.rect);
+
+ opt.features |= QStyleOptionViewItem::WrapText;
+ opt.text = index.data().toString();
+ opt.textElideMode = Qt::ElideRight;
+ opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter;
+
+ QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
+
+ // const int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize);
+ const int iconSize = 48;
+ QRect iconbox = opt.rect;
+ const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1;
+ QRect textRect = opt.rect;
+ QRect textHighlightRect = textRect;
+ // clip the decoration on top, remove width padding
+ textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0);
+
+ textHighlightRect.adjust(0, iconSize + 5, 0, 0);
+
+ // draw background
+ {
+ // FIXME: unused
+ // QSize textSize = viewItemTextSize ( &opt );
+ QPalette::ColorGroup cg;
+ QStyleOptionViewItemV4 opt2(opt);
+
+ if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled))
+ {
+ if (!(opt.state & QStyle::State_Active))
+ cg = QPalette::Inactive;
+ else
+ cg = QPalette::Normal;
+ }
+ else
+ {
+ cg = QPalette::Disabled;
+ }
+ opt2.palette.setCurrentColorGroup(cg);
+
+ // fill in background, if any
+ if (opt.backgroundBrush.style() != Qt::NoBrush)
+ {
+ QPointF oldBO = painter->brushOrigin();
+ painter->setBrushOrigin(opt.rect.topLeft());
+ painter->fillRect(opt.rect, opt.backgroundBrush);
+ painter->setBrushOrigin(oldBO);
+ }
+
+ if (opt.showDecorationSelected)
+ {
+ drawSelectionRect(painter, opt2, opt.rect);
+ drawFocusRect(painter, opt2, opt.rect);
+ // painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) );
+ }
+ else
+ {
+
+ // if ( opt.state & QStyle::State_Selected )
+ {
+ // QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText, opt,
+ // opt.widget );
+ // painter->fillRect ( textHighlightRect, opt.palette.brush ( cg,
+ // QPalette::Highlight ) );
+ drawSelectionRect(painter, opt2, textHighlightRect);
+ drawFocusRect(painter, opt2, textHighlightRect);
+ }
+ }
+ }
+
+ // draw the icon
+ {
+ QIcon::Mode mode = QIcon::Normal;
+ if (!(opt.state & QStyle::State_Enabled))
+ mode = QIcon::Disabled;
+ else if (opt.state & QStyle::State_Selected)
+ mode = QIcon::Selected;
+ QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off;
+
+ iconbox.setHeight(iconSize);
+ opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state);
+ }
+ // set the text colors
+ QPalette::ColorGroup cg =
+ opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
+ if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active))
+ cg = QPalette::Inactive;
+ if (opt.state & QStyle::State_Selected)
+ {
+ painter->setPen(opt.palette.color(cg, QPalette::HighlightedText));
+ }
+ else
+ {
+ painter->setPen(opt.palette.color(cg, QPalette::Text));
+ }
+
+ // draw the text
+ QTextOption textOption;
+ textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
+ textOption.setTextDirection(opt.direction);
+ textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment));
+ QTextLayout textLayout;
+ textLayout.setTextOption(textOption);
+ textLayout.setFont(opt.font);
+ textLayout.setText(opt.text);
+
+ qreal width, height;
+ viewItemTextLayout(textLayout, textRect.width(), height, width);
+
+ const int lineCount = textLayout.lineCount();
+
+ const QRect layoutRect = QStyle::alignedRect(
+ opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect);
+ const QPointF position = layoutRect.topLeft();
+ for (int i = 0; i < lineCount; ++i)
+ {
+ const QTextLine line = textLayout.lineAt(i);
+ line.draw(painter, position);
+ }
+
+ painter->restore();
+}
+
+QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ QStyleOptionViewItemV4 opt = option;
+ initStyleOption(&opt, index);
+ opt.features |= QStyleOptionViewItem::WrapText;
+ opt.text = index.data().toString();
+ opt.textElideMode = Qt::ElideRight;
+ opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter;
+
+ QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
+ const int textMargin =
+ style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1;
+ int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables
+ QSize szz = viewItemTextSize(&opt);
+ height += szz.height();
+ // FIXME: maybe the icon items could scale and keep proportions?
+ QSize sz(100, height);
+ return sz;
+}
diff --git a/gui/widgets/InstanceDelegate.h b/gui/widgets/InstanceDelegate.h
new file mode 100644
index 00000000..6f924405
--- /dev/null
+++ b/gui/widgets/InstanceDelegate.h
@@ -0,0 +1,27 @@
+/* Copyright 2013 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 <QStyledItemDelegate>
+
+class ListViewDelegate : public QStyledItemDelegate
+{
+public:
+ explicit ListViewDelegate ( QObject* parent = 0 );
+protected:
+ void paint ( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
+ QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
+};
diff --git a/gui/widgets/LabeledToolButton.cpp b/gui/widgets/LabeledToolButton.cpp
new file mode 100644
index 00000000..fe3cac45
--- /dev/null
+++ b/gui/widgets/LabeledToolButton.cpp
@@ -0,0 +1,86 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QLabel>
+#include <QVBoxLayout>
+#include <QResizeEvent>
+#include <QStyleOption>
+#include "LabeledToolButton.h"
+#include <QApplication>
+
+/*
+ *
+ * Tool Button with a label on it, instead of the normal text rendering
+ *
+ */
+
+LabeledToolButton::LabeledToolButton(QWidget * parent)
+ : QToolButton(parent)
+ , m_label(new QLabel(this))
+{
+ //QToolButton::setText(" ");
+ m_label->setWordWrap(true);
+ m_label->setMouseTracking(false);
+ m_label->setAlignment(Qt::AlignCenter);
+ m_label->setTextInteractionFlags(Qt::NoTextInteraction);
+ // somehow, this makes word wrap work in the QLabel. yay.
+ m_label->setMinimumWidth(100);
+}
+
+QString LabeledToolButton::text() const
+{
+ return m_label->text();
+}
+
+void LabeledToolButton::setText(const QString & text)
+{
+ m_label->setText(text);
+}
+
+/*!
+ \reimp
+*/
+QSize LabeledToolButton::sizeHint() const
+{
+ /*
+ Q_D(const QToolButton);
+ if (d->sizeHint.isValid())
+ return d->sizeHint;
+ */
+ ensurePolished();
+
+ int w = 0, h = 0;
+ QStyleOptionToolButton opt;
+ initStyleOption(&opt);
+ QSize sz =m_label->sizeHint();
+ w = sz.width();
+ h = sz.height();
+
+ opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height
+ if (popupMode() == MenuButtonPopup)
+ w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this);
+
+ QSize rawSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this);
+ QSize sizeHint = rawSize.expandedTo(QApplication::globalStrut());
+ return sizeHint;
+}
+
+
+
+void LabeledToolButton::resizeEvent(QResizeEvent * event)
+{
+ m_label->setGeometry(QRect(4, 4, width()-8, height()-8));
+ QWidget::resizeEvent(event);
+}
diff --git a/gui/widgets/LabeledToolButton.h b/gui/widgets/LabeledToolButton.h
new file mode 100644
index 00000000..b417f814
--- /dev/null
+++ b/gui/widgets/LabeledToolButton.h
@@ -0,0 +1,37 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <QPushButton>
+#include <QToolButton>
+
+class QLabel;
+
+class LabeledToolButton : public QToolButton
+{
+ Q_OBJECT
+
+ QLabel * m_label;
+
+public:
+ LabeledToolButton(QWidget * parent = 0);
+
+ QString text() const;
+ void setText(const QString & text);
+ virtual QSize sizeHint() const;
+protected:
+ void resizeEvent(QResizeEvent * event);
+};
diff --git a/gui/widgets/MCModInfoFrame.cpp b/gui/widgets/MCModInfoFrame.cpp
new file mode 100644
index 00000000..abcea6c6
--- /dev/null
+++ b/gui/widgets/MCModInfoFrame.cpp
@@ -0,0 +1,111 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QMessageBox>
+#include <QtGui>
+
+#include "MCModInfoFrame.h"
+#include "ui_MCModInfoFrame.h"
+#include "gui/dialogs/CustomMessageBox.h"
+
+void MCModInfoFrame::updateWithMod(Mod &m)
+{
+ if(m.type() == m.MOD_FOLDER)
+ {
+ clear();
+ return;
+ }
+
+ QString text = "";
+ QString name = "";
+ if(m.name().isEmpty()) name = m.mmc_id();
+ else name = m.name();
+
+ if(m.homeurl().isEmpty()) text = name;
+ else text = "<a href=\"" + m.homeurl() + "\">" + name + "</a>";
+ if(!m.authors().isEmpty()) text += " by " + m.authors();
+
+ setModText(text);
+
+ if(m.description().isEmpty())
+ {
+ setModDescription(tr("No description provided in mcmod.info"));
+ }
+ else
+ {
+ setModDescription(m.description());
+ }
+}
+
+void MCModInfoFrame::clear()
+{
+ setModText(tr("Select a mod to view title and authors..."));
+ setModDescription(tr("Select a mod to view description..."));
+}
+
+MCModInfoFrame::MCModInfoFrame(QWidget *parent) :
+ QFrame(parent),
+ ui(new Ui::MCModInfoFrame)
+{
+ ui->setupUi(this);
+}
+
+MCModInfoFrame::~MCModInfoFrame()
+{
+ delete ui;
+}
+
+void MCModInfoFrame::setModText(QString text)
+{
+ ui->label_ModText->setText(text);
+}
+
+void MCModInfoFrame::setModDescription(QString text)
+{
+ ui->label_ModDescription->setToolTip("");
+ QString intermediatetext = text.trimmed();
+ bool prev(false);
+ QChar rem('\n');
+ QString finaltext;
+ finaltext.reserve(intermediatetext.size());
+ foreach(const QChar& c, intermediatetext)
+ {
+ if(c == rem && prev){
+ continue;
+ }
+ prev = c == rem;
+ finaltext += c;
+ }
+ QString labeltext;
+ labeltext.reserve(300);
+ if(finaltext.length() > 290)
+ {
+ ui->label_ModDescription->setOpenExternalLinks(false);
+ ui->label_ModDescription->setTextFormat(Qt::TextFormat::RichText);
+ desc = text;
+ labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>");
+ QObject::connect(ui->label_ModDescription, &QLabel::linkActivated, this, &MCModInfoFrame::modDescEllipsisHandler);
+ }
+ else
+ {
+ ui->label_ModDescription->setTextFormat(Qt::TextFormat::PlainText);
+ labeltext.append(finaltext);
+ }
+ ui->label_ModDescription->setText(labeltext);
+}
+void MCModInfoFrame::modDescEllipsisHandler(const QString &link)
+{
+ CustomMessageBox::selectable(this, tr(""), desc)->show();
+}
diff --git a/gui/widgets/MCModInfoFrame.h b/gui/widgets/MCModInfoFrame.h
new file mode 100644
index 00000000..54c5d674
--- /dev/null
+++ b/gui/widgets/MCModInfoFrame.h
@@ -0,0 +1,46 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <QFrame>
+#include "logic/Mod.h"
+
+namespace Ui
+{
+class MCModInfoFrame;
+}
+
+class MCModInfoFrame : public QFrame
+{
+ Q_OBJECT
+
+public:
+ explicit MCModInfoFrame(QWidget *parent = 0);
+ ~MCModInfoFrame();
+
+ void setModText(QString text);
+ void setModDescription(QString text);
+
+ void updateWithMod(Mod &m);
+ void clear();
+
+public slots:
+ void modDescEllipsisHandler(const QString& link );
+
+private:
+ Ui::MCModInfoFrame *ui;
+ QString desc;
+};
diff --git a/gui/widgets/MCModInfoFrame.ui b/gui/widgets/MCModInfoFrame.ui
new file mode 100644
index 00000000..60e0a65c
--- /dev/null
+++ b/gui/widgets/MCModInfoFrame.ui
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MCModInfoFrame</class>
+ <widget class="QFrame" name="MCModInfoFrame">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>527</width>
+ <height>113</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>120</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Frame</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_ModText">
+ <property name="text">
+ <string>Select a mod to view title and authors...</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_ModDescription">
+ <property name="text">
+ <string>Select a mod to view description...</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/gui/widgets/ModListView.cpp b/gui/widgets/ModListView.cpp
new file mode 100644
index 00000000..358e6331
--- /dev/null
+++ b/gui/widgets/ModListView.cpp
@@ -0,0 +1,62 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ModListView.h"
+#include <QHeaderView>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QDrag>
+#include <QRect>
+
+ModListView::ModListView ( QWidget* parent )
+ :QTreeView ( parent )
+{
+ setAllColumnsShowFocus ( true );
+ setExpandsOnDoubleClick ( false );
+ setRootIsDecorated ( false );
+ setSortingEnabled ( false );
+ setAlternatingRowColors ( true );
+ setSelectionMode ( QAbstractItemView::ContiguousSelection );
+ setHeaderHidden ( false );
+ setSelectionBehavior(QAbstractItemView::SelectRows);
+ setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOn );
+ setHorizontalScrollBarPolicy ( Qt::ScrollBarAsNeeded );
+ setDropIndicatorShown(true);
+ setDragEnabled(true);
+ setDragDropMode(QAbstractItemView::DropOnly);
+ viewport()->setAcceptDrops(true);
+}
+
+void ModListView::setModel ( QAbstractItemModel* model )
+{
+ QTreeView::setModel ( model );
+ auto head = header();
+ head->setStretchLastSection(false);
+ // HACK: this is true for the checkbox column of mod lists
+ auto string = model->headerData(0,head->orientation()).toString();
+ if(!string.size())
+ {
+ head->setSectionResizeMode(0, QHeaderView::ResizeToContents);
+ head->setSectionResizeMode(1, QHeaderView::Stretch);
+ for(int i = 2; i < head->count(); i++)
+ head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
+ }
+ else
+ {
+ head->setSectionResizeMode(0, QHeaderView::Stretch);
+ for(int i = 1; i < head->count(); i++)
+ head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
+ }
+}
diff --git a/gui/widgets/ModListView.h b/gui/widgets/ModListView.h
new file mode 100644
index 00000000..b26fa26b
--- /dev/null
+++ b/gui/widgets/ModListView.h
@@ -0,0 +1,27 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#include <QTreeView>
+
+class Mod;
+
+class ModListView: public QTreeView
+{
+ Q_OBJECT
+public:
+ explicit ModListView ( QWidget* parent = 0 );
+ virtual void setModel ( QAbstractItemModel* model );
+};
diff --git a/gui/widgets/VersionListView.cpp b/gui/widgets/VersionListView.cpp
new file mode 100644
index 00000000..b7f45f27
--- /dev/null
+++ b/gui/widgets/VersionListView.cpp
@@ -0,0 +1,150 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QHeaderView>
+#include <QApplication>
+#include <QMouseEvent>
+#include <QDrag>
+#include <QPainter>
+#include "VersionListView.h"
+#include "Common.h"
+
+VersionListView::VersionListView(QWidget *parent)
+ :QTreeView ( parent )
+{
+ m_emptyString = tr("No versions are currently available.");
+}
+
+void VersionListView::rowsInserted(const QModelIndex &parent, int start, int end)
+{
+ if(!m_itemCount)
+ viewport()->update();
+ m_itemCount += end-start+1;
+ QTreeView::rowsInserted(parent, start, end);
+}
+
+
+void VersionListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
+{
+ m_itemCount -= end-start+1;
+ if(!m_itemCount)
+ viewport()->update();
+ QTreeView::rowsInserted(parent, start, end);
+}
+
+void VersionListView::setModel(QAbstractItemModel *model)
+{
+ m_itemCount = model->rowCount();
+ if(!m_itemCount)
+ viewport()->update();
+ QTreeView::setModel(model);
+}
+
+void VersionListView::reset()
+{
+ if(model())
+ {
+ m_itemCount = model()->rowCount();
+ }
+ viewport()->update();
+ QTreeView::reset();
+}
+
+void VersionListView::setEmptyString(QString emptyString)
+{
+ m_emptyString = emptyString;
+ if(!m_itemCount)
+ {
+ viewport()->update();
+ }
+}
+
+void VersionListView::paintEvent(QPaintEvent *event)
+{
+ if(m_itemCount)
+ {
+ QTreeView::paintEvent(event);
+ }
+ else
+ {
+ paintInfoLabel(event);
+ }
+}
+
+void VersionListView::paintInfoLabel(QPaintEvent *event)
+{
+ int scrollInterval = 500;
+
+ //calculate the rect for the overlay
+ QPainter painter(viewport());
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ const QChar letter = 'Q';
+ QFont font("sans", 20);
+ font.setBold(true);
+
+ QRect bounds = viewport()->geometry();
+ bounds.moveTop(0);
+ QTextLayout layout(m_emptyString, font);
+ qreal height = 0.0;
+ qreal widthUsed = 0.0;
+ QStringList lines = viewItemTextLayout(layout, bounds.width() - 20, height, widthUsed);
+ QRect rect (0,0, widthUsed, height);
+ rect.setWidth(rect.width()+20);
+ rect.setHeight(rect.height()+20);
+ rect.moveCenter(bounds.center());
+ //check if we are allowed to draw in our area
+ if (!event->rect().intersects(rect)) {
+ return;
+ }
+ //draw the letter of the topmost item semitransparent in the middle
+ QColor background = QApplication::palette().color(QPalette::Foreground);
+ QColor foreground = QApplication::palette().color(QPalette::Base);
+ /*
+ background.setAlpha(128 - scrollFade);
+ foreground.setAlpha(128 - scrollFade);
+ */
+ painter.setBrush(QBrush(background));
+ painter.setPen(foreground);
+ painter.drawRoundedRect(rect, 5.0, 5.0);
+ foreground.setAlpha(190);
+ painter.setPen(foreground);
+ painter.setFont(font);
+ painter.drawText(rect, Qt::AlignCenter, lines.join("\n"));
+
+}
+
+/*
+void ModListView::setModel ( QAbstractItemModel* model )
+{
+ QTreeView::setModel ( model );
+ auto head = header();
+ head->setStretchLastSection(false);
+ // HACK: this is true for the checkbox column of mod lists
+ auto string = model->headerData(0,head->orientation()).toString();
+ if(!string.size())
+ {
+ head->setSectionResizeMode(0, QHeaderView::ResizeToContents);
+ head->setSectionResizeMode(1, QHeaderView::Stretch);
+ for(int i = 2; i < head->count(); i++)
+ head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
+ }
+ else
+ {
+ head->setSectionResizeMode(0, QHeaderView::Stretch);
+ for(int i = 1; i < head->count(); i++)
+ head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
+ }
+}
+*/ \ No newline at end of file
diff --git a/gui/widgets/VersionListView.h b/gui/widgets/VersionListView.h
new file mode 100644
index 00000000..af9b1f6a
--- /dev/null
+++ b/gui/widgets/VersionListView.h
@@ -0,0 +1,43 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#include <QTreeView>
+
+class Mod;
+
+class VersionListView : public QTreeView
+{
+ Q_OBJECT
+public:
+ explicit VersionListView(QWidget *parent = 0);
+ virtual void paintEvent(QPaintEvent *event) override;
+ void setEmptyString(QString emptyString);
+ virtual void setModel ( QAbstractItemModel* model );
+
+public slots:
+ virtual void reset() override;
+
+protected slots:
+ virtual void rowsAboutToBeRemoved(const QModelIndex & parent, int start, int end) override;
+ virtual void rowsInserted(const QModelIndex &parent, int start, int end) override;
+
+private: /* methods */
+ void paintInfoLabel(QPaintEvent *event);
+
+private: /* variables */
+ int m_itemCount = 0;
+ QString m_emptyString;
+};