Improve group changing, update instance on version change

Gives a list of existing groups to choose from.
Instances are updated as long as there is at least one valid account.
This commit is contained in:
Petr Mrázek 2013-12-15 18:10:51 +01:00
parent 5a3043398e
commit dd9e04000c
7 changed files with 123 additions and 65 deletions

View File

@ -20,7 +20,6 @@
#include "MainWindow.h" #include "MainWindow.h"
#include "ui_MainWindow.h" #include "ui_MainWindow.h"
#include "keyring.h"
#include <QMenu> #include <QMenu>
#include <QMessageBox> #include <QMessageBox>
@ -177,14 +176,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
statusBar()->addPermanentWidget(m_statusRight, 0); statusBar()->addPermanentWidget(m_statusRight, 0);
// Add "manage accounts" button, right align // Add "manage accounts" button, right align
QWidget* spacer = new QWidget(); QWidget *spacer = new QWidget();
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
ui->mainToolBar->addWidget(spacer); ui->mainToolBar->addWidget(spacer);
accountMenu = new QMenu(this); accountMenu = new QMenu(this);
manageAccountsAction = new QAction(tr("Manage Accounts"), this); manageAccountsAction = new QAction(tr("Manage Accounts"), this);
manageAccountsAction->setCheckable(false); manageAccountsAction->setCheckable(false);
connect(manageAccountsAction, SIGNAL(triggered(bool)), this, SLOT(on_actionManageAccounts_triggered())); connect(manageAccountsAction, SIGNAL(triggered(bool)), this,
SLOT(on_actionManageAccounts_triggered()));
repopulateAccountsMenu(); repopulateAccountsMenu();
@ -193,7 +193,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
accountMenuButton->setMenu(accountMenu); accountMenuButton->setMenu(accountMenu);
accountMenuButton->setPopupMode(QToolButton::InstantPopup); accountMenuButton->setPopupMode(QToolButton::InstantPopup);
accountMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); accountMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
accountMenuButton->setIcon(QPixmap(":/icons/toolbar/noaccount").scaled(48, 48, Qt::KeepAspectRatio)); accountMenuButton->setIcon(
QPixmap(":/icons/toolbar/noaccount").scaled(48, 48, Qt::KeepAspectRatio));
QWidgetAction *accountMenuButtonAction = new QWidgetAction(this); QWidgetAction *accountMenuButtonAction = new QWidgetAction(this);
accountMenuButtonAction->setDefaultWidget(accountMenuButton); accountMenuButtonAction->setDefaultWidget(accountMenuButton);
@ -201,26 +202,28 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
ui->mainToolBar->addAction(accountMenuButtonAction); ui->mainToolBar->addAction(accountMenuButtonAction);
// Update the menu when the active account changes. // 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... // Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit.
connect(MMC->accounts().get(), &MojangAccountList::activeAccountChanged, [this] { activeAccountChanged(); }); // Template hell sucks...
connect(MMC->accounts().get(), &MojangAccountList::listChanged, [this] { repopulateAccountsMenu(); }); connect(MMC->accounts().get(), &MojangAccountList::activeAccountChanged, [this]
{ activeAccountChanged(); });
connect(MMC->accounts().get(), &MojangAccountList::listChanged, [this]
{ repopulateAccountsMenu(); });
std::shared_ptr<MojangAccountList> accounts = MMC->accounts(); std::shared_ptr<MojangAccountList> accounts = MMC->accounts();
// TODO: Nicer way to iterate? // TODO: Nicer way to iterate?
for(int i = 0; i < accounts->count(); i++) for (int i = 0; i < accounts->count(); i++)
{ {
MojangAccountPtr account = accounts->at(i); MojangAccountPtr account = accounts->at(i);
if(account != nullptr) if (account != nullptr)
{ {
auto job = new NetJob("Startup player skins: " + account->username()); auto job = new NetJob("Startup player skins: " + account->username());
for(AccountProfile profile : account->profiles()) for (AccountProfile profile : account->profiles())
{ {
auto meta = MMC->metacache()->resolveEntry("skins", profile.name + ".png"); auto meta = MMC->metacache()->resolveEntry("skins", profile.name + ".png");
auto action = CacheDownload::make( auto action = CacheDownload::make(
QUrl("http://" + URLConstants::SKINS_BASE + profile.name + ".png"), QUrl("http://" + URLConstants::SKINS_BASE + profile.name + ".png"), meta);
meta);
job->addNetAction(action); job->addNetAction(action);
meta->stale = true; meta->stale = true;
} }
@ -245,9 +248,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
// set up the updater object. // set up the updater object.
auto updater = MMC->updateChecker(); auto updater = MMC->updateChecker();
QObject::connect(updater.get(), &UpdateChecker::updateAvailable, this, &MainWindow::updateAvailable); QObject::connect(updater.get(), &UpdateChecker::updateAvailable, this,
&MainWindow::updateAvailable);
// if automatic update checks are allowed, start one. // if automatic update checks are allowed, start one.
if(MMC->settings()->get("AutoUpdate").toBool()) if (MMC->settings()->get("AutoUpdate").toBool())
on_actionCheckUpdate_triggered(); on_actionCheckUpdate_triggered();
} }
@ -321,7 +325,7 @@ void MainWindow::repopulateAccountsMenu()
QAction *action = new QAction(profile.name, this); QAction *action = new QAction(profile.name, this);
action->setData(account->username()); action->setData(account->username());
action->setCheckable(true); action->setCheckable(true);
if(active_username == account->username()) if (active_username == account->username())
{ {
action->setChecked(true); action->setChecked(true);
} }
@ -339,7 +343,7 @@ void MainWindow::repopulateAccountsMenu()
action->setCheckable(true); action->setCheckable(true);
action->setIcon(QPixmap(":/icons/toolbar/noaccount").scaled(48, 48, Qt::KeepAspectRatio)); action->setIcon(QPixmap(":/icons/toolbar/noaccount").scaled(48, 48, Qt::KeepAspectRatio));
action->setData(""); action->setData("");
if(active_username.isEmpty()) if (active_username.isEmpty())
{ {
action->setChecked(true); action->setChecked(true);
} }
@ -356,10 +360,11 @@ void MainWindow::repopulateAccountsMenu()
*/ */
void MainWindow::changeActiveAccount() void MainWindow::changeActiveAccount()
{ {
QAction* sAction = (QAction*) sender(); QAction *sAction = (QAction *)sender();
// Profile's associated Mojang username // Profile's associated Mojang username
// Will need to change when profiles are properly implemented // Will need to change when profiles are properly implemented
if (sAction->data().type() != QVariant::Type::String) return; if (sAction->data().type() != QVariant::Type::String)
return;
QVariant data = sAction->data(); QVariant data = sAction->data();
QString id = ""; QString id = "";
@ -390,7 +395,8 @@ void MainWindow::activeAccountChanged()
} }
// Set the icon to the "no account" icon. // Set the icon to the "no account" icon.
accountMenuButton->setIcon(QPixmap(":/icons/toolbar/noaccount").scaled(48, 48, Qt::KeepAspectRatio)); accountMenuButton->setIcon(
QPixmap(":/icons/toolbar/noaccount").scaled(48, 48, Qt::KeepAspectRatio));
} }
bool MainWindow::eventFilter(QObject *obj, QEvent *ev) bool MainWindow::eventFilter(QObject *obj, QEvent *ev)
@ -426,26 +432,28 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *ev)
void MainWindow::updateAvailable(QString repo, QString versionName, int versionId) void MainWindow::updateAvailable(QString repo, QString versionName, int versionId)
{ {
UpdateDialog dlg; UpdateDialog dlg;
UpdateAction action = (UpdateAction) dlg.exec(); UpdateAction action = (UpdateAction)dlg.exec();
switch(action) switch (action)
{ {
case UPDATE_LATER: case UPDATE_LATER:
QLOG_INFO() << "Update will be installed later."; QLOG_INFO() << "Update will be installed later.";
break; break;
case UPDATE_NOW: case UPDATE_NOW:
downloadUpdates(repo, versionId); downloadUpdates(repo, versionId);
break; break;
case UPDATE_ONEXIT: case UPDATE_ONEXIT:
downloadUpdates(repo, versionId, true); downloadUpdates(repo, versionId, true);
break; break;
} }
} }
void MainWindow::downloadUpdates(QString repo, int versionId, bool installOnExit) void MainWindow::downloadUpdates(QString repo, int versionId, bool installOnExit)
{ {
QLOG_INFO() << "Downloading updates."; QLOG_INFO() << "Downloading updates.";
// TODO: If the user chooses to update on exit, we should download updates in the background. // TODO: If the user chooses to update on exit, we should download updates in the
// Doing so is a bit complicated, because we'd have to make sure it finished downloading before actually exiting MultiMC. // background.
// Doing so is a bit complicated, because we'd have to make sure it finished downloading
// before actually exiting MultiMC.
ProgressDialog updateDlg(this); ProgressDialog updateDlg(this);
DownloadUpdateTask updateTask(repo, versionId, &updateDlg); DownloadUpdateTask updateTask(repo, versionId, &updateDlg);
// If the task succeeds, install the updates. // If the task succeeds, install the updates.
@ -539,15 +547,15 @@ void MainWindow::on_actionAddInstance_triggered()
} }
} }
std::shared_ptr<MojangAccountList> accounts = MMC->accounts(); if (MMC->accounts()->anyAccountIsValid())
MojangAccountPtr account = accounts->activeAccount();
if(account.get() != nullptr && account->accountStatus() != NotVerified)
{ {
ProgressDialog loadDialog(this); ProgressDialog loadDialog(this);
auto update = newInstance->doUpdate(false); auto update = newInstance->doUpdate(false);
connect(update.get(), &Task::failed , [this](QString reason) { connect(update.get(), &Task::failed, [this](QString reason)
{
QString error = QString("Instance load failed: %1").arg(reason); QString error = QString("Instance load failed: %1").arg(reason);
CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show(); CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)
->show();
}); });
loadDialog.exec(update.get()); loadDialog.exec(update.get());
} }
@ -625,8 +633,14 @@ void MainWindow::on_actionChangeInstGroup_triggered()
bool ok = false; bool ok = false;
QString name(m_selectedInstance->group()); QString name(m_selectedInstance->group());
name = QInputDialog::getText(this, tr("Group name"), tr("Enter a new group name."), auto groups = MMC->instances()->getGroups();
QLineEdit::Normal, name, &ok); 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) if (ok)
m_selectedInstance->setGroupPost(name); m_selectedInstance->setGroupPost(name);
} }
@ -810,9 +824,11 @@ void MainWindow::doLaunch()
if (accounts->count() <= 0) if (accounts->count() <= 0)
{ {
// Tell the user they need to log in at least one account in order to play. // Tell the user they need to log in at least one account in order to play.
auto reply = CustomMessageBox::selectable(this, tr("No Accounts"), auto reply = CustomMessageBox::selectable(
tr("In order to play Minecraft, you must have at least one Mojang or Minecraft account logged in to MultiMC." this, tr("No Accounts"),
"Would you like to open the account manager to add an account now?"), 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(); QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)->exec();
if (reply == QMessageBox::Yes) if (reply == QMessageBox::Yes)
@ -825,7 +841,7 @@ void MainWindow::doLaunch()
{ {
// If no default account is set, ask the user which one to use. // If no default account is set, ask the user which one to use.
AccountSelectDialog selectDialog(tr("Which account would you like to use?"), AccountSelectDialog selectDialog(tr("Which account would you like to use?"),
AccountSelectDialog::GlobalDefaultCheckbox, this); AccountSelectDialog::GlobalDefaultCheckbox, this);
selectDialog.exec(); selectDialog.exec();
@ -842,7 +858,7 @@ void MainWindow::doLaunch()
return; return;
// do the login. if the account has an access token, try to refresh it first. // do the login. if the account has an access token, try to refresh it first.
if(account->accountStatus() != NotVerified) if (account->accountStatus() != NotVerified)
{ {
// We'll need to validate the access token to make sure the account is still logged in. // We'll need to validate the access token to make sure the account is still logged in.
ProgressDialog progDialog(this); ProgressDialog progDialog(this);
@ -851,7 +867,7 @@ void MainWindow::doLaunch()
progDialog.exec(task.get()); progDialog.exec(task.get());
auto status = account->accountStatus(); auto status = account->accountStatus();
if(status != NotVerified) if (status != NotVerified)
{ {
updateInstance(m_selectedInstance, account); updateInstance(m_selectedInstance, account);
} }
@ -859,20 +875,22 @@ void MainWindow::doLaunch()
account->downgrade(); account->downgrade();
return; return;
} }
if (loginWithPassword(account, tr("Your account is currently not logged in. Please enter your password to log in again."))) if (loginWithPassword(account, tr("Your account is currently not logged in. Please enter "
"your password to log in again.")))
updateInstance(m_selectedInstance, account); updateInstance(m_selectedInstance, account);
} }
bool MainWindow::loginWithPassword(MojangAccountPtr account, const QString& errorMsg) bool MainWindow::loginWithPassword(MojangAccountPtr account, const QString &errorMsg)
{ {
EditAccountDialog passDialog(errorMsg, this, EditAccountDialog::PasswordField); EditAccountDialog passDialog(errorMsg, this, EditAccountDialog::PasswordField);
if (passDialog.exec() == QDialog::Accepted) if (passDialog.exec() == QDialog::Accepted)
{ {
// To refresh the token, we just create an authenticate task with the given account and the user's password. // To refresh the token, we just create an authenticate task with the given account and
// the user's password.
ProgressDialog progDialog(this); ProgressDialog progDialog(this);
auto task = account->login(passDialog.password()); auto task = account->login(passDialog.password());
progDialog.exec(task.get()); progDialog.exec(task.get());
if(task->successful()) if (task->successful())
return true; return true;
else else
{ {
@ -883,21 +901,20 @@ bool MainWindow::loginWithPassword(MojangAccountPtr account, const QString& erro
return false; return false;
} }
void MainWindow::updateInstance(BaseInstance* instance, MojangAccountPtr account) void MainWindow::updateInstance(BaseInstance *instance, MojangAccountPtr account)
{ {
bool only_prepare = account->accountStatus() != Online; bool only_prepare = account->accountStatus() != Online;
auto updateTask = instance->doUpdate(only_prepare); auto updateTask = instance->doUpdate(only_prepare);
if (!updateTask) if (!updateTask)
{ {
launchInstance(instance, account); launchInstance(instance, account);
return;
} }
else ProgressDialog tDialog(this);
{ connect(updateTask.get(), &Task::succeeded, [this, instance, account]
ProgressDialog tDialog(this); { launchInstance(instance, account); });
connect(updateTask.get(), &Task::succeeded, [this, instance, account] { launchInstance(instance, account); }); connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); tDialog.exec(updateTask.get());
tDialog.exec(updateTask.get());
}
} }
void MainWindow::launchInstance(BaseInstance *instance, MojangAccountPtr account) void MainWindow::launchInstance(BaseInstance *instance, MojangAccountPtr account)
@ -985,13 +1002,24 @@ void MainWindow::on_actionChangeInstMCVersion_triggered()
this, tr("Are you sure?"), this, tr("Are you sure?"),
tr("This will remove any library/version customization you did previously. " tr("This will remove any library/version customization you did previously. "
"This includes things like Forge install and similar."), "This includes things like Forge install and similar."),
QMessageBox::Warning, QMessageBox::Ok, QMessageBox::Abort)->exec(); QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Abort,
QMessageBox::Abort)->exec();
if (result != QMessageBox::Ok) if (result != QMessageBox::Ok)
return; return;
} }
m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor()); m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor());
} }
if (!MMC->accounts()->anyAccountIsValid())
return;
auto updateTask = m_selectedInstance->doUpdate(false /*only_prepare*/);
if (!updateTask)
{
return;
}
ProgressDialog tDialog(this);
connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
tDialog.exec(updateTask.get());
} }
void MainWindow::on_actionChangeInstLWJGLVersion_triggered() void MainWindow::on_actionChangeInstLWJGLVersion_triggered()

