From bc6d1b5304f715ad0d8be27efd6630f820572da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 6 Aug 2016 15:39:29 +0200 Subject: GH-338, GH-513, GH-700 Unify edit instance with console window * The resulting instance window can be closed at any point. * Main window is kept open and running instances are marked with a badge. * Multiple instances can now run from the same MultiMC - it's even more **multi** now. * MultiMC can be entirely closed, keeping Minecraft(s) running. --- application/CMakeLists.txt | 5 +- application/ConsoleWindow.cpp | 260 --------------------- application/ConsoleWindow.h | 66 ------ application/InstancePageProvider.h | 2 + application/InstanceWindow.cpp | 226 ++++++++++++++++++ application/InstanceWindow.h | 68 ++++++ application/LaunchInteraction.cpp | 33 +-- application/LaunchInteraction.h | 5 +- application/MainWindow.cpp | 66 +++++- application/MainWindow.h | 8 + application/SettingsUI.cpp | 9 - application/SettingsUI.h | 2 - application/groupview/InstanceDelegate.cpp | 4 + application/pages/LogPage.cpp | 75 ++++-- application/pages/LogPage.h | 5 +- application/resources/instances/instances.qrc | 1 + application/resources/instances/status-running.png | Bin 0 -> 1059 bytes .../resources/multimc/16x16/status-running.png | Bin 0 -> 675 bytes .../resources/multimc/22x22/status-running.png | Bin 0 -> 957 bytes .../resources/multimc/24x24/status-running.png | Bin 0 -> 1059 bytes .../resources/multimc/32x32/status-running.png | Bin 0 -> 1425 bytes .../resources/multimc/48x48/status-running.png | Bin 0 -> 2288 bytes .../resources/multimc/64x64/status-running.png | Bin 0 -> 3178 bytes application/resources/multimc/multimc.qrc | 9 + .../resources/multimc/scalable/status-running.svg | 187 +++++++++++++++ application/widgets/PageContainer.cpp | 22 +- application/widgets/PageContainer.h | 2 + 27 files changed, 657 insertions(+), 398 deletions(-) delete mode 100644 application/ConsoleWindow.cpp delete mode 100644 application/ConsoleWindow.h create mode 100644 application/InstanceWindow.cpp create mode 100644 application/InstanceWindow.h delete mode 100644 application/SettingsUI.cpp create mode 100644 application/resources/instances/status-running.png create mode 100644 application/resources/multimc/16x16/status-running.png create mode 100644 application/resources/multimc/22x22/status-running.png create mode 100644 application/resources/multimc/24x24/status-running.png create mode 100644 application/resources/multimc/32x32/status-running.png create mode 100644 application/resources/multimc/48x48/status-running.png create mode 100644 application/resources/multimc/64x64/status-running.png create mode 100644 application/resources/multimc/scalable/status-running.svg (limited to 'application') diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index 9d71f977..46c496e2 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -103,12 +103,11 @@ SET(MULTIMC_SOURCES # GUI - windows MainWindow.h MainWindow.cpp - ConsoleWindow.h - ConsoleWindow.cpp + InstanceWindow.h + InstanceWindow.cpp # GUI - settings-specific wrappers for paged dialog SettingsUI.h - SettingsUI.cpp # Processes LaunchInteraction.h diff --git a/application/ConsoleWindow.cpp b/application/ConsoleWindow.cpp deleted file mode 100644 index e620d700..00000000 --- a/application/ConsoleWindow.cpp +++ /dev/null @@ -1,260 +0,0 @@ -/* Copyright 2013-2015 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ConsoleWindow.h" -#include "MultiMC.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include "widgets/PageContainer.h" -#include "pages/LogPage.h" -#include "InstancePageProvider.h" - -#include "icons/IconList.h" - -class LogPageProvider : public BasePageProvider -{ -public: - LogPageProvider(BasePageProviderPtr parent, BasePage * log_page) - { - m_parent = parent; - m_log_page = log_page; - } - virtual QString dialogTitle() {return "Fake";}; - virtual QList getPages() - { - auto pages = m_parent->getPages(); - pages.prepend(m_log_page); - return pages; - } -private: - BasePageProviderPtr m_parent; - BasePage * m_log_page; -}; - -ConsoleWindow::ConsoleWindow(std::shared_ptr proc, QWidget *parent) - : QMainWindow(parent), m_proc(proc) -{ - setAttribute(Qt::WA_DeleteOnClose); - - auto instance = m_proc->instance(); - auto icon = MMC->icons()->getIcon(instance->iconKey()); - QString windowTitle = tr("Console window for ") + instance->name(); - - // Set window properties - { - setWindowIcon(icon); - setWindowTitle(windowTitle); - } - - // Add page container - { - auto mainLayout = new QVBoxLayout; - auto provider = std::make_shared(m_proc->instance()); - auto baseprovider = std::dynamic_pointer_cast(provider); - auto proxy_provider = std::make_shared(baseprovider, new LogPage(m_proc)); - m_container = new PageContainer(proxy_provider, "console", this); - mainLayout->addWidget(m_container); - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0,0,0,0); - setLayout(mainLayout); - setCentralWidget(m_container); - } - - // Add custom buttons to the page container layout. - { - auto horizontalLayout = new QHBoxLayout(); - horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); - horizontalLayout->setContentsMargins(6, -1, 6, -1); - - auto btnHelp = new QPushButton(); - btnHelp->setText(tr("Help")); - horizontalLayout->addWidget(btnHelp); - connect(btnHelp, SIGNAL(clicked(bool)), m_container, SLOT(help())); - - auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout->addSpacerItem(spacer); - - m_killButton = new QPushButton(); - m_killButton->setText(tr("Kill Minecraft")); - horizontalLayout->addWidget(m_killButton); - connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked())); - - m_closeButton = new QPushButton(); - m_closeButton->setText(tr("Close")); - horizontalLayout->addWidget(m_closeButton); - connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(on_closeButton_clicked())); - - m_container->addButtons(horizontalLayout); - } - - // restore window state - { - auto base64State = MMC->settings()->get("ConsoleWindowState").toByteArray(); - restoreState(QByteArray::fromBase64(base64State)); - auto base64Geometry = MMC->settings()->get("ConsoleWindowGeometry").toByteArray(); - restoreGeometry(QByteArray::fromBase64(base64Geometry)); - } - - // Set up tray icon - { - m_trayIcon = new QSystemTrayIcon(icon, this); - m_trayIcon->setToolTip(windowTitle); - - connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), - SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); - m_trayIcon->show(); - } - - // Set up signal connections - connect(m_proc.get(), &LaunchTask::succeeded, this, &ConsoleWindow::onSucceeded); - connect(m_proc.get(), &LaunchTask::failed, this, &ConsoleWindow::onFailed); - connect(m_proc.get(), &LaunchTask::requestProgress, this, &ConsoleWindow::onProgressRequested); - - setMayClose(false); - - if (m_proc->instance()->settings()->get("ShowConsole").toBool()) - { - show(); - } -} - -void ConsoleWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) -{ - switch (reason) - { - case QSystemTrayIcon::Trigger: - { - toggleConsole(); - } - default: - return; - } -} - -void ConsoleWindow::on_closeButton_clicked() -{ - close(); -} - -void ConsoleWindow::setMayClose(bool mayclose) -{ - if(mayclose) - m_closeButton->setText(tr("Close")); - else - m_closeButton->setText(tr("Hide")); - m_mayclose = mayclose; -} - -void ConsoleWindow::toggleConsole() -{ - if (isVisible()) - { - if(!isActiveWindow()) - { - activateWindow(); - return; - } - hide(); - } - else - { - show(); - } -} - -void ConsoleWindow::closeEvent(QCloseEvent *event) -{ - if (!m_mayclose) - { - toggleConsole(); - event->ignore(); - } - else if(m_container->requestClose(event)) - { - MMC->settings()->set("ConsoleWindowState", saveState().toBase64()); - MMC->settings()->set("ConsoleWindowGeometry", saveGeometry().toBase64()); - - emit isClosing(); - m_trayIcon->hide(); - event->accept(); - } -} - -void ConsoleWindow::on_btnKillMinecraft_clicked() -{ - m_killButton->setEnabled(false); - auto response = CustomMessageBox::selectable( - this, tr("Kill Minecraft?"), - tr("This can cause the instance to get corrupted and should only be used if Minecraft " - "is frozen for some reason"), - QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec(); - if (response == QMessageBox::Yes) - m_proc->abort(); - else - m_killButton->setEnabled(true); -} - -void ConsoleWindow::onSucceeded() -{ - m_killButton->setEnabled(false); - setMayClose(true); - if (m_proc->instance()->settings()->get("AutoCloseConsole").toBool() && m_container->requestClose(nullptr)) - { - this->close(); - return; - } - if (!isVisible()) - { - show(); - } - // Raise Window - if (MMC->settings()->get("RaiseConsole").toBool()) - { - raise(); - activateWindow(); - } -} - -void ConsoleWindow::onFailed(QString reason) -{ - m_killButton->setEnabled(false); - setMayClose(true); - if (!isVisible()) - { - show(); - } -} - -void ConsoleWindow::onProgressRequested(Task* task) -{ - ProgressDialog progDialog(this); - m_proc->proceed(); - progDialog.execWithTask(task); -} - - -ConsoleWindow::~ConsoleWindow() -{ - -} diff --git a/application/ConsoleWindow.h b/application/ConsoleWindow.h deleted file mode 100644 index ac5a6fd1..00000000 --- a/application/ConsoleWindow.h +++ /dev/null @@ -1,66 +0,0 @@ -/* Copyright 2013-2015 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "launch/LaunchTask.h" - -class QPushButton; -class PageContainer; -class ConsoleWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit ConsoleWindow(std::shared_ptr proc, QWidget *parent = 0); - virtual ~ConsoleWindow(); - - /** - * @brief specify if the window is allowed to close - * @param mayclose - * used to keep it alive while MC runs - */ - void setMayClose(bool mayclose); - -signals: - void isClosing(); - -private -slots: - void on_closeButton_clicked(); - void on_btnKillMinecraft_clicked(); - - void onSucceeded(); - void onFailed(QString reason); - void onProgressRequested(Task *task); - - // FIXME: add handlers for the other MinecraftLauncher signals (pre/post launch command - // failures) - - void iconActivated(QSystemTrayIcon::ActivationReason); - void toggleConsole(); -protected: - void closeEvent(QCloseEvent *); - -private: - std::shared_ptr m_proc; - bool m_mayclose = true; - QSystemTrayIcon *m_trayIcon = nullptr; - PageContainer *m_container = nullptr; - QPushButton *m_closeButton = nullptr; - QPushButton *m_killButton = nullptr; -}; diff --git a/application/InstancePageProvider.h b/application/InstancePageProvider.h index dfc2e4dd..1d6cc5d7 100644 --- a/application/InstancePageProvider.h +++ b/application/InstancePageProvider.h @@ -3,6 +3,7 @@ #include "minecraft/legacy/LegacyInstance.h" #include #include "pages/BasePage.h" +#include "pages/LogPage.h" #include "pages/VersionPage.h" #include "pages/ModFolderPage.h" #include "pages/ResourcePackPage.h" @@ -29,6 +30,7 @@ public: virtual QList getPages() override { QList values; + values.append(new LogPage(inst)); std::shared_ptr onesix = std::dynamic_pointer_cast(inst); if(onesix) { diff --git a/application/InstanceWindow.cpp b/application/InstanceWindow.cpp new file mode 100644 index 00000000..dfc7b815 --- /dev/null +++ b/application/InstanceWindow.cpp @@ -0,0 +1,226 @@ +/* Copyright 2013-2015 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "InstanceWindow.h" +#include "MultiMC.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "widgets/PageContainer.h" +#include "InstancePageProvider.h" + +#include "icons/IconList.h" + +InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent) + : QMainWindow(parent), m_instance(instance) +{ + setAttribute(Qt::WA_DeleteOnClose); + + auto icon = MMC->icons()->getIcon(m_instance->iconKey()); + QString windowTitle = tr("Console window for ") + m_instance->name(); + + // Set window properties + { + setWindowIcon(icon); + setWindowTitle(windowTitle); + } + + // Add page container + { + auto mainLayout = new QVBoxLayout; + auto provider = std::make_shared(m_instance); + m_container = new PageContainer(provider, "console", this); + mainLayout->addWidget(m_container); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0,0,0,0); + setLayout(mainLayout); + setCentralWidget(m_container); + } + + // Add custom buttons to the page container layout. + { + auto horizontalLayout = new QHBoxLayout(); + horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + horizontalLayout->setContentsMargins(6, -1, 6, -1); + + auto btnHelp = new QPushButton(); + btnHelp->setText(tr("Help")); + horizontalLayout->addWidget(btnHelp); + connect(btnHelp, SIGNAL(clicked(bool)), m_container, SLOT(help())); + + auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout->addSpacerItem(spacer); + + m_killButton = new QPushButton(); + horizontalLayout->addWidget(m_killButton); + setKillButton(m_instance->isRunning()); + connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked())); + + m_closeButton = new QPushButton(); + m_closeButton->setText(tr("Close")); + horizontalLayout->addWidget(m_closeButton); + connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(on_closeButton_clicked())); + + m_container->addButtons(horizontalLayout); + } + + // restore window state + { + auto base64State = MMC->settings()->get("ConsoleWindowState").toByteArray(); + restoreState(QByteArray::fromBase64(base64State)); + auto base64Geometry = MMC->settings()->get("ConsoleWindowGeometry").toByteArray(); + restoreGeometry(QByteArray::fromBase64(base64Geometry)); + } + + // set up instance and launch process recognition + { + auto launchTask = m_instance->getLaunchTask(); + on_InstanceLaunchTask_changed(launchTask); + connect(m_instance.get(), &BaseInstance::launchTaskChanged, + this, &InstanceWindow::on_InstanceLaunchTask_changed); + connect(m_instance.get(), &BaseInstance::runningStatusChanged, + this, &InstanceWindow::on_RunningState_changed); + } + show(); +} + +void InstanceWindow::setKillButton(bool kill) +{ + if(kill) + { + m_killButton->setText(tr("Kill")); + m_killButton->setToolTip(tr("Kill the running instance")); + } + else + { + m_killButton->setText(tr("Launch")); + m_killButton->setToolTip(tr("Launch the instance")); + } +} + +void InstanceWindow::on_InstanceLaunchTask_changed(std::shared_ptr proc) +{ + if(m_proc) + { + disconnect(m_proc.get(), &LaunchTask::succeeded, this, &InstanceWindow::onSucceeded); + disconnect(m_proc.get(), &LaunchTask::failed, this, &InstanceWindow::onFailed); + disconnect(m_proc.get(), &LaunchTask::requestProgress, this, &InstanceWindow::onProgressRequested); + } + + m_proc = proc; + + if(m_proc) + { + // Set up signal connections + connect(m_proc.get(), &LaunchTask::succeeded, this, &InstanceWindow::onSucceeded); + connect(m_proc.get(), &LaunchTask::failed, this, &InstanceWindow::onFailed); + connect(m_proc.get(), &LaunchTask::requestProgress, this, &InstanceWindow::onProgressRequested); + } +} + +void InstanceWindow::on_RunningState_changed(bool running) +{ + setKillButton(running); + m_container->refresh(); +} + +void InstanceWindow::on_closeButton_clicked() +{ + close(); +} + +void InstanceWindow::closeEvent(QCloseEvent *event) +{ + MMC->settings()->set("ConsoleWindowState", saveState().toBase64()); + MMC->settings()->set("ConsoleWindowGeometry", saveGeometry().toBase64()); + + if(m_container->requestClose(event)) + { + emit isClosing(); + event->accept(); + } +} + +void InstanceWindow::on_btnKillMinecraft_clicked() +{ + if(m_instance->isRunning()) + { + auto response = CustomMessageBox::selectable( + this, tr("Kill Minecraft?"), + tr("This can cause the instance to get corrupted and should only be used if Minecraft " + "is frozen for some reason"), + QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec(); + if (response == QMessageBox::Yes) + { + m_proc->abort(); + } + } + else + { + m_launchController.reset(new LaunchController()); + m_launchController->setInstance(m_instance); + m_launchController->setOnline(true); + m_launchController->setParentWidget(this); + m_launchController->start(); + } +} + +void InstanceWindow::onSucceeded() +{ + if (m_instance->settings()->get("AutoCloseConsole").toBool() && m_container->requestClose(nullptr)) + { + this->close(); + return; + } + // Raise Window + if (MMC->settings()->get("RaiseConsole").toBool()) + { + show(); + raise(); + activateWindow(); + } +} + +void InstanceWindow::onFailed(QString reason) +{ +} + +void InstanceWindow::onProgressRequested(Task* task) +{ + ProgressDialog progDialog(this); + m_proc->proceed(); + progDialog.execWithTask(task); +} + +QString InstanceWindow::instanceId() +{ + return m_instance->id(); +} + +bool InstanceWindow::selectPage(QString pageId) +{ + return m_container->selectPage(pageId); +} + +InstanceWindow::~InstanceWindow() +{ +} diff --git a/application/InstanceWindow.h b/application/InstanceWindow.h new file mode 100644 index 00000000..7ffc4142 --- /dev/null +++ b/application/InstanceWindow.h @@ -0,0 +1,68 @@ +/* Copyright 2013-2015 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "LaunchInteraction.h" +#include +#include +#include "launch/LaunchTask.h" +#include "pages/BasePageContainer.h" + +class QPushButton; +class PageContainer; +class InstanceWindow : public QMainWindow, public BasePageContainer +{ + Q_OBJECT + +public: + explicit InstanceWindow(InstancePtr proc, QWidget *parent = 0); + virtual ~InstanceWindow(); + + bool selectPage(QString pageId) override; + + QString instanceId(); + +signals: + void isClosing(); + +private +slots: + void on_closeButton_clicked(); + void on_btnKillMinecraft_clicked(); + + void onSucceeded(); + void onFailed(QString reason); + void onProgressRequested(Task *task); + + void on_InstanceLaunchTask_changed(std::shared_ptr proc); + void on_RunningState_changed(bool running); + +protected: + void closeEvent(QCloseEvent *) override; + +private: + void setKillButton(bool kill); + +private: + std::shared_ptr m_proc; + unique_qobject_ptr m_launchController; + InstancePtr m_instance; + bool m_mayclose = true; + PageContainer *m_container = nullptr; + QPushButton *m_closeButton = nullptr; + QPushButton *m_killButton = nullptr; +}; diff --git a/application/LaunchInteraction.cpp b/application/LaunchInteraction.cpp index 1caa054a..80a3368d 100644 --- a/application/LaunchInteraction.cpp +++ b/application/LaunchInteraction.cpp @@ -1,11 +1,12 @@ #include "LaunchInteraction.h" +#include "MainWindow.h" #include #include "MultiMC.h" #include "dialogs/CustomMessageBox.h" #include "dialogs/AccountSelectDialog.h" #include "dialogs/ProgressDialog.h" #include "dialogs/EditAccountDialog.h" -#include "ConsoleWindow.h" +#include "InstanceWindow.h" #include "BuildConfig.h" #include "JavaCommon.h" #include "SettingsUI.h" @@ -204,13 +205,20 @@ void LaunchController::launchInstance() return; } - if(m_parentWidget) + auto mainWindow = qobject_cast(m_parentWidget); + auto instanceWindow = qobject_cast(m_parentWidget); + if(mainWindow) { - m_parentWidget->hide(); + m_console = mainWindow->showInstanceWindow(m_instance); + } + else if(instanceWindow) + { + // NOOP + } + else + { + m_console = new InstanceWindow(m_instance); } - - m_console = new ConsoleWindow(m_launcher); - connect(m_console, &ConsoleWindow::isClosing, this, &LaunchController::instanceEnded); connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch); m_launcher->prependStep(std::make_shared(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC)); @@ -222,6 +230,7 @@ void LaunchController::readyForLaunch() if (!m_profiler) { m_launcher->proceed(); + emitSucceeded(); return; } @@ -230,6 +239,7 @@ void LaunchController::readyForLaunch() { m_launcher->abort(); QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't start profiler: %1").arg(error)); + emitFailed("Profiler startup failed"); return; } BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this); @@ -246,6 +256,7 @@ void LaunchController::readyForLaunch() msg.setModal(true); msg.exec(); m_launcher->proceed(); + emitSucceeded(); }); connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString & message) { @@ -257,15 +268,7 @@ void LaunchController::readyForLaunch() msg.setModal(true); msg.exec(); m_launcher->abort(); + emitFailed("Profiler startup failed"); }); profilerInstance->beginProfiling(m_launcher); } - -void LaunchController::instanceEnded() -{ - if(m_parentWidget) - { - m_parentWidget->show(); - } - emitSucceeded(); -} diff --git a/application/LaunchInteraction.h b/application/LaunchInteraction.h index b0932e9b..55cb1e58 100644 --- a/application/LaunchInteraction.h +++ b/application/LaunchInteraction.h @@ -3,7 +3,7 @@ #include #include -class ConsoleWindow; +class InstanceWindow; class LaunchController: public Task { Q_OBJECT @@ -36,14 +36,13 @@ private: private slots: void readyForLaunch(); - void instanceEnded(); private: BaseProfilerFactory *m_profiler = nullptr; bool m_online = true; InstancePtr m_instance; QWidget * m_parentWidget = nullptr; - ConsoleWindow *m_console = nullptr; + InstanceWindow *m_console = nullptr; AuthSessionPtr m_session; std::shared_ptr m_launcher; }; diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp index 06d165da..88c0fc09 100644 --- a/application/MainWindow.cpp +++ b/application/MainWindow.cpp @@ -67,6 +67,7 @@ #include #include +#include "InstanceWindow.h" #include "InstancePageProvider.h" #include "InstanceProxyModel.h" #include "JavaCommon.h" @@ -1424,24 +1425,62 @@ void MainWindow::on_actionSettings_triggered() update(); } +InstanceWindow *MainWindow::showInstanceWindow(InstancePtr instance, QString page) +{ + if(!instance) + return nullptr; + auto id = instance->id(); + InstanceWindow * window = nullptr; + + auto iter = m_instanceWindows.find(id); + if(iter != m_instanceWindows.end()) + { + window = *iter; + window->raise(); + window->activateWindow(); + } + else + { + window = new InstanceWindow(instance, this); + m_instanceWindows[id] = window; + connect(window, &InstanceWindow::isClosing, this, &MainWindow::on_instanceWindowClose); + } + if(!page.isEmpty()) + { + window->selectPage(page); + } + return window; +} + +void MainWindow::on_instanceWindowClose() +{ + auto senderWindow = qobject_cast(QObject::sender()); + if(!senderWindow) + { + return; + } + m_instanceWindows.remove(senderWindow->instanceId()); +} + + void MainWindow::on_actionInstanceSettings_triggered() { - SettingsUI::ShowInstancePageDialog(m_selectedInstance, this, "settings"); + showInstanceWindow(m_selectedInstance, "settings"); } void MainWindow::on_actionEditInstNotes_triggered() { - SettingsUI::ShowInstancePageDialog(m_selectedInstance, this, "notes"); + showInstanceWindow(m_selectedInstance, "notes"); } void MainWindow::on_actionEditInstance_triggered() { - SettingsUI::ShowInstancePageDialog(m_selectedInstance, this); + showInstanceWindow(m_selectedInstance); } void MainWindow::on_actionScreenshots_triggered() { - SettingsUI::ShowInstancePageDialog(m_selectedInstance, this, "screenshots"); + showInstanceWindow(m_selectedInstance, "screenshots"); } void MainWindow::on_actionManageAccounts_triggered() @@ -1586,16 +1625,19 @@ void MainWindow::on_actionLaunchInstanceOffline_triggered() void MainWindow::launch(InstancePtr instance, bool online, BaseProfilerFactory *profiler) { - if(!instance->canLaunch()) + if(instance->canLaunch()) { - return; + m_launchController.reset(new LaunchController()); + m_launchController->setInstance(instance); + m_launchController->setOnline(online); + m_launchController->setParentWidget(this); + m_launchController->setProfiler(profiler); + m_launchController->start(); + } + else if (instance->isRunning()) + { + showInstanceWindow(instance, "console"); } - m_launchController.reset(new LaunchController()); - m_launchController->setInstance(instance); - m_launchController->setOnline(online); - m_launchController->setParentWidget(this); - m_launchController->setProfiler(profiler); - m_launchController->start(); } void MainWindow::taskEnd() diff --git a/application/MainWindow.h b/application/MainWindow.h index e3fb0467..3f0ec6e6 100644 --- a/application/MainWindow.h +++ b/application/MainWindow.h @@ -37,6 +37,7 @@ class MinecraftLauncher; class BaseProfilerFactory; class GroupView; class ServerStatus; +class InstanceWindow; class MainWindow : public QMainWindow { @@ -54,6 +55,8 @@ public: void checkSetDefaultJava(); void checkInstancePathForProblems(); + InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString()); + private slots: void onCatToggled(bool); @@ -159,6 +162,8 @@ private slots: */ void downloadUpdates(GoUpdate::Status status); + void on_instanceWindowClose(); + private: void setCatBackground(bool enabled); void updateInstanceToolIcon(QString new_icon); @@ -195,4 +200,7 @@ private: // managed by the application object Task *m_versionLoadTask; + + // map from instance ID to its window + QMap m_instanceWindows; }; diff --git a/application/SettingsUI.cpp b/application/SettingsUI.cpp deleted file mode 100644 index 2a2010de..00000000 --- a/application/SettingsUI.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "SettingsUI.h" -namespace SettingsUI -{ -void ShowInstancePageDialog(InstancePtr instance, QWidget * parent, QString open_page) -{ - auto provider = std::make_shared(instance); - ShowPageDialog(provider, parent, open_page); -} -} diff --git a/application/SettingsUI.h b/application/SettingsUI.h index 1e6dc8d0..5b8badf2 100644 --- a/application/SettingsUI.h +++ b/application/SettingsUI.h @@ -23,6 +23,4 @@ void ShowPageDialog(T raw_provider, QWidget * parent, QString open_page = QStrin dlg.exec(); } } - -void ShowInstancePageDialog(InstancePtr instance, QWidget * parent, QString open_page = QString()); } diff --git a/application/groupview/InstanceDelegate.cpp b/application/groupview/InstanceDelegate.cpp index b9ad353c..359dd3cf 100644 --- a/application/groupview/InstanceDelegate.cpp +++ b/application/groupview/InstanceDelegate.cpp @@ -122,6 +122,10 @@ void drawBadges(QPainter *painter, const QStyleOptionViewItemV4 &option, BaseIns { pixmaps.append("updateavailable"); } + if (instance->isRunning()) + { + pixmaps.append("status-running"); + } // begin easter eggs if (instance->name().contains("btw", Qt::CaseInsensitive) || diff --git a/application/pages/LogPage.cpp b/application/pages/LogPage.cpp index af96148b..de4ed4f3 100644 --- a/application/pages/LogPage.cpp +++ b/application/pages/LogPage.cpp @@ -12,40 +12,57 @@ #include "GuiUtil.h" #include -LogPage::LogPage(std::shared_ptr proc, QWidget *parent) - : QWidget(parent), ui(new Ui::LogPage), m_process(proc) +LogPage::LogPage(InstancePtr instance, QWidget *parent) + : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) { ui->setupUi(this); ui->tabWidget->tabBar()->hide(); - connect(m_process.get(), SIGNAL(log(QString, MessageLevel::Enum)), this, - SLOT(write(QString, MessageLevel::Enum))); // create the format and set its font - defaultFormat = new QTextCharFormat(ui->text->currentCharFormat()); - QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); - bool conversionOk = false; - int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); - if(!conversionOk) { - fontSize = 11; + defaultFormat = new QTextCharFormat(ui->text->currentCharFormat()); + QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); + bool conversionOk = false; + int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); + if(!conversionOk) + { + fontSize = 11; + } + defaultFormat->setFont(QFont(fontFamily, fontSize)); } - defaultFormat->setFont(QFont(fontFamily, fontSize)); // ensure we don't eat all the RAM - auto lineSetting = MMC->settings()->getSetting("ConsoleMaxLines"); - int maxLines = lineSetting->get().toInt(&conversionOk); - if(!conversionOk) { - maxLines = lineSetting->defValue().toInt(); - qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines; + auto lineSetting = MMC->settings()->getSetting("ConsoleMaxLines"); + bool conversionOk = false; + int maxLines = lineSetting->get().toInt(&conversionOk); + if(!conversionOk) + { + maxLines = lineSetting->defValue().toInt(); + qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines; + } + ui->text->setMaximumBlockCount(maxLines); + + m_stopOnOverflow = MMC->settings()->get("ConsoleOverflowStop").toBool(); } - ui->text->setMaximumBlockCount(maxLines); - auto origForeground = ui->text->palette().color(ui->text->foregroundRole()); - auto origBackground = ui->text->palette().color(ui->text->backgroundRole()); - m_colors.reset(new LogColorCache(origForeground, origBackground)); + // set up instance and launch process recognition + { + auto launchTask = m_instance->getLaunchTask(); + if(launchTask) + { + on_InstanceLaunchTask_changed(launchTask); + } + connect(m_instance.get(), &BaseInstance::launchTaskChanged, + this, &LogPage::on_InstanceLaunchTask_changed); + } - m_stopOnOverflow = MMC->settings()->get("ConsoleOverflowStop").toBool(); + // set up text colors and adapt them to the current theme foreground and background + { + auto origForeground = ui->text->palette().color(ui->text->foregroundRole()); + auto origBackground = ui->text->palette().color(ui->text->backgroundRole()); + m_colors.reset(new LogColorCache(origForeground, origBackground)); + } auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); connect(findShortcut, SIGNAL(activated()), SLOT(findActivated())); @@ -62,6 +79,20 @@ LogPage::~LogPage() delete defaultFormat; } +void LogPage::on_InstanceLaunchTask_changed(std::shared_ptr proc) +{ + if(m_process) + { + disconnect(m_process.get(), &LaunchTask::log, this, &LogPage::write); + } + m_process = proc; + if(m_process) + { + ui->text->clear(); + connect(m_process.get(), &LaunchTask::log, this, &LogPage::write); + } +} + bool LogPage::apply() { return true; @@ -69,7 +100,7 @@ bool LogPage::apply() bool LogPage::shouldDisplay() const { - return m_process->instance()->isRunning(); + return m_instance->isRunning() || ui->text->blockCount() > 1; } void LogPage::on_btnPaste_clicked() diff --git a/application/pages/LogPage.h b/application/pages/LogPage.h index 9b694323..e902ad13 100644 --- a/application/pages/LogPage.h +++ b/application/pages/LogPage.h @@ -34,7 +34,7 @@ class LogPage : public QWidget, public BasePage Q_OBJECT public: - explicit LogPage(std::shared_ptr proc, QWidget *parent = 0); + explicit LogPage(InstancePtr instance, QWidget *parent = 0); virtual ~LogPage(); virtual QString displayName() const override { @@ -77,8 +77,11 @@ private slots: void findNextActivated(); void findPreviousActivated(); + void on_InstanceLaunchTask_changed(std::shared_ptr proc); + private: Ui::LogPage *ui; + InstancePtr m_instance; std::shared_ptr m_process; int m_last_scroll_value = 0; bool m_scroll_active = true; diff --git a/application/resources/instances/instances.qrc b/application/resources/instances/instances.qrc index 09ae25d0..b1f1bb4d 100644 --- a/application/resources/instances/instances.qrc +++ b/application/resources/instances/instances.qrc @@ -37,6 +37,7 @@ enderman.png herobrine.png derp.png + status-running.png updateavailable.png diff --git a/application/resources/instances/status-running.png b/application/resources/instances/status-running.png new file mode 100644 index 00000000..ecd64451 Binary files /dev/null and b/application/resources/instances/status-running.png differ diff --git a/application/resources/multimc/16x16/status-running.png b/application/resources/multimc/16x16/status-running.png new file mode 100644 index 00000000..a4c42e39 Binary files /dev/null and b/application/resources/multimc/16x16/status-running.png differ diff --git a/application/resources/multimc/22x22/status-running.png b/application/resources/multimc/22x22/status-running.png new file mode 100644 index 00000000..0dffba18 Binary files /dev/null and b/application/resources/multimc/22x22/status-running.png differ diff --git a/application/resources/multimc/24x24/status-running.png b/application/resources/multimc/24x24/status-running.png new file mode 100644 index 00000000..ecd64451 Binary files /dev/null and b/application/resources/multimc/24x24/status-running.png differ diff --git a/application/resources/multimc/32x32/status-running.png b/application/resources/multimc/32x32/status-running.png new file mode 100644 index 00000000..f561f01a Binary files /dev/null and b/application/resources/multimc/32x32/status-running.png differ diff --git a/application/resources/multimc/48x48/status-running.png b/application/resources/multimc/48x48/status-running.png new file mode 100644 index 00000000..b8c0bf7c Binary files /dev/null and b/application/resources/multimc/48x48/status-running.png differ diff --git a/application/resources/multimc/64x64/status-running.png b/application/resources/multimc/64x64/status-running.png new file mode 100644 index 00000000..38afda0f Binary files /dev/null and b/application/resources/multimc/64x64/status-running.png differ diff --git a/application/resources/multimc/multimc.qrc b/application/resources/multimc/multimc.qrc index 3f7c5e8f..86b472d8 100644 --- a/application/resources/multimc/multimc.qrc +++ b/application/resources/multimc/multimc.qrc @@ -150,6 +150,15 @@ 48x48/status-yellow.png 64x64/status-yellow.png + + 16x16/status-running.png + 24x24/status-running.png + 22x22/status-running.png + 32x32/status-running.png + 48x48/status-running.png + 64x64/status-running.png + scalable/status-running.svg + 16x16/loadermods.png 24x24/loadermods.png diff --git a/application/resources/multimc/scalable/status-running.svg b/application/resources/multimc/scalable/status-running.svg new file mode 100644 index 00000000..18209940 --- /dev/null +++ b/application/resources/multimc/scalable/status-running.svg @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/application/widgets/PageContainer.cpp b/application/widgets/PageContainer.cpp index 90aad26e..04da5c8b 100644 --- a/application/widgets/PageContainer.cpp +++ b/application/widgets/PageContainer.cpp @@ -60,7 +60,6 @@ PageContainer::PageContainer(BasePageProviderPtr pageProvider, QString defaultId createUI(); m_model = new PageModel(this); m_proxyModel = new PageEntryFilterModel(this); - int firstIndex = -1; int counter = 0; auto pages = pageProvider->getPages(); for (auto page : pages) @@ -69,10 +68,6 @@ PageContainer::PageContainer(BasePageProviderPtr pageProvider, QString defaultId page->listIndex = counter; page->setParentContainer(this); counter++; - if (firstIndex == -1) - { - firstIndex = page->stackIndex; - } } m_model->setPages(pages); @@ -111,6 +106,23 @@ bool PageContainer::selectPage(QString pageId) return false; } +void PageContainer::refresh() +{ + m_proxyModel->invalidate(); + if(!m_currentPage->shouldDisplay()) + { + auto index = m_proxyModel->index(0, 0); + if(index.isValid()) + { + m_pageList->setCurrentIndex(index); + } + else + { + // FIXME: unhandled corner case: what to do when there's no page to select? + } + } +} + void PageContainer::createUI() { m_pageStack = new QStackedLayout; diff --git a/application/widgets/PageContainer.h b/application/widgets/PageContainer.h index 381e84e5..84504fb6 100644 --- a/application/widgets/PageContainer.h +++ b/application/widgets/PageContainer.h @@ -45,6 +45,8 @@ public: virtual bool selectPage(QString pageId) override; + void refresh(); + private: void createUI(); private -- cgit v1.2.3