diff options
author | Petr Mrázek <peterix@gmail.com> | 2016-08-06 15:39:29 +0200 |
---|---|---|
committer | Petr Mrázek <peterix@gmail.com> | 2016-08-07 11:48:15 +0200 |
commit | bc6d1b5304f715ad0d8be27efd6630f820572da4 (patch) | |
tree | 6f42bdd351664b6f828247b4860ee3ad723b0971 | |
parent | c44d41ee9b132d4f757658bd62d4b115b7887fe3 (diff) | |
download | MultiMC-bc6d1b5304f715ad0d8be27efd6630f820572da4.tar MultiMC-bc6d1b5304f715ad0d8be27efd6630f820572da4.tar.gz MultiMC-bc6d1b5304f715ad0d8be27efd6630f820572da4.tar.lz MultiMC-bc6d1b5304f715ad0d8be27efd6630f820572da4.tar.xz MultiMC-bc6d1b5304f715ad0d8be27efd6630f820572da4.zip |
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.
29 files changed, 630 insertions, 351 deletions
diff --git a/api/logic/BaseInstance.cpp b/api/logic/BaseInstance.cpp index ce55d5e4..9dee2c38 100644 --- a/api/logic/BaseInstance.cpp +++ b/api/logic/BaseInstance.cpp @@ -92,11 +92,14 @@ bool BaseInstance::isRunning() const void BaseInstance::setRunning(bool running) { - if(running && !m_isRunning) + if(running == m_isRunning) + return; + + if(running) { m_timeStarted = QDateTime::currentDateTime(); } - else if(!running && m_isRunning) + else { qint64 current = settings()->get("totalTimePlayed").toLongLong(); QDateTime timeEnded = QDateTime::currentDateTime(); @@ -104,6 +107,8 @@ void BaseInstance::setRunning(bool running) emit propertiesChanged(this); } m_isRunning = running; + + emit runningStatusChanged(running); } int64_t BaseInstance::totalTimePlayed() const @@ -179,7 +184,7 @@ void BaseInstance::unsetFlag(const BaseInstance::InstanceFlag flag) bool BaseInstance::canLaunch() const { - return !(flags() & VersionBrokenFlag); + return (!(flags() & VersionBrokenFlag)) && (!isRunning()); } bool BaseInstance::reload() @@ -268,3 +273,8 @@ QStringList BaseInstance::extraArguments() const { return Commandline::splitArgs(settings()->get("JvmArgs").toString()); } + +std::shared_ptr<LaunchTask> BaseInstance::getLaunchTask() +{ + return m_launchProcess; +} diff --git a/api/logic/BaseInstance.h b/api/logic/BaseInstance.h index f0fb6096..9a6976cb 100644 --- a/api/logic/BaseInstance.h +++ b/api/logic/BaseInstance.h @@ -157,6 +157,9 @@ public: /// returns a valid launcher (task container) virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0; + /// returns the current launch task (if any) + std::shared_ptr<LaunchTask> getLaunchTask(); + /*! * Returns a task that should be done right before launch * This task should do any extra preparations needed @@ -231,6 +234,10 @@ signals: void flagsChanged(); + void launchTaskChanged(std::shared_ptr<LaunchTask>); + + void runningStatusChanged(bool running); + protected slots: void iconUpdated(QString key); @@ -240,6 +247,7 @@ protected: SettingsObjectPtr m_settings; InstanceFlags m_flags; bool m_isRunning = false; + std::shared_ptr<LaunchTask> m_launchProcess; QDateTime m_timeStarted; }; diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp index f706c16d..b64d9bd2 100644 --- a/api/logic/minecraft/MinecraftInstance.cpp +++ b/api/logic/minecraft/MinecraftInstance.cpp @@ -465,7 +465,9 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s { process->setCensorFilter(createCensorFilterFromSession(session)); } - return process; + m_launchProcess = process; + emit launchTaskChanged(m_launchProcess); + return m_launchProcess; } QString MinecraftInstance::launchMethod() 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 <QScrollBar> -#include <QMessageBox> -#include <QSystemTrayIcon> -#include <QHBoxLayout> -#include <QPushButton> -#include <qlayoutitem.h> -#include <QCloseEvent> - -#include <dialogs/CustomMessageBox.h> -#include <dialogs/ProgressDialog.h> -#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<BasePage *> 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<LaunchTask> 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<InstancePageProvider>(m_proc->instance()); - auto baseprovider = std::dynamic_pointer_cast<BasePageProvider>(provider); - auto proxy_provider = std::make_shared<LogPageProvider>(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/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 <FileSystem.h> #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<BasePage *> getPages() override { QList<BasePage *> values; + values.append(new LogPage(inst)); std::shared_ptr<OneSixInstance> onesix = std::dynamic_pointer_cast<OneSixInstance>(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 <QScrollBar> +#include <QMessageBox> +#include <QHBoxLayout> +#include <QPushButton> +#include <qlayoutitem.h> +#include <QCloseEvent> + +#include <dialogs/CustomMessageBox.h> +#include <dialogs/ProgressDialog.h> +#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<InstancePageProvider>(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<LaunchTask> 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/ConsoleWindow.h b/application/InstanceWindow.h index ac5a6fd1..7ffc4142 100644 --- a/application/ConsoleWindow.h +++ b/application/InstanceWindow.h @@ -16,25 +16,25 @@ #pragma once #include <QMainWindow> +#include "LaunchInteraction.h" +#include <QObjectPtr.h> #include <QSystemTrayIcon> #include "launch/LaunchTask.h" +#include "pages/BasePageContainer.h" class QPushButton; class PageContainer; -class ConsoleWindow : public QMainWindow +class InstanceWindow : public QMainWindow, public BasePageContainer { Q_OBJECT public: - explicit ConsoleWindow(std::shared_ptr<LaunchTask> proc, QWidget *parent = 0); - virtual ~ConsoleWindow(); + explicit InstanceWindow(InstancePtr proc, QWidget *parent = 0); + virtual ~InstanceWindow(); - /** - * @brief specify if the window is allowed to close - * @param mayclose - * used to keep it alive while MC runs - */ - void setMayClose(bool mayclose); + bool selectPage(QString pageId) override; + + QString instanceId(); signals: void isClosing(); @@ -48,18 +48,20 @@ slots: void onFailed(QString reason); void onProgressRequested(Task *task); - // FIXME: add handlers for the other MinecraftLauncher signals (pre/post launch command - // failures) + void on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc); + void on_RunningState_changed(bool running); - void iconActivated(QSystemTrayIcon::ActivationReason); - void toggleConsole(); protected: - void closeEvent(QCloseEvent *); + void closeEvent(QCloseEvent *) override; + +private: + void setKillButton(bool kill); private: std::shared_ptr<LaunchTask> m_proc; + unique_qobject_ptr<LaunchController> m_launchController; + InstancePtr m_instance; bool m_mayclose = true; - QSystemTrayIcon *m_trayIcon = nullptr; 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 <minecraft/auth/MojangAccountList.h> #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<MainWindow *>(m_parentWidget); + auto instanceWindow = qobject_cast<InstanceWindow *>(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<TextPrint>(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 <BaseInstance.h> #include <tools/BaseProfiler.h> -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 <LaunchTask> 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 <updater/UpdateChecker.h> #include <DesktopServices.h> +#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<InstanceWindow *>(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<QString, InstanceWindow *> 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<InstancePageProvider>(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 <ColorCache.h> -LogPage::LogPage(std::shared_ptr<LaunchTask> 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<LaunchTask> 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<LaunchTask> 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<LaunchTask> proc); + private: Ui::LogPage *ui; + InstancePtr m_instance; std::shared_ptr<LaunchTask> 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 @@ <file>enderman.png</file> <file>herobrine.png</file> <file>derp.png</file> + <file>status-running.png</file> <!-- Update. GPLv2, https://code.google.com/p/gnome-colors/ --> <file>updateavailable.png</file> diff --git a/application/resources/instances/status-running.png b/application/resources/instances/status-running.png Binary files differnew file mode 100644 index 00000000..ecd64451 --- /dev/null +++ b/application/resources/instances/status-running.png diff --git a/application/resources/multimc/16x16/status-running.png b/application/resources/multimc/16x16/status-running.png Binary files differnew file mode 100644 index 00000000..a4c42e39 --- /dev/null +++ b/application/resources/multimc/16x16/status-running.png diff --git a/application/resources/multimc/22x22/status-running.png b/application/resources/multimc/22x22/status-running.png Binary files differnew file mode 100644 index 00000000..0dffba18 --- /dev/null +++ b/application/resources/multimc/22x22/status-running.png diff --git a/application/resources/multimc/24x24/status-running.png b/application/resources/multimc/24x24/status-running.png Binary files differnew file mode 100644 index 00000000..ecd64451 --- /dev/null +++ b/application/resources/multimc/24x24/status-running.png diff --git a/application/resources/multimc/32x32/status-running.png b/application/resources/multimc/32x32/status-running.png Binary files differnew file mode 100644 index 00000000..f561f01a --- /dev/null +++ b/application/resources/multimc/32x32/status-running.png diff --git a/application/resources/multimc/48x48/status-running.png b/application/resources/multimc/48x48/status-running.png Binary files differnew file mode 100644 index 00000000..b8c0bf7c --- /dev/null +++ b/application/resources/multimc/48x48/status-running.png diff --git a/application/resources/multimc/64x64/status-running.png b/application/resources/multimc/64x64/status-running.png Binary files differnew file mode 100644 index 00000000..38afda0f --- /dev/null +++ b/application/resources/multimc/64x64/status-running.png 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 @@ <file>48x48/status-yellow.png</file> <file>64x64/status-yellow.png</file> + <!-- A status icon for things that are in progress/running... Our own. --> + <file>16x16/status-running.png</file> + <file>24x24/status-running.png</file> + <file>22x22/status-running.png</file> + <file>32x32/status-running.png</file> + <file>48x48/status-running.png</file> + <file>64x64/status-running.png</file> + <file>scalable/status-running.svg</file> + <!-- Plugin (blue recolor), CC-BY-SA 3.0, Oxygen icons. --> <file>16x16/loadermods.png</file> <file>24x24/loadermods.png</file> 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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + width="32" + height="32" + id="svg2" + inkscape:version="0.91 r13725" + sodipodi:docname="status-running.svg" + inkscape:export-filename="/home/peterix/minecraft/src/MultiMC5/application/resources/multimc/64x64/status-running.png" + inkscape:export-xdpi="180" + inkscape:export-ydpi="180"> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="3840" + inkscape:window-height="2125" + id="namedview32" + showgrid="true" + inkscape:snap-bbox="true" + inkscape:bbox-nodes="false" + inkscape:bbox-paths="false" + inkscape:snap-bbox-midpoints="false" + inkscape:snap-bbox-edge-midpoints="false" + inkscape:object-paths="true" + inkscape:snap-intersection-paths="true" + inkscape:object-nodes="true" + inkscape:snap-smooth-nodes="true" + inkscape:snap-midpoints="false" + inkscape:zoom="10.429825" + inkscape:cx="2.5496161" + inkscape:cy="28.936353" + inkscape:window-x="1200" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1"> + <inkscape:grid + type="xygrid" + id="grid4160" + spacingx="0.5" + spacingy="0.5" + empspacing="8" /> + </sodipodi:namedview> + <defs + id="defs4"> + <linearGradient + id="linearGradient4162"> + <stop + offset="0" + style="stop-color:#0071f1;stop-opacity:1" + id="stop4164" /> + <stop + offset="1" + style="stop-color:#007ec3;stop-opacity:1" + id="stop4166" /> + </linearGradient> + <linearGradient + id="linearGradient3827"> + <stop + id="stop3829" + style="stop-color:#b80000;stop-opacity:1" + offset="0" /> + <stop + id="stop3831" + style="stop-color:#600000;stop-opacity:1" + offset="1" /> + </linearGradient> + <linearGradient + id="linearGradient3801"> + <stop + id="stop3803" + style="stop-color:#f1ab00;stop-opacity:1" + offset="0" /> + <stop + id="stop3805" + style="stop-color:#c39a00;stop-opacity:1" + offset="1" /> + </linearGradient> + <linearGradient + id="linearGradient3005"> + <stop + id="stop3007" + style="stop-color:#ffffff;stop-opacity:1" + offset="0" /> + <stop + id="stop3781" + style="stop-color:#ffffff;stop-opacity:0.49803922" + offset="0.8142857" /> + <stop + id="stop3009" + style="stop-color:#ffffff;stop-opacity:0" + offset="1" /> + </linearGradient> + <filter + color-interpolation-filters="sRGB" + id="filter3797"> + <feGaussianBlur + id="feGaussianBlur3799" + stdDeviation="0.52592593" /> + </filter> + <radialGradient + cx="3.9371533" + cy="7.5016646" + r="2.5" + fx="3.9371533" + fy="7.5016646" + id="radialGradient3807" + xlink:href="#linearGradient4162" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.4496779,1.5407764,-0.90127514,0.84794135,4.9906134,-4.9255796)" /> + <radialGradient + cx="3.9371533" + cy="7.5016646" + r="2.5" + fx="3.9371533" + fy="7.5016646" + id="radialGradient3823" + xlink:href="#linearGradient3827" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.4496779,1.5407764,-0.90127514,0.84794135,4.9906134,-4.9255796)" /> + <radialGradient + cx="3.9371533" + cy="7.5016646" + r="2.5" + fx="3.9371533" + fy="7.5016646" + id="radialGradient3786" + xlink:href="#linearGradient3801" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.4496779,1.5407764,-0.90127514,0.84794135,4.9906134,-4.9255796)" /> + </defs> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + transform="translate(0,-1020.3622)" + id="layer1"> + <path + d="m 8,9.5 a 2.5,2.5 0 1 1 -5,0 2.5,2.5 0 1 1 5,0 z" + transform="matrix(6.4,0,0,6.4,-19.2,975.5622)" + id="path2997" + style="color:#000000;fill:url(#radialGradient3807);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + d="M 31,16 A 15,15 0 1 1 1,16 15,15 0 1 1 31,16 z" + transform="matrix(0.93333333,0,0,0.93333444,1.0666666,1021.4288)" + id="path2999" + style="color:#000000;fill:#009cff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <g + id="g4187"> + <path + inkscape:connector-curvature="0" + id="path4168" + d="m 10.455414,1028.3241 0,16.0761 L 24,1036.3622 Z" + style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.95251006px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cccc" + inkscape:connector-curvature="0" + style="opacity:0.54887217;fill:#ffffff;stroke:none" + id="path3809-3" + d="m 10.5,1044.3622 0,-16 13.5,8 z" /> + </g> + </g> +</svg> 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 |