View File

@ -149,7 +149,7 @@ public:
*/ */
virtual SettingsObject &settings() const; virtual SettingsObject &settings() const;
/// returns a valid update task if update is needed, NULL otherwise /// returns a valid update task
virtual std::shared_ptr<Task> doUpdate(bool only_prepare) = 0; virtual std::shared_ptr<Task> doUpdate(bool only_prepare) = 0;
/// returns a valid minecraft process, ready for launch with the given account. /// returns a valid minecraft process, ready for launch with the given account.

View File

@ -54,11 +54,9 @@ void OneSixUpdate::executeTask()
if (m_only_prepare) if (m_only_prepare)
{ {
if (m_inst->shouldUpdate()) /*
{ * FIXME: in offline mode, do not proceed!
emitFailed("Unable to update instance in offline mode."); */
return;
}
setStatus("Testing the Java installation."); setStatus("Testing the Java installation.");
QString java_path = m_inst->settings().get("JavaPath").toString(); QString java_path = m_inst->settings().get("JavaPath").toString();

View File

@ -414,3 +414,13 @@ void MojangAccountList::setListFilePath(QString path, bool autosave)
m_listFilePath = path; m_listFilePath = path;
m_autosave = autosave; m_autosave = autosave;
} }
bool MojangAccountList::anyAccountIsValid()
{
for(auto account:m_accounts)
{
if(account->accountStatus() != NotVerified)
return true;
}
return false;
}

