diff options
Diffstat (limited to 'application/MainWindow.cpp')
-rw-r--r-- | application/MainWindow.cpp | 2069 |
1 files changed, 2069 insertions, 0 deletions
diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp new file mode 100644 index 00000000..8e235e19 --- /dev/null +++ b/application/MainWindow.cpp @@ -0,0 +1,2069 @@ +/* Copyright 2013-2015 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 "BuildConfig.h" + +#include "MainWindow.h" + + +#include <QtCore/QVariant> +#include <QtWidgets/QAction> +#include <QtWidgets/QApplication> +#include <QtWidgets/QButtonGroup> +#include <QtWidgets/QHBoxLayout> +#include <QtWidgets/QHeaderView> +#include <QtWidgets/QMainWindow> +#include <QtWidgets/QStatusBar> +#include <QtWidgets/QToolBar> +#include <QtWidgets/QWidget> + +class Ui_MainWindow +{ +public: + QAction *actionAddInstance; + QAction *actionViewInstanceFolder; + QAction *actionRefresh; + QAction *actionViewCentralModsFolder; + QAction *actionCheckUpdate; + QAction *actionSettings; + QAction *actionReportBug; + QAction *actionPatreon; + QAction *actionMoreNews; + QAction *actionAbout; + QAction *actionLaunchInstance; + QAction *actionRenameInstance; + QAction *actionChangeInstGroup; + QAction *actionChangeInstIcon; + QAction *actionEditInstNotes; + QAction *actionEditInstance; + QAction *actionViewSelectedInstFolder; + QAction *actionDeleteInstance; + QAction *actionConfig_Folder; + QAction *actionCAT; + QAction *actionCopyInstance; + QAction *actionManageAccounts; + QAction *actionLaunchInstanceOffline; + QAction *actionScreenshots; + QAction *actionInstanceSettings; + QAction *actionExportInstance; + QWidget *centralWidget; + QHBoxLayout *horizontalLayout; + QToolBar *mainToolBar; + QStatusBar *statusBar; + QToolBar *instanceToolBar; + QToolBar *newsToolBar; + + void setupUi(QMainWindow *MainWindow) + { + if (MainWindow->objectName().isEmpty()) + { + MainWindow->setObjectName(QStringLiteral("MainWindow")); + } + MainWindow->resize(694, 563); + MainWindow->setWindowIcon(MMC->getThemedIcon("multimc")); + actionAddInstance = new QAction(MainWindow); + actionAddInstance->setObjectName(QStringLiteral("actionAddInstance")); + actionAddInstance->setIcon(MMC->getThemedIcon("new")); + actionViewInstanceFolder = new QAction(MainWindow); + actionViewInstanceFolder->setObjectName(QStringLiteral("actionViewInstanceFolder")); + actionViewInstanceFolder->setIcon(MMC->getThemedIcon("viewfolder")); + actionRefresh = new QAction(MainWindow); + actionRefresh->setObjectName(QStringLiteral("actionRefresh")); + actionRefresh->setIcon(MMC->getThemedIcon("refresh")); + actionViewCentralModsFolder = new QAction(MainWindow); + actionViewCentralModsFolder->setObjectName(QStringLiteral("actionViewCentralModsFolder")); + actionViewCentralModsFolder->setIcon(MMC->getThemedIcon("centralmods")); + actionCheckUpdate = new QAction(MainWindow); + actionCheckUpdate->setObjectName(QStringLiteral("actionCheckUpdate")); + actionCheckUpdate->setIcon(MMC->getThemedIcon("checkupdate")); + actionSettings = new QAction(MainWindow); + actionSettings->setObjectName(QStringLiteral("actionSettings")); + actionSettings->setIcon(MMC->getThemedIcon("settings")); + actionSettings->setMenuRole(QAction::PreferencesRole); + actionReportBug = new QAction(MainWindow); + actionReportBug->setObjectName(QStringLiteral("actionReportBug")); + actionReportBug->setIcon(MMC->getThemedIcon("bug")); + actionPatreon = new QAction(MainWindow); + actionPatreon->setObjectName(QStringLiteral("actionPatreon")); + actionPatreon->setIcon(MMC->getThemedIcon("patreon")); + actionMoreNews = new QAction(MainWindow); + actionMoreNews->setObjectName(QStringLiteral("actionMoreNews")); + actionMoreNews->setIcon(MMC->getThemedIcon("news")); + actionAbout = new QAction(MainWindow); + actionAbout->setObjectName(QStringLiteral("actionAbout")); + actionAbout->setIcon(MMC->getThemedIcon("about")); + actionAbout->setMenuRole(QAction::AboutRole); + actionLaunchInstance = new QAction(MainWindow); + actionLaunchInstance->setObjectName(QStringLiteral("actionLaunchInstance")); + actionRenameInstance = new QAction(MainWindow); + actionRenameInstance->setObjectName(QStringLiteral("actionRenameInstance")); + actionChangeInstGroup = new QAction(MainWindow); + actionChangeInstGroup->setObjectName(QStringLiteral("actionChangeInstGroup")); + actionChangeInstIcon = new QAction(MainWindow); + actionChangeInstIcon->setObjectName(QStringLiteral("actionChangeInstIcon")); + actionChangeInstIcon->setEnabled(true); + actionChangeInstIcon->setIcon(QIcon(":/icons/instances/infinity")); + actionChangeInstIcon->setIconVisibleInMenu(true); + actionEditInstNotes = new QAction(MainWindow); + actionEditInstNotes->setObjectName(QStringLiteral("actionEditInstNotes")); + actionEditInstance = new QAction(MainWindow); + actionEditInstance->setObjectName(QStringLiteral("actionEditInstance")); + actionViewSelectedInstFolder = new QAction(MainWindow); + actionViewSelectedInstFolder->setObjectName(QStringLiteral("actionViewSelectedInstFolder")); + actionDeleteInstance = new QAction(MainWindow); + actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance")); + actionConfig_Folder = new QAction(MainWindow); + actionConfig_Folder->setObjectName(QStringLiteral("actionConfig_Folder")); + actionCAT = new QAction(MainWindow); + actionCAT->setObjectName(QStringLiteral("actionCAT")); + actionCAT->setCheckable(true); + actionCAT->setIcon(MMC->getThemedIcon("cat")); + actionCopyInstance = new QAction(MainWindow); + actionCopyInstance->setObjectName(QStringLiteral("actionCopyInstance")); + actionCopyInstance->setIcon(MMC->getThemedIcon("copy")); + actionManageAccounts = new QAction(MainWindow); + actionManageAccounts->setObjectName(QStringLiteral("actionManageAccounts")); + actionLaunchInstanceOffline = new QAction(MainWindow); + actionLaunchInstanceOffline->setObjectName(QStringLiteral("actionLaunchInstanceOffline")); + actionScreenshots = new QAction(MainWindow); + actionScreenshots->setObjectName(QStringLiteral("actionScreenshots")); + actionInstanceSettings = new QAction(MainWindow); + actionInstanceSettings->setObjectName(QStringLiteral("actionInstanceSettings")); + actionExportInstance = new QAction(MainWindow); + actionExportInstance->setObjectName(QStringLiteral("actionExportInstance")); + centralWidget = new QWidget(MainWindow); + centralWidget->setObjectName(QStringLiteral("centralWidget")); + horizontalLayout = new QHBoxLayout(centralWidget); + horizontalLayout->setSpacing(0); + horizontalLayout->setContentsMargins(11, 11, 11, 11); + horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + horizontalLayout->setSizeConstraint(QLayout::SetDefaultConstraint); + horizontalLayout->setContentsMargins(0, 0, 0, 0); + MainWindow->setCentralWidget(centralWidget); + mainToolBar = new QToolBar(MainWindow); + mainToolBar->setObjectName(QStringLiteral("mainToolBar")); + mainToolBar->setMovable(false); + mainToolBar->setAllowedAreas(Qt::TopToolBarArea); + mainToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); + mainToolBar->setFloatable(false); + MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar); + statusBar = new QStatusBar(MainWindow); + statusBar->setObjectName(QStringLiteral("statusBar")); + MainWindow->setStatusBar(statusBar); + instanceToolBar = new QToolBar(MainWindow); + instanceToolBar->setObjectName(QStringLiteral("instanceToolBar")); + instanceToolBar->setEnabled(true); + instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea|Qt::RightToolBarArea); + instanceToolBar->setIconSize(QSize(80, 80)); + instanceToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); + instanceToolBar->setFloatable(false); + MainWindow->addToolBar(Qt::RightToolBarArea, instanceToolBar); + newsToolBar = new QToolBar(MainWindow); + newsToolBar->setObjectName(QStringLiteral("newsToolBar")); + newsToolBar->setMovable(false); + newsToolBar->setAllowedAreas(Qt::BottomToolBarArea); + newsToolBar->setIconSize(QSize(16, 16)); + newsToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + newsToolBar->setFloatable(false); + MainWindow->addToolBar(Qt::BottomToolBarArea, newsToolBar); + + mainToolBar->addAction(actionAddInstance); + mainToolBar->addAction(actionCopyInstance); + mainToolBar->addSeparator(); + mainToolBar->addAction(actionViewInstanceFolder); + mainToolBar->addAction(actionViewCentralModsFolder); + mainToolBar->addAction(actionRefresh); + mainToolBar->addSeparator(); + mainToolBar->addAction(actionCheckUpdate); + mainToolBar->addAction(actionSettings); + mainToolBar->addSeparator(); + mainToolBar->addAction(actionReportBug); + mainToolBar->addAction(actionAbout); + mainToolBar->addSeparator(); + mainToolBar->addAction(actionPatreon); + mainToolBar->addAction(actionCAT); + instanceToolBar->addAction(actionChangeInstIcon); + instanceToolBar->addAction(actionLaunchInstance); + instanceToolBar->addAction(actionLaunchInstanceOffline); + instanceToolBar->addAction(actionChangeInstGroup); + instanceToolBar->addSeparator(); + instanceToolBar->addAction(actionEditInstance); + instanceToolBar->addAction(actionInstanceSettings); + instanceToolBar->addAction(actionEditInstNotes); + instanceToolBar->addAction(actionScreenshots); + instanceToolBar->addSeparator(); + instanceToolBar->addAction(actionViewSelectedInstFolder); + instanceToolBar->addAction(actionConfig_Folder); + instanceToolBar->addSeparator(); + instanceToolBar->addAction(actionExportInstance); + instanceToolBar->addAction(actionDeleteInstance); + newsToolBar->addAction(actionMoreNews); + + retranslateUi(MainWindow); + + QMetaObject::connectSlotsByName(MainWindow); + } // setupUi + + void retranslateUi(QMainWindow *MainWindow) + { + MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MultiMC 5", 0)); + actionAddInstance->setText(QApplication::translate("MainWindow", "Add Instance", 0)); + actionAddInstance->setToolTip(QApplication::translate("MainWindow", "Add a new instance.", 0)); + actionAddInstance->setStatusTip(QApplication::translate("MainWindow", "Add a new instance.", 0)); + actionViewInstanceFolder->setText(QApplication::translate("MainWindow", "View Instance Folder", 0)); + actionViewInstanceFolder->setToolTip(QApplication::translate("MainWindow", "Open the instance folder in a file browser.", 0)); + actionViewInstanceFolder->setStatusTip(QApplication::translate("MainWindow", "Open the instance folder in a file browser.", 0)); + actionRefresh->setText(QApplication::translate("MainWindow", "Refresh", 0)); + actionRefresh->setToolTip(QApplication::translate("MainWindow", "Reload the instance list.", 0)); + actionRefresh->setStatusTip(QApplication::translate("MainWindow", "Reload the instance list.", 0)); + actionViewCentralModsFolder->setText(QApplication::translate("MainWindow", "View Central Mods Folder", 0)); + actionViewCentralModsFolder->setToolTip(QApplication::translate("MainWindow", "Open the central mods folder in a file browser.", 0)); + actionViewCentralModsFolder->setStatusTip(QApplication::translate("MainWindow", "Open the central mods folder in a file browser.", 0)); + actionCheckUpdate->setText(QApplication::translate("MainWindow", "Check for Updates", 0)); + actionCheckUpdate->setToolTip(QApplication::translate("MainWindow", "Check for new updates for MultiMC", 0)); + actionCheckUpdate->setStatusTip(QApplication::translate("MainWindow", "Check for new updates for MultiMC", 0)); + actionSettings->setText(QApplication::translate("MainWindow", "Settings", 0)); + actionSettings->setToolTip(QApplication::translate("MainWindow", "Change settings.", 0)); + actionSettings->setStatusTip(QApplication::translate("MainWindow", "Change settings.", 0)); + actionReportBug->setText(QApplication::translate("MainWindow", "Report a Bug", 0)); + actionReportBug->setToolTip(QApplication::translate("MainWindow", "Open the bug tracker to report a bug with MultiMC.", 0)); + actionReportBug->setStatusTip(QApplication::translate("MainWindow", "Open the bug tracker to report a bug with MultiMC.", 0)); + actionPatreon->setText(QApplication::translate("MainWindow", "Support us on Patreon!", 0)); + actionPatreon->setToolTip(QApplication::translate("MainWindow", "Open the MultiMC Patreon page.", 0)); + actionPatreon->setStatusTip(QApplication::translate("MainWindow", "Open the MultiMC Patreon page.", 0)); + actionMoreNews->setText(QApplication::translate("MainWindow", "More News", 0)); + actionMoreNews->setIconText(QApplication::translate("MainWindow", "More news...", 0)); + actionMoreNews->setToolTip(QApplication::translate("MainWindow", "Open the MultiMC development blog to read more news about MultiMC.", 0)); + actionMoreNews->setStatusTip(QApplication::translate("MainWindow", "Open the MultiMC development blog to read more news about MultiMC.", 0)); + actionAbout->setText(QApplication::translate("MainWindow", "About MultiMC", 0)); + actionAbout->setToolTip(QApplication::translate("MainWindow", "View information about MultiMC.", 0)); + actionAbout->setStatusTip(QApplication::translate("MainWindow", "About MultiMC", 0)); + actionLaunchInstance->setText(QApplication::translate("MainWindow", "Play", 0)); + actionLaunchInstance->setToolTip(QApplication::translate("MainWindow", "Launch the selected instance.", 0)); + actionLaunchInstance->setStatusTip(QApplication::translate("MainWindow", "Launch the selected instance.", 0)); + actionRenameInstance->setText(QApplication::translate("MainWindow", "Instance Name", 0)); + actionRenameInstance->setToolTip(QApplication::translate("MainWindow", "Rename the selected instance.", 0)); + actionRenameInstance->setStatusTip(QApplication::translate("MainWindow", "Rename the selected instance.", 0)); + actionChangeInstGroup->setText(QApplication::translate("MainWindow", "Change Group", 0)); + actionChangeInstGroup->setToolTip(QApplication::translate("MainWindow", "Change the selected instance's group.", 0)); + actionChangeInstGroup->setStatusTip(QApplication::translate("MainWindow", "Change the selected instance's group.", 0)); + actionChangeInstIcon->setText(QApplication::translate("MainWindow", "Change Icon", 0)); + actionChangeInstIcon->setToolTip(QApplication::translate("MainWindow", "Change the selected instance's icon.", 0)); + actionChangeInstIcon->setStatusTip(QApplication::translate("MainWindow", "Change the selected instance's icon.", 0)); + actionEditInstNotes->setText(QApplication::translate("MainWindow", "Edit Notes", 0)); + actionEditInstNotes->setToolTip(QApplication::translate("MainWindow", "Edit the notes for the selected instance.", 0)); + actionEditInstNotes->setStatusTip(QApplication::translate("MainWindow", "Edit the notes for the selected instance.", 0)); + actionEditInstance->setText(QApplication::translate("MainWindow", "Edit Instance", 0)); + actionEditInstance->setIconText(QApplication::translate("MainWindow", "Edit Instance", 0)); + actionEditInstance->setToolTip(QApplication::translate("MainWindow", "Change the instance settings, mods and versions.", 0)); + actionEditInstance->setStatusTip(QApplication::translate("MainWindow", "Change the instance settings, mods and versions.", 0)); + actionViewSelectedInstFolder->setText(QApplication::translate("MainWindow", "Instance Folder", 0)); + actionViewSelectedInstFolder->setToolTip(QApplication::translate("MainWindow", "Open the selected instance's root folder in a file browser.", 0)); + actionViewSelectedInstFolder->setStatusTip(QApplication::translate("MainWindow", "Open the selected instance's root folder in a file browser.", 0)); + actionDeleteInstance->setText(QApplication::translate("MainWindow", "Delete", 0)); + actionDeleteInstance->setToolTip(QApplication::translate("MainWindow", "Delete the selected instance.", 0)); + actionDeleteInstance->setStatusTip(QApplication::translate("MainWindow", "Delete the selected instance.", 0)); + actionConfig_Folder->setText(QApplication::translate("MainWindow", "Config Folder", 0)); + actionConfig_Folder->setToolTip(QApplication::translate("MainWindow", "Open the instance's config folder", 0)); + actionCAT->setText(QApplication::translate("MainWindow", "Meow", 0)); + actionCAT->setToolTip(QApplication::translate("MainWindow", "<html><head/><body><p align=\"center\">It's a fluffy kitty :3</p></body></html>", 0)); + actionCopyInstance->setText(QApplication::translate("MainWindow", "Copy Instance", 0)); + actionCopyInstance->setToolTip(QApplication::translate("MainWindow", "Copy the selected instance.", 0)); + actionCopyInstance->setStatusTip(QApplication::translate("MainWindow", "Add a new instance.", 0)); + actionManageAccounts->setText(QApplication::translate("MainWindow", "Manage Accounts", 0)); + actionManageAccounts->setToolTip(QApplication::translate("MainWindow", "Manage your Mojang or Minecraft accounts.", 0)); + actionLaunchInstanceOffline->setText(QApplication::translate("MainWindow", "Play Offline", 0)); + actionLaunchInstanceOffline->setToolTip(QApplication::translate("MainWindow", "Launch the selected instance in offline mode.", 0)); + actionLaunchInstanceOffline->setStatusTip(QApplication::translate("MainWindow", "Launch the selected instance.", 0)); + actionScreenshots->setText(QApplication::translate("MainWindow", "Manage Screenshots", 0)); + actionScreenshots->setToolTip(QApplication::translate("MainWindow", "<html><head/><body><p>View and upload screenshots for this instance</p></body></html>", 0)); + actionInstanceSettings->setText(QApplication::translate("MainWindow", "Instance Settings", 0)); + actionInstanceSettings->setToolTip(QApplication::translate("MainWindow", "Change the settings specific to the instance", 0)); + actionExportInstance->setText(QApplication::translate("MainWindow", "Export Instance", 0)); + mainToolBar->setWindowTitle(QApplication::translate("MainWindow", "Main Toolbar", 0)); + instanceToolBar->setWindowTitle(QApplication::translate("MainWindow", "Instance Toolbar", 0)); + newsToolBar->setWindowTitle(QApplication::translate("MainWindow", "News Toolbar", 0)); + } // retranslateUi + +}; + +namespace Ui { + class MainWindow: public Ui_MainWindow {}; +} // namespace Ui + +#include <QMenu> +#include <QMessageBox> +#include <QInputDialog> + +#include <QDesktopServices> +#include <QKeyEvent> +#include <QUrl> +#include <QDir> +#include <QFileInfo> +#include <QLabel> +#include <QToolButton> +#include <QWidgetAction> +#include <QProgressDialog> +#include <QShortcut> +#include <QFileDialog> + +#include <JlCompress.h> + +#include "osutils.h" +#include "userutils.h" +#include "pathutils.h" + +#include "groupview/GroupView.h" +#include "groupview/InstanceDelegate.h" +#include "InstanceProxyModel.h" + +#include "Platform.h" + +#include "widgets/LabeledToolButton.h" +#include "widgets/ServerStatus.h" + +#include "dialogs/NewInstanceDialog.h" +#include "dialogs/ProgressDialog.h" +#include "dialogs/AboutDialog.h" +#include "dialogs/VersionSelectDialog.h" +#include "dialogs/CustomMessageBox.h" +#include "dialogs/IconPickerDialog.h" +#include "dialogs/CopyInstanceDialog.h" +#include "dialogs/AccountSelectDialog.h" +#include "dialogs/UpdateDialog.h" +#include "dialogs/EditAccountDialog.h" +#include "dialogs/NotificationDialog.h" + +#include "pages/global/MultiMCPage.h" +#include "pages/global/ExternalToolsPage.h" +#include "pages/global/AccountListPage.h" +#include "pages/global/ProxyPage.h" +#include "pages/global/JavaPage.h" +#include "pages/global/MinecraftPage.h" + +#include "ConsoleWindow.h" +#include "pagedialog/PageDialog.h" + +#include "InstanceList.h" +#include "minecraft/MinecraftVersionList.h" +#include "minecraft/LwjglVersionList.h" +#include "icons/IconList.h" +#include "java/JavaVersionList.h" + +#include "auth/flows/AuthenticateTask.h" +#include "auth/flows/RefreshTask.h" + +#include "updater/DownloadTask.h" + +#include "news/NewsChecker.h" + +#include "net/URLConstants.h" +#include "net/NetJob.h" +#include "Env.h" + +#include "BaseInstance.h" +#include "BaseProcess.h" +#include "java/JavaUtils.h" +#include "NagUtils.h" +#include "InstancePageProvider.h" +#include "minecraft/SkinUtils.h" + +//#include "minecraft/LegacyInstance.h" + +#include <updater/UpdateChecker.h> +#include <notifications/NotificationChecker.h> +#include <tasks/ThreadTask.h> +#include <net/CacheDownload.h> + +#include "tools/BaseProfiler.h" + +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) +{ + MultiMCPlatform::fixWM_CLASS(this); + ui->setupUi(this); + + // initialize the news checker + m_newsChecker.reset(new NewsChecker(BuildConfig.NEWS_RSS_URL)); + + QString winTitle = + QString("MultiMC 5 - Version %1").arg(BuildConfig.printableVersionString()); + if (!BuildConfig.BUILD_PLATFORM.isEmpty()) + winTitle += " on " + BuildConfig.BUILD_PLATFORM; + setWindowTitle(winTitle); + + // OSX magic. + setUnifiedTitleAndToolBarOnMac(true); + + // Global shortcuts + { + // FIXME: This is kinda weird. and bad. We need some kind of managed shutdown. + auto q = new QShortcut(QKeySequence::Quit, this); + connect(q, SIGNAL(activated()), qApp, SLOT(quit())); + } + + // 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(MMC->getThemedIcon("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(m_newsChecker.get(), &NewsChecker::newsLoaded, this, + &MainWindow::updateNewsLabel); + updateNewsLabel(); + } + + // Create the instance list widget + { + view = new GroupView(ui->centralWidget); + + 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(ENV.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 ServerStatus(this); + statusBar()->addPermanentWidget(m_statusLeft, 1); + statusBar()->addPermanentWidget(m_statusRight, 0); + + // 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(MMC->getThemedIcon("noaccount")); + + QWidgetAction *accountMenuButtonAction = new QWidgetAction(this); + accountMenuButtonAction->setDefaultWidget(accountMenuButton); + + ui->mainToolBar->addAction(accountMenuButtonAction); + + // set up global pages dialog + { + m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings")); + m_globalSettingsProvider->addPage<MultiMCPage>(); + m_globalSettingsProvider->addPage<MinecraftPage>(); + m_globalSettingsProvider->addPage<JavaPage>(); + m_globalSettingsProvider->addPage<ProxyPage>(); + m_globalSettingsProvider->addPage<ExternalToolsPage>(); + m_globalSettingsProvider->addPage<AccountListPage>(); + } + + // 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(); + + QList<CacheDownloadPtr> skin_dls; + for (int i = 0; i < accounts->count(); i++) + { + auto account = accounts->at(i); + if (account != nullptr) + { + for (auto profile : account->profiles()) + { + auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.name + ".png"); + auto action = CacheDownload::make( + QUrl("http://" + URLConstants::SKINS_BASE + profile.name + ".png"), meta); + skin_dls.append(action); + meta->stale = true; + } + } + } + if (!skin_dls.isEmpty()) + { + auto job = new NetJob("Startup player skins download"); + connect(job, SIGNAL(succeeded()), SLOT(skinJobFinished())); + connect(job, SIGNAL(failed()), SLOT(skinJobFinished())); + for (auto action : skin_dls) + { + job->addNetAction(action); + } + skin_download_job.reset(job); + 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(); + } + + m_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, + &MainWindow::updateNotAvailable); + // if automatic update checks are allowed, start one. + if (MMC->settings()->get("AutoUpdate").toBool()) + { + auto updater = MMC->updateChecker(); + updater->checkForUpdate(MMC->settings()->get("UpdateChannel").toString(), false); + } + auto checker = new NotificationChecker(); + checker->setNotificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL)); + checker->setApplicationChannel(BuildConfig.VERSION_CHANNEL); + checker->setApplicationPlatform(BuildConfig.BUILD_PLATFORM); + checker->setApplicationFullVersion(BuildConfig.FULL_VERSION_STR); + m_notificationChecker.reset(checker); + connect(m_notificationChecker.get(), + &NotificationChecker::notificationCheckFinished, this, + &MainWindow::notificationsChanged); + checker->checkForNotifications(); + } + + setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString()); + + // removing this looks stupid + view->setFocus(); +} + +MainWindow::~MainWindow() +{ + delete ui; + delete proxymodel; +} + +void MainWindow::skinJobFinished() +{ + activeAccountChanged(); + skin_download_job.reset(); +} + +void MainWindow::showInstanceContextMenu(const QPoint &pos) +{ + QList<QAction *> actions; + + QAction *actionSep = new QAction("", this); + actionSep->setSeparator(true); + + bool onInstance = view->indexAt(pos).isValid(); + if (onInstance) + { + actions = ui->instanceToolBar->actions(); + + QAction *actionVoid = new QAction(m_selectedInstance->name(), this); + actionVoid->setEnabled(false); + + QAction *actionRename = new QAction(tr("Rename"), this); + actionRename->setToolTip(ui->actionRenameInstance->toolTip()); + + QAction *actionCopyInstance = new QAction(tr("Copy instance"), this); + actionCopyInstance->setToolTip(ui->actionCopyInstance->toolTip()); + + connect(actionRename, SIGNAL(triggered(bool)), + SLOT(on_actionRenameInstance_triggered())); + connect(actionCopyInstance, SIGNAL(triggered(bool)), + SLOT(on_actionCopyInstance_triggered())); + + actions.replace(1, actionRename); + actions.prepend(actionSep); + actions.prepend(actionVoid); + actions.append(actionCopyInstance); + } + else + { + QAction *actionVoid = new QAction(tr("MultiMC"), this); + actionVoid->setEnabled(false); + + QAction *actionCreateInstance = new QAction(tr("Create instance"), this); + actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip()); + + connect(actionCreateInstance, SIGNAL(triggered(bool)), + SLOT(on_actionAddInstance_triggered())); + + actions.prepend(actionSep); + actions.prepend(actionVoid); + actions.append(actionCreateInstance); + } + QMenu myMenu; + myMenu.addActions(actions); + if (onInstance) + myMenu.setEnabled(m_selectedInstance->canLaunch()); + myMenu.exec(view->mapToGlobal(pos)); +} + +void MainWindow::updateToolsMenu() +{ + if (ui->actionLaunchInstance->menu()) + { + ui->actionLaunchInstance->menu()->deleteLater(); + } + QMenu *launchMenu = new QMenu(this); + QAction *normalLaunch = launchMenu->addAction(tr("Launch")); + connect(normalLaunch, &QAction::triggered, [this]() + { doLaunch(); }); + launchMenu->addSeparator()->setText(tr("Profilers")); + for (auto profiler : MMC->profilers().values()) + { + QAction *profilerAction = launchMenu->addAction(profiler->name()); + QString error; + if (!profiler->check(&error)) + { + profilerAction->setDisabled(true); + profilerAction->setToolTip( + tr("Profiler not setup correctly. Go into settings, \"External Tools\".")); + } + else + { + connect(profilerAction, &QAction::triggered, [this, profiler]() + { doLaunch(true, profiler.get()); }); + } + } + launchMenu->addSeparator()->setText(tr("Tools")); + for (auto tool : MMC->tools().values()) + { + QAction *toolAction = launchMenu->addAction(tool->name()); + QString error; + if (!tool->check(&error)) + { + toolAction->setDisabled(true); + toolAction->setToolTip( + tr("Tool not setup correctly. Go into settings, \"External Tools\".")); + } + else + { + connect(toolAction, &QAction::triggered, [this, tool]() + { tool->createDetachedTool(m_selectedInstance, this)->run(); }); + } + } + ui->actionLaunchInstance->setMenu(launchMenu); +} + +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(MMC->getThemedIcon("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(MMC->getThemedIcon("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() +{ + if (m_newsChecker->isLoadingNews()) + { + newsLabel->setText(tr("Loading news...")); + newsLabel->setEnabled(false); + } + else + { + QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries(); + if (entries.length() > 0) + { + newsLabel->setText(entries[0]->title); + newsLabel->setEnabled(true); + } + else + { + newsLabel->setText(tr("No news available.")); + newsLabel->setEnabled(false); + } + } +} + +void MainWindow::updateAvailable(GoUpdate::Status status) +{ + UpdateDialog dlg; + UpdateAction action = (UpdateAction)dlg.exec(); + switch (action) + { + case UPDATE_LATER: + qDebug() << "Update will be installed later."; + break; + case UPDATE_NOW: + downloadUpdates(status); + break; + case UPDATE_ONEXIT: + downloadUpdates(status, true); + break; + } +} + +void MainWindow::updateNotAvailable() +{ + UpdateDialog dlg(false); + dlg.exec(); +} + +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 = + m_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)) + { + NotificationDialog dialog(entry, this); + if (dialog.exec() == NotificationDialog::DontShowAgain) + { + shownNotifications.append(entry.id); + } + } + } + MMC->settings()->set("ShownNotifications", intListToString(shownNotifications)); +} + +void MainWindow::downloadUpdates(GoUpdate::Status status, bool installOnExit) +{ + qDebug() << "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); + status.rootPath = MMC->rootPath; + + GoUpdate::DownloadTask updateTask(status, &updateDlg); + // If the task succeeds, install the updates. + if (updateDlg.exec(&updateTask)) + { + UpdateFlags baseFlags = None; + if (BuildConfig.UPDATER_DRY_RUN) + baseFlags |= DryRun; + 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("GroupView" + "{" + "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()); + } +} + +static QFileInfo findRecursive(const QString &dir, const QString &name) +{ + for (const auto info : QDir(dir).entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files, QDir::DirsLast)) + { + if (info.isFile() && info.fileName() == name) + { + return info; + } + else if (info.isDir()) + { + const QFileInfo res = findRecursive(info.absoluteFilePath(), name); + if (res.isFile() && res.exists()) + { + return res; + } + } + } + return QFileInfo(); +} + +// FIXME: eliminate, should not be needed +void MainWindow::waitForMinecraftVersions() +{ + 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(); + } +} + +void MainWindow::instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url) +{ + InstancePtr newInstance; + + QString instancesDir = MMC->settings()->get("InstanceDir").toString(); + QString instDirName = DirNameFromString(instName, instancesDir); + QString instDir = PathCombine(instancesDir, instDirName); + + QString archivePath; + if (url.isLocalFile()) + { + archivePath = url.toLocalFile(); + } + else + { + const QString path = url.host() + '/' + url.path(); + auto entry = ENV.metacache()->resolveEntry("general", path); + CacheDownloadPtr dl = CacheDownload::make(url, entry); + NetJob job(tr("Modpack download")); + job.addNetAction(dl); + + // FIXME: possibly causes endless loop problems + ProgressDialog dlDialog(this); + if (dlDialog.exec(&job) != QDialog::Accepted) + { + return; + } + archivePath = entry->getFullPath(); + } + + QTemporaryDir extractTmpDir; + QDir extractDir(extractTmpDir.path()); + qDebug() << "Attempting to create instance from" << archivePath; + if (JlCompress::extractDir(archivePath, extractDir.absolutePath()).isEmpty()) + { + CustomMessageBox::selectable(this, tr("Error"), + tr("Failed to extract modpack"), QMessageBox::Warning)->show(); + return; + } + const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg"); + if (!instanceCfgFile.isFile() || !instanceCfgFile.exists()) + { + CustomMessageBox::selectable(this, tr("Error"), tr("Archive does not contain instance.cfg"))->show(); + return; + } + if (!copyPath(instanceCfgFile.absoluteDir().absolutePath(), instDir)) + { + CustomMessageBox::selectable(this, tr("Error"), tr("Unable to copy instance"))->show(); + return; + } + + auto error = MMC->instances()->loadInstance(newInstance, instDir); + QString errorMsg = tr("Failed to load instance %1: ").arg(instDirName); + switch (error) + { + case InstanceList::UnknownLoadError: + errorMsg += tr("Unkown error"); + CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); + return; + case InstanceList::NotAnInstance: + errorMsg += tr("Not an instance"); + CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); + return; + } + + newInstance->setName(instName); + newInstance->setIconKey(instIcon); + newInstance->setGroupInitial(instGroup); + MMC->instances()->add(InstancePtr(newInstance)); + MMC->instances()->saveGroupList(); + + finalizeInstance(newInstance); +} + +void MainWindow::instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version) +{ + InstancePtr newInstance; + + QString instancesDir = MMC->settings()->get("InstanceDir").toString(); + QString instDirName = DirNameFromString(instName, instancesDir); + QString instDir = PathCombine(instancesDir, instDirName); + auto error = MMC->instances()->createInstance(newInstance, version, instDir); + QString errorMsg = tr("Failed to create instance %1: ").arg(instDirName); + switch (error) + { + case InstanceList::NoCreateError: + break; + + case InstanceList::InstExists: + { + errorMsg += tr("An instance with the given directory name already exists."); + CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); + return; + } + + case InstanceList::CantCreateDir: + { + errorMsg += tr("Failed to create the instance directory."); + CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); + return; + } + + default: + { + errorMsg += tr("Unknown instance loader error %1").arg(error); + CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); + return; + } + } + newInstance->setName(instName); + newInstance->setIconKey(instIcon); + newInstance->setGroupInitial(instGroup); + MMC->instances()->add(InstancePtr(newInstance)); + MMC->instances()->saveGroupList(); + finalizeInstance(newInstance); +} + +void MainWindow::finalizeInstance(InstancePtr inst) +{ + if (MMC->accounts()->anyAccountIsValid()) + { + ProgressDialog loadDialog(this); + auto update = inst->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_actionAddInstance_triggered() +{ + waitForMinecraftVersions(); + + NewInstanceDialog newInstDlg(this); + if (!newInstDlg.exec()) + return; + + MMC->settings()->set("LastUsedGroupForNewInstance", newInstDlg.instGroup()); + + const QUrl modpackUrl = newInstDlg.modpackUrl(); + + + if (modpackUrl.isValid()) + { + instanceFromZipPack(newInstDlg.instName(), newInstDlg.instGroup(), newInstDlg.iconKey(), modpackUrl); + } + else + { + instanceFromVersion(newInstDlg.instName(), newInstDlg.instGroup(), newInstDlg.iconKey(), newInstDlg.selectedVersion()); + } +} + +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); + + InstancePtr newInstance; + auto error = MMC->instances()->copyInstance(newInstance, m_selectedInstance, instDir); + + QString errorMsg = tr("Failed to create instance %1: ").arg(instDirName); + switch (error) + { + case InstanceList::NoCreateError: + newInstance->setName(copyInstDlg.instName()); + newInstance->setGroupInitial(copyInstDlg.instGroup()); + newInstance->setIconKey(copyInstDlg.iconKey()); + MMC->instances()->add(newInstance); + return; + + case InstanceList::InstExists: + { + errorMsg += tr("An instance with the given directory name already exists."); + CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); + break; + } + + case InstanceList::CantCreateDir: + { + errorMsg += tr("Failed to create the instance directory."); + CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); + break; + } + + default: + { + errorMsg += tr("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 = ENV.icons()->getBigIcon(dlg.selectedIconKey); + ui->actionChangeInstIcon->setIcon(ico); + } +} + +void MainWindow::iconUpdated(QString icon) +{ + if (icon == m_currentInstIcon) + { + ui->actionChangeInstIcon->setIcon(ENV.icons()->getBigIcon(m_currentInstIcon)); + } +} + +void MainWindow::updateInstanceToolIcon(QString new_icon) +{ + m_currentInstIcon = new_icon; + ui->actionChangeInstIcon->setIcon(ENV.icons()->getBigIcon(m_currentInstIcon)); +} + +void MainWindow::setSelectedInstanceById(const QString &id) +{ + if (id.isNull()) + return; + const QModelIndex index = MMC->instances()->getInstanceIndexById(id); + if (index.isValid()) + { + QModelIndex 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(MMC->settings()->get("UpdateChannel").toString(), true); +} + +template <typename T> +void ShowPageDialog(T raw_provider, QWidget * parent, QString open_page = QString()) +{ + auto provider = std::dynamic_pointer_cast<BasePageProvider>(raw_provider); + if(!provider) + return; + PageDialog dlg(provider, open_page, parent); + dlg.exec(); +} + +void ShowInstancePageDialog(InstancePtr instance, QWidget * parent, QString open_page = QString()) +{ + auto provider = std::make_shared<InstancePageProvider>(instance); + ShowPageDialog(provider, parent, open_page); +} + +void MainWindow::on_actionSettings_triggered() +{ + ShowPageDialog(m_globalSettingsProvider, this, "global-settings"); + // FIXME: quick HACK to make this work. improve, optimize. + proxymodel->invalidate(); + proxymodel->sort(0); + updateToolsMenu(); + update(); +} + +void MainWindow::on_actionInstanceSettings_triggered() +{ + ShowInstancePageDialog(m_selectedInstance, this, "settings"); +} + +void MainWindow::on_actionEditInstNotes_triggered() +{ + ShowInstancePageDialog(m_selectedInstance, this, "notes"); +} + +void MainWindow::on_actionEditInstance_triggered() +{ + ShowInstancePageDialog(m_selectedInstance, this); +} + +void MainWindow::on_actionScreenshots_triggered() +{ + ShowInstancePageDialog(m_selectedInstance, this, "screenshots"); +} + + +void MainWindow::on_actionManageAccounts_triggered() +{ + ShowPageDialog(m_globalSettingsProvider, this, "accounts"); +} + +void MainWindow::on_actionReportBug_triggered() +{ + openWebPage(QUrl("https://github.com/MultiMC/MultiMC5/issues")); +} + +void MainWindow::on_actionPatreon_triggered() +{ + openWebPage(QUrl("http://www.patreon.com/multimc")); +} + +void MainWindow::on_actionMoreNews_triggered() +{ + openWebPage(QUrl("http://multimc.org/posts.html")); +} + +void MainWindow::newsButtonClicked() +{ + QList<NewsEntryPtr> entries = m_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(); + } + } +} + +#include <pathutils.h> + +bool compressSubDir(QuaZip* zip, QString dir, QString origDir, QString prefix) +{ + if (!zip) return false; + if (zip->getMode()!=QuaZip::mdCreate && zip->getMode()!=QuaZip::mdAppend && zip->getMode()!=QuaZip::mdAdd) + { + return false; + } + + QDir directory(dir); + if (!directory.exists()) return false; + + QDir origDirectory(origDir); + if (dir != origDir) + { + QuaZipFile dirZipFile(zip); + auto dirPrefix = PathCombine(prefix, origDirectory.relativeFilePath(dir)) + "/"; + if (!dirZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(dirPrefix, dir), 0, 0, 0)) + { + return false; + } + dirZipFile.close(); + } + + QFileInfoList files = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden); + for (auto file: files) + { + if(!compressSubDir(zip,file.absoluteFilePath(),origDir, prefix)) + { + return false; + } + } + + files = directory.entryInfoList(QDir::Files); + for (auto file: files) + { + if(!file.isFile()) + { + continue; + } + + if(file.absoluteFilePath()==zip->getZipName()) + { + continue; + } + + QString filename = origDirectory.relativeFilePath(file.absoluteFilePath()); + if(prefix.size()) + { + filename = PathCombine(prefix, filename); + } + if (!JlCompress::compressFile(zip,file.absoluteFilePath(),filename)) + { + return false; + } + } + + return true; +} + +bool compressDir(QString zipFile, QString dir, QString prefix = QString()) +{ + QuaZip zip(zipFile); + QDir().mkpath(QFileInfo(zipFile).absolutePath()); + if(!zip.open(QuaZip::mdCreate)) + { + QFile::remove(zipFile); + return false; + } + + QSet<QString> added; + if (!compressSubDir(&zip,dir,dir,prefix)) + { + QFile::remove(zipFile); + return false; + } + zip.close(); + if(zip.getZipError()!=0) + { + QFile::remove(zipFile); + return false; + } + return true; +} + +void MainWindow::on_actionExportInstance_triggered() +{ + if (m_selectedInstance) + { + auto name = RemoveInvalidFilenameChars(m_selectedInstance->name()); + + const QString output = QFileDialog::getSaveFileName(this, tr("Export %1") + .arg(m_selectedInstance->name()), + PathCombine(QDir::homePath(), name + ".zip") , "Zip (*.zip)"); + if (output.isNull()) + { + return; + } + if (QFile::exists(output)) + { + int ret = QMessageBox::question(this, tr("Overwrite?"), tr("This file already exists. Do you want to overwrite it?"), + QMessageBox::No, QMessageBox::Yes); + if (ret == QMessageBox::No) + { + return; + } + } + + if (!compressDir(output, m_selectedInstance->instanceRoot(), name)) + { + QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); + } + } +} + +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::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; + QString id = index.data(InstanceList::InstanceIDRole).toString(); + InstancePtr inst = MMC->instances()->getInstanceById(id); + if (!inst) + return; + + 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, BaseProfilerFactory *profiler) +{ + 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: + { + qCritical() << "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, profiler); + } + else + { + launchInstance(m_selectedInstance, session, profiler); + } + tryagain = false; + } + } + } +} + +void MainWindow::updateInstance(InstancePtr instance, AuthSessionPtr session, + BaseProfilerFactory *profiler) +{ + auto updateTask = instance->doUpdate(); + if (!updateTask) + { + launchInstance(instance, session, profiler); + return; + } + ProgressDialog tDialog(this); + connect(updateTask.get(), &Task::succeeded, [this, instance, session, profiler] + { launchInstance(instance, session, profiler); }); + connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); + tDialog.exec(updateTask.get()); +} + +void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, + BaseProfilerFactory *profiler) +{ + Q_ASSERT_X(instance != NULL, "launchInstance", "instance is NULL"); + Q_ASSERT_X(session.get() != nullptr, "launchInstance", "session is NULL"); + + QString launchScript; + + BaseProcess *proc = instance->prepareForLaunch(session); + if (!proc) + return; + + this->hide(); + + console = new ConsoleWindow(proc); + connect(console, SIGNAL(isClosing()), this, SLOT(instanceEnded())); + + proc->setHeader("MultiMC version: " + BuildConfig.printableVersionString() + "\n\n"); + proc->arm(); + + if (profiler) + { + QString error; + if (!profiler->check(&error)) + { + QMessageBox::critical(this, tr("Error"), + tr("Couldn't start profiler: %1").arg(error)); + proc->abort(); + return; + } + BaseProfiler *profilerInstance = profiler->createProfiler(instance, this); + QProgressDialog dialog; + dialog.setMinimum(0); + dialog.setMaximum(0); + dialog.setValue(0); + dialog.setLabelText(tr("Waiting for profiler...")); + connect(&dialog, &QProgressDialog::canceled, profilerInstance, + &BaseProfiler::abortProfiling); + dialog.show(); + connect(profilerInstance, &BaseProfiler::readyToLaunch, + [&dialog, this, proc](const QString & message) + { + dialog.accept(); + QMessageBox msg; + msg.setText(tr("The game launch is delayed until you press the " + "button. This is the right time to setup the profiler, as the " + "profiler server is running now.\n\n%1").arg(message)); + msg.setWindowTitle(tr("Waiting")); + msg.setIcon(QMessageBox::Information); + msg.addButton(tr("Launch"), QMessageBox::AcceptRole); + msg.exec(); + proc->launch(); + }); + connect(profilerInstance, &BaseProfiler::abortLaunch, + [&dialog, this, proc](const QString & message) + { + dialog.accept(); + QMessageBox msg; + msg.setText(tr("Couldn't start the profiler: %1").arg(message)); + msg.setWindowTitle(tr("Error")); + msg.setIcon(QMessageBox::Critical); + msg.addButton(QMessageBox::Ok); + msg.exec(); + proc->abort(); + }); + profilerInstance->beginProfiling(proc); + dialog.exec(); + } + else + { + 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(); +} + +// BrowserDialog +void MainWindow::openWebPage(QUrl url) +{ + QDesktopServices::openUrl(url); +} + +void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + if(!current.isValid()) + { + MMC->settings()->set("SelectedInstance", QString()); + selectionBad(); + return; + } + QString id = current.data(InstanceList::InstanceIDRole).toString(); + m_selectedInstance = MMC->instances()->getInstanceById(id); + if ( m_selectedInstance ) + { + ui->instanceToolBar->setEnabled(m_selectedInstance->canLaunch()); + renameButton->setText(m_selectedInstance->name()); + m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); + updateInstanceToolIcon(m_selectedInstance->iconKey()); + + updateToolsMenu(); + + MMC->settings()->set("SelectedInstance", m_selectedInstance->id()); + } + else + { + MMC->settings()->set("SelectedInstance", QString()); + selectionBad(); + return; + } +} + +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::instanceEnded() +{ + this->show(); +} + +void MainWindow::checkSetDefaultJava() +{ + const QString javaHack = "IntelHack"; + bool askForJava = false; + do + { + QString currentHostName = QHostInfo::localHostName(); + QString oldHostName = MMC->settings()->get("LastHostname").toString(); + if (currentHostName != oldHostName) + { + MMC->settings()->set("LastHostname", currentHostName); + askForJava = true; + break; + } + QString currentJavaPath = MMC->settings()->get("JavaPath").toString(); + if (currentJavaPath.isEmpty()) + { + askForJava = true; + break; + } + if(!currentJavaPath.contains('/')) + { + currentJavaPath = QStandardPaths::findExecutable(currentJavaPath); + } + QFile currentJavaBin(currentJavaPath); + if(!currentJavaBin.exists()) + { + askForJava = true; + break; + } + #if defined Q_OS_WIN32 + QString currentHack = MMC->settings()->get("JavaDetectionHack").toString(); + if (currentHack != javaHack) + { + CustomMessageBox::selectable( + this, tr("Java detection forced"), + tr("Because of graphics performance issues caused by Intel drivers on Windows, " + "MultiMC java detection was forced. Please select a Java " + "version.<br/><br/>If you have custom java versions set for your instances, " + "make sure you use the 'javaw.exe' executable."), + QMessageBox::Warning)->exec(); + askForJava = true; + break; + } + #endif + } while (0); + + if (askForJava) + { + qDebug() << "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); + MMC->settings()->set("JavaDetectionHack", javaHack); + } + else + MMC->settings()->set("JavaPath", QString("java")); + } +} + +void MainWindow::checkInstancePathForProblems() +{ + QString instanceFolder = MMC->settings()->get("InstanceDir").toString(); + if (checkProblemticPathJava(QDir(instanceFolder))) + { + QMessageBox warning; + warning.setText(tr( + "Your instance folder contains \'!\' and this is known to cause Java problems!")); + warning.setInformativeText( + tr("You have now three options: <br/>" + " - ignore this warning <br/>" + " - change the instance dir in the settings <br/>" + " - move this installation of MultiMC5 to a different folder")); + warning.setDefaultButton(QMessageBox::Ok); + warning.exec(); + } +} |