PrismLauncher/gui/mainwindow.cpp
Andrew 3b38e5f924 Implemented LWJGL version lists.
The LWJGL list actually doesn't use tasks for loading. Instead, it takes advantage of the
QNetworkAccessManager's asynchronous requests. This is a system that I may look to implement for other version
lists and things such as the Minecraft version list and possibly even instance mod lists.

Loading things this way means that code that wants to load a list can simply call the load list function, rather
than having to get a task from the list and execute the task. Unfortunately, it also means we can't have task
progress dialogs for loading lists, but it shouldn't really be too difficult to write one that works with this
system.

At some point in the future, I'll probably end up putting all the code for this method of loading lists into a
base class and then update the other lists to support it.
2013-05-17 11:53:22 -05:00

484 lines
13 KiB
C++

/* 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 "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMenu>
#include <QMessageBox>
#include <QInputDialog>
#include <QApplication>
#include <QDesktopServices>
#include <QUrl>
#include <QDir>
#include <QFileInfo>
#include "osutils.h"
#include "userutils.h"
#include "pathutils.h"
#include "gui/settingsdialog.h"
#include "gui/newinstancedialog.h"
#include "gui/logindialog.h"
#include "gui/taskdialog.h"
#include "gui/browserdialog.h"
#include "gui/aboutdialog.h"
#include "gui/versionselectdialog.h"
#include "gui/lwjglselectdialog.h"
#include "gui/consolewindow.h"
#include "kcategorizedview.h"
#include "kcategorydrawer.h"
#include "instancelist.h"
#include "appsettings.h"
#include "version.h"
#include "logintask.h"
#include "gameupdatetask.h"
#include "instance.h"
#include "minecraftprocess.h"
#include "instancemodel.h"
#include "instancedelegate.h"
#include "minecraftversionlist.h"
#include "lwjglversionlist.h"
// Opens the given file in the default application.
// TODO: Move this somewhere.
void openInDefaultProgram ( QString filename );
MainWindow::MainWindow ( QWidget *parent ) :
QMainWindow ( parent ),
ui ( new Ui::MainWindow ),
instList ( globalSettings->get ( "InstanceDir" ).toString() )
{
ui->setupUi ( this );
// Set active instance to null.
m_activeInst = NULL;
// Create the widget
view = new KCategorizedView ( ui->centralWidget );
drawer = new KCategoryDrawer ( view );
/*
QPalette pal = view->palette();
pal.setBrush(QPalette::Base, QBrush(QPixmap(QString::fromUtf8(":/backgrounds/kitteh"))));
view->setPalette(pal);
*/
// 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);\
// }");
view->setSelectionMode ( QAbstractItemView::SingleSelection );
//view->setSpacing( KDialog::spacingHint() );
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);
model = new InstanceModel ( instList,this );
proxymodel = new InstanceProxyModel ( this );
proxymodel->setSortRole ( KCategorizedSortFilterProxyModel::CategorySortRole );
proxymodel->setFilterRole ( KCategorizedSortFilterProxyModel::CategorySortRole );
//proxymodel->setDynamicSortFilter ( true );
proxymodel->setSourceModel ( model );
proxymodel->sort ( 0 );
view->setFrameShape ( QFrame::NoFrame );
ui->horizontalLayout->addWidget ( view );
setWindowTitle ( QString ( "MultiMC %1" ).arg ( Version::current.toString() ) );
// TODO: Make this work with the new settings system.
// restoreGeometry(settings->getConfig().value("MainWindowGeometry", saveGeometry()).toByteArray());
// restoreState(settings->getConfig().value("MainWindowState", saveState()).toByteArray());
view->setModel ( proxymodel );
connect(view, SIGNAL(doubleClicked(const QModelIndex &)),
this, SLOT(instanceActivated(const QModelIndex &)));
// Load the instances.
instList.loadList();
// just a test
/*
instList.at(0)->setGroup("TEST GROUP");
instList.at(0)->setName("TEST ITEM");
*/
if (!MinecraftVersionList::getMainList().isLoaded())
{
m_versionLoadTask = MinecraftVersionList::getMainList().getLoadTask();
startTask(m_versionLoadTask);
}
if (!LWJGLVersionList::get().isLoaded())
{
LWJGLVersionList::get().loadList();
}
}
MainWindow::~MainWindow()
{
delete ui;
delete proxymodel;
delete model;
delete drawer;
}
void MainWindow::instanceActivated ( QModelIndex index )
{
if(!index.isValid())
return;
Instance * inst = (Instance *) index.data(InstanceModel::InstancePointerRole).value<void *>();
doLogin();
}
void MainWindow::on_actionAddInstance_triggered()
{
if (!MinecraftVersionList::getMainList().isLoaded() &&
m_versionLoadTask && m_versionLoadTask->isRunning())
{
QEventLoop waitLoop;
waitLoop.connect(m_versionLoadTask, SIGNAL(ended()), SLOT(quit()));
waitLoop.exec();
}
NewInstanceDialog *newInstDlg = new NewInstanceDialog ( this );
if (newInstDlg->exec())
{
Instance *newInstance = NULL;
QString instDirName = DirNameFromString(newInstDlg->instName());
QString instDir = PathCombine(globalSettings->get("InstanceDir").toString(),
instDirName);
InstanceLoader::InstLoaderError error = InstanceLoader::get().
createInstance(newInstance, instDir);
if (error == InstanceLoader::NoError)
{
newInstance->setName(newInstDlg->instName());
newInstance->setIntendedVersion(newInstDlg->selectedVersion()->descriptor());
instList.add(InstancePtr(newInstance));
}
else
{
QString errorMsg = QString("Failed to create instance %1: ").
arg(instDirName);
switch (error)
{
case InstanceLoader::InstExists:
errorMsg += "An instance with the given directory name already exists.";
break;
case InstanceLoader::CantCreateDir:
errorMsg += "Failed to create the instance directory.";
break;
default:
errorMsg += QString("Unknown instance loader error %1").
arg(error);
break;
}
QMessageBox::warning(this, "Error", errorMsg);
}
}
}
void MainWindow::on_actionChangeInstGroup_triggered()
{
Instance* inst = selectedInstance();
if(inst)
{
QString name ( inst->group() );
name = QInputDialog::getText ( this, tr ( "Group name" ), tr ( "Enter a new group name." ), QLineEdit::Normal, name );
inst->setGroup(name);
}
}
void MainWindow::on_actionViewInstanceFolder_triggered()
{
openInDefaultProgram ( globalSettings->get ( "InstanceDir" ).toString() );
}
void MainWindow::on_actionRefresh_triggered()
{
instList.loadList();
}
void MainWindow::on_actionViewCentralModsFolder_triggered()
{
openInDefaultProgram ( globalSettings->get ( "CentralModsDir" ).toString() );
}
void MainWindow::on_actionCheckUpdate_triggered()
{
}
void MainWindow::on_actionSettings_triggered()
{
SettingsDialog dialog ( this );
dialog.exec();
}
void MainWindow::on_actionReportBug_triggered()
{
//QDesktopServices::openUrl(QUrl("http://bugs.forkk.net/"));
openWebPage ( QUrl ( "http://bugs.forkk.net/" ) );
}
void MainWindow::on_actionNews_triggered()
{
//QDesktopServices::openUrl(QUrl("http://news.forkk.net/"));
openWebPage ( QUrl ( "http://news.forkk.net/" ) );
}
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::closeEvent ( QCloseEvent *event )
{
// Save the window state and geometry.
// TODO: Make this work with the new settings system.
// settings->getConfig().setValue("MainWindowGeometry", saveGeometry());
// settings->getConfig().setValue("MainWindowState", saveState());
QMainWindow::closeEvent ( event );
}
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 ) );
}
Instance* MainWindow::selectedInstance()
{
QAbstractItemView * iv = view;
auto smodel = iv->selectionModel();
QModelIndex mindex;
if(smodel->hasSelection())
{
auto rows = smodel->selectedRows();
mindex = rows.at(0);
}
if(mindex.isValid())
{
return (Instance *) mindex.data(InstanceModel::InstancePointerRole).value<void *>();
}
else
return nullptr;
}
void MainWindow::on_actionLaunchInstance_triggered()
{
Instance* inst = selectedInstance();
if(inst)
{
doLogin();
}
}
void MainWindow::doLogin(const QString& errorMsg)
{
if (!selectedInstance())
return;
LoginDialog* loginDlg = new LoginDialog(this, errorMsg);
if (loginDlg->exec())
{
UserInfo uInfo(loginDlg->getUsername(), loginDlg->getPassword());
TaskDialog* tDialog = new TaskDialog(this);
LoginTask* loginTask = new LoginTask(uInfo, tDialog);
connect(loginTask, SIGNAL(loginComplete(LoginResponse)),
SLOT(onLoginComplete(LoginResponse)), Qt::QueuedConnection);
connect(loginTask, SIGNAL(loginFailed(QString)),
SLOT(doLogin(QString)), Qt::QueuedConnection);
m_activeInst = selectedInstance();
tDialog->exec(loginTask);
}
}
void MainWindow::onLoginComplete(LoginResponse response)
{
Q_ASSERT_X(m_activeInst != NULL, "onLoginComplete", "no active instance is set");
if (!m_activeInst->shouldUpdateGame())
{
launchInstance(m_activeInst, response);
}
else
{
TaskDialog *tDialog = new TaskDialog(this);
GameUpdateTask *updateTask = new GameUpdateTask(response, m_activeInst);
connect(updateTask, SIGNAL(gameUpdateComplete(LoginResponse)),
SLOT(onGameUpdateComplete(LoginResponse)));
connect(updateTask, SIGNAL(gameUpdateError(QString)), SLOT(onGameUpdateError(QString)));
tDialog->exec(updateTask);
}
}
void MainWindow::onGameUpdateComplete(LoginResponse response)
{
launchInstance(response);
}
void MainWindow::onGameUpdateError(QString error)
{
QMessageBox::warning(this, "Error downloading instance", error);
}
void MainWindow::launchInstance(LoginResponse response)
{
Q_ASSERT_X(m_activeInst != NULL, "onLoginComplete", "no active instance is set");
launchInstance(m_activeInst, response);
}
void MainWindow::launchInstance(QString instID, LoginResponse response)
{
Instance *instance = instList.getInstanceById(instID).data();
Q_ASSERT_X(instance != NULL, "launchInstance", "instance ID does not correspond to a valid instance");
launchInstance(instance, response);
}
void MainWindow::launchInstance(Instance *instance, LoginResponse response)
{
Q_ASSERT_X(instance != NULL, "launchInstance", "instance is NULL");
console = new ConsoleWindow();
proc = new MinecraftProcess(instance, response.username(), response.sessionID());
console->show();
//connect(proc, SIGNAL(ended()), SLOT(onTerminated()));
connect(proc, SIGNAL(log(QString, MessageLevel::Enum)),
console, SLOT(write(QString, MessageLevel::Enum)));
proc->launch();
}
void MainWindow::taskStart(Task *task)
{
// Nothing to do here yet.
}
void MainWindow::taskEnd(Task *task)
{
if (task == m_versionLoadTask)
m_versionLoadTask = NULL;
delete task;
}
void MainWindow::startTask(Task *task)
{
connect(task, SIGNAL(started(Task*)), SLOT(taskStart(Task*)));
connect(task, SIGNAL(ended(Task*)), SLOT(taskEnd(Task*)));
task->startTask();
}
// 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" );
QMessageBox::warning ( this, "Not useful", "A Dummy Shortcut was created. it will not do anything productive" );
}
// BrowserDialog
void MainWindow::openWebPage ( QUrl url )
{
BrowserDialog *browser = new BrowserDialog ( this );
browser->load ( url );
browser->exec();
}
void openInDefaultProgram ( QString filename )
{
QDesktopServices::openUrl ( "file:///" + QFileInfo ( filename ).absolutePath() );
}
void MainWindow::on_actionChangeInstMCVersion_triggered()
{
if (view->selectionModel()->selectedIndexes().count() < 1)
return;
Instance *inst = selectedInstance();
VersionSelectDialog *vselect = new VersionSelectDialog(inst->versionList(), this);
if (vselect->exec() && vselect->selectedVersion())
{
inst->setIntendedVersion(vselect->selectedVersion()->descriptor());
}
}
void MainWindow::on_actionChangeInstLWJGLVersion_triggered()
{
Instance *inst = selectedInstance();
if (!inst)
return;
LWJGLSelectDialog *lselect = new LWJGLSelectDialog(this);
if (lselect->exec())
{
}
}