View File

@ -127,6 +127,11 @@ public:
*/ */
virtual void setActiveAccount(const QString &username); virtual void setActiveAccount(const QString &username);
/*!
* Returns true if any of the account is at least Validated
*/
bool anyAccountIsValid();
signals: signals:
/*! /*!
* Signal emitted to indicate that the account list has changed. * Signal emitted to indicate that the account list has changed.

View File

@ -117,6 +117,11 @@ void InstanceList::groupChanged()
saveGroupList(); saveGroupList();
} }
QStringList InstanceList::getGroups()
{
return m_groups.toList();
}
void InstanceList::saveGroupList() void InstanceList::saveGroupList()
{ {
QString groupFileName = m_instDir + "/instgroups.json"; QString groupFileName = m_instDir + "/instgroups.json";
@ -126,7 +131,7 @@ void InstanceList::saveGroupList()
if (!groupFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) if (!groupFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
{ {
// An error occurred. Ignore it. // An error occurred. Ignore it.
QLOG_ERROR() << "Failed to read instance group file."; QLOG_ERROR() << "Failed to save instance group file.";
return; return;
} }
QTextStream out(&groupFile); QTextStream out(&groupFile);
@ -137,6 +142,10 @@ void InstanceList::saveGroupList()
QString group = instance->group(); QString group = instance->group();
if (group.isEmpty()) if (group.isEmpty())
continue; continue;
// keep a list/set of groups for choosing
m_groups.insert(group);
if (!groupMap.count(group)) if (!groupMap.count(group))
{ {
QSet<QString> set; QSet<QString> set;
@ -253,6 +262,9 @@ void InstanceList::loadGroupList(QMap<QString, QString> &groupMap)
continue; continue;
} }
// keep a list/set of groups for choosing
m_groups.insert(groupName);
// Iterate through the list of instances in the group. // Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray(); QJsonArray instancesArray = groupObj.value("instances").toArray();

View File

@ -17,6 +17,7 @@
#include <QObject> #include <QObject>
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QSet>
#include "categorizedsortfilterproxymodel.h" #include "categorizedsortfilterproxymodel.h"
#include <QIcon> #include <QIcon>
@ -97,6 +98,9 @@ public:
InstancePtr getInstanceById(QString id) const; InstancePtr getInstanceById(QString id) const;
QModelIndex getInstanceIndexById(const QString &id) const; QModelIndex getInstanceIndexById(const QString &id) const;
// FIXME: instead of iterating through all instances and forming a set, keep the set around
QStringList getGroups();
signals: signals:
void dataIsInvalid(); void dataIsInvalid();
@ -116,6 +120,7 @@ private:
protected: protected:
QString m_instDir; QString m_instDir;
QList<InstancePtr> m_instances; QList<InstancePtr> m_instances;
QSet<QString> m_groups;
}; };
class InstanceProxyModel : public KCategorizedSortFilterProxyModel class InstanceProxyModel : public KCategorizedSortFilterProxyModel