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: [PATCH] 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. --- api/logic/BaseInstance.cpp | 16 +- api/logic/BaseInstance.h | 8 + api/logic/minecraft/MinecraftInstance.cpp | 4 +- application/CMakeLists.txt | 5 +- application/ConsoleWindow.cpp | 260 ------------------ application/InstancePageProvider.h | 2 + application/InstanceWindow.cpp | 226 +++++++++++++++ .../{ConsoleWindow.h => InstanceWindow.h} | 32 ++- 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 + .../resources/instances/status-running.png | Bin 0 -> 1059 bytes .../multimc/16x16/status-running.png | Bin 0 -> 675 bytes .../multimc/22x22/status-running.png | Bin 0 -> 957 bytes .../multimc/24x24/status-running.png | Bin 0 -> 1059 bytes .../multimc/32x32/status-running.png | Bin 0 -> 1425 bytes .../multimc/48x48/status-running.png | Bin 0 -> 2288 bytes .../multimc/64x64/status-running.png | Bin 0 -> 3178 bytes application/resources/multimc/multimc.qrc | 9 + .../multimc/scalable/status-running.svg | 187 +++++++++++++ application/widgets/PageContainer.cpp | 22 +- application/widgets/PageContainer.h | 2 + 29 files changed, 630 insertions(+), 351 deletions(-) delete mode 100644 application/ConsoleWindow.cpp create mode 100644 application/InstanceWindow.cpp rename application/{ConsoleWindow.h => InstanceWindow.h} (66%) 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 diff --git a/api/logic/BaseInstance.cpp b/api/logic/BaseInstance.cpp index ce55d5e44..9dee2c384 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 BaseInstance::getLaunchTask() +{ + return m_launchProcess; +} diff --git a/api/logic/BaseInstance.h b/api/logic/BaseInstance.h index f0fb60964..9a6976cb6 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 createLaunchTask(AuthSessionPtr account) = 0; + /// returns the current launch task (if any) + std::shared_ptr 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); + + 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 m_launchProcess; QDateTime m_timeStarted; }; diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp index f706c16d2..b64d9bd2d 100644 --- a/api/logic/minecraft/MinecraftInstance.cpp +++ b/api/logic/minecraft/MinecraftInstance.cpp @@ -465,7 +465,9 @@ std::shared_ptr 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 9d71f9777..46c496e27 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 e620d700f..000000000 --- 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/InstancePageProvider.h b/application/InstancePageProvider.h index dfc2e4dd6..1d6cc5d7e 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 000000000..dfc7b8158 --- /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/ConsoleWindow.h b/application/InstanceWindow.h similarity index 66% rename from application/ConsoleWindow.h rename to application/InstanceWindow.h index ac5a6fd19..7ffc41429 100644 --- a/application/ConsoleWindow.h +++ b/application/InstanceWindow.h @@ -16,25 +16,25 @@ #pragma once #include +#include "LaunchInteraction.h" +#include #include #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 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 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 m_proc; + unique_qobject_ptr 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 1caa054af..80a3368d5 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 b0932e9b5..55cb1e588 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 06d165da6..88c0fc09a 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 e3fb04674..3f0ec6e6f 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 2a2010de0..000000000 --- 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 1e6dc8d0a..5b8badf21 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 b9ad353c6..359dd3cfd 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 af96148be..de4ed4f3e 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 9b694323b..e902ad134 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 09ae25d00..b1f1bb4d8 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 0000000000000000000000000000000000000000..ecd64451f0f6ee861adf9a3090986da80b3ce991 GIT binary patch literal 1059 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4mJh`h6m-gKNuJoSc;uILpV4%IBGajIv5xj zI14-?iy0WW5nsOTr4n8zQDu__k2q{uR zwx^TM&(inyy&mgD7b-7Mxc+Ijs$)igX2Ik-VJVkY(kuBch25WgS0O7$s59JgyTaBb z1`Mo@1#6aG_TKs7y@kpnhf@ln>S6!v7QMcEl&xQ?yX)g(j-Aa5ctw9cIFZA>P5QJtkNjqgjor?f=lwf6~`8u7OJd?81F-z?oaxof`F zpVWv>&%&wqr9X>FByRkFvbsTxE9{_dCHD{4`mdi}HqN!F3Fi!6e(%|#C2peEQ_Lrd zW$?b+8f5nWrmADEb4G;lDT`%gkJ=poPP`MN8d$g6(4IfQX0_+ST))-<~e_ z@MGh2&VPR@+j>473{Z2No|JCk5xL=AUshj<%%jz7Ra^YuH{H14)bx~Lif0Sc;{00) zkw0c%yuo4HRW|*mRCnRO?}>5s-wlq3$X@&(p_XB(!o zEUZ?!HOE0?hurxS_SPS?{omd@pM1aii>uWFjcyAErDxxd%@Igo(fzx>q1JqR{ul0W zsnES|ZA!A69#2l~xnjRtVVdRdNC``JxA}{6gXi)dO8ENDac15l@dw+!D*TnI*jOR_ ze)_s*>(kT4+3L;vc$)(gj&LiSsY%U#`r%eu(G@ej|Nrm!c6CvS@P&>AY#M&GAr`%@lYKLU9Yv1U8{b@Vd)eBpSsa{Px+)c4gMMksX+_yqt!(vLBc7j> z+27i5w7DbQ^`erPDF3`H(WP8@#UIq-d|VfMoRnhz8*rKT=FN{AE~({qy*D}Y`~3UA z_s>Wl{5Io+Jo9^*OR^3-3R}3Hbd~RJ=O~^3QaOLs)1+e4$c{xll03CdwTc;e4GS1E zR)j3r!k5i@;T(5UTvy5Ty{t1fC~F)}>Yu4_jU_0++=joduDLgf@BGWhuP*NSV*E-m z*R84IhWjn~*UU>=JlIXPmJ4v$evtKf<|o8)?y}@(?dmS?_~j>s9#`f_oSA0jC!({T zJydRzN9nY0&!leOy=!YTSL&(X6{VeDShn1Iroyl`upzOtpqgX0b=Qn#CtHo3bzVx<$|7s-8w>Qi?X8)Ql$KtVWjJ0qV{Y}?t>yb8y?PvP zZ@a0nwn1!~V+$87FarYvgQu&X%Q~loCIA_2Bvt?b literal 0 HcmV?d00001 diff --git a/application/resources/multimc/22x22/status-running.png b/application/resources/multimc/22x22/status-running.png new file mode 100644 index 0000000000000000000000000000000000000000..0dffba18fbca0ca7c102088e722569f23748419c GIT binary patch literal 957 zcmeAS@N?(olHy`uVBq!ia0y~yU=Rag4mJh`h9g^YtQi;>Sc;uILpV4%IBGajIv5xj zI14-?iy0U=TR@m`)uZG}1_lPn64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xh zq!<{Otvp>ELo9mdPW8_T2^Be3|9qdh@mindQ|h<19@OhtbVXrBpmq|Ips?5_i7b<4 z7hm}GvWZ>V(&ponyv5U5O|aAK!m?WDXoZrtmnHXh zf8SLxBPzYEujcgkeeb`Y+xz{_=a!;ZA7&XW<6XRc(U;hcxHgFcK8gng7VSSiL;20^ zf_)(|UGv_|it4=3{bISm+f5owSM@Bojx>E#oZno-#=+zw-*(gX%a)$0?CM!yv4cXs)n8lI8U@o*g({KmZ8b;0F+`zAl75kkhoPKm`x2J81oe$eVYYk!b znH&olCLdn&dzb8`dL5p!m>s3O9m4M3`4JyqA1-gO^1hoC5oet~@5ki+u)jWz(sn-{ zGBtnS$F=cCuYEzP$g<-HUTx+2GP$Ew)zo!gg>TdJdG*uwy%Vo^`{?(TzrR=`qUB<4 zGiW$Oi6+TCo|f_RGP{)Yt4jT??fb-M->lrlo&Lrrb8(U4G=_<}TuZ})W%qs*VibJ% zkN36Zk9To)JBw%Z{>^vz7PQfD8;8Qm16Ll0-H37s4HbQxyH=+9x2V;6*#-Z$pMRd; zFU*<|(6KEd*l>qaq_|<`mi@~vx!BEre&V9=8~d`Zc(Zp+U%j78moRRa$6eKO^G#Di zw(fGj`3t{4K3uc;QRbIt*Df7%5}Obxe(`?bj$N#>ExmD0kK<nqPBKu8~h~KI!-OsgVzxwsjOoVyIRnsfx&B8zHolpFD!0Tu*`Jmz1 zC1$-XC+D^6E0nQYnR{0;{E2yb!HI8=BS#vSMVb0aU*!+?9rthlUpS$;WAmI@ZXRoR z)@N#F$}c;vIGJs=x?tw>v(xs~$BE15r`IzJ-fVv=Ie{Z|tAKAqR}8z@p=qc5nXkFu zbpElddAnKl|6@~LN-ubwBDD0Dz)hw#%)1{|r7%exy7eLb*5?-)D}UV2bF<&IdH$w9 Q3=9kmp00i_>zopr05xH>X#fBK literal 0 HcmV?d00001 diff --git a/application/resources/multimc/24x24/status-running.png b/application/resources/multimc/24x24/status-running.png new file mode 100644 index 0000000000000000000000000000000000000000..ecd64451f0f6ee861adf9a3090986da80b3ce991 GIT binary patch literal 1059 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4mJh`h6m-gKNuJoSc;uILpV4%IBGajIv5xj zI14-?iy0WW5nsOTr4n8zQDu__k2q{uR zwx^TM&(inyy&mgD7b-7Mxc+Ijs$)igX2Ik-VJVkY(kuBch25WgS0O7$s59JgyTaBb z1`Mo@1#6aG_TKs7y@kpnhf@ln>S6!v7QMcEl&xQ?yX)g(j-Aa5ctw9cIFZA>P5QJtkNjqgjor?f=lwf6~`8u7OJd?81F-z?oaxof`F zpVWv>&%&wqr9X>FByRkFvbsTxE9{_dCHD{4`mdi}HqN!F3Fi!6e(%|#C2peEQ_Lrd zW$?b+8f5nWrmADEb4G;lDT`%gkJ=poPP`MN8d$g6(4IfQX0_+ST))-<~e_ z@MGh2&VPR@+j>473{Z2No|JCk5xL=AUshj<%%jz7Ra^YuH{H14)bx~Lif0Sc;{00) zkw0c%yuo4HRW|*mRCnRO?}>5s-wlq3$X@&(p_XB(!o zEUZ?!HOE0?hurxS_SPS?{omd@pM1aii>uWFjcyAErDxxd%@Igo(fzx>q1JqR{ul0W zsnES|ZA!A69#2l~xnjRtVVdRdNC``JxA}{6gXi)dO8ENDac15l@dw+!D*TnI*jOR_ ze)_s*>(kT4+3L;vc$)(gj&LiSsY%U#`r%eu(G@ej|Nrm!c6CvS@P&>AY#M&^e0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa~Ne4Z|jArY->r`P*T4V5@<|9tQJzX$tQH!N~F6?QIaMXIK;KxXpQn17DiC8nE0 zzny*-G-=V!OpT00v4zv@54aUCQ@t9qHYxjxz(JkgD>EgxDq1PBe0lXW?%nTsm*wB@ z?clg4|Ml;4+vj&b+uVO|`Mh%e^F2)-AA_Q;f}S+b>|eI5!OQu?Mh?ysdM!5G&kR|3 zB^uex?i8v`+Mu-NXxF)z391~H{EDm^O-mlmV;9`iRBCM3>+4YXeXYkQ(R8YdW>;#W zcULT^xprEaTf=U*OzLVE&W`PekGO2e+&Hm4L24Su|H?JVhTR&cD_>_Z9({W=A=@R> zij!}~3=z*RH@2k<9;^H~*F1r3M&*>Vd5jyTg|8}US;e^YvA_hoS6)S#2j-OY6U$X4pGevlzYp)e7@f6N)RlWS-%4{9 zaW$if>sqI?Cg?ePc$74rHeB=n#S_CfYu7F`(mpWdF6)Qu3tzZe2rJqhmGa(tM=0Qu z74MZ?4$hT}*S(xSXU>^R>GP86eO0|}swMZo&-_s0rmmF9q`~OqlOFongg5a{_k;(V zn(xX5_IOTK+xYTqZ_S1xLDsXU3+gs=E|Hwcu$x=v&)c%T z$yxgTzPsM})RBq3XX_jqA8q0L!&jvBuUlZcgz3iPj~`Axzb~Qqt=zu-wX8ZPc;7$y z9O19%_ljdP>lXHp-<*P?jvQ%AEZDHK^_1@W4@-@u>l0-&1nWF&?;dQ-xi)c;oNt~= zfgqFJ>Y!G~>}>7)klbgF?e~%;go~X>{842#0O4H zQ_lVrTiPWcRI~R&Ax}!ek_i(Jn=JV3(jg;q+WCn=%*WW;1uPZ3EH@q{yh$n8m8|@| zdX2Ja&9#`7L8sE!OXe8;kz1b^#q%onK&OC!x!5ElhyD$z?7!CJe4od?FTk!P>eQ_} zbDe$N?VemZ=Cfp*Xvn<-*6vD2dKsTmdKI;Vst0Fh0Rv;Y7A literal 0 HcmV?d00001 diff --git a/application/resources/multimc/48x48/status-running.png b/application/resources/multimc/48x48/status-running.png new file mode 100644 index 0000000000000000000000000000000000000000..b8c0bf7cdb220b37b35222d7c1adceed13ad507d GIT binary patch literal 2288 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 Hr-UW|VM_5* literal 0 HcmV?d00001 diff --git a/application/resources/multimc/multimc.qrc b/application/resources/multimc/multimc.qrc index 3f7c5e8fa..86b472d86 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 000000000..182099405 --- /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 90aad26e1..04da5c8b8 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 381e84e53..84504fb65 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