diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt
index 0ee6714aa..e35ef66ef 100644
--- a/app/common/CMakeLists.txt
+++ b/app/common/CMakeLists.txt
@@ -21,6 +21,8 @@ set(OLIVE_SOURCES
common/cancelableobject.h
common/channellayout.h
common/clamp.h
+ common/commandlineparser.h
+ common/commandlineparser.cpp
common/crashhandler.h
common/crashhandler.cpp
common/crashpadinterface.cpp
diff --git a/app/common/commandlineparser.cpp b/app/common/commandlineparser.cpp
new file mode 100644
index 000000000..584f6f1f3
--- /dev/null
+++ b/app/common/commandlineparser.cpp
@@ -0,0 +1,149 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2019 Olive Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+***/
+
+#include "commandlineparser.h"
+
+#include
+#include
+
+CommandLineParser::~CommandLineParser()
+{
+ foreach (const KnownOption& o, options_) {
+ delete o.option;
+ }
+
+ foreach (const KnownPositionalArgument& a, positional_args_) {
+ delete a.option;
+ }
+}
+
+const CommandLineParser::Option *CommandLineParser::AddOption(const QStringList &strings, const QString &description)
+{
+ Option* o = new Option();
+
+ options_.append({strings, description, o});
+
+ return o;
+}
+
+const CommandLineParser::PositionalArgument *CommandLineParser::AddPositionalArgument(const QString &name, const QString &description, bool required)
+{
+ PositionalArgument* a = new PositionalArgument();
+
+ positional_args_.append({name, description, a, required});
+
+ return a;
+}
+
+void CommandLineParser::Process(int argc, char **argv)
+{
+ int positional_index = 0;
+
+ for (int i=1; iSet();
+ matched_known = true;
+ goto found_flag;
+ }
+ }
+ }
+
+found_flag:
+ if (!matched_known) {
+ qWarning() << "Unknown parameter:" << argv[i];
+ }
+
+ } else {
+ // Must be a positional flag
+ if (positional_index < positional_args_.size()) {
+ positional_args_[positional_index].option->SetSetting(argv[i]);
+ positional_index++;
+ } else {
+ qWarning() << "Unknown parameter:" << argv[i];
+ }
+ }
+ }
+}
+
+void CommandLineParser::PrintHelp(const char* filename)
+{
+ printf("%s %s\n",
+ QCoreApplication::applicationName().toUtf8().constData(),
+ QCoreApplication::applicationVersion().toUtf8().constData());
+
+ printf("Copyright (C) 2018-2020 Olive Team\n");
+
+ QString positional_args;
+ for (int i=0; i 0) {
+ positional_args.append(' ');
+ }
+
+ positional_args.append('[');
+ positional_args.append(positional_args_.at(i).name);
+ positional_args.append(']');
+ }
+
+ const char* basename;
+#ifdef Q_OS_WINDOWS
+ basename = strrchr(filename, '\\');
+ if (!basename) {
+ basename = strrchr(filename, '/');
+ }
+#else
+ basename = strrchr(filename, '/');
+#endif
+
+ printf("Usage: %s [options] %s\n\n", basename + 1, positional_args.toUtf8().constData());
+
+ foreach (const KnownOption& o, options_) {
+ QString all_args;
+
+ for (int i=0; i 0) {
+ all_args.append(QStringLiteral(", "));
+ }
+
+ const QString& this_arg = o.args.at(i);
+
+ all_args.append('-');
+ all_args.append(this_arg);
+ }
+
+ printf(" %s\n", all_args.toUtf8().constData());
+
+ printf(" %s\n\n", o.description.toUtf8().constData());
+ }
+
+ printf("\n");
+}
diff --git a/app/common/commandlineparser.h b/app/common/commandlineparser.h
new file mode 100644
index 000000000..ce568f3db
--- /dev/null
+++ b/app/common/commandlineparser.h
@@ -0,0 +1,107 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2019 Olive Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+***/
+
+#ifndef COMMANDLINEPARSER_H
+#define COMMANDLINEPARSER_H
+
+#include
+
+#include "common/define.h"
+
+class CommandLineParser
+{
+public:
+ ~CommandLineParser();
+
+ DISABLE_COPY_MOVE(CommandLineParser);
+
+ class Option {
+ public:
+ Option()
+ {
+ is_set_ = false;
+ }
+
+ bool IsSet() const
+ {
+ return is_set_;
+ }
+
+ void Set()
+ {
+ is_set_ = true;
+ }
+
+ private:
+ bool is_set_;
+
+ };
+
+ class PositionalArgument
+ {
+ public:
+ PositionalArgument() = default;
+
+ const QString& GetSetting() const
+ {
+ return setting_;
+ }
+
+ void SetSetting(const QString& s)
+ {
+ setting_ = s;
+ }
+
+ private:
+ QString setting_;
+
+ };
+
+ CommandLineParser() = default;
+
+ const Option* AddOption(const QStringList& strings, const QString& description);
+
+ const PositionalArgument* AddPositionalArgument(const QString& name, const QString& description, bool required = false);
+
+ void Process(int argc, char** argv);
+
+ void PrintHelp(const char* filename);
+
+private:
+ struct KnownOption {
+ QStringList args;
+ QString description;
+ Option* option;
+ };
+
+ struct KnownPositionalArgument {
+ QString name;
+ QString description;
+ PositionalArgument* option;
+ bool required;
+ };
+
+ QVector options_;
+
+ QVector positional_args_;
+
+};
+
+#endif // COMMANDLINEPARSER_H
diff --git a/app/core.cpp b/app/core.cpp
index 4ab066d1c..9790246ed 100644
--- a/app/core.cpp
+++ b/app/core.cpp
@@ -22,7 +22,6 @@
#include
#include
-#include
#include
#include
#include
@@ -64,93 +63,23 @@
OLIVE_NAMESPACE_ENTER
-Core Core::instance_;
+Core* Core::instance_ = nullptr;
-Core::Core() :
+Core::Core(const CoreParams& params) :
main_window_(nullptr),
tool_(Tool::kPointer),
addable_object_(Tool::kAddableEmpty),
snapping_(true),
- gui_active_(false)
+ core_params_(params)
{
+ // Store reference to this object, making the assumption that Core will only ever be made in
+ // main(). This will obviously break if not.
+ instance_ = this;
}
Core *Core::instance()
{
- return &instance_;
-}
-
-int Core::execute(QCoreApplication* a)
-{
- int exit_code = 1;
-
- //
- // Parse command line arguments
- //
-
- QCommandLineParser parser;
- QCommandLineOption help_option = parser.addHelpOption();
- QCommandLineOption version_option = parser.addVersionOption();
-
- // Project from command line option
- // FIXME: What's the correct way to make a visually "optional" positional argument, or is manually adding square
- // brackets like this correct?
- parser.addPositionalArgument("[project]", tr("Project to open on startup"));
-
- // Create fullscreen option
- QCommandLineOption fullscreen_option({"f", "fullscreen"}, tr("Start in full screen mode"));
- parser.addOption(fullscreen_option);
-
- // Create headless export option
- QCommandLineOption headless_export_option({"x", "export"}, tr("Export project from command line"));
- parser.addOption(headless_export_option);
-
- // Parse options
- parser.process(*a);
-
- if (parser.isSet(help_option) || parser.isSet(version_option)) {
- // These options don't launch any of the application proper
- return a->exec();
- }
-
- // Start core
- OLIVE_NAMESPACE::Core::instance()->Start();
-
- QStringList args = parser.positionalArguments();
-
- // Detect project to load on startup
- if (!args.isEmpty()) {
- startup_project_ = args.first();
- }
-
- gui_active_ = !parser.isSet(headless_export_option);
-
- if (gui_active_) {
-
- // Start GUI
- StartGUI(parser.isSet(fullscreen_option));
-
- // If we have a startup
- QMetaObject::invokeMethod(this, "OpenStartupProject", Qt::QueuedConnection);
-
- // Run application loop and receive exit code
- exit_code = a->exec();
-
- } else {
-
- if (parser.isSet(headless_export_option)) {
- // Start a headless export
- if (StartHeadlessExport()) {
- exit_code = 0;
- }
- }
-
- }
-
- // Clear core memory
- OLIVE_NAMESPACE::Core::instance()->Stop();
-
- return exit_code;
+ return instance_;
}
void Core::DeclareTypesForQt()
@@ -204,6 +133,22 @@ void Core::Start()
//
qInfo() << "Using Qt version:" << qVersion();
+
+ switch (core_params_.run_mode()) {
+ case CoreParams::kRunNormal:
+ // Start GUI
+ StartGUI(core_params_.fullscreen());
+
+ // If we have a startup
+ QMetaObject::invokeMethod(this, "OpenStartupProject", Qt::QueuedConnection);
+ break;
+ case CoreParams::kHeadlessExport:
+ qInfo() << "Headless export is not fully implemented yet";
+ break;
+ case CoreParams::kHeadlessPreCache:
+ qInfo() << "Headless pre-cache is not fully implemented yet";
+ break;
+ }
}
void Core::Stop()
@@ -571,18 +516,20 @@ void Core::ProjectWasModified(bool e)
bool Core::StartHeadlessExport()
{
- if (startup_project_.isEmpty()) {
+ const QString& startup_project = core_params_.startup_project();
+
+ if (startup_project.isEmpty()) {
qCritical().noquote() << tr("You must specify a project file to export");
return false;
}
- if (!QFileInfo::exists(startup_project_)) {
+ if (!QFileInfo::exists(startup_project)) {
qCritical().noquote() << tr("Specified project does not exist");
return false;
}
// Start a load task and try running it
- ProjectLoadTask plm(startup_project_);
+ ProjectLoadTask plm(startup_project);
CLITaskDialog task_dialog(&plm);
if (task_dialog.Run()) {
@@ -651,21 +598,24 @@ bool Core::StartHeadlessExport()
void Core::OpenStartupProject()
{
+ const QString& startup_project = core_params_.startup_project();
+ bool startup_project_exists = !startup_project.isEmpty() && QFileInfo::exists(startup_project);
+
// Load startup project
- if (!startup_project_.isEmpty() && !QFileInfo::exists(startup_project_)) {
+ if (!startup_project_exists && !startup_project.isEmpty()) {
QMessageBox::warning(main_window_,
tr("Failed to open startup file"),
- tr("The project \"%1\" doesn't exist. A new project will be started instead.").arg(startup_project_),
+ tr("The project \"%1\" doesn't exist. "
+ "A new project will be started instead.").arg(startup_project),
QMessageBox::Ok);
-
- startup_project_.clear();
}
- if (startup_project_.isEmpty()) {
+ if (startup_project_exists) {
+ // If a startup project was set and exists, open it now
+ OpenProjectInternal(startup_project);
+ } else {
// If no load project is set, create a new one on open
CreateNewProject();
- } else {
- OpenProjectInternal(startup_project_);
}
}
@@ -1228,4 +1178,10 @@ void Core::OpenProject()
}
}
+Core::CoreParams::CoreParams() :
+ mode_(kRunNormal),
+ run_fullscreen_(false)
+{
+}
+
OLIVE_NAMESPACE_EXIT
diff --git a/app/core.h b/app/core.h
index 01d941f0a..71fa43b75 100644
--- a/app/core.h
+++ b/app/core.h
@@ -52,12 +52,62 @@ class Core : public QObject
{
Q_OBJECT
public:
+ class CoreParams
+ {
+ public:
+ CoreParams();
+
+ enum RunMode {
+ kRunNormal,
+ kHeadlessExport,
+ kHeadlessPreCache
+ };
+
+ bool fullscreen() const
+ {
+ return run_fullscreen_;
+ }
+
+ void set_fullscreen(bool e)
+ {
+ run_fullscreen_ = e;
+ }
+
+ RunMode run_mode() const
+ {
+ return mode_;
+ }
+
+ void set_run_mode(RunMode m)
+ {
+ mode_ = m;
+ }
+
+ const QString startup_project() const
+ {
+ return startup_project_;
+ }
+
+ void set_startup_project(const QString& p)
+ {
+ startup_project_ = p;
+ }
+
+ private:
+ RunMode mode_;
+
+ QString startup_project_;
+
+ bool run_fullscreen_;
+
+ };
+
/**
* @brief Core Constructor
*
* Currently empty
*/
- Core();
+ Core(const CoreParams& params);
/**
* @brief Core object accessible from anywhere in the code
@@ -66,7 +116,10 @@ public:
*/
static Core* instance();
- int execute(QCoreApplication *a);
+ const CoreParams& core_params() const
+ {
+ return core_params_;
+ }
/**
* @brief Start Olive Core
@@ -407,14 +460,6 @@ private:
*/
MainWindow* main_window_;
- /**
- * @brief Internal startup project object
- *
- * If the user specifies a project file on the command line, the command line parser in Start() will write the
- * project URL here to be loaded once Olive has finished initializing.
- */
- QString startup_project_;
-
/**
* @brief List of currently open projects
*/
@@ -456,14 +501,14 @@ private:
QStringList recent_projects_;
/**
- * @brief Internal variable for whether the GUI is active
+ * @brief Parameters set up in main() determining how the program should run
*/
- bool gui_active_;
+ CoreParams core_params_;
/**
* @brief Static singleton core instance
*/
- static Core instance_;
+ static Core* instance_;
private slots:
void SaveAutorecovery();
diff --git a/app/main.cpp b/app/main.cpp
index d58b65ebf..3cfa34db2 100644
--- a/app/main.cpp
+++ b/app/main.cpp
@@ -34,34 +34,23 @@ extern "C" {
#include
#include
+#include
#include
#include "core.h"
+#include "common/commandlineparser.h"
#include "common/debug.h"
#ifdef USE_CRASHPAD
#include "common/crashpadinterface.h"
#endif // USE_CRASHPAD
-int main(int argc, char *argv[]) {
- // Set OpenGL display profile (3.2 Core)
- QSurfaceFormat format;
- format.setVersion(3, 2);
- format.setDepthBufferSize(24);
- format.setProfile(QSurfaceFormat::CoreProfile);
- QSurfaceFormat::setDefaultFormat(format);
-
- //QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
- QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
-
- // Create application instance
- QApplication a(argc, argv);
-
- // Set application metadata
- QCoreApplication::setOrganizationName("olivevideoeditor.org");
- QCoreApplication::setOrganizationDomain("olivevideoeditor.org");
- QCoreApplication::setApplicationName("Olive");
+int main(int argc, char *argv[])
+{
+ // Set up debug handler
+ qInstallMessageHandler(OLIVE_NAMESPACE::DebugHandler);
+ // Generate version string
QString app_version = APPVERSION;
#ifdef GITHASH
// Anything after the hyphen is considered "unimportant" information. Text BEFORE the hyphen is used in version
@@ -70,14 +59,85 @@ int main(int argc, char *argv[]) {
app_version.append(GITHASH);
#endif
+ // Set application metadata
+ QCoreApplication::setOrganizationName("olivevideoeditor.org");
+ QCoreApplication::setOrganizationDomain("olivevideoeditor.org");
+ QCoreApplication::setApplicationName("Olive");
+
QCoreApplication::setApplicationVersion(app_version);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0))
QGuiApplication::setDesktopFileName("org.olivevideoeditor.Olive");
#endif
- // Set up debug handler
- qInstallMessageHandler(OLIVE_NAMESPACE::DebugHandler);
+ //
+ // Parse command line arguments
+ //
+
+ OLIVE_NAMESPACE::Core::CoreParams startup_params;
+
+ CommandLineParser parser;
+
+ const CommandLineParser::Option* help_option =
+ parser.AddOption({QStringLiteral("h"), QStringLiteral("-help")},
+ QCoreApplication::translate("main", "Show this help text"));
+
+ const CommandLineParser::Option* version_option =
+ parser.AddOption({QStringLiteral("v"), QStringLiteral("-version")},
+ QCoreApplication::translate("main", "Show application version"));
+
+ const CommandLineParser::Option* fullscreen_option =
+ parser.AddOption({QStringLiteral("f"), QStringLiteral("-fullscreen")},
+ QCoreApplication::translate("main", "Start in full-screen mode"));
+
+ const CommandLineParser::Option* export_option =
+ parser.AddOption({QStringLiteral("x"), QStringLiteral("-export")},
+ QCoreApplication::translate("main", "Export only (No GUI)"));
+
+ const CommandLineParser::PositionalArgument* project_argument =
+ parser.AddPositionalArgument(QStringLiteral("project"),
+ QCoreApplication::translate("main", "Project to open on startup"));
+
+ parser.Process(argc, argv);
+
+ if (help_option->IsSet()) {
+ // Show help
+ parser.PrintHelp(argv[0]);
+ return 0;
+ }
+
+ if (version_option->IsSet()) {
+ // Print version
+ printf("%s\n", app_version.toUtf8().constData());
+ return 0;
+ }
+
+ if (export_option->IsSet()) {
+ startup_params.set_run_mode(OLIVE_NAMESPACE::Core::CoreParams::kHeadlessExport);
+ }
+
+ startup_params.set_fullscreen(fullscreen_option->IsSet());
+
+ startup_params.set_startup_project(project_argument->GetSetting());
+
+ // Set OpenGL display profile (3.2 Core)
+ QSurfaceFormat format;
+ format.setVersion(3, 2);
+ format.setDepthBufferSize(24);
+ format.setProfile(QSurfaceFormat::CoreProfile);
+ QSurfaceFormat::setDefaultFormat(format);
+
+ // Enable application automatically using higher resolution images from icons
+ QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+
+ // Create application instance
+ std::unique_ptr a;
+
+ if (startup_params.run_mode() == OLIVE_NAMESPACE::Core::CoreParams::kRunNormal) {
+ a.reset(new QApplication(argc, argv));
+ } else {
+ a.reset(new QCoreApplication(argc, argv));
+ }
// Register FFmpeg codecs and filters (deprecated in 4.0+)
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
@@ -94,5 +154,14 @@ int main(int argc, char *argv[]) {
}
#endif // USE_CRASHPAD
- return OLIVE_NAMESPACE::Core::instance()->execute(&a);
+ // Start core
+ OLIVE_NAMESPACE::Core c(startup_params);
+ c.Start();
+
+ int ret = a->exec();
+
+ // Clear core memory
+ c.Stop();
+
+ return ret;
}