diff options
-rw-r--r-- | CMakeLists.txt | 14 | ||||
-rw-r--r-- | gui/browserdialog.cpp | 76 | ||||
-rw-r--r-- | gui/browserdialog.h | 41 | ||||
-rw-r--r-- | gui/browserdialog.ui | 92 | ||||
-rw-r--r-- | gui/mainwindow.cpp | 39 | ||||
-rw-r--r-- | gui/mainwindow.h | 5 | ||||
-rw-r--r-- | main.cpp | 88 | ||||
-rw-r--r-- | util/cmdutils.cpp | 433 | ||||
-rw-r--r-- | util/cmdutils.h | 237 | ||||
-rw-r--r-- | util/userutil.cpp | 113 | ||||
-rw-r--r-- | util/userutil.h | 17 |
11 files changed, 1146 insertions, 9 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index ed930d3c..2ab4ca97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,9 +112,12 @@ gui/settingsdialog.cpp gui/newinstancedialog.cpp gui/logindialog.cpp gui/taskdialog.cpp +gui/browserdialog.cpp util/pathutils.cpp util/osutils.cpp +util/userutil.cpp +util/cmdutils.cpp java/javautils.cpp java/annotations.cpp @@ -130,6 +133,7 @@ gui/settingsdialog.h gui/newinstancedialog.h gui/logindialog.h gui/taskdialog.h +gui/browserdialog.h data/appsettings.h data/inifile.h @@ -145,6 +149,8 @@ data/siglist_imp.h util/apputils.h util/pathutils.h util/osutils.h +util/userutil.h +util/cmdutils.h multimc_pragma.h @@ -167,6 +173,7 @@ gui/settingsdialog.ui gui/newinstancedialog.ui gui/logindialog.ui gui/taskdialog.ui +gui/browserdialog.ui ) ################################ Install ################################ @@ -194,10 +201,15 @@ QT5_WRAP_UI(MULTIMC_UI ${MULTIMC5_UIS}) QT5_ADD_RESOURCES(MULTIMC_QRC multimc.qrc) add_executable(MultiMC MACOSX_BUNDLE WIN32 ${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) +IF (WIN32) +install(TARGETS MultiMC RUNTIME DESTINATION .) +ELSE() +install(TARGETS MultiMC RUNTIME DESTINATION bin) +ENDIF() ################ Dirs ################ diff --git a/gui/browserdialog.cpp b/gui/browserdialog.cpp new file mode 100644 index 00000000..40c50c3f --- /dev/null +++ b/gui/browserdialog.cpp @@ -0,0 +1,76 @@ +#include "browserdialog.h" +#include "ui_browserdialog.h" + +#include <QtWebKit/QWebHistory> + +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 <QDialog> + +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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BrowserDialog</class> + <widget class="QDialog" name="BrowserDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>535</width> + <height>400</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="toolbarLayout"> + <item> + <widget class="QCommandLinkButton" name="btnBack"> + <property name="maximumSize"> + <size> + <width>100</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>Back</string> + </property> + <property name="icon"> + <iconset theme="go-previous"/> + </property> + </widget> + </item> + <item> + <widget class="QCommandLinkButton" name="btnForward"> + <property name="maximumSize"> + <size> + <width>100</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>Forward</string> + </property> + <property name="icon"> + <iconset theme="go-next"/> + </property> + </widget> + </item> + <item> + <spacer name="toolbarSpacer_1"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QWebView" name="webView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="url"> + <url> + <string>about:blank</string> + </url> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>QWebView</class> + <extends>QWidget</extends> + <header>QtWebKitWidgets/QWebView</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index bc0840a0..0ca9fde1 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -1,5 +1,9 @@ /* Copyright 2013 MultiMC Contributors * + * Authors: Andrew Okin + * Peterix + * Orochimarufan <orochimarufan.x3@gmail.com> + * * 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 @@ -18,16 +22,21 @@ #include <QMenu> #include <QMessageBox> - +#include <QInputDialog> +#include <QApplication> #include <QDesktopServices> #include <QUrl> +#include <QDir> #include "util/osutils.h" +#include "util/userutil.h" +#include "util/pathutils.h" #include "gui/settingsdialog.h" #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 +45,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), - ui(new Ui::MainWindow) + ui(new Ui::MainWindow) { ui->setupUi(this); @@ -88,12 +97,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 +166,23 @@ void MainWindow::onLoginComplete(LoginResponse response) QString("Logged in as %1 with session ID %2."). 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(), QApplication::instance()->applicationFilePath(), QStringList() << "-dl" << QDir::currentPath() << "test", name, "application-x-octet-stream"); + + QMessageBox::warning(this, "Not useful", "A Dummy Shortcut was created. it will not do anything productive"); +} + +// 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..f2dfbc70 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(); @@ -61,6 +64,8 @@ private slots: void on_actionLaunchInstance_triggered(); + + void on_actionMakeDesktopShortcut_triggered(); void doLogin(const QString& errorMsg = ""); @@ -1,6 +1,7 @@ - /* Copyright 2013 MultiMC Contributors * + * Authors: Orochimarufan <orochimarufan.x3@gmail.com> + * * 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 @@ -14,26 +15,105 @@ * limitations under the License. */ -#include "gui/mainwindow.h" +#include <iostream> + #include <QApplication> +#include <QDir> -#include "data/appsettings.h" +#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", "<path>"); + // --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", "<inst>"); + + // parse the arguments + QHash<QString, QVariant> 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>("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 <orochimarufan.x3@gmail.com> + * + * 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<PositionalDef *> 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<OptionDef *> 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<OptionDef *> 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<PositionalDef *> it2(m_positionals); + while(it2.hasNext()) + { + PositionalDef *param = it2.next(); + usage << " " << (param->required ? "<" : "["); + usage << param->name; + usage << (param->required ? ">" : "]"); + } + + return usage.join(""); +} + +// parsing +QHash<QString, QVariant> Parser::parse(QStringList argv) throw (ParsingError) +{ + QHash<QString, QVariant> map; + + QStringListIterator it(argv); + QString programName = it.next(); + + QString optionPrefix; + QString flagPrefix; + QListIterator<PositionalDef *> 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<OptionDef *> 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<OptionDef *> it(m_optionList); + while(it.hasNext()) + { + OptionDef *option = it.next(); + it.remove(); + delete option; + } + + QMutableListIterator<PositionalDef *> 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 <orochimarufan.x3@gmail.com> + * + * 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 <exception> + +#include <QString> +#include <QVariant> +#include <QHash> +#include <QStringList> + +/** + * @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<QString, QVariant> 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<QString, OptionDef *> m_options; + QHash<QChar, OptionDef *> m_flags; + QHash<QString, CommonDef *> m_params; + QList<PositionalDef *> m_positionals; + QList<OptionDef *> m_optionList; + + void getPrefix(QString &opt, QString &flag); +}; + +} +} + +#endif // CMDUTILS_H 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 <QStandardPaths> +#include <QFile> +#include <QTextStream> + +#include "osutils.h" +#include "pathutils.h" + +// Win32 crap +#if WINDOWS + +#include <windows.h> +#include <winnls.h> +#include <shobjidl.h> +#include <objbase.h> +#include <objidl.h> +#include <shlguid.h> +#include <shlobj.h> + +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 <QString> + +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 |