diff options
30 files changed, 547 insertions, 329 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 16026015..195e71e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -304,6 +304,8 @@ SET(MULTIMC_SOURCES gui/pages/LegacyUpgradePage.h gui/pages/LegacyJarModPage.cpp gui/pages/LegacyJarModPage.h + gui/pages/LogPage.cpp + gui/pages/LogPage.h gui/pages/InstanceSettingsPage.cpp gui/pages/InstanceSettingsPage.h gui/pages/ScreenshotsPage.cpp @@ -606,13 +608,13 @@ SET(MULTIMC_SOURCES SET(MULTIMC_UIS # Windows gui/MainWindow.ui - gui/ConsoleWindow.ui # Option pages gui/pages/VersionPage.ui gui/pages/ModFolderPage.ui gui/pages/LegacyUpgradePage.ui gui/pages/LegacyJarModPage.ui + gui/pages/LogPage.ui gui/pages/InstanceSettingsPage.ui gui/pages/NotesPage.ui gui/pages/ScreenshotsPage.ui diff --git a/gui/ConsoleWindow.cpp b/gui/ConsoleWindow.cpp index 692ae886..62908d29 100644 --- a/gui/ConsoleWindow.cpp +++ b/gui/ConsoleWindow.cpp @@ -14,27 +14,117 @@ */ #include "ConsoleWindow.h" -#include "ui_ConsoleWindow.h" #include "MultiMC.h" #include <QScrollBar> #include <QMessageBox> #include <QSystemTrayIcon> +#include <QHBoxLayout> +#include <QPushButton> +#include <qlayoutitem.h> #include <gui/Platform.h> #include <gui/dialogs/CustomMessageBox.h> #include <gui/dialogs/ProgressDialog.h> +#include "widgets/PageContainer.h" +#include "pages/LogPage.h" -#include "logic/net/PasteUpload.h" #include "logic/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(MinecraftProcess *mcproc, QWidget *parent) - : QMainWindow(parent), ui(new Ui::ConsoleWindow), proc(mcproc) + : QMainWindow(parent), m_proc(mcproc) { MultiMCPlatform::fixWM_CLASS(this); - ui->setupUi(this); - connect(mcproc, SIGNAL(log(QString, MessageLevel::Enum)), this, - SLOT(write(QString, MessageLevel::Enum))); + + 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::dynamic_pointer_cast<BasePageProvider>(m_proc->instance()); + auto proxy_provider = std::make_shared<LogPageProvider>(provider, 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(mcproc, SIGNAL(ended(InstancePtr, int, QProcess::ExitStatus)), this, SLOT(onEnded(InstancePtr, int, QProcess::ExitStatus))); connect(mcproc, SIGNAL(prelaunch_failed(InstancePtr, int, QProcess::ExitStatus)), this, @@ -42,34 +132,12 @@ ConsoleWindow::ConsoleWindow(MinecraftProcess *mcproc, QWidget *parent) connect(mcproc, SIGNAL(launch_failed(InstancePtr)), this, SLOT(onLaunchFailed(InstancePtr))); - restoreState( - QByteArray::fromBase64(MMC->settings()->get("ConsoleWindowState").toByteArray())); - restoreGeometry( - QByteArray::fromBase64(MMC->settings()->get("ConsoleWindowGeometry").toByteArray())); - - QString iconKey = proc->instance()->iconKey(); - QString name = proc->instance()->name(); - auto icon = MMC->icons()->getIcon(iconKey); - setWindowIcon(icon); - m_trayIcon = new QSystemTrayIcon(icon, this); - // TODO add screenshot upload as a menu item in the tray icon - QString consoleTitle = tr("Console window for ") + name; - m_trayIcon->setToolTip(consoleTitle); - setWindowTitle(consoleTitle); + setMayClose(false); - connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), - SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); - m_trayIcon->show(); if (mcproc->instance()->settings().get("ShowConsole").toBool()) { show(); } - setMayClose(false); -} - -ConsoleWindow::~ConsoleWindow() -{ - delete ui; } void ConsoleWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) @@ -85,110 +153,23 @@ void ConsoleWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) } } -void ConsoleWindow::writeColor(QString text, const char *color, const char * background) -{ - // append a paragraph - QString newtext; - newtext += "<span style=\""; - { - if (color) - newtext += QString("color:") + color + ";"; - if (background) - newtext += QString("background-color:") + background + ";"; - newtext += "font-family: monospace;"; - } - newtext += "\">"; - newtext += text.toHtmlEscaped(); - newtext += "</span>"; - ui->text->appendHtml(newtext); -} - -void ConsoleWindow::write(QString data, MessageLevel::Enum mode) -{ - QScrollBar *bar = ui->text->verticalScrollBar(); - int max_bar = bar->maximum(); - int val_bar = bar->value(); - if(isVisible()) - { - if (m_scroll_active) - { - m_scroll_active = (max_bar - val_bar) <= 1; - } - else - { - m_scroll_active = val_bar == max_bar; - } - } - if (data.endsWith('\n')) - data = data.left(data.length() - 1); - QStringList paragraphs = data.split('\n'); - QStringList filtered; - for (QString ¶graph : paragraphs) - { - // Quick hack for - if(paragraph.contains("Detected an attempt by a mod null to perform game activity during mod construction")) - continue; - filtered.append(paragraph.trimmed()); - } - QListIterator<QString> iter(filtered); - if (mode == MessageLevel::MultiMC) - while (iter.hasNext()) - writeColor(iter.next(), "blue", 0); - else if (mode == MessageLevel::Error) - while (iter.hasNext()) - writeColor(iter.next(), "red", 0); - else if (mode == MessageLevel::Warning) - while (iter.hasNext()) - writeColor(iter.next(), "orange", 0); - else if (mode == MessageLevel::Fatal) - while (iter.hasNext()) - writeColor(iter.next(), "red", "black"); - else if (mode == MessageLevel::Debug) - while (iter.hasNext()) - writeColor(iter.next(), "green", 0); - else if (mode == MessageLevel::PrePost) - while (iter.hasNext()) - writeColor(iter.next(), "grey", 0); - // TODO: implement other MessageLevels - else - while (iter.hasNext()) - writeColor(iter.next(), 0, 0); - if(isVisible()) - { - if (m_scroll_active) - { - bar->setValue(bar->maximum()); - } - m_last_scroll_value = bar->value(); - } -} - -void ConsoleWindow::clear() -{ - ui->text->clear(); -} - void ConsoleWindow::on_closeButton_clicked() { close(); } -void ConsoleWindow::on_btnScreenshots_clicked() -{ -} - void ConsoleWindow::setMayClose(bool mayclose) { if(mayclose) - ui->closeButton->setText(tr("Close")); + m_closeButton->setText(tr("Close")); else - ui->closeButton->setText(tr("Hide")); + m_closeButton->setText(tr("Hide")); m_mayclose = mayclose; } void ConsoleWindow::toggleConsole() { - QScrollBar *bar = ui->text->verticalScrollBar(); + //QScrollBar *bar = ui->text->verticalScrollBar(); if (isVisible()) { if(!isActiveWindow()) @@ -196,15 +177,17 @@ void ConsoleWindow::toggleConsole() activateWindow(); return; } + /* int max_bar = bar->maximum(); int val_bar = m_last_scroll_value = bar->value(); m_scroll_active = (max_bar - val_bar) <= 1; + */ hide(); } else { show(); - isTopLevel(); + /* if (m_scroll_active) { bar->setValue(bar->maximum()); @@ -213,6 +196,7 @@ void ConsoleWindow::toggleConsole() { bar->setValue(m_last_scroll_value); } + */ } } @@ -235,25 +219,23 @@ void ConsoleWindow::closeEvent(QCloseEvent *event) void ConsoleWindow::on_btnKillMinecraft_clicked() { - ui->btnKillMinecraft->setEnabled(false); + 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) - proc->killMinecraft(); + m_proc->killMinecraft(); else - ui->btnKillMinecraft->setEnabled(true); + m_killButton->setEnabled(true); } void ConsoleWindow::onEnded(InstancePtr instance, int code, QProcess::ExitStatus status) { bool peacefulExit = code == 0 && status != QProcess::CrashExit; - ui->btnKillMinecraft->setEnabled(false); - + m_killButton->setEnabled(false); setMayClose(true); - if (instance->settings().get("AutoCloseConsole").toBool()) { if (peacefulExit) @@ -262,15 +244,8 @@ void ConsoleWindow::onEnded(InstancePtr instance, int code, QProcess::ExitStatus return; } } - /* - if(!peacefulExit) - { - m_trayIcon->showMessage(tr("Oh no!"), tr("Minecraft crashed!"), QSystemTrayIcon::Critical); - } - */ if (!isVisible()) show(); - // Raise Window if (MMC->settings()->get("RaiseConsole").toBool()) { @@ -281,23 +256,10 @@ void ConsoleWindow::onEnded(InstancePtr instance, int code, QProcess::ExitStatus void ConsoleWindow::onLaunchFailed(InstancePtr instance) { - ui->btnKillMinecraft->setEnabled(false); + m_killButton->setEnabled(false); setMayClose(true); if (!isVisible()) show(); } - -void ConsoleWindow::on_btnPaste_clicked() -{ - auto text = ui->text->toPlainText(); - ProgressDialog dialog(this); - PasteUpload *paste = new PasteUpload(this, text); - dialog.exec(paste); - if (!paste->successful()) - { - CustomMessageBox::selectable(this, "Upload failed", paste->failReason(), - QMessageBox::Critical)->exec(); - } -} diff --git a/gui/ConsoleWindow.h b/gui/ConsoleWindow.h index 17c64392..97600baa 100644 --- a/gui/ConsoleWindow.h +++ b/gui/ConsoleWindow.h @@ -19,18 +19,15 @@ #include <QSystemTrayIcon> #include "logic/MinecraftProcess.h" -namespace Ui -{ -class ConsoleWindow; -} - +class QPushButton; +class PageContainer; class ConsoleWindow : public QMainWindow { Q_OBJECT public: explicit ConsoleWindow(MinecraftProcess *proc, QWidget *parent = 0); - ~ConsoleWindow(); + virtual ~ConsoleWindow() {}; /** * @brief specify if the window is allowed to close @@ -39,38 +36,12 @@ public: */ void setMayClose(bool mayclose); -private: - /** - * @brief write a colored paragraph - * @param data the string - * @param color the css color name - * this will only insert a single paragraph. - * \n are ignored. a real \n is always appended. - */ - void writeColor(QString text, const char *color, const char *background); - signals: void isClosing(); -public -slots: - /** - * @brief write a string - * @param data the string - * @param mode the WriteMode - * lines have to be put through this as a whole! - */ - void write(QString data, MessageLevel::Enum level = MessageLevel::MultiMC); - - /** - * @brief clear the text widget - */ - void clear(); - private slots: void on_closeButton_clicked(); - void on_btnScreenshots_clicked(); void on_btnKillMinecraft_clicked(); void onEnded(InstancePtr instance, int code, QProcess::ExitStatus status); @@ -79,18 +50,16 @@ slots: // FIXME: add handlers for the other MinecraftProcess signals (pre/post launch command // failures) - void on_btnPaste_clicked(); void iconActivated(QSystemTrayIcon::ActivationReason); void toggleConsole(); protected: void closeEvent(QCloseEvent *); private: - Ui::ConsoleWindow *ui = nullptr; - MinecraftProcess *proc = nullptr; + MinecraftProcess *m_proc = nullptr; bool m_mayclose = true; - int m_last_scroll_value = 0; - bool m_scroll_active = true; QSystemTrayIcon *m_trayIcon = nullptr; - int m_saved_offset = 0; + PageContainer *m_container = nullptr; + QPushButton *m_closeButton = nullptr; + QPushButton *m_killButton = nullptr; }; diff --git a/gui/ConsoleWindow.ui b/gui/ConsoleWindow.ui deleted file mode 100644 index c2307ecc..00000000 --- a/gui/ConsoleWindow.ui +++ /dev/null @@ -1,86 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ConsoleWindow</class> - <widget class="QMainWindow" name="ConsoleWindow"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>640</width> - <height>440</height> - </rect> - </property> - <property name="windowTitle"> - <string>MultiMC Console</string> - </property> - <widget class="QWidget" name="centralwidget"> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QPlainTextEdit" name="text"> - <property name="undoRedoEnabled"> - <bool>false</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - <property name="plainText"> - <string notr="true"/> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - <property name="centerOnScroll"> - <bool>false</bool> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="leftMargin"> - <number>6</number> - </property> - <property name="rightMargin"> - <number>6</number> - </property> - <item> - <widget class="QPushButton" name="btnPaste"> - <property name="text"> - <string>Upload Log</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="btnKillMinecraft"> - <property name="text"> - <string>&Kill Minecraft</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="closeButton"> - <property name="text"> - <string>&Close</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </widget> - <resources/> - <connections/> -</ui> diff --git a/gui/dialogs/SettingsDialog.cpp b/gui/dialogs/SettingsDialog.cpp index f7333055..65b17885 100644 --- a/gui/dialogs/SettingsDialog.cpp +++ b/gui/dialogs/SettingsDialog.cpp @@ -420,7 +420,7 @@ void SettingsDialog::loadSettings(SettingsObject *s) foreach(const QString & lang, QDir(MMC->staticData() + "/translations") .entryList(QStringList() << "*.qm", QDir::Files)) { - QLocale locale(lang.section(QRegExp("[_\.]"), 1)); + QLocale locale(lang.section(QRegExp("[_\\.]"), 1)); ui->languageBox->addItem(QLocale::languageToString(locale.language()), locale); } ui->languageBox->setCurrentIndex( diff --git a/gui/pages/InstanceSettingsPage.cpp b/gui/pages/InstanceSettingsPage.cpp index b4a6405f..6e2ce238 100644 --- a/gui/pages/InstanceSettingsPage.cpp +++ b/gui/pages/InstanceSettingsPage.cpp @@ -23,13 +23,19 @@ QString InstanceSettingsPage::id() return "settings"; } -InstanceSettingsPage::InstanceSettingsPage(SettingsObject *s, QWidget *parent) - : QWidget(parent), ui(new Ui::InstanceSettingsPage), m_settings(s) +InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) + : QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst) { + m_settings = &(inst->settings()); ui->setupUi(this); loadSettings(); } +bool InstanceSettingsPage::shouldDisplay() +{ + return !m_instance->isRunning(); +} + InstanceSettingsPage::~InstanceSettingsPage() { delete ui; diff --git a/gui/pages/InstanceSettingsPage.h b/gui/pages/InstanceSettingsPage.h index db37d8d8..2447168f 100644 --- a/gui/pages/InstanceSettingsPage.h +++ b/gui/pages/InstanceSettingsPage.h @@ -32,15 +32,14 @@ class InstanceSettingsPage : public QWidget, public BasePage Q_OBJECT public: - explicit InstanceSettingsPage(SettingsObject *s, QWidget *parent = 0); + explicit InstanceSettingsPage(BaseInstance *inst, QWidget *parent = 0); virtual ~InstanceSettingsPage(); virtual QString displayName() override; virtual QIcon icon() override; virtual QString id() override; virtual bool apply(); - virtual QString helpPage() override { return "Instance-settings"; }; -private: - void updateCheckboxStuff(); + virtual QString helpPage() override { return "Instance-settings"; } + virtual bool shouldDisplay(); private slots: void on_javaDetectBtn_clicked(); @@ -54,6 +53,7 @@ private slots: void loadSettings(); private: Ui::InstanceSettingsPage *ui; + BaseInstance *m_instance; SettingsObject *m_settings; std::shared_ptr<JavaChecker> checker; }; diff --git a/gui/pages/LegacyJarModPage.cpp b/gui/pages/LegacyJarModPage.cpp index f0f3d753..b1c0d49a 100644 --- a/gui/pages/LegacyJarModPage.cpp +++ b/gui/pages/LegacyJarModPage.cpp @@ -54,6 +54,11 @@ QString LegacyJarModPage::displayName() return tr("Jar Mods"); } +bool LegacyJarModPage::shouldDisplay() +{ + return !m_inst->isRunning(); +} + QIcon LegacyJarModPage::icon() { return QIcon::fromTheme("plugin-red"); diff --git a/gui/pages/LegacyJarModPage.h b/gui/pages/LegacyJarModPage.h index 0b28777b..016f4a8f 100644 --- a/gui/pages/LegacyJarModPage.h +++ b/gui/pages/LegacyJarModPage.h @@ -38,6 +38,7 @@ public: virtual QIcon icon(); virtual QString id(); virtual QString helpPage() override { return "Legacy-jar-mods"; }; + virtual bool shouldDisplay(); private slots: diff --git a/gui/pages/LegacyUpgradePage.cpp b/gui/pages/LegacyUpgradePage.cpp index 02729c79..bb54210c 100644 --- a/gui/pages/LegacyUpgradePage.cpp +++ b/gui/pages/LegacyUpgradePage.cpp @@ -1,4 +1,5 @@ #include "LegacyUpgradePage.h" +#include <logic/LegacyInstance.h> #include "ui_LegacyUpgradePage.h" QString LegacyUpgradePage::displayName() @@ -31,3 +32,8 @@ void LegacyUpgradePage::on_upgradeButton_clicked() { // now what? } + +bool LegacyUpgradePage::shouldDisplay() +{ + return !m_inst->isRunning(); +}
\ No newline at end of file diff --git a/gui/pages/LegacyUpgradePage.h b/gui/pages/LegacyUpgradePage.h index 4f287e95..eb816a7a 100644 --- a/gui/pages/LegacyUpgradePage.h +++ b/gui/pages/LegacyUpgradePage.h @@ -37,6 +37,7 @@ public: virtual QIcon icon() override; virtual QString id() override; virtual QString helpPage() override { return "Legacy-upgrade"; }; + virtual bool shouldDisplay(); private slots: void on_upgradeButton_clicked(); diff --git a/gui/pages/LogPage.cpp b/gui/pages/LogPage.cpp new file mode 100644 index 00000000..dd088862 --- /dev/null +++ b/gui/pages/LogPage.cpp @@ -0,0 +1,137 @@ +#include "LogPage.h" +#include <gui/dialogs/CustomMessageBox.h> +#include <gui/dialogs/ProgressDialog.h> +#include <logic/MinecraftProcess.h> +#include <QtGui/QIcon> +#include "ui_LogPage.h" +#include "logic/net/PasteUpload.h" +#include <QScrollBar> + +QString LogPage::displayName() +{ + return tr("Minecraft Log"); +} + +QIcon LogPage::icon() +{ + return QIcon::fromTheme("refresh"); +} + +QString LogPage::id() +{ + return "console"; +} + +LogPage::LogPage(MinecraftProcess *proc, QWidget *parent) + : QWidget(parent), ui(new Ui::LogPage), m_process(proc) +{ + ui->setupUi(this); + connect(m_process, SIGNAL(log(QString, MessageLevel::Enum)), this, + SLOT(write(QString, MessageLevel::Enum))); +} + +LogPage::~LogPage() +{ + delete ui; +} + +bool LogPage::apply() +{ + return true; +} + +bool LogPage::shouldDisplay() +{ + return m_process->instance()->isRunning(); +} + +void LogPage::on_btnPaste_clicked() +{ + auto text = ui->text->toPlainText(); + ProgressDialog dialog(this); + PasteUpload *paste = new PasteUpload(this, text); + dialog.exec(paste); + if (!paste->successful()) + { + CustomMessageBox::selectable(this, "Upload failed", paste->failReason(), + QMessageBox::Critical)->exec(); + } +} + +void LogPage::writeColor(QString text, const char *color, const char * background) +{ + // append a paragraph + QString newtext; + newtext += "<span style=\""; + { + if (color) + newtext += QString("color:") + color + ";"; + if (background) + newtext += QString("background-color:") + background + ";"; + newtext += "font-family: monospace;"; + } + newtext += "\">"; + newtext += text.toHtmlEscaped(); + newtext += "</span>"; + ui->text->appendHtml(newtext); +} + +void LogPage::write(QString data, MessageLevel::Enum mode) +{ + QScrollBar *bar = ui->text->verticalScrollBar(); + int max_bar = bar->maximum(); + int val_bar = bar->value(); + if(isVisible()) + { + if (m_scroll_active) + { + m_scroll_active = (max_bar - val_bar) <= 1; + } + else + { + m_scroll_active = val_bar == max_bar; + } + } + if (data.endsWith('\n')) + data = data.left(data.length() - 1); + QStringList paragraphs = data.split('\n'); + QStringList filtered; + for (QString ¶graph : paragraphs) + { + // Quick hack for + if(paragraph.contains("Detected an attempt by a mod null to perform game activity during mod construction")) + continue; + filtered.append(paragraph.trimmed()); + } + QListIterator<QString> iter(filtered); + if (mode == MessageLevel::MultiMC) + while (iter.hasNext()) + writeColor(iter.next(), "blue", 0); + else if (mode == MessageLevel::Error) + while (iter.hasNext()) + writeColor(iter.next(), "red", 0); + else if (mode == MessageLevel::Warning) + while (iter.hasNext()) + writeColor(iter.next(), "orange", 0); + else if (mode == MessageLevel::Fatal) + while (iter.hasNext()) + writeColor(iter.next(), "red", "black"); + else if (mode == MessageLevel::Debug) + while (iter.hasNext()) + writeColor(iter.next(), "green", 0); + else if (mode == MessageLevel::PrePost) + while (iter.hasNext()) + writeColor(iter.next(), "grey", 0); + // TODO: implement other MessageLevels + else + while (iter.hasNext()) + writeColor(iter.next(), 0, 0); + if(isVisible()) + { + if (m_scroll_active) + { + bar->setValue(bar->maximum()); + } + m_last_scroll_value = bar->value(); + } +} diff --git a/gui/pages/LogPage.h b/gui/pages/LogPage.h new file mode 100644 index 00000000..7cdea2c1 --- /dev/null +++ b/gui/pages/LogPage.h @@ -0,0 +1,72 @@ +/* Copyright 2014 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 <QWidget> + +#include <logic/BaseInstance.h> +#include <logic/net/NetJob.h> +#include <logic/MinecraftProcess.h> +#include "BasePage.h" + +class EnabledItemFilter; +class MinecraftProcess; +namespace Ui +{ +class LogPage; +} + +class LogPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit LogPage(MinecraftProcess *proc, QWidget *parent = 0); + virtual ~LogPage(); + virtual QString displayName() override; + virtual QIcon icon() override; + virtual QString id() override; + virtual bool apply(); + virtual QString helpPage() override { return "Minecraft-Log"; }; + virtual bool shouldDisplay(); + +private: + /** + * @brief write a colored paragraph + * @param data the string + * @param color the css color name + * this will only insert a single paragraph. + * \n are ignored. a real \n is always appended. + */ + void writeColor(QString text, const char *color, const char *background); + +private slots: + /** + * @brief write a string + * @param data the string + * @param mode the WriteMode + * lines have to be put through this as a whole! + */ + void write(QString data, MessageLevel::Enum level = MessageLevel::MultiMC); + void on_btnPaste_clicked(); + +private: + Ui::LogPage *ui; + MinecraftProcess *m_process; + int m_last_scroll_value = 0; + bool m_scroll_active = true; + int m_saved_offset = 0; +}; diff --git a/gui/pages/LogPage.ui b/gui/pages/LogPage.ui new file mode 100644 index 00000000..00b611b5 --- /dev/null +++ b/gui/pages/LogPage.ui @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>LogPage</class> + <widget class="QWidget" name="LogPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>831</width> + <height>596</height> + </rect> + </property> + <property name="windowTitle"> + <string>Log</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QPlainTextEdit" name="text"> + <property name="undoRedoEnabled"> + <bool>false</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="plainText"> + <string notr="true"/> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + <property name="centerOnScroll"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QPushButton" name="btnPaste"> + <property name="text"> + <string>Upload Log</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/gui/pages/ModFolderPage.cpp b/gui/pages/ModFolderPage.cpp index 6d5c6226..2035e57a 100644 --- a/gui/pages/ModFolderPage.cpp +++ b/gui/pages/ModFolderPage.cpp @@ -48,11 +48,12 @@ QString ModFolderPage::id() return m_id; } -ModFolderPage::ModFolderPage(std::shared_ptr<ModList> mods, QString id, QString iconName, +ModFolderPage::ModFolderPage(BaseInstance * inst, std::shared_ptr<ModList> mods, QString id, QString iconName, QString displayName, QString helpPage, QWidget *parent) : QWidget(parent), ui(new Ui::ModFolderPage) { ui->setupUi(this); + m_inst = inst; m_mods = mods; m_id = id; m_displayName = displayName; @@ -72,6 +73,13 @@ ModFolderPage::~ModFolderPage() delete ui; } +bool ModFolderPage::shouldDisplay() +{ + if(m_inst) + return !m_inst->isRunning(); + return true; +} + bool ModFolderPage::modListFilter(QKeyEvent *keyEvent) { switch (keyEvent->key()) diff --git a/gui/pages/ModFolderPage.h b/gui/pages/ModFolderPage.h index b4e05928..c193f4c1 100644 --- a/gui/pages/ModFolderPage.h +++ b/gui/pages/ModFolderPage.h @@ -32,17 +32,19 @@ class ModFolderPage : public QWidget, public BasePage Q_OBJECT public: - explicit ModFolderPage(std::shared_ptr<ModList> mods, QString id, QString iconName, + explicit ModFolderPage(BaseInstance * inst, std::shared_ptr<ModList> mods, QString id, QString iconName, QString displayName, QString helpPage = "" , QWidget *parent = 0); virtual ~ModFolderPage(); virtual QString displayName() override; virtual QIcon icon() override; virtual QString id() override; virtual QString helpPage() override { return m_helpName; }; + virtual bool shouldDisplay(); protected: bool eventFilter(QObject *obj, QEvent *ev); bool modListFilter(QKeyEvent *ev); - +protected: + BaseInstance * m_inst; private: Ui::ModFolderPage *ui; std::shared_ptr<ModList> m_mods; diff --git a/gui/pages/ResourcePackPage.h b/gui/pages/ResourcePackPage.h index 9332a6fa..06367905 100644 --- a/gui/pages/ResourcePackPage.h +++ b/gui/pages/ResourcePackPage.h @@ -5,10 +5,9 @@ class ResourcePackPage : public ModFolderPage { public: explicit ResourcePackPage(BaseInstance *instance, QWidget *parent = 0) - : ModFolderPage(instance->resourcePackList(), "resourcepacks", "resourcepacks", + : ModFolderPage(instance, instance->resourcePackList(), "resourcepacks", "resourcepacks", tr("Resource packs"), "Resource-packs", parent) { - m_inst = instance; } virtual ~ResourcePackPage() {}; @@ -17,6 +16,4 @@ public: return !m_inst->traits().contains("no-texturepacks") && !m_inst->traits().contains("texturepacks"); } -private: - BaseInstance *m_inst; }; diff --git a/gui/pages/ScreenshotsPage.cpp b/gui/pages/ScreenshotsPage.cpp index 3dc144ca..051bc12d 100644 --- a/gui/pages/ScreenshotsPage.cpp +++ b/gui/pages/ScreenshotsPage.cpp @@ -18,43 +18,63 @@ #include "logic/screenshots/ImgurAlbumCreation.h" #include "logic/tasks/SequentialTask.h" -class ThumbnailProvider : public QFileIconProvider -{ -public: - virtual ~ThumbnailProvider() {}; - virtual QIcon icon(const QFileInfo &info) const - { - QImage image(info.absoluteFilePath()); - if (image.isNull()) - { - return QFileIconProvider::icon(info); - } - QImage thumbnail = image.scaledToWidth(256, Qt::SmoothTransformation); - return QIcon(QPixmap::fromImage(thumbnail)); - } -}; - class FilterModel : public QIdentityProxyModel { +public: virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const { auto model = sourceModel(); - if(!model) + if (!model) return QVariant(); - QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); - if(role == Qt::DisplayRole || role == Qt::EditRole) + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); + return result.toString().remove(QRegExp("\\.png$")); + } + if (role == Qt::DecorationRole) { - return result.toString().remove(QRegExp("\.png$")); + QVariant result = sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); + QString filePath = result.toString(); + if(thumbnailCache.contains(filePath)) + { + return thumbnailCache[filePath]; + } + bool failed = false; + QFileInfo info(filePath); + failed |= info.isDir(); + failed |= (info.suffix().compare("png", Qt::CaseInsensitive) != 0); + // WARNING: really an IF! this is purely for using break instead of goto... + while(!failed) + { + QImage image(info.absoluteFilePath()); + if (image.isNull()) + { + // TODO: schedule a retry. + failed = true; + break; + } + QImage thumbnail = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); + QIcon icon(QPixmap::fromImage(thumbnail)); + // the casts are a hack for the stupid method being const. + ((QMap<QString, QIcon> &)thumbnailCache).insert(filePath, icon); + return icon; + } + // we failed anyway... + return sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FileIconRole); + } + else + { + QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); + return result; } - return result; } virtual bool setData(const QModelIndex &index, const QVariant &value, - int role = Qt::EditRole) + int role = Qt::EditRole) { auto model = sourceModel(); - if(!model) + if (!model) return false; - if(role != Qt::EditRole) + if (role != Qt::EditRole) return false; // FIXME: this is a workaround for a bug in QFileSystemModel, where it doesn't // sort after renames @@ -64,22 +84,25 @@ class FilterModel : public QIdentityProxyModel } return model->setData(mapToSource(index), value.toString() + ".png", role); } +private: + QMap<QString, QIcon> thumbnailCache; }; class CenteredEditingDelegate : public QStyledItemDelegate { public: - explicit CenteredEditingDelegate(QObject *parent = 0) - : QStyledItemDelegate(parent) + explicit CenteredEditingDelegate(QObject *parent = 0) : QStyledItemDelegate(parent) + { + } + virtual ~CenteredEditingDelegate() { } - virtual ~CenteredEditingDelegate() {} virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { auto widget = QStyledItemDelegate::createEditor(parent, option, index); - auto foo = dynamic_cast<QLineEdit *> (widget); - if(foo) + auto foo = dynamic_cast<QLineEdit *>(widget); + if (foo) { foo->setAlignment(Qt::AlignHCenter); foo->setFrame(true); @@ -111,7 +134,6 @@ ScreenshotsPage::ScreenshotsPage(BaseInstance *instance, QWidget *parent) m_filterModel.reset(new FilterModel()); m_filterModel->setSourceModel(m_model.get()); m_model->setFilter(QDir::Files | QDir::Writable | QDir::Readable); - m_model->setIconProvider(new ThumbnailProvider); m_model->setReadOnly(false); m_folder = PathCombine(instance->minecraftRoot(), "screenshots"); m_valid = ensureFolderPathExists(m_folder); @@ -218,11 +240,11 @@ void ScreenshotsPage::on_deleteBtn_clicked() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No); std::unique_ptr<QMessageBox> box(mbox); - if(box->exec() != QMessageBox::Yes) + if (box->exec() != QMessageBox::Yes) return; auto selected = ui->listView->selectionModel()->selectedIndexes(); - for(auto item : selected) + for (auto item : selected) { m_model->remove(item); } diff --git a/gui/pages/TexturePackPage.h b/gui/pages/TexturePackPage.h index b1a2f544..69a47204 100644 --- a/gui/pages/TexturePackPage.h +++ b/gui/pages/TexturePackPage.h @@ -5,16 +5,13 @@ class TexturePackPage : public ModFolderPage { public: explicit TexturePackPage(BaseInstance *instance, QWidget *parent = 0) - : ModFolderPage(instance->texturePackList(), "texturepacks", "resourcepacks", + : ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks", tr("Texture packs"), "Texture-packs", parent) { - m_inst = instance; } virtual ~TexturePackPage() {}; virtual bool shouldDisplay() override { return m_inst->traits().contains("texturepacks"); } -private: - BaseInstance *m_inst; }; diff --git a/gui/pages/VersionPage.cpp b/gui/pages/VersionPage.cpp index 3fd98d27..34599111 100644 --- a/gui/pages/VersionPage.cpp +++ b/gui/pages/VersionPage.cpp @@ -66,6 +66,11 @@ QString VersionPage::id() return "version"; } +bool VersionPage::shouldDisplay() +{ + return !m_inst->isRunning(); +} + VersionPage::VersionPage(OneSixInstance *inst, QWidget *parent) : QWidget(parent), ui(new Ui::VersionPage), m_inst(inst) { diff --git a/gui/pages/VersionPage.h b/gui/pages/VersionPage.h index a8bc1515..dfbb6741 100644 --- a/gui/pages/VersionPage.h +++ b/gui/pages/VersionPage.h @@ -37,6 +37,7 @@ public: virtual QIcon icon() override; virtual QString id() override; virtual QString helpPage() override { return "Instance-version"; }; + virtual bool shouldDisplay(); private slots: diff --git a/gui/widgets/IconLabel.cpp b/gui/widgets/IconLabel.cpp index 1bfe8dc9..773f0b99 100644 --- a/gui/widgets/IconLabel.cpp +++ b/gui/widgets/IconLabel.cpp @@ -7,7 +7,7 @@ #include <QRect> IconLabel::IconLabel(QWidget *parent, QIcon icon, QSize size) - : QWidget(parent), m_icon(icon), m_size(size) + : QWidget(parent), m_size(size), m_icon(icon) { setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); } diff --git a/gui/widgets/PageContainer.cpp b/gui/widgets/PageContainer.cpp index fd2a1e28..237e7224 100644 --- a/gui/widgets/PageContainer.cpp +++ b/gui/widgets/PageContainer.cpp @@ -138,6 +138,12 @@ void PageContainer::addButtons(QWidget *buttons) m_layout->addWidget(buttons, 2, 0, 1, 2); } +void PageContainer::addButtons(QLayout *buttons) +{ + m_layout->addLayout(buttons, 2, 0, 1, 2); +} + + void PageContainer::showPage(int row) { if(row != -1) diff --git a/gui/widgets/PageContainer.h b/gui/widgets/PageContainer.h index d56c6bff..c0f17e90 100644 --- a/gui/widgets/PageContainer.h +++ b/gui/widgets/PageContainer.h @@ -18,6 +18,7 @@ #include <QModelIndex> #include <gui/pages/BasePageProvider.h> +class QLayout; class IconLabel; class QSortFilterProxyModel; class PageModel; @@ -36,6 +37,7 @@ public: virtual ~PageContainer() {}; void addButtons(QWidget * buttons); + void addButtons(QLayout * buttons); bool requestClose(QCloseEvent *event); private: diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp index 0088530f..15bf5ab6 100644 --- a/logic/BaseInstance.cpp +++ b/logic/BaseInstance.cpp @@ -106,6 +106,18 @@ QString BaseInstance::id() const return QFileInfo(instanceRoot()).fileName(); } +bool BaseInstance::isRunning() const +{ + I_D(BaseInstance); + return d->m_isRunning; +} + +void BaseInstance::setRunning(bool running) const +{ + I_D(BaseInstance); + d->m_isRunning = running; +} + QString BaseInstance::instanceType() const { I_D(BaseInstance); diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index 9598549b..5f5378e7 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -65,6 +65,9 @@ public: /// be unique. virtual QString id() const; + virtual void setRunning(bool running) const; + virtual bool isRunning() const; + /// get the type of this instance QString instanceType() const; diff --git a/logic/BaseInstance_p.h b/logic/BaseInstance_p.h index 999ff545..6498454f 100644 --- a/logic/BaseInstance_p.h +++ b/logic/BaseInstance_p.h @@ -32,4 +32,5 @@ public: QString m_group; std::shared_ptr<SettingsObject> m_settings; QSet<BaseInstance::InstanceFlag> m_flags; + bool m_isRunning = false; }; diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index dd6c0719..5c15616a 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -52,14 +52,14 @@ QList<BasePage *> LegacyInstance::getPages() QList<BasePage *> values; values.append(new LegacyUpgradePage(this)); values.append(new LegacyJarModPage(this)); - values.append(new ModFolderPage(loaderModList(), "mods", "plugin-blue", tr("Loader mods"), + values.append(new ModFolderPage(this, loaderModList(), "mods", "plugin-blue", tr("Loader mods"), "Loader-mods")); - values.append(new ModFolderPage(coreModList(), "coremods", "plugin-green", tr("Core mods"), + values.append(new ModFolderPage(this, coreModList(), "coremods", "plugin-green", tr("Core mods"), "Core-mods")); values.append(new TexturePackPage(this)); values.append(new NotesPage(this)); values.append(new ScreenshotsPage(this)); - values.append(new InstanceSettingsPage(&settings())); + values.append(new InstanceSettingsPage(this)); return values; } diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp index b268a4cc..a3ffedba 100644 --- a/logic/MinecraftProcess.cpp +++ b/logic/MinecraftProcess.cpp @@ -71,6 +71,9 @@ MinecraftProcess::MinecraftProcess(InstancePtr inst) : m_instance(inst) connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardOutput, this, &MinecraftProcess::on_prepost_stdOut); } + + // a process has been constructed for the instance. It is running from MultiMC POV + m_instance->setRunning(true); } void MinecraftProcess::setWorkdir(QString path) @@ -254,6 +257,8 @@ void MinecraftProcess::finish(int code, ExitStatus status) // run post-exit postLaunch(); m_instance->cleanupAfterRun(); + // no longer running... + m_instance->setRunning(false); emit ended(m_instance, code, status); } @@ -304,6 +309,8 @@ bool MinecraftProcess::preLaunch() m_instance->cleanupAfterRun(); emit prelaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), m_prepostlaunchprocess.exitStatus()); + // not running, failed + m_instance->setRunning(false); return false; } else @@ -343,6 +350,8 @@ bool MinecraftProcess::postLaunch() MessageLevel::Error); emit postlaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), m_prepostlaunchprocess.exitStatus()); + // not running, failed + m_instance->setRunning(false); } else emit log(tr("Post-Launch command ran successfully.\n\n")); @@ -460,6 +469,8 @@ void MinecraftProcess::arm() emit log(tr("Could not launch minecraft!"), MessageLevel::Error); m_instance->cleanupAfterRun(); emit launch_failed(m_instance); + // not running, failed + m_instance->setRunning(false); return; } // send the launch script to the launcher part diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index 2069faad..7bac3424 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -63,15 +63,15 @@ QList<BasePage *> OneSixInstance::getPages() { QList<BasePage *> values; values.append(new VersionPage(this)); - values.append(new ModFolderPage(loaderModList(), "mods", "plugin-blue", tr("Loader mods"), + values.append(new ModFolderPage(this, loaderModList(), "mods", "plugin-blue", tr("Loader mods"), "Loader-mods")); - values.append(new ModFolderPage(coreModList(), "coremods", "plugin-green", tr("Core mods"), + values.append(new ModFolderPage(this, coreModList(), "coremods", "plugin-green", tr("Core mods"), "Core-mods")); values.append(new ResourcePackPage(this)); values.append(new TexturePackPage(this)); values.append(new NotesPage(this)); values.append(new ScreenshotsPage(this)); - values.append(new InstanceSettingsPage(&settings())); + values.append(new InstanceSettingsPage(this)); return values; } |