From 79c5ae121b4f4de24fe3edc57bf3f0e3a8fccfe4 Mon Sep 17 00:00:00 2001 From: Orochimarufan Date: Wed, 13 Feb 2013 00:35:35 +0100 Subject: [PATCH 1/8] add first iteration of the integrated browser --- CMakeLists.txt | 7 +++- gui/browserdialog.cpp | 76 +++++++++++++++++++++++++++++++++++ gui/browserdialog.h | 41 +++++++++++++++++++ gui/browserdialog.ui | 92 +++++++++++++++++++++++++++++++++++++++++++ gui/mainwindow.cpp | 18 +++++++-- gui/mainwindow.h | 3 ++ 6 files changed, 233 insertions(+), 4 deletions(-) create mode 100644 gui/browserdialog.cpp create mode 100644 gui/browserdialog.h create mode 100644 gui/browserdialog.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index f67b8346..efa93f8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,6 +106,7 @@ gui/settingsdialog.cpp gui/newinstancedialog.cpp gui/logindialog.cpp gui/taskdialog.cpp +gui/browserdialog.cpp util/pathutils.cpp util/osutils.cpp @@ -124,6 +125,7 @@ gui/settingsdialog.h gui/newinstancedialog.h gui/logindialog.h gui/taskdialog.h +gui/browserdialog.h data/appsettings.h data/inifile.h @@ -159,6 +161,7 @@ gui/settingsdialog.ui gui/newinstancedialog.ui gui/logindialog.ui gui/taskdialog.ui +gui/browserdialog.ui ) IF(WIN32) @@ -172,11 +175,13 @@ QT5_WRAP_UI(MULTIMC_UI ${MULTIMC5_UIS}) QT5_ADD_RESOURCES(MULTIMC_QRC multimc.qrc) add_executable(MultiMC ${MULTIMC_SOURCES} ${MULTIMC_HEADERS} ${MULTIMC_UI} ${MULTIMC_QRC}) -qt5_use_modules(MultiMC Widgets Network) +qt5_use_modules(MultiMC Widgets Network WebKitWidgets) target_link_libraries(MultiMC quazip patchlib ${MultiMC_LINK_ADDITIONAL_LIBS}) add_dependencies(MultiMC MultiMCLauncher) install(TARGETS MultiMC RUNTIME DESTINATION .) +SET(Qt5_DIR $ENV{QTDIR}) + IF(WIN32) IF(CMAKE_BUILD_TYPE STREQUAL "Debug") SET(D "d") diff --git a/gui/browserdialog.cpp b/gui/browserdialog.cpp new file mode 100644 index 00000000..58f185ce --- /dev/null +++ b/gui/browserdialog.cpp @@ -0,0 +1,76 @@ +#include "browserdialog.h" +#include "ui_browserdialog.h" + +#include + +BrowserDialog::BrowserDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::BrowserDialog), + m_pageTitleInWindowTitle(true), + m_windowTitleFormat("%1") +{ + ui->setupUi(this); + ui->webView->setPage(new QWebPage()); + refreshWindowTitle(); + resize(800, 600); +} + +BrowserDialog::~BrowserDialog() +{ + delete ui; +} + +// Navigation Buttons +void BrowserDialog::on_btnBack_clicked() +{ + ui->webView->back(); +} + +void BrowserDialog::on_btnForward_clicked() +{ + ui->webView->forward(); +} + +void BrowserDialog::on_webView_urlChanged(const QUrl &url) +{ + Q_UNUSED(url); + qDebug("urlChanged"); + ui->btnBack->setEnabled(ui->webView->history()->canGoBack()); + ui->btnForward->setEnabled(ui->webView->history()->canGoForward()); +} + +// Window Title Magic +void BrowserDialog::refreshWindowTitle() +{ + qDebug("refreshTitle"); + if (m_pageTitleInWindowTitle) + setWindowTitle(m_windowTitleFormat.arg(ui->webView->title())); + else + setWindowTitle(m_windowTitleFormat); +} + +void BrowserDialog::setPageTitleInWindowTitle(bool enable) +{ + m_pageTitleInWindowTitle = enable; + refreshWindowTitle(); +} + +void BrowserDialog::setWindowTitleFormat(QString format) +{ + m_windowTitleFormat = format; + refreshWindowTitle(); +} + +void BrowserDialog::on_webView_titleChanged(const QString &title) +{ + qDebug("titleChanged"); + if (m_pageTitleInWindowTitle) + setWindowTitle(m_windowTitleFormat.arg(title)); +} + +// Public access Methods +void BrowserDialog::load(const QUrl &url) +{ + qDebug("load"); + ui->webView->setUrl(url); +} diff --git a/gui/browserdialog.h b/gui/browserdialog.h new file mode 100644 index 00000000..9d3587ef --- /dev/null +++ b/gui/browserdialog.h @@ -0,0 +1,41 @@ +#ifndef BROWSERDIALOG_H +#define BROWSERDIALOG_H + +#include + +namespace Ui { +class BrowserDialog; +} + +class BrowserDialog : public QDialog +{ + Q_OBJECT + +public: + explicit BrowserDialog(QWidget *parent = 0); + ~BrowserDialog(); + + void load(const QUrl &url); + + void setPageTitleInWindowTitle(bool enable); + bool pageTitleInWindowTitle(void) { return m_pageTitleInWindowTitle; } + + void setWindowTitleFormat(QString format); + QString windowTitleFormat(void) { return m_windowTitleFormat; } + +private: + Ui::BrowserDialog *ui; + + bool m_pageTitleInWindowTitle; + QString m_windowTitleFormat; + + void refreshWindowTitle(void); + +private slots: + void on_btnBack_clicked(void); + void on_btnForward_clicked(void); + void on_webView_urlChanged(const QUrl &url); + void on_webView_titleChanged(const QString &title); +}; + +#endif // BROWSERDIALOG_H diff --git a/gui/browserdialog.ui b/gui/browserdialog.ui new file mode 100644 index 00000000..f32b9822 --- /dev/null +++ b/gui/browserdialog.ui @@ -0,0 +1,92 @@ + + + BrowserDialog + + + + 0 + 0 + 535 + 400 + + + + Dialog + + + + + + + + + 100 + 16777215 + + + + Back + + + + + + + + + + + 100 + 16777215 + + + + Forward + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + + about:blank + + + + + + + + + QWebView + QWidget +
QtWebKitWidgets/QWebView
+
+
+ + +
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index bc0840a0..271a67fb 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -28,6 +28,7 @@ #include "gui/newinstancedialog.h" #include "gui/logindialog.h" #include "gui/taskdialog.h" +#include "gui/browserdialog.h" #include "data/appsettings.h" #include "data/version.h" @@ -36,7 +37,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), - ui(new Ui::MainWindow) + ui(new Ui::MainWindow) { ui->setupUi(this); @@ -88,12 +89,14 @@ void MainWindow::on_actionSettings_triggered() void MainWindow::on_actionReportBug_triggered() { - QDesktopServices::openUrl(QUrl("http://bugs.forkk.net/")); + //QDesktopServices::openUrl(QUrl("http://bugs.forkk.net/")); + openWebPage(QUrl("http://bugs.forkk.net/")); } void MainWindow::on_actionNews_triggered() { - QDesktopServices::openUrl(QUrl("http://news.forkk.net/")); + //QDesktopServices::openUrl(QUrl("http://news.forkk.net/")); + openWebPage(QUrl("http://news.forkk.net/")); } void MainWindow::on_actionAbout_triggered() @@ -155,3 +158,12 @@ void MainWindow::onLoginComplete(LoginResponse response) QString("Logged in as %1 with session ID %2."). arg(response.getUsername(), response.getSessionID())); } + +// BrowserDialog +void MainWindow::openWebPage(QUrl url) +{ + BrowserDialog *browser = new BrowserDialog(this); + + browser->load(url); + browser->exec(); +} diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 28ca341a..cf6a9dbc 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -35,6 +35,9 @@ public: ~MainWindow(); void closeEvent(QCloseEvent *event); + + // Browser Dialog + void openWebPage(QUrl url); private slots: void on_actionAbout_triggered(); From 369b1c55c99aa8bdcd2d57ab4aad3633343f1417 Mon Sep 17 00:00:00 2001 From: Orochimarufan Date: Wed, 13 Feb 2013 04:03:15 +0100 Subject: [PATCH 2/8] implement desktop shortcut creation. windows code not tested. --- CMakeLists.txt | 2 + gui/browserdialog.cpp | 8 +-- gui/mainwindow.cpp | 14 ++++++ gui/mainwindow.h | 2 + util/userutil.cpp | 113 ++++++++++++++++++++++++++++++++++++++++++ util/userutil.h | 17 +++++++ 6 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 util/userutil.cpp create mode 100644 util/userutil.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bbac3bdb..95a16f60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,6 +110,7 @@ gui/browserdialog.cpp util/pathutils.cpp util/osutils.cpp +util/userutil.cpp java/javautils.cpp java/annotations.cpp @@ -139,6 +140,7 @@ data/loginresponse.h util/apputils.h util/pathutils.h util/osutils.h +util/userutil.h multimc_pragma.h diff --git a/gui/browserdialog.cpp b/gui/browserdialog.cpp index 58f185ce..40c50c3f 100644 --- a/gui/browserdialog.cpp +++ b/gui/browserdialog.cpp @@ -34,7 +34,7 @@ void BrowserDialog::on_btnForward_clicked() void BrowserDialog::on_webView_urlChanged(const QUrl &url) { Q_UNUSED(url); - qDebug("urlChanged"); + //qDebug("urlChanged"); ui->btnBack->setEnabled(ui->webView->history()->canGoBack()); ui->btnForward->setEnabled(ui->webView->history()->canGoForward()); } @@ -42,7 +42,7 @@ void BrowserDialog::on_webView_urlChanged(const QUrl &url) // Window Title Magic void BrowserDialog::refreshWindowTitle() { - qDebug("refreshTitle"); + //qDebug("refreshTitle"); if (m_pageTitleInWindowTitle) setWindowTitle(m_windowTitleFormat.arg(ui->webView->title())); else @@ -63,7 +63,7 @@ void BrowserDialog::setWindowTitleFormat(QString format) void BrowserDialog::on_webView_titleChanged(const QString &title) { - qDebug("titleChanged"); + //qDebug("titleChanged"); if (m_pageTitleInWindowTitle) setWindowTitle(m_windowTitleFormat.arg(title)); } @@ -71,6 +71,6 @@ void BrowserDialog::on_webView_titleChanged(const QString &title) // Public access Methods void BrowserDialog::load(const QUrl &url) { - qDebug("load"); + //qDebug("load"); ui->webView->setUrl(url); } diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 271a67fb..9bbc4c38 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -18,11 +18,14 @@ #include #include +#include #include #include #include "util/osutils.h" +#include "util/userutil.h" +#include "util/pathutils.h" #include "gui/settingsdialog.h" #include "gui/newinstancedialog.h" @@ -159,6 +162,17 @@ void MainWindow::onLoginComplete(LoginResponse response) arg(response.getUsername(), response.getSessionID())); } +// Create A Desktop Shortcut +void MainWindow::on_actionMakeDesktopShortcut_triggered() +{ + QString name("Test"); + name = QInputDialog::getText(this, tr("MultiMC Shortcut"), tr("Enter a Shortcut Name."), QLineEdit::Normal, name); + + Util::createShortCut(Util::getDesktopDir(), "test", QStringList() << "-d" << "lol", name, "application-x-octet-stream"); + + QMessageBox::warning(this, "Stupidness", "A Dummy Shortcut was created. the current instance model doesnt allow for anything more"); +} + // BrowserDialog void MainWindow::openWebPage(QUrl url) { diff --git a/gui/mainwindow.h b/gui/mainwindow.h index cf6a9dbc..f2dfbc70 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -64,6 +64,8 @@ private slots: void on_actionLaunchInstance_triggered(); + + void on_actionMakeDesktopShortcut_triggered(); void doLogin(const QString& errorMsg = ""); diff --git a/util/userutil.cpp b/util/userutil.cpp new file mode 100644 index 00000000..9a7b2e12 --- /dev/null +++ b/util/userutil.cpp @@ -0,0 +1,113 @@ +#include "userutil.h" + +#include +#include +#include + +#include "osutils.h" +#include "pathutils.h" + +// Win32 crap +#if WINDOWS + +#include +#include +#include +#include +#include +#include +#include + +bool called_coinit = false; + +HRESULT CreateLink(LPCSTR linkPath, LPCWSTR targetPath, LPCWSTR args) +{ + HRESULT hres; + + if (!called_coinit) + { + hres = CoInitialize(NULL); + called_coinit = true; + + if (!SUCCEEDED(hres)) + { + qWarning("Failed to initialize COM. Error 0x%08X", hres); + return hres; + } + } + + + IShellLink* link; + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&link); + + if (SUCCEEDED(hres)) + { + IPersistFile* persistFile; + + link->SetPath(targetPath); + link->SetArguments(args); + + hres = link->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); + if (SUCCEEDED(hres)) + { + WCHAR wstr[MAX_PATH]; + + MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); + + hres = persistFile->Save(wstr, TRUE); + persistFile->Release(); + } + link->Release(); + } + return hres; +} + +#endif + +QString Util::getDesktopDir() +{ + return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); +} + +// Cross-platform Shortcut creation +bool Util::createShortCut(QString location, QString dest, QStringList args, QString name, QString icon) +{ +#if LINUX + location = PathCombine(location, name + ".desktop"); + qDebug("location: %s", qPrintable(location)); + + QFile f(location); + f.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream stream(&f); + + QString argstring; + if (!args.empty()) + argstring = " '" + args.join("' '") + "'"; + + stream << "[Desktop Entry]" << "\n"; + stream << "Type=Application" << "\n"; + stream << "TryExec=" << dest.toLocal8Bit() << "\n"; + stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n"; + stream << "Name=" << name.toLocal8Bit() << "\n"; + stream << "Icon=" << icon.toLocal8Bit() << "\n"; + + stream.flush(); + f.close(); + + f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther); + + return true; +#elif WINDOWS + QFile file(path, name + ".lnk"); + WCHAR *file_w; + WCHAR *dest_w; + WCHAR *args_w; + file.fileName().toWCharArray(file_w); + dest.toWCharArray(dest_w); + args.toWCharArray(args_w); + return SUCCEEDED(CreateLink(file_w, dest_w, args_w)); +#else + qWarning("Desktop Shortcuts not supported on your platform!"); + return false; +#endif +} diff --git a/util/userutil.h b/util/userutil.h new file mode 100644 index 00000000..11c13ed0 --- /dev/null +++ b/util/userutil.h @@ -0,0 +1,17 @@ +#ifndef USERUTIL_H +#define USERUTIL_H + +#include + +namespace Util +{ + // Get the Directory representing the User's Desktop + QString getDesktopDir(); + + // Create a shortcut at *location*, pointing to *dest* called with the arguments *args* + // call it *name* and assign it the icon *icon* + // return true if operation succeeded + bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation); +} + +#endif // USERUTIL_H From c523a2c752bb7b071715f6c4eac18f36bcd2c162 Mon Sep 17 00:00:00 2001 From: Orochimarufan Date: Wed, 20 Feb 2013 00:07:52 +0100 Subject: [PATCH 3/8] implement commandline parsing --- CMakeLists.txt | 2 + gui/mainwindow.cpp | 11 +- main.cpp | 88 ++++++++- util/cmdutils.cpp | 433 +++++++++++++++++++++++++++++++++++++++++++++ util/cmdutils.h | 237 +++++++++++++++++++++++++ 5 files changed, 764 insertions(+), 7 deletions(-) create mode 100644 util/cmdutils.cpp create mode 100644 util/cmdutils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c8f7090..a1bd58ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,6 +117,7 @@ gui/browserdialog.cpp util/pathutils.cpp util/osutils.cpp util/userutil.cpp +util/cmdutils.cpp java/javautils.cpp java/annotations.cpp @@ -147,6 +148,7 @@ util/apputils.h util/pathutils.h util/osutils.h util/userutil.h +util/cmdutils.h multimc_pragma.h diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 9bbc4c38..0ca9fde1 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -1,4 +1,8 @@ /* Copyright 2013 MultiMC Contributors + * + * Authors: Andrew Okin + * Peterix + * Orochimarufan * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +23,10 @@ #include #include #include - +#include #include #include +#include #include "util/osutils.h" #include "util/userutil.h" @@ -168,9 +173,9 @@ void MainWindow::on_actionMakeDesktopShortcut_triggered() QString name("Test"); name = QInputDialog::getText(this, tr("MultiMC Shortcut"), tr("Enter a Shortcut Name."), QLineEdit::Normal, name); - Util::createShortCut(Util::getDesktopDir(), "test", QStringList() << "-d" << "lol", name, "application-x-octet-stream"); + Util::createShortCut(Util::getDesktopDir(), QApplication::instance()->applicationFilePath(), QStringList() << "-dl" << QDir::currentPath() << "test", name, "application-x-octet-stream"); - QMessageBox::warning(this, "Stupidness", "A Dummy Shortcut was created. the current instance model doesnt allow for anything more"); + QMessageBox::warning(this, "Not useful", "A Dummy Shortcut was created. it will not do anything productive"); } // BrowserDialog diff --git a/main.cpp b/main.cpp index 6e840317..efabc20a 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,6 @@ - /* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,26 +15,105 @@ * limitations under the License. */ -#include "gui/mainwindow.h" +#include + #include +#include + +#include "gui/mainwindow.h" #include "data/appsettings.h" - #include "data/loginresponse.h" +#include "util/cmdutils.h" + +using namespace Util::Commandline; + int main(int argc, char *argv[]) { + // initialize Qt QApplication app(argc, argv); app.setOrganizationName("Forkk"); app.setApplicationName("MultiMC 5"); - + + // Print app header + std::cout << "MultiMC 5" << std::endl; + std::cout << "(c) 2013 MultiMC contributors" << std::endl << std::endl; + + // Commandline parsing + Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); + + // --help + parser.addSwitch("help"); + parser.addShortOpt("help", 'h'); + parser.addDocumentation("help", "displays help on command line parameters"); + // --dir + parser.addOption("dir", app.applicationDirPath()); + parser.addShortOpt("dir", 'd'); + parser.addDocumentation("dir", "use the supplied directory as MultiMC root instead of the binary location (use '.' for current)"); + // --update + parser.addOption("update"); + parser.addShortOpt("update", 'u'); + parser.addDocumentation("update", "replaces the given file with the running executable", ""); + // --quietupdate + parser.addSwitch("quietupdate"); + parser.addShortOpt("quietupdate", 'U'); + parser.addDocumentation("quietupdate", "doesn't restart MultiMC after installing updates"); + // --launch + parser.addOption("launch"); + parser.addShortOpt("launch", 'l'); + parser.addDocumentation("launch", "tries to launch the given instance", ""); + + // parse the arguments + QHash args; + try { + args = parser.parse(app.arguments()); + } catch(ParsingError e) { + std::cerr << "CommandLineError: " << e.what() << std::endl; + return 1; + } + + // display help and exit + if (args["help"].toBool()) { + std::cout << qPrintable(parser.compileHelp(app.arguments()[0])); + return 0; + } + + // update + // Note: cwd is always the current executable path! + if (!args["update"].isNull()) + { + std::cout << "Performing MultiMC update: " << qPrintable(args["update"].toString()) << std::endl; + QDir::setCurrent(app.applicationDirPath()); + QFile file(app.applicationFilePath()); + file.copy(args["update"].toString()); + if(args["quietupdate"].toBool()) + return 0; + } + + // change directory + QDir::setCurrent(args["dir"].toString()); + + // launch instance. + if (!args["launch"].isNull()) + { + std::cout << "Launching instance: " << qPrintable(args["launch"].toString()) << std::endl; + // TODO: make it launch the an instance. + // needs the new instance model to be complete + std::cerr << "Launching Instances is not implemented yet!" << std::endl; + return 255; + } + + // load settings settings = new AppSettings(&app); // Register meta types. qRegisterMetaType("LoginResponse"); + // show window MainWindow mainWin; mainWin.show(); + // loop return app.exec(); } diff --git a/util/cmdutils.cpp b/util/cmdutils.cpp new file mode 100644 index 00000000..890f07f8 --- /dev/null +++ b/util/cmdutils.cpp @@ -0,0 +1,433 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan + * + * 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 "cmdutils.h" + +/** + * @file utils/cmdutils.cpp + */ + +namespace Util { +namespace Commandline { + +Parser::Parser(FlagStyle flagStyle, ArgumentStyle argStyle) +{ + m_flagStyle = flagStyle; + m_argStyle = argStyle; +} + +// styles setter/getter +void Parser::setArgumentStyle(ArgumentStyle style) +{ + m_argStyle = style; +} +ArgumentStyle Parser::argumentStyle() +{ + return m_argStyle; +} + +void Parser::setFlagStyle(FlagStyle style) +{ + m_flagStyle = style; +} +FlagStyle Parser::flagStyle() +{ + return m_flagStyle; +} + +// setup methods +void Parser::addSwitch(QString name, bool def) throw (const char *) +{ + if (m_params.contains(name)) + throw "Name not unique"; + + OptionDef *param = new OptionDef; + param->type = OptionType::Switch; + param->name = name; + param->metavar = QString("<%1>").arg(name); + param->def = def; + + m_options[name] = param; + m_params[name] = (CommonDef *)param; + m_optionList.append(param); +} + +void Parser::addOption(QString name, QVariant def) throw (const char *) +{ + if (m_params.contains(name)) + throw "Name not unique"; + + OptionDef *param = new OptionDef; + param->type = OptionType::Option; + param->name = name; + param->metavar = QString("<%1>").arg(name); + param->def = def; + + m_options[name] = param; + m_params[name] = (CommonDef *)param; + m_optionList.append(param); +} + +void Parser::addArgument(QString name, bool required, QVariant def) throw (const char *) +{ + if (m_params.contains(name)) + throw "Name not unique"; + + PositionalDef *param = new PositionalDef; + param->name = name; + param->def = def; + param->required = required; + + m_positionals.append(param); + m_params[name] = (CommonDef *)param; +} + +void Parser::addDocumentation(QString name, QString doc, QString metavar) throw (const char *) +{ + if (!m_params.contains(name)) + throw "Name does not exist"; + + CommonDef *param = m_params[name]; + param->doc = doc; + if (!metavar.isNull()) + param->metavar = metavar; +} + +void Parser::addShortOpt(QString name, QChar flag) throw (const char *) +{ + if (!m_params.contains(name)) + throw "Name does not exist"; + if (!m_options.contains(name)) + throw "Name is not an Option or Swtich"; + + OptionDef *param = m_options[name]; + m_flags[flag] = param; + param->flag = flag; +} + +// help methods +QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags) +{ + QStringList help; + help << compileUsage(progName, useFlags) << "\r\n"; + + // positionals + if (!m_positionals.isEmpty()) + { + help << "\r\n"; + help << "Positional arguments:\r\n"; + QListIterator it2(m_positionals); + while(it2.hasNext()) + { + PositionalDef *param = it2.next(); + help << " " << param->metavar; + help << " " << QString(helpIndent - param->name.length() - 1, ' '); + help << param->doc << "\r\n"; + } + } + + // Options + if (!m_optionList.isEmpty()) + { + help << "\r\n"; + QString optPrefix, flagPrefix; + getPrefix(optPrefix, flagPrefix); + + help << "Options & Switches:\r\n"; + QListIterator it(m_optionList); + while(it.hasNext()) + { + OptionDef *option = it.next(); + help << " "; + int nameLength = optPrefix.length() + option->name.length(); + if (!option->flag.isNull()) + { + nameLength += 3 + flagPrefix.length(); + help << flagPrefix << option->flag << ", "; + } + help << optPrefix << option->name; + if (option->type == OptionType::Option) + { + QString arg = QString("%1%2").arg(((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar); + nameLength += arg.length(); + help << arg; + } + help << " " << QString(helpIndent - nameLength - 1, ' '); + help << option->doc << "\r\n"; + } + } + + return help.join(""); +} + +QString Parser::compileUsage(QString progName, bool useFlags) +{ + QStringList usage; + usage << "Usage: " << progName; + + QString optPrefix, flagPrefix; + getPrefix(optPrefix, flagPrefix); + + // options + QListIterator it(m_optionList); + while(it.hasNext()) + { + OptionDef *option = it.next(); + usage << " ["; + if (!option->flag.isNull() && useFlags) + usage << flagPrefix << option->flag; + else + usage << optPrefix << option->name; + if (option->type == OptionType::Option) + usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar; + usage << "]"; + } + + // arguments + QListIterator it2(m_positionals); + while(it2.hasNext()) + { + PositionalDef *param = it2.next(); + usage << " " << (param->required ? "<" : "["); + usage << param->name; + usage << (param->required ? ">" : "]"); + } + + return usage.join(""); +} + +// parsing +QHash Parser::parse(QStringList argv) throw (ParsingError) +{ + QHash map; + + QStringListIterator it(argv); + QString programName = it.next(); + + QString optionPrefix; + QString flagPrefix; + QListIterator positionals(m_positionals); + QStringList expecting; + + getPrefix(optionPrefix, flagPrefix); + + while (it.hasNext()) + { + QString arg = it.next(); + + if (!expecting.isEmpty()) + // we were expecting an argument + { + QString name = expecting.first(); + + if (map.contains(name)) + throw ParsingError(QString("Option %2%1 was given multiple times").arg(name, optionPrefix)); + + map[name] = QVariant(arg); + + expecting.removeFirst(); + continue; + } + + if (arg.startsWith(optionPrefix)) + // we have an option + { + //qDebug("Found option %s", qPrintable(arg)); + + QString name = arg.mid(optionPrefix.length()); + QString equals; + + if ((m_argStyle == ArgumentStyle::Equals || m_argStyle == ArgumentStyle::SpaceAndEquals) && name.contains("=")) + { + int i = name.indexOf("="); + equals = name.mid(i+1); + name = name.left(i); + } + + if (m_options.contains(name)) + { + if (map.contains(name)) + throw ParsingError(QString("Option %2%1 was given multiple times").arg(name, optionPrefix)); + + OptionDef *option = m_options[name]; + if (option->type == OptionType::Switch) + map[name] = true; + else //if (option->type == OptionType::Option) + { + if (m_argStyle == ArgumentStyle::Space) + expecting.append(name); + else if (!equals.isNull()) + map[name] = equals; + else if (m_argStyle == ArgumentStyle::SpaceAndEquals) + expecting.append(name); + else + throw ParsingError(QString("Option %2%1 reqires an argument.").arg(name, optionPrefix)); + } + + continue; + } + + throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix)); + } + + if (arg.startsWith(flagPrefix)) + // we have (a) flag(s) + { + //qDebug("Found flags %s", qPrintable(arg)); + + QString flags = arg.mid(flagPrefix.length()); + QString equals; + + if ((m_argStyle == ArgumentStyle::Equals || m_argStyle == ArgumentStyle::SpaceAndEquals) && flags.contains("=")) + { + int i = flags.indexOf("="); + equals = flags.mid(i+1); + flags = flags.left(i); + } + + for (int i = 0; i < flags.length(); i++) + { + QChar flag = flags.at(i); + + if (!m_flags.contains(flag)) + throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix)); + + OptionDef *option = m_flags[flag]; + + if (map.contains(option->name)) + throw ParsingError(QString("Option %2%1 was given multiple times").arg(option->name, optionPrefix)); + + if (option->type == OptionType::Switch) + map[option->name] = true; + else //if (option->type == OptionType::Option) + { + if (m_argStyle == ArgumentStyle::Space) + expecting.append(option->name); + else if (!equals.isNull()) + if (i == flags.length()-1) + map[option->name] = equals; + else + throw ParsingError(QString("Flag %4%2 of Argument-requiring Option %1 not last flag in %4%3").arg(option->name, flag, flags, flagPrefix)); + else if (m_argStyle == ArgumentStyle::SpaceAndEquals) + expecting.append(option->name); + else + throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)").arg(option->name, flag, flagPrefix)); + } + } + + continue; + } + + // must be a positional argument + if (!positionals.hasNext()) + throw ParsingError(QString("Don't know what to do with '%1'").arg(arg)); + + PositionalDef *param = positionals.next(); + + map[param->name] = arg; + } + + // check if we're missing something + if (!expecting.isEmpty()) + throw ParsingError(QString("Was still expecting arguments for %2%1").arg(expecting.join(QString(", ")+optionPrefix), optionPrefix)); + + while (positionals.hasNext()) + { + PositionalDef *param = positionals.next(); + if (param->required) + throw ParsingError(QString("Missing required positional argument '%1'").arg(param->name)); + else + map[param->name] = param->def; + } + + // fill out gaps + QListIterator iter(m_optionList); + while (iter.hasNext()) + { + OptionDef *option = iter.next(); + if (!map.contains(option->name)) + map[option->name] = option->def; + } + + return map; +} + +//clear defs +void Parser::clear() +{ + m_flags.clear(); + m_params.clear(); + m_options.clear(); + + QMutableListIterator it(m_optionList); + while(it.hasNext()) + { + OptionDef *option = it.next(); + it.remove(); + delete option; + } + + QMutableListIterator it2(m_positionals); + while(it2.hasNext()) + { + PositionalDef *arg = it2.next(); + it2.remove(); + delete arg; + } +} + +//Destructor +Parser::~Parser() +{ + clear(); +} + +//getPrefix +void Parser::getPrefix(QString &opt, QString &flag) +{ + if (m_flagStyle == FlagStyle::Windows) + opt = flag = "/"; + else if (m_flagStyle == FlagStyle::Unix) + opt = flag = "-"; + //else if (m_flagStyle == FlagStyle::GNU) + else { + opt = "--"; + flag = "-"; + } +} + +// ParsingError +ParsingError::ParsingError(const QString &what) throw() +{ + m_what = what; +} +ParsingError::ParsingError(const ParsingError &e) throw() +{ + m_what = e.m_what; +} + +char *ParsingError::what() const throw() +{ + return m_what.toLocal8Bit().data(); +} +QString ParsingError::qwhat() const throw() +{ + return m_what; +} + +} +} diff --git a/util/cmdutils.h b/util/cmdutils.h new file mode 100644 index 00000000..10f8a9db --- /dev/null +++ b/util/cmdutils.h @@ -0,0 +1,237 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan + * + * 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. + */ + +#ifndef CMDUTILS_H +#define CMDUTILS_H + +#include + +#include +#include +#include +#include + +/** + * @file utils/cmdutils.h + * @brief commandline parsing utilities + */ + +namespace Util { +namespace Commandline { + +/** + * @brief The FlagStyle enum + * Specifies how flags are decorated + */ +enum class FlagStyle { + GNU, /**< --option and -o (GNU Style) */ + Unix, /**< -option and -o (Unix Style) */ + Windows, /**< /option and /o (Windows Style) */ +#ifdef Q_OS_WIN32 + Default = Windows +#else + Default = GNU +#endif +}; + +/** + * @brief The ArgumentStyle enum + */ +enum class ArgumentStyle { + Space, /**< --option=value */ + Equals, /**< --option value */ + SpaceAndEquals, /**< --option[= ]value */ +#ifdef Q_OS_WIN32 + Default = Equals +#else + Default = SpaceAndEquals +#endif +}; + +/** + * @brief The ParsingError class + */ +class ParsingError : public std::exception +{ +public: + ParsingError(const QString &what) throw(); + ParsingError(const ParsingError &e) throw(); + ~ParsingError() throw() {} + char *what() const throw(); + QString qwhat() const throw(); +private: + QString m_what; +}; + +/** + * @brief The Parser class + */ +class Parser +{ +public: + /** + * @brief Parser constructor + * @param flagStyle the FlagStyle to use in this Parser + * @param argStyle the ArgumentStyle to use in this Parser + */ + Parser(FlagStyle flagStyle=FlagStyle::Default, ArgumentStyle argStyle=ArgumentStyle::Default); + + /** + * @brief set the flag style + * @param style + */ + void setFlagStyle(FlagStyle style); + + /** + * @brief get the flag style + * @return + */ + FlagStyle flagStyle(); + + /** + * @brief set the argument style + * @param style + */ + void setArgumentStyle(ArgumentStyle style); + + /** + * @brief get the argument style + * @return + */ + ArgumentStyle argumentStyle(); + + /** + * @brief define a boolean switch + * @param name the parameter name + * @param def the default value + */ + void addSwitch(QString name, bool def=false) throw (const char *); + + /** + * @brief define an option that takes an additional argument + * @param name the parameter name + * @param def the default value + */ + void addOption(QString name, QVariant def=QVariant()) throw (const char *); + + /** + * @brief define a positional argument + * @param name the parameter name + * @param required wether this argument is required + * @param def the default value + */ + void addArgument(QString name, bool required=true, QVariant def=QVariant()) throw (const char *); + + /** + * @brief adds a flag to an existing parameter + * @param name the (existing) parameter name + * @param flag the flag character + * @see addSwitch addArgument addOption + * Note: any one parameter can only have one flag + */ + void addShortOpt(QString name, QChar flag) throw (const char *); + + /** + * @brief adds documentation to a Parameter + * @param name the parameter name + * @param metavar a string to be displayed as placeholder for the value + * @param doc a QString containing the documentation + */ + void addDocumentation(QString name, QString doc, QString metavar=QString()) throw (const char *); + + /** + * @brief generate a help message + * @param progName the program name to use in the help message + * @param helpIndent how much the parameter documentation should be indented + * @param flagsInUsage whether we should use flags instead of options in the usage + * @return a help message + */ + QString compileHelp(QString progName, int helpIndent=22, bool flagsInUsage=true); + + /** + * @brief generate a short usage message + * @param progName the program name to use in the usage message + * @param useFlags whether we should use flags instead of options + * @return a usage message + */ + QString compileUsage(QString progName, bool useFlags=true); + + /** + * @brief parse + * @param argv a QStringList containing the program ARGV + * @return a QHash mapping argument names to their values + */ + QHash parse(QStringList argv) throw (ParsingError); + + /** + * @brief clear all definitions + */ + void clear(); + + ~Parser(); + +private: + FlagStyle m_flagStyle; + ArgumentStyle m_argStyle; + + enum class OptionType { + Switch, + Option + }; + + // Important: the common part MUST BE COMMON ON ALL THREE structs + struct CommonDef { + QString name; + QString doc; + QString metavar; + QVariant def; + }; + + struct OptionDef { + // common + QString name; + QString doc; + QString metavar; + QVariant def; + // option + OptionType type; + QChar flag; + }; + + struct PositionalDef { + // common + QString name; + QString doc; + QString metavar; + QVariant def; + // positional + bool required; + }; + + QHash m_options; + QHash m_flags; + QHash m_params; + QList m_positionals; + QList m_optionList; + + void getPrefix(QString &opt, QString &flag); +}; + +} +} + +#endif // CMDUTILS_H From 576e979df4a54df9bf5ffeae3559f488b3045268 Mon Sep 17 00:00:00 2001 From: Orochimarufan Date: Thu, 21 Feb 2013 19:35:52 +0100 Subject: [PATCH 4/8] Implement About Dialog Prepared XDG icon theme in :/icons/multimc. will only be usefull as soon as Qt decides to support custom fallback themes. use the resources directly for now. --- CMakeLists.txt | 3 + gui/aboutdialog.cpp | 23 ++++ gui/aboutdialog.h | 22 +++ gui/aboutdialog.ui | 288 ++++++++++++++++++++++++++++++++++++++++ gui/mainwindow.cpp | 4 +- main.cpp | 17 ++- multimc.qrc | 4 + resources/XdgIcon.theme | 12 ++ util/cmdutils.cpp | 27 ++-- util/cmdutils.h | 22 +-- 10 files changed, 397 insertions(+), 25 deletions(-) create mode 100644 gui/aboutdialog.cpp create mode 100644 gui/aboutdialog.h create mode 100644 gui/aboutdialog.ui create mode 100644 resources/XdgIcon.theme diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ab4ca97..1853a8f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,6 +113,7 @@ gui/newinstancedialog.cpp gui/logindialog.cpp gui/taskdialog.cpp gui/browserdialog.cpp +gui/aboutdialog.cpp util/pathutils.cpp util/osutils.cpp @@ -134,6 +135,7 @@ gui/newinstancedialog.h gui/logindialog.h gui/taskdialog.h gui/browserdialog.h +gui/aboutdialog.h data/appsettings.h data/inifile.h @@ -174,6 +176,7 @@ gui/newinstancedialog.ui gui/logindialog.ui gui/taskdialog.ui gui/browserdialog.ui +gui/aboutdialog.ui ) ################################ Install ################################ diff --git a/gui/aboutdialog.cpp b/gui/aboutdialog.cpp new file mode 100644 index 00000000..876f3044 --- /dev/null +++ b/gui/aboutdialog.cpp @@ -0,0 +1,23 @@ +#include "aboutdialog.h" +#include "ui_aboutdialog.h" + +#include +#include + +AboutDialog::AboutDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::AboutDialog) +{ + ui->setupUi(this); + + ui->icon->setPixmap(QIcon(":/icons/multimc/scalable/apps/multimc.svg").pixmap(64)); + + connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); + + QApplication::instance()->connect(ui->aboutQt, SIGNAL(clicked()), SLOT(aboutQt())); +} + +AboutDialog::~AboutDialog() +{ + delete ui; +} diff --git a/gui/aboutdialog.h b/gui/aboutdialog.h new file mode 100644 index 00000000..d462de28 --- /dev/null +++ b/gui/aboutdialog.h @@ -0,0 +1,22 @@ +#ifndef ABOUTDIALOG_H +#define ABOUTDIALOG_H + +#include + +namespace Ui { +class AboutDialog; +} + +class AboutDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AboutDialog(QWidget *parent = 0); + ~AboutDialog(); + +private: + Ui::AboutDialog *ui; +}; + +#endif // ABOUTDIALOG_H diff --git a/gui/aboutdialog.ui b/gui/aboutdialog.ui new file mode 100644 index 00000000..6b8f906d --- /dev/null +++ b/gui/aboutdialog.ui @@ -0,0 +1,288 @@ + + + AboutDialog + + + + 0 + 0 + 450 + 400 + + + + + 450 + 400 + + + + Dialog + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 15 + + + + MultiMC + + + Qt::AlignCenter + + + + + + + 0 + + + + + 0 + 0 + 432 + 153 + + + + About + + + + + + MultiMC is a custom launcher that makes managing Minecraft easier by allowing you to have multiple installations of Minecraft at once. + + + Qt::AlignCenter + + + true + + + + + + + + 8 + true + + + + © 2013 MultiMC Contributors + + + Qt::AlignCenter + + + + + + + + 10 + + + + <html><head/><body><p><a href="http://github.com/Forkk/MultiMC5"><span style=" text-decoration: underline; color:#0000ff;">http://github.com/Forkk/MultiMC5</span></a></p></body></html> + + + Qt::AlignCenter + + + + + + + + + 0 + 0 + 432 + 153 + + + + Credits + + + + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Andrew Okin &lt;<a href="mailto:forkk@forkk.net"><span style=" text-decoration: underline; color:#0000ff;">forkk@forkk.net</span></a>&gt;</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Petr Mrázek &lt;<a href="mailto:peterix@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">peterix@gmail.com</span></a>&gt;</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Orochimarufan &lt;<a href="mailto:orochimarufan.x3@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">orochimarufan.x3@gmail.com</span></a>&gt;</p></body></html> + + + + + + + + License + + + + + + + 0 + 0 + + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2012 MultiMC Contributors</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">you may not use this file except in compliance with the License.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">You may obtain a copy of the License at</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> http://www.apache.org/licenses/LICENSE-2.0</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Unless required by applicable law or agreed to in writing, software</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">See the License for the specific language governing permissions and</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">limitations under the License.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">MultiMC uses bspatch, </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2003-2005 Colin Percival</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">All rights reserved</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Redistribution and use in source and binary forms, with or without</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">modification, are permitted providing that the following conditions</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">are met: </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">1. Redistributions of source code must retain the above copyright</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> notice, this list of conditions and the following disclaimer.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">2. Redistributions in binary form must reproduce the above copyright</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> notice, this list of conditions and the following disclaimer in the</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> documentation and/or other materials provided with the distribution.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">POSSIBILITY OF SUCH DAMAGE.</span></p></body></html> + + + + + + + + + + + + + About Qt + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + + + + + + + + diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 0ca9fde1..43c5cd82 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -37,6 +37,7 @@ #include "gui/logindialog.h" #include "gui/taskdialog.h" #include "gui/browserdialog.h" +#include "gui/aboutdialog.h" #include "data/appsettings.h" #include "data/version.h" @@ -109,7 +110,8 @@ void MainWindow::on_actionNews_triggered() void MainWindow::on_actionAbout_triggered() { - + AboutDialog dialog(this); + dialog.exec(); } void MainWindow::on_mainToolBar_visibilityChanged(bool) diff --git a/main.cpp b/main.cpp index efabc20a..fb7a5061 100644 --- a/main.cpp +++ b/main.cpp @@ -27,6 +27,8 @@ #include "util/cmdutils.h" +#include "config.h" + using namespace Util::Commandline; int main(int argc, char *argv[]) @@ -46,7 +48,11 @@ int main(int argc, char *argv[]) // --help parser.addSwitch("help"); parser.addShortOpt("help", 'h'); - parser.addDocumentation("help", "displays help on command line parameters"); + parser.addDocumentation("help", "display this help and exit."); + // --version + parser.addSwitch("version"); + parser.addShortOpt("version", 'V'); + parser.addDocumentation("version", "display program version and exit."); // --dir parser.addOption("dir", app.applicationDirPath()); parser.addShortOpt("dir", 'd'); @@ -70,6 +76,7 @@ int main(int argc, char *argv[]) args = parser.parse(app.arguments()); } catch(ParsingError e) { std::cerr << "CommandLineError: " << e.what() << std::endl; + std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." << std::endl; return 1; } @@ -79,16 +86,24 @@ int main(int argc, char *argv[]) return 0; } + // display version and exit + if (args["version"].toBool()) { + std::cout << VERSION_STR << " " << JENKINS_BUILD_TAG << " " << (ARCH==x64?"x86_64":"x86") << std::endl; + return 0; + } + // update // Note: cwd is always the current executable path! if (!args["update"].isNull()) { std::cout << "Performing MultiMC update: " << qPrintable(args["update"].toString()) << std::endl; + QString cwd = QDir::currentPath(); QDir::setCurrent(app.applicationDirPath()); QFile file(app.applicationFilePath()); file.copy(args["update"].toString()); if(args["quietupdate"].toBool()) return 0; + QDir::setCurrent(cwd); } // change directory diff --git a/multimc.qrc b/multimc.qrc index d0171fa3..7008206b 100644 --- a/multimc.qrc +++ b/multimc.qrc @@ -30,4 +30,8 @@ resources/MultiMCLauncher.jar + + resources/icons/multimc.svg + resources/XdgIcon.theme + diff --git a/resources/XdgIcon.theme b/resources/XdgIcon.theme new file mode 100644 index 00000000..ad26482e --- /dev/null +++ b/resources/XdgIcon.theme @@ -0,0 +1,12 @@ +[Icon Theme] +Name=MultiMC +Comment=MultiMC Default Icons +Inherits=default +Directories=scalable/apps + +[scalable/apps] +Size=48 +Type=scalable +MinSize=1 +MaxSize=512 +Context=Applications diff --git a/util/cmdutils.cpp b/util/cmdutils.cpp index 890f07f8..bd52cf8f 100644 --- a/util/cmdutils.cpp +++ b/util/cmdutils.cpp @@ -50,7 +50,7 @@ FlagStyle Parser::flagStyle() } // setup methods -void Parser::addSwitch(QString name, bool def) throw (const char *) +void Parser::addSwitch(QString name, bool def) { if (m_params.contains(name)) throw "Name not unique"; @@ -66,7 +66,7 @@ void Parser::addSwitch(QString name, bool def) throw (const char *) m_optionList.append(param); } -void Parser::addOption(QString name, QVariant def) throw (const char *) +void Parser::addOption(QString name, QVariant def) { if (m_params.contains(name)) throw "Name not unique"; @@ -82,7 +82,7 @@ void Parser::addOption(QString name, QVariant def) throw (const char *) m_optionList.append(param); } -void Parser::addArgument(QString name, bool required, QVariant def) throw (const char *) +void Parser::addArgument(QString name, bool required, QVariant def) { if (m_params.contains(name)) throw "Name not unique"; @@ -91,12 +91,13 @@ void Parser::addArgument(QString name, bool required, QVariant def) throw (const param->name = name; param->def = def; param->required = required; + param->metavar = name; m_positionals.append(param); m_params[name] = (CommonDef *)param; } -void Parser::addDocumentation(QString name, QString doc, QString metavar) throw (const char *) +void Parser::addDocumentation(QString name, QString doc, QString metavar) { if (!m_params.contains(name)) throw "Name does not exist"; @@ -107,7 +108,7 @@ void Parser::addDocumentation(QString name, QString doc, QString metavar) throw param->metavar = metavar; } -void Parser::addShortOpt(QString name, QChar flag) throw (const char *) +void Parser::addShortOpt(QString name, QChar flag) { if (!m_params.contains(name)) throw "Name does not exist"; @@ -135,7 +136,7 @@ QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags) { PositionalDef *param = it2.next(); help << " " << param->metavar; - help << " " << QString(helpIndent - param->name.length() - 1, ' '); + help << " " << QString(helpIndent - param->metavar.length() - 1, ' '); help << param->doc << "\r\n"; } } @@ -203,7 +204,7 @@ QString Parser::compileUsage(QString progName, bool useFlags) { PositionalDef *param = it2.next(); usage << " " << (param->required ? "<" : "["); - usage << param->name; + usage << param->metavar; usage << (param->required ? ">" : "]"); } @@ -211,7 +212,7 @@ QString Parser::compileUsage(QString progName, bool useFlags) } // parsing -QHash Parser::parse(QStringList argv) throw (ParsingError) +QHash Parser::parse(QStringList argv) { QHash map; @@ -411,20 +412,20 @@ void Parser::getPrefix(QString &opt, QString &flag) } // ParsingError -ParsingError::ParsingError(const QString &what) throw() +ParsingError::ParsingError(const QString &what) { m_what = what; } -ParsingError::ParsingError(const ParsingError &e) throw() +ParsingError::ParsingError(const ParsingError &e) { m_what = e.m_what; } -char *ParsingError::what() const throw() +const char *ParsingError::what() const throw() { - return m_what.toLocal8Bit().data(); + return m_what.toLocal8Bit().constData(); } -QString ParsingError::qwhat() const throw() +QString ParsingError::qwhat() const { return m_what; } diff --git a/util/cmdutils.h b/util/cmdutils.h index 10f8a9db..690c96fd 100644 --- a/util/cmdutils.h +++ b/util/cmdutils.h @@ -68,11 +68,11 @@ enum class ArgumentStyle { class ParsingError : public std::exception { public: - ParsingError(const QString &what) throw(); - ParsingError(const ParsingError &e) throw(); + ParsingError(const QString &what); + ParsingError(const ParsingError &e); ~ParsingError() throw() {} - char *what() const throw(); - QString qwhat() const throw(); + const char *what() const throw(); + QString qwhat() const; private: QString m_what; }; @@ -119,14 +119,14 @@ public: * @param name the parameter name * @param def the default value */ - void addSwitch(QString name, bool def=false) throw (const char *); + void addSwitch(QString name, bool def=false); /** * @brief define an option that takes an additional argument * @param name the parameter name * @param def the default value */ - void addOption(QString name, QVariant def=QVariant()) throw (const char *); + void addOption(QString name, QVariant def=QVariant()); /** * @brief define a positional argument @@ -134,7 +134,7 @@ public: * @param required wether this argument is required * @param def the default value */ - void addArgument(QString name, bool required=true, QVariant def=QVariant()) throw (const char *); + void addArgument(QString name, bool required=true, QVariant def=QVariant()); /** * @brief adds a flag to an existing parameter @@ -143,15 +143,17 @@ public: * @see addSwitch addArgument addOption * Note: any one parameter can only have one flag */ - void addShortOpt(QString name, QChar flag) throw (const char *); + void addShortOpt(QString name, QChar flag); /** * @brief adds documentation to a Parameter * @param name the parameter name * @param metavar a string to be displayed as placeholder for the value * @param doc a QString containing the documentation + * Note: on positional arguments, metavar replaces the name as displayed. + * on options , metavar replaces the value placeholder */ - void addDocumentation(QString name, QString doc, QString metavar=QString()) throw (const char *); + void addDocumentation(QString name, QString doc, QString metavar=QString()); /** * @brief generate a help message @@ -175,7 +177,7 @@ public: * @param argv a QStringList containing the program ARGV * @return a QHash mapping argument names to their values */ - QHash parse(QStringList argv) throw (ParsingError); + QHash parse(QStringList argv); /** * @brief clear all definitions From d891032219c3faaa13a9d9a4d52659866e6773da Mon Sep 17 00:00:00 2001 From: Orochimarufan Date: Thu, 21 Feb 2013 21:55:35 +0100 Subject: [PATCH 5/8] use QtCreator's $QTPATH in CMake --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 88e7e4d4..86619f0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -264,6 +264,10 @@ ADD_DEPENDENCIES(MultiMC MultiMCLauncher libmmcutil libmmcsettings libmmcinst) ################################ INSTALLATION AND PACKAGING ################################ +# use QtCreator's QTDIR var +IF(DEFINED ENV{QTDIR}) + SET(Qt5_DIR $ENV{QTDIR}) +ENDIF() ######## Plugin and library folders ######## @@ -280,7 +284,7 @@ ENDIF() IF(APPLE) SET(PLUGIN_DEST_DIR MultiMC.app/Contents/MacOS) SET(QTCONF_DEST_DIR MultiMC.app/Contents/Resources) - SET(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app") + SET(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app") ENDIF() SET(QT_PLUGINS_DIR ${Qt5_DIR}/plugins) From 9f174ad4e7853b5864d7478ce97d7afa75d76636 Mon Sep 17 00:00:00 2001 From: Orochimarufan Date: Fri, 22 Feb 2013 16:17:31 +0100 Subject: [PATCH 6/8] Implement Instance launching Use --launch to test --- CMakeLists.txt | 3 + data/minecraftprocess.cpp | 214 +++++++++++++++++++++++++++++++++ data/minecraftprocess.h | 94 +++++++++++++++ libinstance/include/instance.h | 3 + libinstance/src/instance.cpp | 8 +- libutil/include/siglist.h | 2 +- main.cpp | 119 ++++++++++++++++-- multimc.qrc | 2 +- 8 files changed, 425 insertions(+), 20 deletions(-) create mode 100644 data/minecraftprocess.cpp create mode 100644 data/minecraftprocess.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 272250ad..dceb4615 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,7 @@ IF(DEFINED MultiMC_BUILD_TAG) MESSAGE(STATUS "Build tag: ${MultiMC_BUILD_TAG}") ELSE() MESSAGE(STATUS "No build tag specified.") + SET(MultiMC_BUILD_TAG custom) ENDIF() # Architecture detection @@ -168,6 +169,7 @@ gui/aboutdialog.h data/version.h data/userinfo.h data/loginresponse.h +data/minecraftprocess.h data/plugin/pluginmanager.h @@ -195,6 +197,7 @@ data/userinfo.cpp data/loginresponse.cpp data/plugin/pluginmanager.cpp +data/minecraftprocess.cpp gui/mainwindow.cpp gui/modeditwindow.cpp diff --git a/data/minecraftprocess.cpp b/data/minecraftprocess.cpp new file mode 100644 index 00000000..4cbe7208 --- /dev/null +++ b/data/minecraftprocess.cpp @@ -0,0 +1,214 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan + * + * 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 "minecraftprocess.h" + +#include +#include +#include +#include +#include + +#include "instance.h" + +#include "osutils.h" +#include "pathutils.h" + +#define LAUNCHER_FILE "MultiMCLauncher.jar" +#define IBUS "@im=ibus" + +// commandline splitter +QStringList MinecraftProcess::splitArgs(QString args) +{ + QStringList argv; + QString current; + bool escape = false; + QChar inquotes; + for (int i=0; iiconKey()).save(destination); +} + +inline void MinecraftProcess::extractLauncher(QString destination) +{ + QFile(":/launcher/launcher.jar").copy(destination); +} + +void MinecraftProcess::prepare(InstancePtr inst) +{ + extractLauncher(PathCombine(inst->minecraftDir(), LAUNCHER_FILE)); + extractIcon(inst, PathCombine(inst->minecraftDir(), "icon.png")); +} + +// constructor +MinecraftProcess::MinecraftProcess(InstancePtr inst, QString user, QString session, ConsoleWindow *console) : + m_instance(inst), m_user(user), m_session(session), m_console(console) +{ + connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(finish(int, QProcess::ExitStatus))); + + // prepare the process environment + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + +#ifdef LINUX + // Strip IBus + if (env.value("XMODIFIERS").contains(IBUS)) + env.insert("XMODIFIERS", env.value("XMODIFIERS").replace(IBUS, "")); +#endif + + // export some infos + env.insert("INST_NAME", inst->name()); + env.insert("INST_ID", inst->id()); + env.insert("INST_DIR", QDir(inst->rootDir()).absolutePath()); + + this->setProcessEnvironment(env); + m_prepostlaunchprocess.setProcessEnvironment(env); + + // set the cwd + QDir mcDir(inst->minecraftDir()); + this->setWorkingDirectory(mcDir.absolutePath()); + m_prepostlaunchprocess.setWorkingDirectory(mcDir.absolutePath()); + + //TODO: do console redirection +} + +void MinecraftProcess::finish(int code, ExitStatus status) +{ + if (status != NormalExit) + { + //TODO: error handling + } + + m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code)); + + // run post-exit + if (!m_instance->getPostExitCommand().isEmpty()) + { + m_prepostlaunchprocess.start(m_instance->getPostExitCommand()); + m_prepostlaunchprocess.waitForFinished(); + if (m_prepostlaunchprocess.exitStatus() != NormalExit) + { + //TODO: error handling + } + } + + emit ended(); +} + +void MinecraftProcess::launch() +{ + if (!m_instance->getPreLaunchCommand().isEmpty()) + { + m_prepostlaunchprocess.start(m_instance->getPreLaunchCommand()); + m_prepostlaunchprocess.waitForFinished(); + if (m_prepostlaunchprocess.exitStatus() != NormalExit) + { + //TODO: error handling + return; + } + } + + m_instance->setLastLaunch(); + + prepare(m_instance); + + genArgs(); + + qDebug("Minecraft folder is: '%s'", qPrintable(workingDirectory())); + qDebug("Instance launched with arguments: '%s'", qPrintable(m_arguments.join("' '"))); + + start(m_instance->getJavaPath(), m_arguments); + if (!waitForStarted()) + { + //TODO: error handling + } +} + +void MinecraftProcess::genArgs() +{ + // start fresh + m_arguments.clear(); + + // window size + QString windowSize; + if (m_instance->getLaunchMaximized()) + windowSize = "max"; + else + windowSize = QString("%1x%2").arg(m_instance->getMinecraftWinWidth()).arg(m_instance->getMinecraftWinHeight()); + + // window title + QString windowTitle; + windowTitle.append("MultiMC: ").append(m_instance->name()); + + // Java arguments + m_arguments.append(splitArgs(m_instance->getJvmArgs())); + +#ifdef OSX + // OSX dock icon and name + m_arguments << "-Xdock:icon=icon.png"; + m_arguments << QString("-Xdock:name=\"%1\"").arg(windowTitle); +#endif + + // lwjgl + QString lwjgl = m_instance->lwjglVersion(); + if (lwjgl != "Mojang") + lwjgl = QDir(settings->getLWJGLDir() + "/" + lwjgl).absolutePath(); + + // launcher arguments + m_arguments << QString("-Xms%1m").arg(m_instance->getMinMemAlloc()); + m_arguments << QString("-Xmx%1m").arg(m_instance->getMaxMemAlloc()); + m_arguments << "-jar" << LAUNCHER_FILE; + m_arguments << m_user; + m_arguments << m_session; + m_arguments << windowTitle; + m_arguments << windowSize; + m_arguments << lwjgl; +} diff --git a/data/minecraftprocess.h b/data/minecraftprocess.h new file mode 100644 index 00000000..99a3bed6 --- /dev/null +++ b/data/minecraftprocess.h @@ -0,0 +1,94 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan + * + * 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. + */ +#ifndef MINECRAFTPROCESS_H +#define MINECRAFTPROCESS_H + +#include + +class ConsoleWindow; + +#include "instance.h" + +/** + * @file data/minecraftprocess.h + * @brief The MinecraftProcess class + */ +class MinecraftProcess : public QProcess +{ + Q_OBJECT +public: + /** + * @brief MinecraftProcess constructor + * @param inst the Instance pointer to launch + * @param user the minecraft username + * @param session the minecraft session id + * @param console the instance console window + */ + MinecraftProcess(InstancePtr inst, QString user, QString session, ConsoleWindow *console); + + /** + * @brief launch minecraft + */ + void launch(); + + /** + * @brief extract the instance icon + * @param inst the instance + * @param destination the destination path + */ + static inline void extractIcon(InstancePtr inst, QString destination); + + /** + * @brief extract the MultiMC launcher.jar + * @param destination the destination path + */ + static inline void extractLauncher(QString destination); + + /** + * @brief prepare the launch by extracting icon and launcher + * @param inst the instance + */ + static void prepare(InstancePtr inst); + + /** + * @brief split a string into argv items like a shell would do + * @param args the argument string + * @return a QStringList containing all arguments + */ + static QStringList splitArgs(QString args); + +signals: + /** + * @brief emitted when mc has finished and the PostLaunchCommand was run + */ + void ended(); + +protected: + ConsoleWindow *m_console; + InstancePtr m_instance; + QString m_user; + QString m_session; + QProcess m_prepostlaunchprocess; + QStringList m_arguments; + + void genArgs(); + +protected slots: + void finish(int, QProcess::ExitStatus status); +}; + +#endif // MINECRAFTPROCESS_H diff --git a/libinstance/include/instance.h b/libinstance/include/instance.h index 7b3c001d..995b88d8 100644 --- a/libinstance/include/instance.h +++ b/libinstance/include/instance.h @@ -339,4 +339,7 @@ private: QString m_rootDir; }; +// pointer for lazy people +typedef QSharedPointer InstancePtr; + #endif // INSTANCE_H diff --git a/libinstance/src/instance.cpp b/libinstance/src/instance.cpp index c79c0213..c10286b6 100644 --- a/libinstance/src/instance.cpp +++ b/libinstance/src/instance.cpp @@ -50,13 +50,9 @@ QString Instance::minecraftDir() const QFileInfo dotMCDir(PathCombine(rootDir(), ".minecraft")); if (dotMCDir.exists() && !mcDir.exists()) - { - return dotMCDir.path(); - } + return dotMCDir.filePath(); else - { - return mcDir.path(); - } + return mcDir.filePath(); } QString Instance::binDir() const diff --git a/libutil/include/siglist.h b/libutil/include/siglist.h index dcae7c04..24b1a889 100644 --- a/libutil/include/siglist.h +++ b/libutil/include/siglist.h @@ -71,7 +71,7 @@ public: virtual QList &operator =(const QList &other); - +protected: // Signal emitted after an item is added to the list. // Contains a reference to item and the item's new index. virtual void onItemAdded(const T &item, int index) = 0; diff --git a/main.cpp b/main.cpp index 4a4d8e7e..4f072b38 100644 --- a/main.cpp +++ b/main.cpp @@ -22,9 +22,14 @@ #include #include "gui/mainwindow.h" +#include "gui/logindialog.h" +#include "gui/taskdialog.h" +#include "instancelist.h" #include "appsettings.h" #include "data/loginresponse.h" +#include "tasks/logintask.h" +#include "data/minecraftprocess.h" #include "data/plugin/pluginmanager.h" @@ -35,6 +40,98 @@ using namespace Util::Commandline; +// Commandline instance launcher +class InstanceLauncher : public QObject +{ + Q_OBJECT +private: + InstanceList instances; + QString instId; + InstancePtr instance; + MinecraftProcess *proc; +public: + InstanceLauncher(QString instId) : QObject(), instances(settings->getInstanceDir()) + { + this->instId = instId; + } + +private: + InstancePtr findInstance(QString instId) + { + QListIterator iter(instances); + InstancePtr inst; + while(iter.hasNext()) + { + inst = iter.next(); + if (inst->id() == instId) + break; + } + if (inst->id() != instId) + return InstancePtr(); + else + return iter.peekPrevious(); + } + +private slots: + void onTerminated() + { + std::cout << "Minecraft exited" << std::endl; + QApplication::instance()->quit(); + } + + void onLoginComplete(LoginResponse response) + { + // TODO: console + proc = new MinecraftProcess(instance, response.getUsername(), response.getSessionID(), nullptr); + connect(proc, SIGNAL(ended()), SLOT(onTerminated())); + proc->launch(); + /*if (proc->pid() == 0) + { + std::cout << "Could not start instance." << std::endl; + QApplication::instance()->quit(); + return; + }*/ + } + + void doLogin(const QString &errorMsg) + { + LoginDialog* loginDlg = new LoginDialog(nullptr, errorMsg); + if (loginDlg->exec()) + { + UserInfo uInfo(loginDlg->getUsername(), loginDlg->getPassword()); + + TaskDialog* tDialog = new TaskDialog(nullptr); + LoginTask* loginTask = new LoginTask(uInfo, tDialog); + connect(loginTask, SIGNAL(loginComplete(LoginResponse)), + SLOT(onLoginComplete(LoginResponse)), Qt::QueuedConnection); + connect(loginTask, SIGNAL(loginFailed(QString)), + SLOT(doLogin(QString)), Qt::QueuedConnection); + tDialog->exec(loginTask); + } + //onLoginComplete(LoginResponse("Offline","Offline", 1)); + } + +public: + int launch() + { + std::cout << "Loading Instances..." << std::endl; + instances.loadList(); + + std::cout << "Launching Instance '" << qPrintable(instId) << "'" << std::endl; + instance = findInstance(instId); + if (instance.isNull()) + { + std::cout << "Could not find instance requested. note that you have to specify the ID, not the NAME" << std::endl; + return 1; + } + + std::cout << "Logging in..." << std::endl; + doLogin(""); + + return QApplication::instance()->exec(); + } +}; + int main(int argc, char *argv[]) { // initialize Qt @@ -44,7 +141,7 @@ int main(int argc, char *argv[]) // Print app header std::cout << "MultiMC 5" << std::endl; - std::cout << "(c) 2013 MultiMC contributors" << std::endl << std::endl; + std::cout << "(c) 2013 MultiMC Contributors" << std::endl << std::endl; // Commandline parsing Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); @@ -92,7 +189,9 @@ int main(int argc, char *argv[]) // display version and exit if (args["version"].toBool()) { - std::cout << VERSION_STR << " " << JENKINS_BUILD_TAG << " " << (ARCH==x64?"x86_64":"x86") << std::endl; + std::cout << "Version " << VERSION_STR << std::endl; + std::cout << "Git " << GIT_COMMIT << std::endl; + std::cout << "Tag: " << JENKINS_BUILD_TAG << " " << (ARCH==x64?"x86_64":"x86") << std::endl; return 0; } @@ -113,16 +212,6 @@ int main(int argc, char *argv[]) // change directory QDir::setCurrent(args["dir"].toString()); - // launch instance. - if (!args["launch"].isNull()) - { - std::cout << "Launching instance: " << qPrintable(args["launch"].toString()) << std::endl; - // TODO: make it launch the an instance. - // needs the new instance model to be complete - std::cerr << "Launching Instances is not implemented yet!" << std::endl; - return 255; - } - // load settings settings = new AppSettings(&app); @@ -133,6 +222,10 @@ int main(int argc, char *argv[]) PluginManager::get().loadPlugins(PathCombine(qApp->applicationDirPath(), "plugins")); PluginManager::get().initInstanceTypes(); + // launch instance. + if (!args["launch"].isNull()) + return InstanceLauncher(args["launch"].toString()).launch(); + // show main window MainWindow mainWin; mainWin.show(); @@ -140,3 +233,5 @@ int main(int argc, char *argv[]) // loop return app.exec(); } + +#include "main.moc" diff --git a/multimc.qrc b/multimc.qrc index 7008206b..cfcc9829 100644 --- a/multimc.qrc +++ b/multimc.qrc @@ -28,7 +28,7 @@ resources/icons/multimc.svg - resources/MultiMCLauncher.jar + resources/MultiMCLauncher.jar resources/icons/multimc.svg From 3a173648e789f30b2843241ee38e694d16e10358 Mon Sep 17 00:00:00 2001 From: Orochimarufan Date: Fri, 22 Feb 2013 18:18:23 +0100 Subject: [PATCH 7/8] Implement ConsoleWindow --- CMakeLists.txt | 3 ++ data/minecraftprocess.cpp | 38 ++++++++++++++++++-- data/minecraftprocess.h | 6 +++- gui/consolewindow.cpp | 73 +++++++++++++++++++++++++++++++++++++++ gui/consolewindow.h | 69 ++++++++++++++++++++++++++++++++++++ gui/consolewindow.ui | 66 +++++++++++++++++++++++++++++++++++ main.cpp | 13 ++++--- 7 files changed, 257 insertions(+), 11 deletions(-) create mode 100644 gui/consolewindow.cpp create mode 100644 gui/consolewindow.h create mode 100644 gui/consolewindow.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index 03f1f514..070dcc15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,6 +165,7 @@ gui/logindialog.h gui/taskdialog.h gui/browserdialog.h gui/aboutdialog.h +gui/consolewindow.h data/version.h data/userinfo.h @@ -208,6 +209,7 @@ gui/logindialog.cpp gui/taskdialog.cpp gui/browserdialog.cpp gui/aboutdialog.cpp +gui/consolewindow.cpp java/javautils.cpp java/annotations.cpp @@ -228,6 +230,7 @@ gui/logindialog.ui gui/taskdialog.ui gui/browserdialog.ui gui/aboutdialog.ui +gui/consolewindow.ui ) diff --git a/data/minecraftprocess.cpp b/data/minecraftprocess.cpp index 4cbe7208..d08b767c 100644 --- a/data/minecraftprocess.cpp +++ b/data/minecraftprocess.cpp @@ -117,9 +117,33 @@ MinecraftProcess::MinecraftProcess(InstancePtr inst, QString user, QString sessi this->setWorkingDirectory(mcDir.absolutePath()); m_prepostlaunchprocess.setWorkingDirectory(mcDir.absolutePath()); - //TODO: do console redirection + // std channels + connect(this, SIGNAL(readyReadStandardError()), SLOT(on_stdErr())); + connect(this, SIGNAL(readyReadStandardOutput()), SLOT(on_stdOut())); } +// console window +void MinecraftProcess::on_stdErr() +{ + if (m_console != nullptr) + m_console->write(readAllStandardError(), ConsoleWindow::ERROR); +} + +void MinecraftProcess::on_stdOut() +{ + if (m_console != nullptr) + m_console->write(readAllStandardOutput(), ConsoleWindow::DEFAULT); +} + +void MinecraftProcess::log(QString text) +{ + if (m_console != nullptr) + m_console->write(text); + else + qDebug(qPrintable(text)); +} + +// exit handler void MinecraftProcess::finish(int code, ExitStatus status) { if (status != NormalExit) @@ -127,6 +151,8 @@ void MinecraftProcess::finish(int code, ExitStatus status) //TODO: error handling } + log("Minecraft exited."); + m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code)); // run post-exit @@ -140,6 +166,9 @@ void MinecraftProcess::finish(int code, ExitStatus status) } } + if (m_console != nullptr) + m_console->setMayClose(true); + emit ended(); } @@ -162,14 +191,17 @@ void MinecraftProcess::launch() genArgs(); - qDebug("Minecraft folder is: '%s'", qPrintable(workingDirectory())); - qDebug("Instance launched with arguments: '%s'", qPrintable(m_arguments.join("' '"))); + log(QString("Minecraft folder is: '%1'").arg(workingDirectory())); + log(QString("Instance launched with arguments: '%1'").arg(m_arguments.join("' '"))); start(m_instance->getJavaPath(), m_arguments); if (!waitForStarted()) { //TODO: error handling } + + if(m_console != nullptr) + m_console->setMayClose(false); } void MinecraftProcess::genArgs() diff --git a/data/minecraftprocess.h b/data/minecraftprocess.h index 99a3bed6..bede9486 100644 --- a/data/minecraftprocess.h +++ b/data/minecraftprocess.h @@ -19,7 +19,7 @@ #include -class ConsoleWindow; +#include "gui/consolewindow.h" #include "instance.h" @@ -86,9 +86,13 @@ protected: QStringList m_arguments; void genArgs(); + void log(QString text); protected slots: void finish(int, QProcess::ExitStatus status); + void on_stdErr(); + void on_stdOut(); + }; #endif // MINECRAFTPROCESS_H diff --git a/gui/consolewindow.cpp b/gui/consolewindow.cpp new file mode 100644 index 00000000..1d84fe04 --- /dev/null +++ b/gui/consolewindow.cpp @@ -0,0 +1,73 @@ +#include "consolewindow.h" +#include "ui_consolewindow.h" + +#include + +ConsoleWindow::ConsoleWindow(QWidget *parent) : + QDialog(parent), + ui(new Ui::ConsoleWindow), + m_mayclose(true) +{ + ui->setupUi(this); +} + +ConsoleWindow::~ConsoleWindow() +{ + delete ui; +} + +void ConsoleWindow::writeColor(QString text, const char *color) +{ + // append a paragraph + if (color != nullptr) + ui->text->appendHtml(QString("%2").arg(color).arg(text)); + else + ui->text->appendPlainText(text); + // scroll down + QScrollBar *bar = ui->text->verticalScrollBar(); + bar->setValue(bar->maximum()); +} + +void ConsoleWindow::write(QString data, WriteMode mode) +{ + if (data.endsWith('\n')) + data = data.left(data.length()-1); + QStringList paragraphs = data.split('\n'); + QListIterator iter(paragraphs); + if (mode == MULTIMC) + while(iter.hasNext()) + writeColor(iter.next(), "blue"); + else if (mode == ERROR) + while(iter.hasNext()) + writeColor(iter.next(), "red"); + else + while(iter.hasNext()) + writeColor(iter.next()); +} + +void ConsoleWindow::clear() +{ + ui->text->clear(); +} + +void ConsoleWindow::on_closeButton_clicked() +{ + close(); +} + +void ConsoleWindow::setMayClose(bool mayclose) +{ + m_mayclose = mayclose; + if (mayclose) + ui->closeButton->setEnabled(true); + else + ui->closeButton->setEnabled(false); +} + +void ConsoleWindow::closeEvent(QCloseEvent * event) +{ + if(!m_mayclose) + event->ignore(); + else + QDialog::closeEvent(event); +} diff --git a/gui/consolewindow.h b/gui/consolewindow.h new file mode 100644 index 00000000..1d322afb --- /dev/null +++ b/gui/consolewindow.h @@ -0,0 +1,69 @@ +#ifndef CONSOLEWINDOW_H +#define CONSOLEWINDOW_H + +#include + +namespace Ui { +class ConsoleWindow; +} + +class ConsoleWindow : public QDialog +{ + Q_OBJECT + +public: + /** + * @brief The WriteMode enum + * defines how stuff is displayed + */ + enum WriteMode { + DEFAULT, + ERROR, + MULTIMC + }; + + explicit ConsoleWindow(QWidget *parent = 0); + ~ConsoleWindow(); + + /** + * @brief specify if the window is allowed to close + * @param mayclose + * used to keep it alive while MC runs + */ + void setMayClose(bool mayclose); + +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, WriteMode mode=MULTIMC); + + /** + * @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 data, const char *color=nullptr); + + /** + * @brief clear the text widget + */ + void clear(); + +private slots: + void on_closeButton_clicked(); + +protected: + void closeEvent(QCloseEvent *); + +private: + Ui::ConsoleWindow *ui; + bool m_mayclose; +}; + +#endif // CONSOLEWINDOW_H diff --git a/gui/consolewindow.ui b/gui/consolewindow.ui new file mode 100644 index 00000000..f8c87aa0 --- /dev/null +++ b/gui/consolewindow.ui @@ -0,0 +1,66 @@ + + + ConsoleWindow + + + + 0 + 0 + 600 + 400 + + + + MultiMC Console + + + + + + + 10 + + + + false + + + true + + + + + + false + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + + + + + + + + diff --git a/main.cpp b/main.cpp index 4f072b38..5cac6ab0 100644 --- a/main.cpp +++ b/main.cpp @@ -24,6 +24,7 @@ #include "gui/mainwindow.h" #include "gui/logindialog.h" #include "gui/taskdialog.h" +#include "gui/consolewindow.h" #include "instancelist.h" #include "appsettings.h" @@ -49,6 +50,7 @@ private: QString instId; InstancePtr instance; MinecraftProcess *proc; + ConsoleWindow *console; public: InstanceLauncher(QString instId) : QObject(), instances(settings->getInstanceDir()) { @@ -82,15 +84,12 @@ private slots: void onLoginComplete(LoginResponse response) { // TODO: console - proc = new MinecraftProcess(instance, response.getUsername(), response.getSessionID(), nullptr); + console = new ConsoleWindow(); + proc = new MinecraftProcess(instance, response.getUsername(), response.getSessionID(), console); + //if (instance->getShowConsole()) + console->show(); connect(proc, SIGNAL(ended()), SLOT(onTerminated())); proc->launch(); - /*if (proc->pid() == 0) - { - std::cout << "Could not start instance." << std::endl; - QApplication::instance()->quit(); - return; - }*/ } void doLogin(const QString &errorMsg) From 4fd5bdb01b27c99f16c9acf3c7ac13c7ea719800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 23 Feb 2013 00:56:17 +0100 Subject: [PATCH 8/8] *Full* folder name is the instance ID, not just the first part. --- libinstance/src/instance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libinstance/src/instance.cpp b/libinstance/src/instance.cpp index c10286b6..eb9fdff5 100644 --- a/libinstance/src/instance.cpp +++ b/libinstance/src/instance.cpp @@ -28,7 +28,7 @@ Instance::Instance(const QString &rootDir, QObject *parent) : QString Instance::id() const { - return QFileInfo(rootDir()).baseName(); + return QFileInfo(rootDir()).fileName(); } QString Instance::rootDir() const