From bff06e00e55d4193f662bf6769546488ac880931 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Mon, 20 Jul 2026 01:38:42 +0800 Subject: [PATCH] refactor: split Core into EngineCore (engine) and Core (UI) EngineCore (new app/coreengine.{h,cpp}) owns every engine-safe part of the old Core singleton: CoreParams, lifecycle of the engine managers, UndoStack, tool/snapping/timecode state, locale, autorecovery, recent projects, footage filters, clipboard, project registry, type declarations, and the proxy toggle. UI dependencies are inverted through hooks instead: status-bar/cache-full signals and std::function handlers for image-sequence confirmation, footage relink, OTIO import, project save/close and layout load (same pattern as Config::ErrorHandler). Core (app/) now derives from EngineCore and keeps only UI behavior: the main window, dialogs, panel heuristics, import/export flows and project lifecycle presentation. Its public API is unchanged (all inherited), and Core::instance() covariantly static_casts the engine singleton. The render worker constructs EngineCore directly, making it the first binary that no longer needs the UI side of Core. ~25 engine call sites move from core.h to coreengine.h; a dozen more drop a vestigial core.h include (gaining direct includes for symbols they were borrowing transitively). Full gtest suite green (1986 tests, 0 failures). --- app/CMakeLists.txt | 2 + app/config/config.cpp | 1 - app/config/config.h | 10 +- app/core.cpp | 871 ++--------------- app/core.h | 565 ++--------- app/coreengine.cpp | 924 ++++++++++++++++++ app/coreengine.h | 617 ++++++++++++ app/main.cpp | 1 + app/node/color/colormanager/colormanager.cpp | 2 +- app/node/color/ociolut/ociolut.cpp | 6 +- .../cornerpin/cornerpindistortnode.cpp | 1 - app/node/distort/crop/cropdistortnode.cpp | 1 - app/node/generator/shape/shapenodebase.cpp | 1 - app/node/generator/text/textv3.cpp | 4 +- app/node/gizmo/text.cpp | 4 +- app/node/inputdragger.cpp | 1 - app/node/node.cpp | 1 - app/node/output/viewer/viewer.cpp | 4 +- app/node/project.cpp | 1 - app/node/project/footage/footage.cpp | 4 +- app/node/project/serializer/serializer.cpp | 6 +- app/pluginSupport/oliveplugininstance.cpp | 6 +- app/pluginSupport/paraminstance.cpp | 3 +- app/pluginSupport/paraminstance.h | 1 - app/render/diskmanager.cpp | 4 +- app/render/plugin/pluginrenderer.cpp | 4 +- app/render/rendermanager.cpp | 1 - app/render/videoparams.cpp | 2 +- app/task/project/import/import.cpp | 6 +- app/task/project/loadotio/loadotio.cpp | 4 +- app/task/project/save/save.cpp | 1 - app/timeline/timelinemarker.cpp | 2 +- app/undo/undocommand.cpp | 2 +- worker/workermain.cpp | 14 +- 34 files changed, 1764 insertions(+), 1313 deletions(-) create mode 100644 app/coreengine.cpp create mode 100644 app/coreengine.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 3d1c7bdc7..121d8cb65 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -19,6 +19,8 @@ set(OLIVE_SOURCES core.h core.cpp + coreengine.h + coreengine.cpp ) #set(OLIVE_RESOURCES) diff --git a/app/config/config.cpp b/app/config/config.cpp index 6940b4a71..62ce48cc4 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -32,7 +32,6 @@ #include "common/dropworkflowbehavior.h" #include "common/filefunctions.h" #include "common/xmlutils.h" -#include "core.h" #include "timeline/timelinecommon.h" #include "ui/colorcoding.h" diff --git a/app/config/config.h b/app/config/config.h index 0db3226d8..34817f9cd 100644 --- a/app/config/config.h +++ b/app/config/config.h @@ -56,6 +56,14 @@ public: static void set_error_handler(ErrorHandler handler); + /** + * @brief Report an error through the registered error handler + * + * Public so engine-layer code (e.g. EngineCore) can surface errors to + * the user without depending on the UI itself. + */ + static void report_error(const QString &title, const QString &message); + QVariant operator[](const QString &) const; QVariant &operator[](const QString &); @@ -79,8 +87,6 @@ private: static ErrorHandler error_handler_; - static void report_error(const QString &title, const QString &message); - static QString get_config_file_path(); }; diff --git a/app/core.cpp b/app/core.cpp index a033083d7..aa98a8cf5 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -22,15 +22,14 @@ #include "core.h" #include -#include #include +#include +#include #include #include -#include #include #include #include -#include #include "window/mainwindow/mainwindowundo.h" #ifdef Q_OS_WINDOWS #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) @@ -40,8 +39,6 @@ #include "audio/audiomanager.h" #include "cli/clitask/clitaskdialog.h" -#include "codec/conformmanager.h" -#include "codec/proxymanager.h" #include "common/filefunctions.h" #include "common/xmlutils.h" #include "config/config.h" @@ -56,16 +53,11 @@ #include "dialog/sequence/sequence.h" #include "dialog/task/task.h" #include "dialog/preferences/preferences.h" -#include "node/color/colormanager/colormanager.h" -#include "node/factory.h" #include "node/nodeundo.h" -#include "node/project/serializer/serializer.h" #include "panel/panelmanager.h" #include "panel/project/project.h" #include "panel/viewer/viewer.h" #include "render/diskmanager.h" -#include "render/framemanager.h" -#include "render/rendermanager.h" #ifdef USE_OTIO #include "task/project/loadotio/loadotio.h" #include "task/project/saveotio/saveotio.h" @@ -74,194 +66,58 @@ #include "dialog/projectimport/projectimporterrordialog.h" #include "task/project/load/load.h" #include "task/project/save/save.h" -#include "task/taskmanager.h" #include "ui/style/style.h" -#include "undo/undostack.h" #include "widget/menu/menushared.h" #include "window/mainwindow/mainwindow.h" -namespace -{ - -QStringList footage_video_extensions() -{ - return QStringList{ - QStringLiteral("mp4"), QStringLiteral("mov"), QStringLiteral("m4v"), - QStringLiteral("avi"), QStringLiteral("mpg"), QStringLiteral("mpeg"), - QStringLiteral("m2ts"), QStringLiteral("mts"), QStringLiteral("ts"), - QStringLiteral("webm"), QStringLiteral("wmv"), QStringLiteral("flv"), - QStringLiteral("3gp"), QStringLiteral("3g2"), QStringLiteral("mxf") - }; -} - -QStringList footage_audio_extensions() -{ - return QStringList{ QStringLiteral("wav"), QStringLiteral("mp3"), - QStringLiteral("flac"), QStringLiteral("aac"), - QStringLiteral("ogg"), QStringLiteral("opus"), - QStringLiteral("m4a"), QStringLiteral("alac"), - QStringLiteral("aif"), QStringLiteral("aiff"), - QStringLiteral("aifc"), QStringLiteral("wma") }; -} - -QStringList footage_image_extensions() -{ - return QStringList{ QStringLiteral("png"), QStringLiteral("jpg"), - QStringLiteral("jpeg"), QStringLiteral("tif"), - QStringLiteral("tiff"), QStringLiteral("bmp"), - QStringLiteral("gif"), QStringLiteral("exr"), - QStringLiteral("dpx"), QStringLiteral("webp") }; -} - -QString build_footage_filter_group(const QString &label, - const QStringList &extensions) -{ - QStringList patterns; - patterns.reserve(extensions.size()); - for (const QString &ext : extensions) { - patterns.append(QStringLiteral("*.%1").arg(ext)); - } - - return QStringLiteral("%1 (%2)").arg(label, - patterns.join(QLatin1Char(' '))); -} - -QString build_footage_file_dialog_filter() -{ - QStringList all = footage_video_extensions() + footage_audio_extensions() + - footage_image_extensions(); - all.removeDuplicates(); - - QStringList groups; - groups << build_footage_filter_group(QObject::tr("Common Media Files"), all); - groups << build_footage_filter_group(QObject::tr("Video Files"), - footage_video_extensions()); - groups << build_footage_filter_group(QObject::tr("Audio Files"), - footage_audio_extensions()); - groups << build_footage_filter_group(QObject::tr("Image Files"), - footage_image_extensions()); - - return groups.join(QStringLiteral(";;")); -} - -} // namespace - namespace olive { -Core *Core::instance_ = nullptr; - Core::Core(const CoreParams ¶ms) - : main_window_(nullptr) - , open_project_(nullptr) - , tool_(Tool::k_pointer) - , addable_object_(Tool::k_addable_empty) - , snapping_(true) - , core_params_(params) - , magic_(false) - , pixel_sampling_users_(0) - , shown_cache_full_warning_(false) + : EngineCore(params) + , main_window_(nullptr) { - // 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; + // Register the UI handlers that the engine uses to request user interaction + set_confirm_image_sequence_handler( + [this](const QString &filename) { + return confirm_image_sequence(filename); + }); - translator_ = new QTranslator(this); -} + set_relink_handler([this](QVector footage) { + FootageRelinkDialog frd(footage, main_window_); + return frd.exec() != QDialog::Rejected; + }); -Core *Core::instance() -{ - return instance_; -} + set_save_project_handler([this](const QString &override_filename) { + save_project_internal(override_filename); + }); -QString Core::footage_file_dialog_filter() -{ - return build_footage_file_dialog_filter(); -} + set_close_project_handler([this] { return close_project(false); }); -QStringList Core::allowed_footage_extensions() -{ - QStringList all = footage_video_extensions() + footage_audio_extensions() + - footage_image_extensions(); - all.removeDuplicates(); - return all; -} + set_load_layout_handler([this](const MainWindowLayoutInfo &layout) { + main_window_->load_layout(layout); + }); -bool Core::is_footage_extension_allowed(const QString &path) -{ - const QString ext = QFileInfo(path).suffix().toLower(); - if (ext.isEmpty()) { - return false; - } - - return allowed_footage_extensions().contains(ext); -} - -void Core::declare_types_for_qt() -{ - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); +#ifdef USE_OTIO + set_otio_import_handler([this](const QList &sequences) { + return DialogImportOTIOShow(sequences); + }); +#endif } void Core::start() { - // Load application config - Config::load(); - - // Set locale based on either startup arg, config, or auto-detect - set_startup_locale(); - - // Declare custom types for Qt signal/slot system - declare_types_for_qt(); - - // Set up node factory/library - NodeFactory::initialize(); - - // Set up color manager's default config - ColorManager::set_up_default_config(); - - // Initialize task manager - TaskManager::create_instance(); - - // Initialize ConformManager - ConformManager::create_instance(); - - // Initialize ProxyManager - ProxyManager::create_instance(); - - // Initialize RenderManager - RenderManager::create_instance(); - - // Initialize FrameManager - FrameManager::create_instance(); - - // Initialize project serializers - ProjectSerializer::initialize(); + // Start the engine (config, locale, managers, autorecovery, recent projects) + EngineCore::start(); // // Start application // - qInfo() << "Using Qt version:" << qVersion(); - - switch (core_params_.run_mode()) { + switch (core_params().run_mode()) { case CoreParams::k_run_normal: // Start GUI - start_gui(core_params_.fullscreen()); + start_gui(core_params().fullscreen()); // If we have a startup QMetaObject::invokeMethod(this, "open_startup_project", @@ -274,52 +130,24 @@ void Core::start() qInfo() << "Headless pre-cache is not fully implemented yet"; break; } - - // Manual crash triggering - if (core_params_.crash_on_startup()) { - const int interval = 5000; - qInfo() << "Manual crash was triggered. Application will crash in" - << interval << "ms"; - QTimer *crash_timer = new QTimer(this); - crash_timer->setInterval(interval); - connect(crash_timer, &QTimer::timeout, this, [] { abort(); }); - crash_timer->start(); - } } void Core::stop() { - // Assume all projects have closed gracefully and no auto-recovery is necessary - autorecovered_projects_.clear(); - save_unrecovered_list(); - - // Save Config - Config::save(); - - ProjectSerializer::destroy(); - - ConformManager::destroy_instance(); - - ProxyManager::destroy_instance(); - - FrameManager::destroy_instance(); - - RenderManager::destroy_instance(); - + // Tear down the UI services first MenuShared::destroy_instance(); - TaskManager::destroy_instance(); - PanelManager::destroy_instance(); AudioManager::destroy_instance(); DiskManager::destroy_instance(); - NodeFactory::destroy(); - delete main_window_; main_window_ = nullptr; + + // Then tear down the engine + EngineCore::stop(); } MainWindow *Core::main_window() @@ -327,11 +155,6 @@ MainWindow *Core::main_window() return main_window_; } -UndoStack *Core::undo_stack() -{ - return &undo_stack_; -} - void Core::import_files(const QStringList &urls, Folder *parent) { if (urls.isEmpty()) { @@ -381,87 +204,6 @@ void Core::import_files(const QStringList &urls, Folder *parent) task_dialog->open(); } -const Tool::Item &Core::tool() const -{ - return tool_; -} - -const Tool::AddableObject &Core::get_selected_addable_object() const -{ - return addable_object_; -} - -const QString &Core::get_selected_transition() const -{ - return selected_transition_; -} - -void Core::set_selected_addable_object(const Tool::AddableObject &obj) -{ - addable_object_ = obj; - emit addable_object_changed(addable_object_); -} - -void Core::set_selected_transition_object(const QString &obj) -{ - selected_transition_ = obj; -} - -void Core::clear_open_recent_list() -{ - recent_projects_.clear(); - save_recent_projects_list(); - emit open_recent_list_changed(); -} - -void Core::create_new_project() -{ - // If we already have an empty/new project, switch to it - if (close_project(false)) { - Project *p = new Project(); - p->initialize(); - add_open_project(p); - } -} - -const bool &Core::snapping() const -{ - return snapping_; -} - -const QStringList &Core::get_recent_projects() const -{ - return recent_projects_; -} - -void Core::set_tool(const Tool::Item &tool) -{ - tool_ = tool; - - emit tool_changed(tool_); -} - -void Core::set_snapping(const bool &b) -{ - snapping_ = b; - - emit snapping_changed(snapping_); -} - -void Core::set_use_proxy_media(bool enabled) -{ - Config::current()[QStringLiteral("UseProxyMedia")] = enabled; - - // Invalidate all footage so viewers re-evaluate with the new proxy state - if (open_project_) { - for (Node *n : open_project_->nodes()) { - if (Footage *footage = dynamic_cast(n)) { - footage->invalidate_all(Footage::k_filename_input); - } - } - } -} - void Core::dialog_about_show() { AboutDialog a(false, main_window_); @@ -529,7 +271,7 @@ void Core::dialog_export_show() #ifdef USE_OTIO bool Core::DialogImportOTIOShow(const QList &sequences) { - Project *active_project = GetActiveProject(); + Project *active_project = get_active_project(); OTIOPropertiesDialog opd(sequences, active_project); return opd.exec() == QDialog::Accepted; } @@ -613,63 +355,6 @@ void Core::create_new_sequence() } } -void Core::add_open_project(Project *p, bool add_to_recents) -{ - // Ensure project is not open at the moment - if (open_project_ == p) { - return; - } - - // If we currently have an empty project, close it first - if (open_project_) { - close_project(false); - } - - set_active_project(p); - - if (!p->filename().isEmpty() && add_to_recents) { - push_recently_opened_project(p->filename()); - } -} - -bool Core::add_open_project_from_task(Task *task, bool add_to_recents) -{ - ProjectLoadBaseTask *load_task = static_cast(task); - - if (!load_task->is_cancelled()) { - Project *project = load_task->get_loaded_project(); - - if (validate_footage_in_loaded_project(project, project->get_saved_url())) { - add_open_project(project, add_to_recents); - main_window_->load_layout(load_task->get_loaded_layout()); - - return true; - } else { - delete project; - create_new_project(); - } - } - - return false; -} - -void Core::set_active_project(Project *p) -{ - if (open_project_) { - disconnect(open_project_, &Project::modified_changed, this, - &Core::project_was_modified); - } - - open_project_ = p; - RenderManager::instance()->set_project(p); - main_window_->set_project(p); - - if (open_project_) { - connect(open_project_, &Project::modified_changed, this, - &Core::project_was_modified); - } -} - void Core::import_task_complete(Task *task) { ProjectImportTask *import_task = static_cast(task); @@ -730,7 +415,7 @@ void Core::import_task_complete(Task *task) d.exec(); } - undo_stack_.push( + undo_stack()->push( command, tr("Imported %1 File(s)").arg(import_task->get_imported_footage().size())); @@ -753,14 +438,9 @@ bool Core::confirm_image_sequence(const QString &filename) return (mb.exec() == QMessageBox::Yes); } -void Core::project_was_modified(bool e) -{ - main_window_->setWindowModified(e); -} - bool Core::start_headless_export() { - const QString &startup_project = core_params_.startup_project(); + const QString &startup_project = core_params().startup_project(); if (startup_project.isEmpty()) { qCritical().noquote() @@ -847,7 +527,7 @@ bool Core::start_headless_export() void Core::open_startup_project() { - const QString &startup_project = core_params_.startup_project(); + const QString &startup_project = core_params().startup_project(); bool startup_project_exists = !startup_project.isEmpty() && QFileInfo::exists(startup_project); @@ -869,27 +549,6 @@ void Core::open_startup_project() } } -void Core::add_recovery_project_from_task(Task *task) -{ - if (add_open_project_from_task(task, false)) { - ProjectLoadBaseTask *load_task = - static_cast(task); - - Project *project = load_task->get_loaded_project(); - - // Clearing the filename will force the user to re-save it somewhere else - project->set_filename(QString()); - - // Forcing a UUID regeneration will prevent it from saving auto-recoveries in the same place - // the original project did - project->regenerate_uuid(); - - // Setting modified will ensure that the program doesn't close and lose the project without - // prompting the user first - project->set_modified(true); - } -} - void Core::start_gui(bool full_screen) { // Set UI style @@ -925,6 +584,16 @@ void Core::start_gui(bool full_screen) // Create main window and open it main_window_ = new MainWindow(); + // Route engine notifications to the UI + connect(this, &EngineCore::status_message_show, main_window_->statusBar(), + &QStatusBar::showMessage); + connect(this, &EngineCore::status_message_clear, main_window_->statusBar(), + &QStatusBar::clearMessage); + connect(this, &EngineCore::cache_full_warning_requested, this, + &Core::show_cache_full_warning); + connect(this, &EngineCore::active_project_changed, this, + &Core::on_active_project_changed); + if (full_screen) { main_window_->showFullScreen(); } else { @@ -939,26 +608,6 @@ void Core::start_gui(bool full_screen) main_window_->windowHandle(), true); #endif #endif - - // Start autorecovery timer using the config value as its interval - set_autorecovery_interval(OAK_CONFIG("AutorecoveryInterval").toInt()); - connect(&autorecovery_timer_, &QTimer::timeout, this, - &Core::save_autorecovery); - autorecovery_timer_.start(); - - // Load recently opened projects list - { - QFile recent_projects_file(get_recent_projects_file_path()); - if (recent_projects_file.open(QFile::ReadOnly | QFile::Text)) { - QString r = QString::fromUtf8(recent_projects_file.readAll()); - if (!r.isEmpty()) { - recent_projects_ = r.split('\n'); - } - recent_projects_file.close(); - } - - emit open_recent_list_changed(); - } } void Core::save_project_internal(const QString &override_filename) @@ -1041,42 +690,6 @@ ViewerOutput *Core::get_sequence_to_export() return nullptr; } -QString Core::get_auto_recovery_index_filename() -{ - return QDir(QStandardPaths::writableLocation( - QStandardPaths::AppLocalDataLocation)) - .filePath(QStringLiteral("unrecovered")); -} - -void Core::save_unrecovered_list() -{ - QFile autorecovery_index(get_auto_recovery_index_filename()); - - if (autorecovered_projects_.isEmpty()) { - // Recovery list is empty, delete file if exists - if (autorecovery_index.exists()) { - autorecovery_index.remove(); - } - } else if (autorecovery_index.open(QFile::WriteOnly)) { - // Overwrite recovery list with current list - QTextStream ts(&autorecovery_index); - - bool first = true; - foreach (const QUuid &uuid, autorecovered_projects_) { - if (first) { - first = false; - } else { - ts << QStringLiteral("\n"); - } - ts << uuid.toString(); - } - - autorecovery_index.close(); - } else { - qWarning() << "Failed to save unrecovered list"; - } -} - bool Core::revert_project_internal(bool by_opening_existing) { if (open_project_->filename().isEmpty()) { @@ -1119,108 +732,11 @@ bool Core::revert_project_internal(bool by_opening_existing) return false; } -void Core::save_recent_projects_list() -{ - // Save recently opened projects - QFile recent_projects_file(get_recent_projects_file_path()); - if (recent_projects_file.open(QFile::WriteOnly | QFile::Text)) { - recent_projects_file.write(recent_projects_.join('\n').toUtf8()); - recent_projects_file.close(); - } -} - -void Core::save_autorecovery() -{ - if (OAK_CONFIG("AutorecoveryEnabled").toBool()) { - if (open_project_ && !open_project_->has_autorecovery_been_saved()) { - QDir project_autorecovery_dir( - QDir(FileFunctions::get_auto_recovery_root()) - .filePath(open_project_->get_uuid().toString())); - if (FileFunctions::directory_is_valid(project_autorecovery_dir)) { - QString this_autorecovery_path = - project_autorecovery_dir.filePath( - QStringLiteral("%1.ove").arg(QString::number( - QDateTime::currentSecsSinceEpoch()))); - - save_project_internal(this_autorecovery_path); - - open_project_->set_autorecovery_saved(true); - - // Keep track of projects that where the "newest" save is the recovery project - if (!autorecovered_projects_.contains( - open_project_->get_uuid())) { - autorecovered_projects_.append(open_project_->get_uuid()); - } - - qDebug() << "Saved auto-recovery to:" << this_autorecovery_path; - - // Write human-readable real name so it's not just a UUID - { - QFile realname_file(project_autorecovery_dir.filePath( - QStringLiteral("realname.txt"))); - realname_file.open(QFile::WriteOnly); - realname_file.write( - open_project_->pretty_filename().toUtf8()); - realname_file.close(); - } - - int64_t max_recoveries_per_file = - OAK_CONFIG("AutorecoveryMaximum").toLongLong(); - - // Since we write an extra file, increment total allowed files by 1 - max_recoveries_per_file++; - - // Delete old entries - QStringList recovery_files = project_autorecovery_dir.entryList( - QDir::Files | QDir::NoDotAndDotDot, QDir::Name); - while (recovery_files.size() > max_recoveries_per_file) { - bool deleted = false; - for (int i = 0; i < recovery_files.size(); i++) { - const QString &f = recovery_files.at(i); - - if (f.endsWith(QStringLiteral(".ove"), - Qt::CaseInsensitive)) { - QString delete_full_path = - project_autorecovery_dir.filePath(f); - qDebug() - << "Deleted old recovery:" << delete_full_path; - QFile::remove(delete_full_path); - recovery_files.removeAt(i); - deleted = true; - break; - } - } - - if (!deleted) { - // For some reason none of the files were deletable. Break so we don't end up in - // an infinite loop. - break; - } - } - } else { - QMessageBox::critical( - main_window_, tr("Auto-Recovery Error"), - tr("Failed to save auto-recovery to \"%1\". " - "Oak Video Editor may not have permission to this directory.") - .arg(project_autorecovery_dir.absolutePath())); - } - } - - // Save index - save_unrecovered_list(); - } -} - void Core::project_save_succeeded(Task *task) { Project *p = static_cast(task)->get_project(); - push_recently_opened_project(p->filename()); - - p->set_modified(false); - - autorecovered_projects_.removeOne(p->get_uuid()); - save_unrecovered_list(); + on_project_saved(p); show_status_bar_message(tr("Saved to \"%1\" successfully").arg(p->filename())); } @@ -1242,35 +758,6 @@ Folder *Core::get_selected_folder_in_active_project() const } } -Timecode::Display Core::get_timecode_display() const -{ - return static_cast( - OAK_CONFIG("TimecodeDisplay").toInt()); -} - -void Core::set_timecode_display(Timecode::Display d) -{ - OAK_CONFIG("TimecodeDisplay") = d; - - emit timecode_display_changed(d); -} - -void Core::set_autorecovery_interval(int minutes) -{ - // Convert minutes to milliseconds - autorecovery_timer_.setInterval(minutes * 60000); -} - -void Core::copy_string_to_clipboard(const QString &s) -{ - QGuiApplication::clipboard()->setText(s); -} - -QString Core::paste_string_from_clipboard() -{ - return QGuiApplication::clipboard()->text(); -} - QString Core::get_project_filter(bool include_any_filter) { static const QVector> filters = { @@ -1305,38 +792,6 @@ QString Core::get_project_filter(bool include_any_filter) return filter_strings.join(QStringLiteral(";;")); } -QString Core::get_recent_projects_file_path() -{ - return QDir(FileFunctions::get_configuration_location()) - .filePath(QStringLiteral("recent")); -} - -void Core::set_startup_locale() -{ - // Set language - if (!core_params_.startup_language().isEmpty()) { - if (translator_->load(core_params_.startup_language()) && - QApplication::installTranslator(translator_)) { - return; - } else { - qWarning() - << "Failed to load translation file. Falling back to defaults."; - } - } - - QString use_locale = OAK_CONFIG("Language").toString(); - - if (use_locale.isEmpty()) { - // No configured locale, auto-detect the system's locale - use_locale = QLocale::system().name(); - } - - if (!set_language(use_locale)) { - qWarning() << "Trying to use locale" << use_locale - << "but couldn't find a translation for it"; - } -} - bool Core::save_project() { if (open_project_->filename().isEmpty()) { @@ -1348,20 +803,6 @@ bool Core::save_project() } } -void Core::show_status_bar_message(const QString &s, int timeout) -{ - // The main window only exists after StartGUI(); in tests and other - // contexts that construct Core without a window, do nothing. - if (main_window_) { - main_window_->statusBar()->showMessage(s, timeout); - } -} - -void Core::clear_status_bar_message() -{ - main_window_->statusBar()->clearMessage(); -} - void Core::open_recovery_project(const QString &filename) { open_project_internal(filename, true); @@ -1424,39 +865,28 @@ void Core::browse_auto_recoveries() ard.exec(); } -void Core::request_pixel_sampling_in_viewers(bool e) +void Core::show_cache_full_warning() { - if (e) { - if (pixel_sampling_users_ == 0) { - // Signal to start pixel sampling - emit color_picker_enabled(true); - } - - pixel_sampling_users_++; - } else { - pixel_sampling_users_--; - - if (pixel_sampling_users_ == 0) { - // Signal to end pixel sampling - emit color_picker_enabled(false); - } - } + QMessageBox::warning( + main_window_, tr("Disk Cache Full"), + tr("The disk cache is currently full and Oak Video Editor is having to delete old " + "frames to keep it within the limits set in the Disk preferences. This " + "will result in SIGNIFICANTLY reduced cache performance.\n\n" + "To remedy this, please do one of the following:\n\n" + "1. Manually clear the disk cache in Disk preferences.\n" + "2. Increase the maximum disk cache size in Disk preferences.\n" + "3. Reduce usage of the disk cache (e.g. disable auto-cache or only cache specific sections of your sequence).")); } -void Core::warn_cache_full() +void Core::on_active_project_changed(Project *p) { - if (!shown_cache_full_warning_ && main_window_) { - shown_cache_full_warning_ = true; + main_window_->set_project(p); - QMessageBox::warning( - main_window_, tr("Disk Cache Full"), - tr("The disk cache is currently full and Oak Video Editor is having to delete old " - "frames to keep it within the limits set in the Disk preferences. This " - "will result in SIGNIFICANTLY reduced cache performance.\n\n" - "To remedy this, please do one of the following:\n\n" - "1. Manually clear the disk cache in Disk preferences.\n" - "2. Increase the maximum disk cache size in Disk preferences.\n" - "3. Reduce usage of the disk cache (e.g. disable auto-cache or only cache specific sections of your sequence).")); + if (p) { + // Keep the window's modified state in sync with the project. The + // connection is removed automatically when the project is deleted. + connect(p, &Project::modified_changed, main_window_, + &QMainWindow::setWindowModified); } } @@ -1493,30 +923,6 @@ void Core::revert_project() revert_project_internal(false); } -void Core::push_recently_opened_project(const QString &s) -{ - if (s.isEmpty()) { - return; - } - - int existing_index = recent_projects_.indexOf(s); - - if (existing_index >= 0) { - recent_projects_.move(existing_index, 0); - } else { - recent_projects_.prepend(s); - - const int k_maximum_recent_projects = 10; - while (recent_projects_.size() > k_maximum_recent_projects) { - recent_projects_.removeLast(); - } - } - - save_recent_projects_list(); - - emit open_recent_list_changed(); -} - void Core::open_project_internal(const QString &filename, bool recovery_project) { if (open_project_) { @@ -1575,27 +981,6 @@ void Core::import_single_file(const QString &f) } } -int Core::count_files_in_file_list(const QFileInfoList &filenames) -{ - int file_count = 0; - - foreach (const QFileInfo &f, filenames) { - // For some reason QDir::NoDotAndDotDot doesn't work with entryInfoList, so we have to check manually - if (f.fileName() == "." || f.fileName() == "..") { - continue; - } else if (f.isDir()) { - QFileInfoList info_list = - QDir(f.absoluteFilePath()).entryInfoList(); - - file_count += count_files_in_file_list(info_list); - } else { - file_count++; - } - } - - return file_count; -} - bool Core::label_nodes(const QVector &nodes, MultiUndoCommand *parent) { if (nodes.isEmpty()) { @@ -1628,8 +1013,8 @@ bool Core::label_nodes(const QVector &nodes, MultiUndoCommand *parent) if (parent) { parent->add_child(rename_command); } else { - undo_stack_.push(rename_command, - tr("Renamed %1 Node(s)").arg(nodes.size())); + undo_stack()->push(rename_command, + tr("Renamed %1 Node(s)").arg(nodes.size())); } return true; @@ -1638,26 +1023,9 @@ bool Core::label_nodes(const QVector &nodes, MultiUndoCommand *parent) return false; } -Sequence *Core::create_new_sequence_for_project(const QString &format, - Project *project) -{ - Sequence *new_sequence = new Sequence(); - - // Get default name for this sequence (in the format "Sequence N", the first that doesn't exist) - int sequence_number = 1; - QString sequence_name; - do { - sequence_name = format.arg(sequence_number); - sequence_number++; - } while (project->root()->child_exists_with_name(sequence_name)); - new_sequence->set_label(sequence_name); - - return new_sequence; -} - void Core::open_project_from_recent_list(int index) { - const QString &open_fn = recent_projects_.at(index); + const QString &open_fn = get_recent_projects().at(index); if (QFileInfo::exists(open_fn)) { open_project_internal(open_fn); @@ -1667,11 +1035,7 @@ void Core::open_project_from_recent_list(int index) tr("The project \"%1\" doesn't exist. Would you like to remove this file from the recent list?") .arg(open_fn), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - recent_projects_.removeAt(index); - - save_recent_projects_list(); - - emit open_recent_list_changed(); + remove_recently_opened_project(index); } } @@ -1709,7 +1073,7 @@ bool Core::close_project(bool auto_open_new, bool ignore_modified) } // For safety, the undo stack is cleared so no commands try to affect a freed project - undo_stack_.clear(); + undo_stack()->clear(); Project *tmp = open_project_; set_active_project(nullptr); @@ -1760,94 +1124,6 @@ void Core::cache_active_sequence(bool in_out_only) } } -QString strip_windows_drive_letter(QString s) -{ - // HACK: On Windows, absolute paths are saved with a drive letter (e.g. "C:\video.mp4"). Below, - // we use Qt's relative path system to resolve when an entire project may be in a different - // folder, but the files are all in the same place relatively to the project. Unfortunately, - // Qt chooses not to understand paths from Windows on non-Windows platforms, which causes - // this to break when a project is moving from Windows to non-Windows. To resolve that, if - // we're on a non-Windows platform and we detect a Windows path (i.e. a path with a drive - // letter at the start), we strip it off. We also convert any back-slashes to forward-slashes - // because on Windows they are interchangeable and on non-Windows they are not. -#ifndef Q_OS_WINDOWS - if (s.size() >= 2) { - if (s.at(0).isLetter() && s.at(1) == ':') { - s = s.mid(2); - s.replace('\\', '/'); - } - } -#endif - - return s; -} - -bool Core::validate_footage_in_loaded_project(Project *project, - const QString &project_saved_url) -{ - QVector footage_we_couldnt_validate; - - for (Node *n : project->nodes()) { - if (Footage *footage = dynamic_cast(n)) { - QString footage_fn = strip_windows_drive_letter(footage->filename()); - QString project_fn = strip_windows_drive_letter(project_saved_url); - - if (!QFileInfo::exists(footage_fn) && - !project_saved_url.isEmpty()) { - // If the footage doesn't exist, it might have moved with the project - const QString &project_current_url = project->filename(); - - if (project_current_url != project_fn) { - // Project has definitely moved, try to resolve relative paths - QDir saved_dir(QFileInfo(project_fn).dir()); - QDir true_dir(QFileInfo(project_current_url).dir()); - - QString relative_filename = - saved_dir.relativeFilePath(footage_fn); - QString transformed_abs_filename = - true_dir.filePath(relative_filename); - - if (QFileInfo::exists(transformed_abs_filename)) { - // Use this file instead - qInfo() << "Resolved" << footage_fn << "relatively to" - << transformed_abs_filename; - footage->set_filename(transformed_abs_filename); - } - } - } - - if (QFileInfo::exists(footage->filename())) { - // Assume valid - footage->set_valid(); - } else { - footage_we_couldnt_validate.append(footage); - } - } - } - - if (!footage_we_couldnt_validate.isEmpty()) { - FootageRelinkDialog frd(footage_we_couldnt_validate, main_window_); - if (frd.exec() == QDialog::Rejected) { - return false; - } - } - - return true; -} - -bool Core::set_language(const QString &locale) -{ - QApplication::removeTranslator(translator_); - - QString resource_path = QStringLiteral(":/ts/%1").arg(locale); - if (translator_->load(resource_path) && - QApplication::installTranslator(translator_)) { - return true; - } - - return false; -} - void Core::open_project() { QString file = QFileDialog::getOpenFileName( @@ -1858,11 +1134,4 @@ void Core::open_project() } } -Core::CoreParams::CoreParams() - : mode_(k_run_normal) - , run_fullscreen_(false) - , crash_(false) -{ -} - } diff --git a/app/core.h b/app/core.h index 1675e356a..1f5628c39 100644 --- a/app/core.h +++ b/app/core.h @@ -22,18 +22,7 @@ #ifndef OAK_CORE_H #define OAK_CORE_H -#include -#include -#include -#include -#include - -#include "node/project/footage/footage.h" -#include "node/project.h" -#include "node/project/sequence/sequence.h" -#include "task/task.h" -#include "tool/tool.h" -#include "undo/undostack.h" +#include "coreengine.h" namespace olive { @@ -43,257 +32,104 @@ class MainWindow; /** * @brief The main central Olive application instance_ * - * This runs both in GUI and CLI modes (and handles what to init based on that). - * It also contains various global functions/variables for use throughout Olive. + * This is the UI-facing derivation of EngineCore. It runs both in GUI and + * CLI modes (and handles what to init based on that). All UI-independent + * engine state lives in the base class EngineCore; this class adds the main + * window, dialogs and other user interaction on top of it. * * The "public slots" are usually user-triggered actions and can be connected to UI elements (e.g. creating a folder, * opening the import dialog, etc.) */ -class Core : public QObject { +class Core : public EngineCore { Q_OBJECT public: - class CoreParams { - public: - CoreParams(); - - enum RunMode { k_run_normal, k_headless_export, k_headless_pre_cache }; - - 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; - } - - const QString &startup_language() const - { - return startup_language_; - } - - void set_startup_language(const QString &s) - { - startup_language_ = s; - } - - bool crash_on_startup() const - { - return crash_; - } - - void set_crash_on_startup(bool e) - { - crash_ = true; - } - - private: - RunMode mode_; - - QString startup_project_; - - QString startup_language_; - - bool run_fullscreen_; - - bool crash_; - }; - /** - * @brief Core Constructor - * - * Currently empty - */ + * @brief Core Constructor + * + * Registers the UI handlers that EngineCore uses to request user + * interaction. + */ Core(const CoreParams ¶ms); /** - * @brief Core object accessible from anywhere in the code - * - * Use this to access Core functions. - */ - static Core *instance(); - - static QString footage_file_dialog_filter(); - static QStringList allowed_footage_extensions(); - static bool is_footage_extension_allowed(const QString &path); - - const CoreParams &core_params() const + * @brief Core object accessible from anywhere in the code + * + * Use this to access Core functions. This is simply EngineCore::instance() + * cast to Core, which is safe because the application entry point (main()) + * always constructs a Core. + */ + static Core *instance() { - return core_params_; + return static_cast(EngineCore::instance()); } /** - * @brief Start Olive Core - * - * Main application launcher. Parses command line arguments and constructs main window (if entering a GUI mode). - */ + * @brief Start Olive Core + * + * Main application launcher. Starts the engine first, then the GUI (if entering a GUI mode). + */ void start(); /** - * @brief Stop Olive Core - * - * Ends all threads and frees all memory ready for the application to exit. - */ + * @brief Stop Olive Core + * + * Tears down the UI services first, then the engine, ready for the application to exit. + */ void stop(); /** - * @brief Retrieve main window instance_ - * - * @return - * - * Pointer to the olive::MainWindow object, or nullptr if running in CLI mode. - */ + * @brief Retrieve main window instance_ + * + * @return + * + * Pointer to the olive::MainWindow object, or nullptr if running in CLI mode. + */ MainWindow *main_window(); /** - * @brief Retrieve UndoStack object - */ - UndoStack *undo_stack(); - - /** - * @brief Import a list of files - * - * FIXME: I kind of hate this, it needs a model to update correctly. Is there a way that Items can signal enough to - * make passing references to the model unnecessary? - * - * @param urls - */ + * @brief Import a list of files + * + * FIXME: I kind of hate this, it needs a model to update correctly. Is there a way that Items can signal enough to + * make passing references to the model unnecessary? + * + * @param urls + */ void import_files(const QStringList &urls, Folder *parent); /** - * @brief Get the currently active tool - */ - const Tool::Item &tool() const; - - /** - * @brief Get the currently selected object that the add tool should make (if the add tool is active) - */ - const Tool::AddableObject &get_selected_addable_object() const; - - /** - * @brief Get the currently selected node that the transition tool should make (if the transition tool is active) - */ - const QString &get_selected_transition() const; - - /** - * @brief Get current snapping value - */ - const bool &snapping() const; - - /** - * @brief Returns a list of the most recently opened/saved projects - */ - const QStringList &get_recent_projects() const; - - /** - * @brief Get the currently active project - * - * Uses the UI/Panel system to determine which Project was the last focused on and assumes this is the active Project - * that the user wishes to work on. - * - * @return - * - * The active Project file, or nullptr if the heuristic couldn't find one. - */ + * @brief Get the currently active project + * + * Uses the UI/Panel system to determine which Project was the last focused on and assumes this is the active Project + * that the user wishes to work on. + * + * @return + * + * The active Project file, or nullptr if the heuristic couldn't find one. + */ Project *get_active_project() const; Folder *get_selected_folder_in_active_project() const; /** - * @brief Gets current timecode display mode - */ - Timecode::Display get_timecode_display() const; - - /** - * @brief Sets current timecode display mode - */ - void set_timecode_display(Timecode::Display d); - - /** - * @brief Set how frequently an autorecovery should be saved (if the project has changed, see SetProjectModified()) - */ - void set_autorecovery_interval(int minutes); - - static void copy_string_to_clipboard(const QString &s); - - static QString paste_string_from_clipboard(); - - /** - * @brief Recursively count files in a file/directory list - */ - static int count_files_in_file_list(const QFileInfoList &filenames); - - /** - * @brief Show a dialog to the user to rename a set of nodes - */ + * @brief Show a dialog to the user to rename a set of nodes + */ bool label_nodes(const QVector &nodes, MultiUndoCommand *parent = nullptr); /** - * @brief Create a new sequence named appropriately for the active project - */ - static Sequence *create_new_sequence_for_project(const QString &format, - Project *project); - static Sequence *create_new_sequence_for_project(Project *project) - { - return create_new_sequence_for_project(tr("Sequence %1"), project); - } - - /** - * @brief Opens a project from the recently opened list - */ + * @brief Opens a project from the recently opened list + */ void open_project_from_recent_list(int index); /** - * @brief Closes a project - */ + * @brief Closes a project + */ bool close_project(bool auto_open_new, bool ignore_modified = false); /** - * @brief Runs a modal cache task on the currently active sequence - */ + * @brief Runs a modal cache task on the currently active sequence + */ void cache_active_sequence(bool in_out_only); - /** - * @brief Check each footage object for whether it still exists or has changed - */ - bool validate_footage_in_loaded_project(Project *project, - const QString &project_saved_url); - - /** - * @brief Changes the current language - */ - bool set_language(const QString &locale); - - /** - * @brief Show message in main window's status bar - * - * Shorthand for Core::instance()->main_window()->statusBar()->showMessage(); - */ - void show_status_bar_message(const QString &s, int timeout = 0); - - void clear_status_bar_message(); - void open_recovery_project(const QString &filename); void open_node_in_viewer(ViewerOutput *viewer); @@ -301,307 +137,112 @@ public: void open_export_dialog_for_viewer(ViewerOutput *viewer, bool start_still_image); - bool is_magic_enabled() const - { - return magic_; - } - public slots: /** - * @brief Starts an open file dialog to load a project from file - */ + * @brief Starts an open file dialog to load a project from file + */ void open_project(); /** - * @brief Saves the current project - */ + * @brief Saves the current project + */ bool save_project(); /** - * @brief Performs a "save as" on the current project - */ + * @brief Performs a "save as" on the current project + */ bool save_project_as(); void revert_project(); /** - * @brief Set the current application-wide tool - * - * @param tool - */ - void set_tool(const Tool::Item &tool); - - /** - * @brief Set the current snapping setting - */ - void set_snapping(const bool &b); - - /** - * @brief Globally enable or disable decoding from proxy media - * - * When disabled, all footage decodes from its original media regardless of - * each footage's individual proxy setting. The per-footage settings are - * preserved and take effect again when this is re-enabled. - */ - void set_use_proxy_media(bool enabled); - - /** - * @brief Show an About dialog - */ + * @brief Show an About dialog + */ void dialog_about_show(); /** - * @brief Open the import footage dialog and import the files selected (runs ImportFiles()) - */ + * @brief Open the import footage dialog and import the files selected (runs ImportFiles()) + */ void dialog_import_show(); /** - * @brief Show Preferences dialog - */ + * @brief Show Preferences dialog + */ void dialog_preferences_show(int start_tab = 0); /** - * @brief Show Project Properties dialog - */ + * @brief Show Project Properties dialog + */ void dialog_project_properties_show(); /** - * @brief Show Export dialog - */ + * @brief Show Export dialog + */ void dialog_export_show(); /** - * @brief Show OTIO import dialog - */ + * @brief Show OTIO import dialog + */ #ifdef USE_OTIO bool DialogImportOTIOShow(const QList &sequences); #endif /** - * @brief Create a new folder in the currently active project - */ + * @brief Create a new folder in the currently active project + */ void create_new_folder(); /** - * @brief Create a new sequence in the currently active project - */ + * @brief Create a new sequence in the currently active project + */ void create_new_sequence(); - /** - * @brief Set the currently selected object that the add tool should make - */ - void set_selected_addable_object(const Tool::AddableObject &obj); - - /** - * @brief Set the currently selected object that the add tool should make - */ - void set_selected_transition_object(const QString &obj); - - /** - * @brief Clears the list of recently opened/saved projects - */ - void clear_open_recent_list(); - - /** - * @brief Creates a new empty project and opens it - */ - void create_new_project(); - void check_for_auto_recoveries(); void browse_auto_recoveries(); - void request_pixel_sampling_in_viewers(bool e); - - void warn_cache_full(); - - void set_magic(bool e) - { - magic_ = e; - } - -signals: - /** - * @brief Signal emitted when the tool is changed from somewhere - */ - void tool_changed(const Tool::Item &tool); - - /** - * @brief Signal emitted when addable object changes through SetSelectedAddableObject - */ - void addable_object_changed(Tool::AddableObject o); - - /** - * @brief Signal emitted when the snapping setting is changed - */ - void snapping_changed(const bool &b); - - /** - * @brief Signal emitted when the default timecode display mode changed - */ - void timecode_display_changed(Timecode::Display d); - - /** - * @brief Signal emitted when a change is made to the open recent list - */ - void open_recent_list_changed(); - - /** - * @brief Enable mouse color sampling functionality on all viewers - * - * This can be slow, so we only turn it on when we need it. - */ - void color_picker_enabled(bool e); - - /** - * @brief A viewer with color picked enabled has emitted a color - */ - void color_picker_color_emitted(const Color &reference, const Color &display); - private: /** - * @brief Get the file filter than can be used with QFileDialog to open and save compatible projects - */ + * @brief Get the file filter than can be used with QFileDialog to open and save compatible projects + */ static QString get_project_filter(bool include_any_filter); /** - * @brief Returns the filename where the recently opened/saved projects should be stored - */ - static QString get_recent_projects_file_path(); - - /** - * @brief Called only on startup to set the locale - */ - void set_startup_locale(); - - /** - * @brief Adds a filename to the top of the recently opened projects list (or moves it if it already exists) - */ - void push_recently_opened_project(const QString &s); - - /** - * @brief Declare custom types/classes for Qt's signal/slot system - * - * Qt's signal/slot system requires types to be declared. In the interest of doing this only at startup, we contain - * them all in a function here. - */ - void declare_types_for_qt(); - - /** - * @brief Start GUI portion of Olive - * - * Starts services and objects required for the GUI of Olive. It's guaranteed that running without this function will - * create an application instance_ that is completely valid minus the UI (e.g. for CLI modes). - */ + * @brief Start GUI portion of Olive + * + * Starts services and objects required for the GUI of Olive. It's guaranteed that running without this function will + * create an application instance_ that is completely valid minus the UI (e.g. for CLI modes). + */ void start_gui(bool full_screen); /** - * @brief Internal function for saving a project to a file - */ + * @brief Internal function for saving a project to a file + */ void save_project_internal(const QString &override_filename = QString()); /** - * @brief Retrieves the currently most active sequence for exporting - */ + * @brief Retrieves the currently most active sequence for exporting + */ ViewerOutput *get_sequence_to_export(); - static QString get_auto_recovery_index_filename(); - - void save_unrecovered_list(); - bool revert_project_internal(bool by_opening_existing); - void save_recent_projects_list(); + /** + * @brief Shows the "disk cache full" warning (connected to EngineCore::cache_full_warning_requested) + */ + void show_cache_full_warning(); /** - * @brief Adds a project to the "open projects" list - */ - void add_open_project(olive::Project *p, bool add_to_recents = false); - - bool add_open_project_from_task(Task *task, bool add_to_recents); - - void set_active_project(Project *p); + * @brief Applies a new active project to the main window (connected to EngineCore::active_project_changed) + */ + void on_active_project_changed(Project *p); /** - * @brief Internal main window object - */ + * @brief Internal main window object + */ MainWindow *main_window_; - /** - * @brief List of currently open projects - */ - Project *open_project_; - - /** - * @brief Currently active tool - */ - Tool::Item tool_; - - /** - * @brief Currently active addable object - */ - Tool::AddableObject addable_object_; - - /** - * @brief Currently selected transition - */ - QString selected_transition_; - - /** - * @brief Current snapping setting - */ - bool snapping_; - - /** - * @brief Internal timer for saving autorecovery files - */ - QTimer autorecovery_timer_; - - /** - * @brief Application-wide undo stack instance_ - */ - UndoStack undo_stack_; - - /** - * @brief List of most recently opened/saved projects - */ - QStringList recent_projects_; - - /** - * @brief Parameters set up in main() determining how the program should run - */ - CoreParams core_params_; - - /** - * @brief Static singleton core instance_ - */ - static Core *instance_; - - /** - * @brief Internal translator - */ - QTranslator *translator_; - - /** - * @brief List of projects that are unsaved but have autorecovery projects - */ - QVector autorecovered_projects_; - - /** - * @brief Do something debug related - */ - bool magic_; - - /** - * @brief How many widgets currently need pixel sampling access - */ - int pixel_sampling_users_; - - bool shown_cache_full_warning_; - private slots: - void save_autorecovery(); - void project_save_succeeded(Task *task); bool add_open_project_from_task_and_add_to_recents(Task *task) @@ -613,17 +254,13 @@ private slots: bool confirm_image_sequence(const QString &filename); - void project_was_modified(bool e); - bool start_headless_export(); void open_startup_project(); - void add_recovery_project_from_task(Task *task); - /** - * @brief Internal project open - */ + * @brief Internal project open + */ void open_project_internal(const QString &filename, bool recovery_project = false); diff --git a/app/coreengine.cpp b/app/coreengine.cpp new file mode 100644 index 000000000..ec8b0719a --- /dev/null +++ b/app/coreengine.cpp @@ -0,0 +1,924 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 Olive Team + Modifications Copyright (C) 2025 mikesolar + + 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 "coreengine.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audio/audiovisualwaveform.h" +#include "codec/conformmanager.h" +#include "codec/decoder.h" +#include "codec/proxymanager.h" +#include "common/filefunctions.h" +#include "config/config.h" +#include "node/color/colormanager/colormanager.h" +#include "node/factory.h" +#include "node/project/serializer/serializer.h" +#include "render/framemanager.h" +#include "render/rendermanager.h" +#include "task/project/load/loadbasetask.h" +#include "task/taskmanager.h" + +namespace +{ + +QStringList footage_video_extensions() +{ + return QStringList{ + QStringLiteral("mp4"), QStringLiteral("mov"), QStringLiteral("m4v"), + QStringLiteral("avi"), QStringLiteral("mpg"), QStringLiteral("mpeg"), + QStringLiteral("m2ts"), QStringLiteral("mts"), QStringLiteral("ts"), + QStringLiteral("webm"), QStringLiteral("wmv"), QStringLiteral("flv"), + QStringLiteral("3gp"), QStringLiteral("3g2"), QStringLiteral("mxf") + }; +} + +QStringList footage_audio_extensions() +{ + return QStringList{ QStringLiteral("wav"), QStringLiteral("mp3"), + QStringLiteral("flac"), QStringLiteral("aac"), + QStringLiteral("ogg"), QStringLiteral("opus"), + QStringLiteral("m4a"), QStringLiteral("alac"), + QStringLiteral("aif"), QStringLiteral("aiff"), + QStringLiteral("aifc"), QStringLiteral("wma") }; +} + +QStringList footage_image_extensions() +{ + return QStringList{ QStringLiteral("png"), QStringLiteral("jpg"), + QStringLiteral("jpeg"), QStringLiteral("tif"), + QStringLiteral("tiff"), QStringLiteral("bmp"), + QStringLiteral("gif"), QStringLiteral("exr"), + QStringLiteral("dpx"), QStringLiteral("webp") }; +} + +QString build_footage_filter_group(const QString &label, + const QStringList &extensions) +{ + QStringList patterns; + patterns.reserve(extensions.size()); + for (const QString &ext : extensions) { + patterns.append(QStringLiteral("*.%1").arg(ext)); + } + + return QStringLiteral("%1 (%2)").arg(label, + patterns.join(QLatin1Char(' '))); +} + +QString build_footage_file_dialog_filter() +{ + QStringList all = footage_video_extensions() + footage_audio_extensions() + + footage_image_extensions(); + all.removeDuplicates(); + + QStringList groups; + groups << build_footage_filter_group(QObject::tr("Common Media Files"), all); + groups << build_footage_filter_group(QObject::tr("Video Files"), + footage_video_extensions()); + groups << build_footage_filter_group(QObject::tr("Audio Files"), + footage_audio_extensions()); + groups << build_footage_filter_group(QObject::tr("Image Files"), + footage_image_extensions()); + + return groups.join(QStringLiteral(";;")); +} + +} // namespace + +namespace olive +{ + +EngineCore *EngineCore::instance_ = nullptr; + +EngineCore::EngineCore(const CoreParams ¶ms) + : open_project_(nullptr) + , tool_(Tool::k_pointer) + , addable_object_(Tool::k_addable_empty) + , snapping_(true) + , core_params_(params) + , magic_(false) + , pixel_sampling_users_(0) + , shown_cache_full_warning_(false) +{ + // Store reference to this object, making the assumption that EngineCore will only ever be made in + // main(). This will obviously break if not. + instance_ = this; + + translator_ = new QTranslator(this); +} + +EngineCore *EngineCore::instance() +{ + return instance_; +} + +QString EngineCore::footage_file_dialog_filter() +{ + return build_footage_file_dialog_filter(); +} + +QStringList EngineCore::allowed_footage_extensions() +{ + QStringList all = footage_video_extensions() + footage_audio_extensions() + + footage_image_extensions(); + all.removeDuplicates(); + return all; +} + +bool EngineCore::is_footage_extension_allowed(const QString &path) +{ + const QString ext = QFileInfo(path).suffix().toLower(); + if (ext.isEmpty()) { + return false; + } + + return allowed_footage_extensions().contains(ext); +} + +void EngineCore::declare_types_for_qt() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); +} + +void EngineCore::start() +{ + // Load application config + Config::load(); + + // Set locale based on either startup arg, config, or auto-detect + set_startup_locale(); + + // Declare custom types for Qt signal/slot system + declare_types_for_qt(); + + // Set up node factory/library + NodeFactory::initialize(); + + // Set up color manager's default config + ColorManager::set_up_default_config(); + + // Initialize task manager + TaskManager::create_instance(); + + // Initialize ConformManager + ConformManager::create_instance(); + + // Initialize ProxyManager + ProxyManager::create_instance(); + + // Initialize RenderManager + RenderManager::create_instance(); + + // Initialize FrameManager + FrameManager::create_instance(); + + // Initialize project serializers + ProjectSerializer::initialize(); + + qInfo() << "Using Qt version:" << qVersion(); + + // Start autorecovery timer using the config value as its interval + set_autorecovery_interval(OAK_CONFIG("AutorecoveryInterval").toInt()); + connect(&autorecovery_timer_, &QTimer::timeout, this, + &EngineCore::save_autorecovery); + autorecovery_timer_.start(); + + // Load recently opened projects list + { + QFile recent_projects_file(get_recent_projects_file_path()); + if (recent_projects_file.open(QFile::ReadOnly | QFile::Text)) { + QString r = QString::fromUtf8(recent_projects_file.readAll()); + if (!r.isEmpty()) { + recent_projects_ = r.split('\n'); + } + recent_projects_file.close(); + } + + emit open_recent_list_changed(); + } + + // Manual crash triggering + if (core_params_.crash_on_startup()) { + const int interval = 5000; + qInfo() << "Manual crash was triggered. Application will crash in" + << interval << "ms"; + QTimer *crash_timer = new QTimer(this); + crash_timer->setInterval(interval); + connect(crash_timer, &QTimer::timeout, this, [] { abort(); }); + crash_timer->start(); + } +} + +void EngineCore::stop() +{ + // Assume all projects have closed gracefully and no auto-recovery is necessary + autorecovered_projects_.clear(); + save_unrecovered_list(); + + // Save Config + Config::save(); + + ProjectSerializer::destroy(); + + ConformManager::destroy_instance(); + + ProxyManager::destroy_instance(); + + FrameManager::destroy_instance(); + + RenderManager::destroy_instance(); + + TaskManager::destroy_instance(); + + NodeFactory::destroy(); +} + +UndoStack *EngineCore::undo_stack() +{ + return &undo_stack_; +} + +const Tool::Item &EngineCore::tool() const +{ + return tool_; +} + +const Tool::AddableObject &EngineCore::get_selected_addable_object() const +{ + return addable_object_; +} + +const QString &EngineCore::get_selected_transition() const +{ + return selected_transition_; +} + +void EngineCore::set_selected_addable_object(const Tool::AddableObject &obj) +{ + addable_object_ = obj; + emit addable_object_changed(addable_object_); +} + +void EngineCore::set_selected_transition_object(const QString &obj) +{ + selected_transition_ = obj; +} + +void EngineCore::clear_open_recent_list() +{ + recent_projects_.clear(); + save_recent_projects_list(); + emit open_recent_list_changed(); +} + +void EngineCore::create_new_project() +{ + // If we already have an empty/new project, switch to it + bool closed = close_project_handler_ ? close_project_handler_() : + close_open_project_without_prompt(); + if (closed) { + Project *p = new Project(); + p->initialize(); + add_open_project(p); + } +} + +const bool &EngineCore::snapping() const +{ + return snapping_; +} + +const QStringList &EngineCore::get_recent_projects() const +{ + return recent_projects_; +} + +void EngineCore::set_tool(const Tool::Item &tool) +{ + tool_ = tool; + + emit tool_changed(tool_); +} + +void EngineCore::set_snapping(const bool &b) +{ + snapping_ = b; + + emit snapping_changed(snapping_); +} + +void EngineCore::set_use_proxy_media(bool enabled) +{ + Config::current()[QStringLiteral("UseProxyMedia")] = enabled; + + // Invalidate all footage so viewers re-evaluate with the new proxy state + if (open_project_) { + for (Node *n : open_project_->nodes()) { + if (Footage *footage = dynamic_cast(n)) { + footage->invalidate_all(Footage::k_filename_input); + } + } + } +} + +void EngineCore::add_open_project(Project *p, bool add_to_recents) +{ + // Ensure project is not open at the moment + if (open_project_ == p) { + return; + } + + // If we currently have an empty project, close it first + if (open_project_) { + if (close_project_handler_) { + // The return value is intentionally ignored, preserving the + // historical behavior of this function + close_project_handler_(); + } else { + close_open_project_without_prompt(); + } + } + + set_active_project(p); + + if (!p->filename().isEmpty() && add_to_recents) { + push_recently_opened_project(p->filename()); + } +} + +bool EngineCore::add_open_project_from_task(Task *task, bool add_to_recents) +{ + ProjectLoadBaseTask *load_task = static_cast(task); + + if (!load_task->is_cancelled()) { + Project *project = load_task->get_loaded_project(); + + if (validate_footage_in_loaded_project(project, project->get_saved_url())) { + add_open_project(project, add_to_recents); + + if (load_layout_handler_) { + load_layout_handler_(load_task->get_loaded_layout()); + } + + return true; + } else { + delete project; + create_new_project(); + } + } + + return false; +} + +void EngineCore::set_active_project(Project *p) +{ + open_project_ = p; + RenderManager::instance()->set_project(p); + + // The UI layer sets the project on the main window and tracks its + // modified state through this signal + emit active_project_changed(p); +} + +bool EngineCore::confirm_image_sequence(const QString &filename) +{ + if (confirm_image_sequence_handler_) { + return confirm_image_sequence_handler_(filename); + } + + // Without a UI handler (headless), accept the image sequence + return true; +} + +#ifdef USE_OTIO +bool EngineCore::show_otio_import_dialog(const QList &sequences) +{ + if (otio_import_handler_) { + return otio_import_handler_(sequences); + } + + // Without a UI handler (headless), accept the import + return true; +} +#endif + +void EngineCore::add_recovery_project_from_task(Task *task) +{ + if (add_open_project_from_task(task, false)) { + ProjectLoadBaseTask *load_task = + static_cast(task); + + Project *project = load_task->get_loaded_project(); + + // Clearing the filename will force the user to re-save it somewhere else + project->set_filename(QString()); + + // Forcing a UUID regeneration will prevent it from saving auto-recoveries in the same place + // the original project did + project->regenerate_uuid(); + + // Setting modified will ensure that the program doesn't close and lose the project without + // prompting the user first + project->set_modified(true); + } +} + +QString EngineCore::get_auto_recovery_index_filename() +{ + return QDir(QStandardPaths::writableLocation( + QStandardPaths::AppLocalDataLocation)) + .filePath(QStringLiteral("unrecovered")); +} + +void EngineCore::save_unrecovered_list() +{ + QFile autorecovery_index(get_auto_recovery_index_filename()); + + if (autorecovered_projects_.isEmpty()) { + // Recovery list is empty, delete file if exists + if (autorecovery_index.exists()) { + autorecovery_index.remove(); + } + } else if (autorecovery_index.open(QFile::WriteOnly)) { + // Overwrite recovery list with current list + QTextStream ts(&autorecovery_index); + + bool first = true; + foreach (const QUuid &uuid, autorecovered_projects_) { + if (first) { + first = false; + } else { + ts << QStringLiteral("\n"); + } + ts << uuid.toString(); + } + + autorecovery_index.close(); + } else { + qWarning() << "Failed to save unrecovered list"; + } +} + +void EngineCore::save_recent_projects_list() +{ + // Save recently opened projects + QFile recent_projects_file(get_recent_projects_file_path()); + if (recent_projects_file.open(QFile::WriteOnly | QFile::Text)) { + recent_projects_file.write(recent_projects_.join('\n').toUtf8()); + recent_projects_file.close(); + } +} + +void EngineCore::save_autorecovery() +{ + if (OAK_CONFIG("AutorecoveryEnabled").toBool()) { + if (open_project_ && !open_project_->has_autorecovery_been_saved()) { + QDir project_autorecovery_dir( + QDir(FileFunctions::get_auto_recovery_root()) + .filePath(open_project_->get_uuid().toString())); + if (FileFunctions::directory_is_valid(project_autorecovery_dir)) { + QString this_autorecovery_path = + project_autorecovery_dir.filePath( + QStringLiteral("%1.ove").arg(QString::number( + QDateTime::currentSecsSinceEpoch()))); + + // The actual save goes through the UI layer since it + // involves UI state (the main window layout) + if (save_project_handler_) { + save_project_handler_(this_autorecovery_path); + } + + open_project_->set_autorecovery_saved(true); + + // Keep track of projects that where the "newest" save is the recovery project + if (!autorecovered_projects_.contains( + open_project_->get_uuid())) { + autorecovered_projects_.append(open_project_->get_uuid()); + } + + qDebug() << "Saved auto-recovery to:" << this_autorecovery_path; + + // Write human-readable real name so it's not just a UUID + { + QFile realname_file(project_autorecovery_dir.filePath( + QStringLiteral("realname.txt"))); + realname_file.open(QFile::WriteOnly); + realname_file.write( + open_project_->pretty_filename().toUtf8()); + realname_file.close(); + } + + int64_t max_recoveries_per_file = + OAK_CONFIG("AutorecoveryMaximum").toLongLong(); + + // Since we write an extra file, increment total allowed files by 1 + max_recoveries_per_file++; + + // Delete old entries + QStringList recovery_files = project_autorecovery_dir.entryList( + QDir::Files | QDir::NoDotAndDotDot, QDir::Name); + while (recovery_files.size() > max_recoveries_per_file) { + bool deleted = false; + for (int i = 0; i < recovery_files.size(); i++) { + const QString &f = recovery_files.at(i); + + if (f.endsWith(QStringLiteral(".ove"), + Qt::CaseInsensitive)) { + QString delete_full_path = + project_autorecovery_dir.filePath(f); + qDebug() + << "Deleted old recovery:" << delete_full_path; + QFile::remove(delete_full_path); + recovery_files.removeAt(i); + deleted = true; + break; + } + } + + if (!deleted) { + // For some reason none of the files were deletable. Break so we don't end up in + // an infinite loop. + break; + } + } + } else { + // The engine cannot show dialogs, report through the + // application's registered error handler instead + Config::report_error( + tr("Auto-Recovery Error"), + tr("Failed to save auto-recovery to \"%1\". " + "Oak Video Editor may not have permission to this directory.") + .arg(project_autorecovery_dir.absolutePath())); + } + } + + // Save index + save_unrecovered_list(); + } +} + +Timecode::Display EngineCore::get_timecode_display() const +{ + return static_cast( + OAK_CONFIG("TimecodeDisplay").toInt()); +} + +void EngineCore::set_timecode_display(Timecode::Display d) +{ + OAK_CONFIG("TimecodeDisplay") = d; + + emit timecode_display_changed(d); +} + +void EngineCore::set_autorecovery_interval(int minutes) +{ + // Convert minutes to milliseconds + autorecovery_timer_.setInterval(minutes * 60000); +} + +void EngineCore::copy_string_to_clipboard(const QString &s) +{ + QGuiApplication::clipboard()->setText(s); +} + +QString EngineCore::paste_string_from_clipboard() +{ + return QGuiApplication::clipboard()->text(); +} + +QString EngineCore::get_recent_projects_file_path() +{ + return QDir(FileFunctions::get_configuration_location()) + .filePath(QStringLiteral("recent")); +} + +void EngineCore::set_startup_locale() +{ + // Set language + if (!core_params_.startup_language().isEmpty()) { + if (translator_->load(core_params_.startup_language()) && + QCoreApplication::installTranslator(translator_)) { + return; + } else { + qWarning() + << "Failed to load translation file. Falling back to defaults."; + } + } + + QString use_locale = OAK_CONFIG("Language").toString(); + + if (use_locale.isEmpty()) { + // No configured locale, auto-detect the system's locale + use_locale = QLocale::system().name(); + } + + if (!set_language(use_locale)) { + qWarning() << "Trying to use locale" << use_locale + << "but couldn't find a translation for it"; + } +} + +void EngineCore::show_status_bar_message(const QString &s, int timeout) +{ + emit status_message_show(s, timeout); +} + +void EngineCore::clear_status_bar_message() +{ + emit status_message_clear(); +} + +void EngineCore::request_pixel_sampling_in_viewers(bool e) +{ + if (e) { + if (pixel_sampling_users_ == 0) { + // Signal to start pixel sampling + emit color_picker_enabled(true); + } + + pixel_sampling_users_++; + } else { + pixel_sampling_users_--; + + if (pixel_sampling_users_ == 0) { + // Signal to end pixel sampling + emit color_picker_enabled(false); + } + } +} + +void EngineCore::warn_cache_full() +{ + if (!shown_cache_full_warning_) { + shown_cache_full_warning_ = true; + + emit cache_full_warning_requested(); + } +} + +void EngineCore::push_recently_opened_project(const QString &s) +{ + if (s.isEmpty()) { + return; + } + + int existing_index = recent_projects_.indexOf(s); + + if (existing_index >= 0) { + recent_projects_.move(existing_index, 0); + } else { + recent_projects_.prepend(s); + + const int k_maximum_recent_projects = 10; + while (recent_projects_.size() > k_maximum_recent_projects) { + recent_projects_.removeLast(); + } + } + + save_recent_projects_list(); + + emit open_recent_list_changed(); +} + +void EngineCore::on_project_saved(Project *p) +{ + push_recently_opened_project(p->filename()); + + p->set_modified(false); + + autorecovered_projects_.removeOne(p->get_uuid()); + save_unrecovered_list(); +} + +void EngineCore::remove_recently_opened_project(int index) +{ + recent_projects_.removeAt(index); + + save_recent_projects_list(); + + emit open_recent_list_changed(); +} + +int EngineCore::count_files_in_file_list(const QFileInfoList &filenames) +{ + int file_count = 0; + + foreach (const QFileInfo &f, filenames) { + // For some reason QDir::NoDotAndDotDot doesn't work with entryInfoList, so we have to check manually + if (f.fileName() == "." || f.fileName() == "..") { + continue; + } else if (f.isDir()) { + QFileInfoList info_list = + QDir(f.absoluteFilePath()).entryInfoList(); + + file_count += count_files_in_file_list(info_list); + } else { + file_count++; + } + } + + return file_count; +} + +Sequence *EngineCore::create_new_sequence_for_project(const QString &format, + Project *project) +{ + Sequence *new_sequence = new Sequence(); + + // Get default name for this sequence (in the format "Sequence N", the first that doesn't exist) + int sequence_number = 1; + QString sequence_name; + do { + sequence_name = format.arg(sequence_number); + sequence_number++; + } while (project->root()->child_exists_with_name(sequence_name)); + new_sequence->set_label(sequence_name); + + return new_sequence; +} + +QString strip_windows_drive_letter(QString s) +{ + // HACK: On Windows, absolute paths are saved with a drive letter (e.g. "C:\video.mp4"). Below, + // we use Qt's relative path system to resolve when an entire project may be in a different + // folder, but the files are all in the same place relatively to the project. Unfortunately, + // Qt chooses not to understand paths from Windows on non-Windows platforms, which causes + // this to break when a project is moving from Windows to non-Windows. To resolve that, if + // we're on a non-Windows platform and we detect a Windows path (i.e. a path with a drive + // letter at the start), we strip it off. We also convert any back-slashes to forward-slashes + // because on Windows they are interchangeable and on non-Windows they are not. +#ifndef Q_OS_WINDOWS + if (s.size() >= 2) { + if (s.at(0).isLetter() && s.at(1) == ':') { + s = s.mid(2); + s.replace('\\', '/'); + } + } +#endif + + return s; +} + +bool EngineCore::validate_footage_in_loaded_project(Project *project, + const QString &project_saved_url) +{ + QVector footage_we_couldnt_validate; + + for (Node *n : project->nodes()) { + if (Footage *footage = dynamic_cast(n)) { + QString footage_fn = strip_windows_drive_letter(footage->filename()); + QString project_fn = strip_windows_drive_letter(project_saved_url); + + if (!QFileInfo::exists(footage_fn) && + !project_saved_url.isEmpty()) { + // If the footage doesn't exist, it might have moved with the project + const QString &project_current_url = project->filename(); + + if (project_current_url != project_fn) { + // Project has definitely moved, try to resolve relative paths + QDir saved_dir(QFileInfo(project_fn).dir()); + QDir true_dir(QFileInfo(project_current_url).dir()); + + QString relative_filename = + saved_dir.relativeFilePath(footage_fn); + QString transformed_abs_filename = + true_dir.filePath(relative_filename); + + if (QFileInfo::exists(transformed_abs_filename)) { + // Use this file instead + qInfo() << "Resolved" << footage_fn << "relatively to" + << transformed_abs_filename; + footage->set_filename(transformed_abs_filename); + } + } + } + + if (QFileInfo::exists(footage->filename())) { + // Assume valid + footage->set_valid(); + } else { + footage_we_couldnt_validate.append(footage); + } + } + } + + if (!footage_we_couldnt_validate.isEmpty()) { + // Let the UI layer offer to relink the missing footage + if (relink_handler_ && !relink_handler_(footage_we_couldnt_validate)) { + return false; + } + } + + return true; +} + +bool EngineCore::set_language(const QString &locale) +{ + QCoreApplication::removeTranslator(translator_); + + QString resource_path = QStringLiteral(":/ts/%1").arg(locale); + if (translator_->load(resource_path) && + QCoreApplication::installTranslator(translator_)) { + return true; + } + + return false; +} + +bool EngineCore::close_open_project_without_prompt() +{ + if (open_project_) { + // For safety, the undo stack is cleared so no commands try to affect a freed project + undo_stack_.clear(); + + Project *tmp = open_project_; + set_active_project(nullptr); + delete tmp; + } + + return true; +} + +void EngineCore::set_confirm_image_sequence_handler( + ConfirmImageSequenceHandler handler) +{ + confirm_image_sequence_handler_ = std::move(handler); +} + +void EngineCore::set_relink_handler(FootageRelinkHandler handler) +{ + relink_handler_ = std::move(handler); +} + +void EngineCore::set_save_project_handler(SaveProjectHandler handler) +{ + save_project_handler_ = std::move(handler); +} + +void EngineCore::set_close_project_handler(CloseProjectHandler handler) +{ + close_project_handler_ = std::move(handler); +} + +void EngineCore::set_load_layout_handler(LoadLayoutHandler handler) +{ + load_layout_handler_ = std::move(handler); +} + +#ifdef USE_OTIO +void EngineCore::set_otio_import_handler(OtioImportHandler handler) +{ + otio_import_handler_ = std::move(handler); +} +#endif + +EngineCore::CoreParams::CoreParams() + : mode_(k_run_normal) + , run_fullscreen_(false) + , crash_(false) +{ +} + +} diff --git a/app/coreengine.h b/app/coreengine.h new file mode 100644 index 000000000..89fcc7c50 --- /dev/null +++ b/app/coreengine.h @@ -0,0 +1,617 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 Olive Team + Modifications Copyright (C) 2025 mikesolar + + 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 OAK_COREENGINE_H +#define OAK_COREENGINE_H + +#include +#include +#include +#include +#include + +#include + +#include "node/project/footage/footage.h" +#include "node/project.h" +#include "node/project/sequence/sequence.h" +#include "node/project/serializer/mainwindowlayoutinfo.h" +#include "task/task.h" +#include "tool/tool.h" +#include "undo/undostack.h" + +namespace olive +{ + +/** + * @brief The UI-independent engine core of the Olive application + * + * EngineCore holds the global application state that does not depend on the + * UI (no widgets, dialogs, panels or windows) and can therefore run headless + * (e.g. in the render worker or in tests). The UI layer (olive::Core) + * derives from this class and registers handlers / connects signals for + * everything that requires user interaction (see the std::function handlers + * below, modelled after Config::ErrorHandler). + * + * This class must only include engine-side headers (olive/core, node/, undo/, + * task/, timeline/, codec/, common/, config/, tool/, audio/, render/). It must + * never include widget/, dialog/, panel/, window/ or ui/ headers. + */ +class EngineCore : public QObject { + Q_OBJECT +public: + class CoreParams { + public: + CoreParams(); + + enum RunMode { k_run_normal, k_headless_export, k_headless_pre_cache }; + + 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; + } + + const QString &startup_language() const + { + return startup_language_; + } + + void set_startup_language(const QString &s) + { + startup_language_ = s; + } + + bool crash_on_startup() const + { + return crash_; + } + + void set_crash_on_startup(bool e) + { + crash_ = true; + } + + private: + RunMode mode_; + + QString startup_project_; + + QString startup_language_; + + bool run_fullscreen_; + + bool crash_; + }; + + /** + * @brief EngineCore Constructor + */ + EngineCore(const CoreParams ¶ms); + + /** + * @brief EngineCore object accessible from anywhere in the code + * + * Use this to access engine functions. This assumes the object is only + * ever constructed once at the application entry point. + */ + static EngineCore *instance(); + + static QString footage_file_dialog_filter(); + static QStringList allowed_footage_extensions(); + static bool is_footage_extension_allowed(const QString &path); + + const CoreParams &core_params() const + { + return core_params_; + } + + /** + * @brief Start the engine + * + * Initializes everything that is independent of the UI: config, locale, + * meta types, and the global engine managers (NodeFactory, ColorManager, + * TaskManager, ConformManager, ProxyManager, RenderManager, FrameManager, + * ProjectSerializer). Also loads the recent projects list and starts the + * autorecovery timer. + */ + void start(); + + /** + * @brief Stop the engine + * + * Frees the global engine managers and saves the config. In a UI build, + * call this after the UI services have been torn down. + */ + void stop(); + + /** + * @brief Retrieve UndoStack object + */ + UndoStack *undo_stack(); + + /** + * @brief Get the currently active tool + */ + const Tool::Item &tool() const; + + /** + * @brief Get the currently selected object that the add tool should make (if the add tool is active) + */ + const Tool::AddableObject &get_selected_addable_object() const; + + /** + * @brief Get the currently selected node that the transition tool should make (if the transition tool is active) + */ + const QString &get_selected_transition() const; + + /** + * @brief Get current snapping value + */ + const bool &snapping() const; + + /** + * @brief Returns a list of the most recently opened/saved projects + */ + const QStringList &get_recent_projects() const; + + /** + * @brief Gets current timecode display mode + */ + Timecode::Display get_timecode_display() const; + + /** + * @brief Sets current timecode display mode + */ + void set_timecode_display(Timecode::Display d); + + /** + * @brief Set how frequently an autorecovery should be saved (if the project has changed, see SetProjectModified()) + */ + void set_autorecovery_interval(int minutes); + + static void copy_string_to_clipboard(const QString &s); + + static QString paste_string_from_clipboard(); + + /** + * @brief Recursively count files in a file/directory list + */ + static int count_files_in_file_list(const QFileInfoList &filenames); + + /** + * @brief Create a new sequence named appropriately for the given project + */ + static Sequence *create_new_sequence_for_project(const QString &format, + Project *project); + static Sequence *create_new_sequence_for_project(Project *project) + { + return create_new_sequence_for_project(tr("Sequence %1"), project); + } + + /** + * @brief Check each footage object for whether it still exists or has changed + * + * Missing footage is passed to the relink handler registered by the UI + * layer. Without a handler, the project is accepted as-is. + */ + bool validate_footage_in_loaded_project(Project *project, + const QString &project_saved_url); + + /** + * @brief Changes the current language + */ + bool set_language(const QString &locale); + + /** + * @brief Show a message in the status bar + * + * The engine cannot show UI itself, so this only emits + * status_message_show(). The UI layer connects the signal to the main + * window's status bar. + */ + void show_status_bar_message(const QString &s, int timeout = 0); + + void clear_status_bar_message(); + + bool is_magic_enabled() const + { + return magic_; + } + + /** + * @brief Handler confirming that a file should be imported as an image sequence + * + * Registered by the UI layer (e.g. a QMessageBox-based prompt). Without + * a handler, the import is accepted. + */ + using ConfirmImageSequenceHandler = + std::function; + void set_confirm_image_sequence_handler(ConfirmImageSequenceHandler handler); + + /** + * @brief Handler offering to relink footage that could not be validated + * + * Registered by the UI layer (e.g. a FootageRelinkDialog). Should return + * false to reject the project. Without a handler, the project is accepted. + */ + using FootageRelinkHandler = std::function)>; + void set_relink_handler(FootageRelinkHandler handler); + + /** + * @brief Handler performing the actual project save for autorecovery + * + * Registered by the UI layer, since saving involves UI state (the main + * window layout). Without a handler, the actual file write is skipped. + */ + using SaveProjectHandler = + std::function; + void set_save_project_handler(SaveProjectHandler handler); + + /** + * @brief Handler closing the currently open project + * + * Registered by the UI layer, which may prompt the user to save first. + * Should return false if the close was cancelled. Without a handler, the + * project is closed silently. + */ + using CloseProjectHandler = std::function; + void set_close_project_handler(CloseProjectHandler handler); + + /** + * @brief Handler applying a loaded main window layout after a project load + */ + using LoadLayoutHandler = + std::function; + void set_load_layout_handler(LoadLayoutHandler handler); + + /** + * @brief Update engine state after a project was successfully saved + * + * Pushes the project to the top of the recent list, clears its modified + * flag and removes it from the unrecovered list. + */ + void on_project_saved(Project *p); + + /** + * @brief Removes a project from the recently opened list (e.g. if it no longer exists) + */ + void remove_recently_opened_project(int index); + +#ifdef USE_OTIO + /** + * @brief Handler showing the OTIO import options dialog + * + * Registered by the UI layer. Without a handler, the import is accepted. + */ + using OtioImportHandler = + std::function &sequences)>; + void set_otio_import_handler(OtioImportHandler handler); +#endif + +public slots: + /** + * @brief Set the current application-wide tool + * + * @param tool + */ + void set_tool(const Tool::Item &tool); + + /** + * @brief Set the current snapping setting + */ + void set_snapping(const bool &b); + + /** + * @brief Globally enable or disable decoding from proxy media + * + * When disabled, all footage decodes from its original media regardless of + * each footage's individual proxy setting. The per-footage settings are + * preserved and take effect again when this is re-enabled. + */ + void set_use_proxy_media(bool enabled); + + /** + * @brief Set the currently selected object that the add tool should make + */ + void set_selected_addable_object(const Tool::AddableObject &obj); + + /** + * @brief Set the currently selected object that the add tool should make + */ + void set_selected_transition_object(const QString &obj); + + /** + * @brief Clears the list of recently opened/saved projects + */ + void clear_open_recent_list(); + + /** + * @brief Creates a new empty project and opens it + * + * The currently open project is closed through the close handler, so the + * UI layer may prompt to save first. + */ + void create_new_project(); + + void request_pixel_sampling_in_viewers(bool e); + + /** + * @brief Warn that the disk cache is full (at most once) + * + * Only emits cache_full_warning_requested(); the UI layer shows the + * actual dialog. + */ + void warn_cache_full(); + + void set_magic(bool e) + { + magic_ = e; + } + + /** + * @brief Invokable forwarder for the image sequence confirmation + * + * Import tasks run on worker threads and call this slot through + * QMetaObject::invokeMethod with Qt::BlockingQueuedConnection. It runs + * on the main thread and forwards to the registered handler; without a + * handler, the import is accepted. + */ + bool confirm_image_sequence(const QString &filename); + +#ifdef USE_OTIO + /** + * @brief Invokable forwarder for the OTIO import dialog + * + * Same threading pattern as confirm_image_sequence(). + */ + bool show_otio_import_dialog(const QList &sequences); +#endif + +signals: + /** + * @brief Signal emitted when the tool is changed from somewhere + */ + void tool_changed(const Tool::Item &tool); + + /** + * @brief Signal emitted when addable object changes through SetSelectedAddableObject + */ + void addable_object_changed(Tool::AddableObject o); + + /** + * @brief Signal emitted when the snapping setting is changed + */ + void snapping_changed(const bool &b); + + /** + * @brief Signal emitted when the default timecode display mode changed + */ + void timecode_display_changed(Timecode::Display d); + + /** + * @brief Signal emitted when a change is made to the open recent list + */ + void open_recent_list_changed(); + + /** + * @brief Enable mouse color sampling functionality on all viewers + * + * This can be slow, so we only turn it on when we need it. + */ + void color_picker_enabled(bool e); + + /** + * @brief A viewer with color picked enabled has emitted a color + */ + void color_picker_color_emitted(const Color &reference, const Color &display); + + /** + * @brief Request showing a message in the main window's status bar + */ + void status_message_show(const QString &message, int timeout); + + /** + * @brief Request clearing the main window's status bar + */ + void status_message_clear(); + + /** + * @brief Request showing the "disk cache full" warning to the user + */ + void cache_full_warning_requested(); + + /** + * @brief Signal emitted when the active (open) project changed + * + * The UI layer uses this to set the project on the main window. + */ + void active_project_changed(Project *p); + +protected: + /** + * @brief Adds a project to the "open projects" list + */ + void add_open_project(olive::Project *p, bool add_to_recents = false); + + bool add_open_project_from_task(Task *task, bool add_to_recents); + + void set_active_project(Project *p); + + /** + * @brief Currently open project + * + * Protected so the UI layer (olive::Core) can read it. + */ + Project *open_project_; + + static QString get_auto_recovery_index_filename(); + +protected slots: + void add_recovery_project_from_task(Task *task); + +private: + /** + * @brief Returns the filename where the recently opened/saved projects should be stored + */ + static QString get_recent_projects_file_path(); + + /** + * @brief Called only on startup to set the locale + */ + void set_startup_locale(); + + /** + * @brief Adds a filename to the top of the recently opened projects list (or moves it if it already exists) + */ + void push_recently_opened_project(const QString &s); + + /** + * @brief Declare custom types/classes for Qt's signal/slot system + * + * Qt's signal/slot system requires types to be declared. In the interest of doing this only at startup, we contain + * them all in a function here. + */ + void declare_types_for_qt(); + + void save_unrecovered_list(); + + void save_recent_projects_list(); + + /** + * @brief Close the open project without any user prompt + * + * Fallback for the close handler when no UI layer is present. + */ + bool close_open_project_without_prompt(); + + /** + * @brief Currently active tool + */ + Tool::Item tool_; + + /** + * @brief Currently active addable object + */ + Tool::AddableObject addable_object_; + + /** + * @brief Currently selected transition + */ + QString selected_transition_; + + /** + * @brief Current snapping setting + */ + bool snapping_; + + /** + * @brief Internal timer for saving autorecovery files + */ + QTimer autorecovery_timer_; + + /** + * @brief Application-wide undo stack instance_ + */ + UndoStack undo_stack_; + + /** + * @brief List of most recently opened/saved projects + */ + QStringList recent_projects_; + + /** + * @brief Parameters set up in main() determining how the program should run + */ + CoreParams core_params_; + + /** + * @brief Static singleton engine instance_ + */ + static EngineCore *instance_; + + /** + * @brief Internal translator + */ + QTranslator *translator_; + + /** + * @brief List of projects that are unsaved but have autorecovery projects + */ + QVector autorecovered_projects_; + + /** + * @brief Do something debug related + */ + bool magic_; + + /** + * @brief How many widgets currently need pixel sampling access + */ + int pixel_sampling_users_; + + bool shown_cache_full_warning_; + + ConfirmImageSequenceHandler confirm_image_sequence_handler_; + + FootageRelinkHandler relink_handler_; + + SaveProjectHandler save_project_handler_; + + CloseProjectHandler close_project_handler_; + + LoadLayoutHandler load_layout_handler_; + +#ifdef USE_OTIO + OtioImportHandler otio_import_handler_; +#endif + +private slots: + void save_autorecovery(); +}; + +} + +#endif // OAK_COREENGINE_H diff --git a/app/main.cpp b/app/main.cpp index 579ad32d9..6bc161d06 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -43,6 +43,7 @@ #include "common/debug.h" #include "node/project/serializer/serializer.h" #include "version.h" +#include "window/mainwindow/mainwindow.h" #ifdef _WIN32 #include diff --git a/app/node/color/colormanager/colormanager.cpp b/app/node/color/colormanager/colormanager.cpp index 59dcce7c3..cf949098a 100644 --- a/app/node/color/colormanager/colormanager.cpp +++ b/app/node/color/colormanager/colormanager.cpp @@ -27,7 +27,7 @@ #include "common/define.h" #include "common/filefunctions.h" #include "config/config.h" -#include "core.h" +#include "node/project.h" namespace olive { diff --git a/app/node/color/ociolut/ociolut.cpp b/app/node/color/ociolut/ociolut.cpp index e96b89ba0..e9e1b54db 100644 --- a/app/node/color/ociolut/ociolut.cpp +++ b/app/node/color/ociolut/ociolut.cpp @@ -25,7 +25,7 @@ #include -#include "core.h" +#include "coreengine.h" #include "node/color/colormanager/colormanager.h" #include "render/lutlibrary.h" #include "render/previewautocacher.h" @@ -204,8 +204,8 @@ void OCIOLutNode::set_last_error(const QString &error) const // Make the error visible to the user instead of failing silently, but only // from the main process (the render worker has no status bar) - if (!error.isEmpty() && is_main_process() && Core::instance()) { - Core::instance()->show_status_bar_message(error, 10000); + if (!error.isEmpty() && is_main_process() && EngineCore::instance()) { + EngineCore::instance()->show_status_bar_message(error, 10000); } } diff --git a/app/node/distort/cornerpin/cornerpindistortnode.cpp b/app/node/distort/cornerpin/cornerpindistortnode.cpp index 32faba763..400e130db 100644 --- a/app/node/distort/cornerpin/cornerpindistortnode.cpp +++ b/app/node/distort/cornerpin/cornerpindistortnode.cpp @@ -22,7 +22,6 @@ #include "cornerpindistortnode.h" #include "common/lerp.h" -#include "core.h" namespace olive { diff --git a/app/node/distort/crop/cropdistortnode.cpp b/app/node/distort/crop/cropdistortnode.cpp index 0fb5e28da..905c4ecc6 100644 --- a/app/node/distort/crop/cropdistortnode.cpp +++ b/app/node/distort/crop/cropdistortnode.cpp @@ -22,7 +22,6 @@ #include "cropdistortnode.h" #include "common/util.h" -#include "core.h" #include "node/sliderdisplaytype.h" namespace olive diff --git a/app/node/generator/shape/shapenodebase.cpp b/app/node/generator/shape/shapenodebase.cpp index 6f59b341d..d09084ed4 100644 --- a/app/node/generator/shape/shapenodebase.cpp +++ b/app/node/generator/shape/shapenodebase.cpp @@ -25,7 +25,6 @@ #include #include "common/util.h" -#include "core.h" #include "node/nodeundo.h" namespace olive diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp index 25fb6f2aa..298588c80 100644 --- a/app/node/generator/text/textv3.cpp +++ b/app/node/generator/text/textv3.cpp @@ -26,7 +26,7 @@ #include #include "common/html.h" -#include "core.h" +#include "coreengine.h" #include "node/project.h" #include "node/nodeundo.h" @@ -300,7 +300,7 @@ void TextGeneratorV3::gizmo_deactivated() void TextGeneratorV3::set_vertical_alignment_undoable(Qt::Alignment a) { - Core::instance()->undo_stack()->push( + EngineCore::instance()->undo_stack()->push( new NodeParamSetStandardValueCommand(NodeInput(this, k_vertical_alignment_input), get_our_alignment_from_qts(a)), diff --git a/app/node/gizmo/text.cpp b/app/node/gizmo/text.cpp index 0d8f96cad..0574befd3 100644 --- a/app/node/gizmo/text.cpp +++ b/app/node/gizmo/text.cpp @@ -21,7 +21,7 @@ #include "text.h" -#include "core.h" +#include "coreengine.h" #include "undo/undocommand.h" namespace olive @@ -45,7 +45,7 @@ void TextGizmo::update_input_html(const QString &s, const Rational &time) MultiUndoCommand *command = new MultiUndoCommand(); Node::set_value_at_time(input_.input(), time, s, input_.track(), command, true); - Core::instance()->undo_stack()->push(command, tr("Edit Text")); + EngineCore::instance()->undo_stack()->push(command, tr("Edit Text")); } } diff --git a/app/node/inputdragger.cpp b/app/node/inputdragger.cpp index 7208dc6e2..40bddefe6 100644 --- a/app/node/inputdragger.cpp +++ b/app/node/inputdragger.cpp @@ -21,7 +21,6 @@ #include "inputdragger.h" -#include "core.h" #include "node.h" #include "nodeundo.h" diff --git a/app/node/node.cpp b/app/node/node.cpp index bc9f29102..91a89e472 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -27,7 +27,6 @@ #include #include "common/lerp.h" -#include "core.h" #include "config/config.h" #include "node/group/group.h" #include "node/project/serializer/typeserializer.h" diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 8f6e7ea79..979b1c02d 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -22,7 +22,7 @@ #include "viewer.h" #include "config/config.h" -#include "core.h" +#include "coreengine.h" #include "node/traverser.h" namespace olive @@ -107,7 +107,7 @@ QVariant ViewerOutput::data(const DataType &d) const case duration: { Rational using_timebase; Timecode::Display using_display = - Core::instance()->get_timecode_display(); + EngineCore::instance()->get_timecode_display(); // Get first enabled streams VideoParams video = get_first_enabled_video_stream(); diff --git a/app/node/project.cpp b/app/node/project.cpp index 74cad39f8..770ca20b8 100644 --- a/app/node/project.cpp +++ b/app/node/project.cpp @@ -27,7 +27,6 @@ #include "common/current.h" #include "common/qtutils.h" #include "common/xmlutils.h" -#include "core.h" #include "node/color/ociobase/ociobase.h" #include "node/factory.h" #include "node/group/group.h" diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 7643303a2..d2bc4e125 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -27,13 +27,15 @@ #include #include #include +#include #include "codec/decoder.h" #include "common/filefunctions.h" #include "common/qtutils.h" #include "common/xmlutils.h" #include "config/config.h" -#include "core.h" +#include "node/color/colormanager/colormanager.h" +#include "node/project.h" #include "render/job/footagejob.h" #include "ui/icons/icons.h" diff --git a/app/node/project/serializer/serializer.cpp b/app/node/project/serializer/serializer.cpp index 8645bdb1c..d0b0b4506 100644 --- a/app/node/project/serializer/serializer.cpp +++ b/app/node/project/serializer/serializer.cpp @@ -26,7 +26,7 @@ #include #include "common/xmlutils.h" -#include "core.h" +#include "coreengine.h" #include "node/group/group.h" #include "serializer190219.h" #include "serializer210528.h" @@ -156,7 +156,7 @@ ProjectSerializer::Result ProjectSerializer::load(Project *project, ProjectSerializer::Result ProjectSerializer::paste(LoadType load_type, Project *project) { - QString clipboard = Core::paste_string_from_clipboard(); + QString clipboard = EngineCore::paste_string_from_clipboard(); if (clipboard.isEmpty()) { return k_no_data; } @@ -257,7 +257,7 @@ ProjectSerializer::Result ProjectSerializer::copy(const SaveData &data) ProjectSerializer::Result res = ProjectSerializer::save(&writer, data); if (res == k_success) { - Core::copy_string_to_clipboard(copy_str); + EngineCore::copy_string_to_clipboard(copy_str); } return res; diff --git a/app/pluginSupport/oliveplugininstance.cpp b/app/pluginSupport/oliveplugininstance.cpp index ea0579fd5..a6d0bea14 100644 --- a/app/pluginSupport/oliveplugininstance.cpp +++ b/app/pluginSupport/oliveplugininstance.cpp @@ -22,7 +22,7 @@ #include "ofxCore.h" #include "ofxMessage.h" #include "common/current.h" -#include "core.h" +#include "coreengine.h" #include "dialog/progress/progress.h" #include "node/output/viewer/viewer.h" #include "panel/panelmanager.h" @@ -401,7 +401,7 @@ OfxStatus OlivePluginInstance::editEnd() "Edit Parameters"); } } - Core::instance()->undo_stack()->push(edit_command_, label); + EngineCore::instance()->undo_stack()->push(edit_command_, label); edit_command_ = nullptr; edit_label_.clear(); edit_first_label_.clear(); @@ -437,7 +437,7 @@ void OlivePluginInstance::submit_undo_command(UndoCommand *command, return; } - Core::instance()->undo_stack()->push(command, label); + EngineCore::instance()->undo_stack()->push(command, label); } void OlivePluginInstance::progressStart(const std::string &message, diff --git a/app/pluginSupport/paraminstance.cpp b/app/pluginSupport/paraminstance.cpp index 650265ed4..a56da89dc 100644 --- a/app/pluginSupport/paraminstance.cpp +++ b/app/pluginSupport/paraminstance.cpp @@ -19,6 +19,7 @@ #include "paraminstance.h" +#include "coreengine.h" #include "oliveplugininstance.h" namespace olive @@ -47,7 +48,7 @@ void submit_undo_command(const std::shared_ptr &node, return; } - Core::instance()->undo_stack()->push(command, label); + EngineCore::instance()->undo_stack()->push(command, label); } } } diff --git a/app/pluginSupport/paraminstance.h b/app/pluginSupport/paraminstance.h index 72f2770d0..663e8c777 100644 --- a/app/pluginSupport/paraminstance.h +++ b/app/pluginSupport/paraminstance.h @@ -30,7 +30,6 @@ #include "ofxhParam.h" #include "node/nodeundo.h" #include "node/plugins/plugin.h" -#include "core.h" #include "undo/undocommand.h" #include "common/current.h" #include diff --git a/app/render/diskmanager.cpp b/app/render/diskmanager.cpp index 6111b25aa..c72bbfb06 100644 --- a/app/render/diskmanager.cpp +++ b/app/render/diskmanager.cpp @@ -30,7 +30,7 @@ #include "common/filefunctions.h" #include "config/config.h" -#include "core.h" +#include "coreengine.h" #include "dialog/diskcache/diskcachedialog.h" namespace olive @@ -370,7 +370,7 @@ bool DiskCacheFolder::delete_least_recent() bool e = delete_file_internal(hash_to_delete); if (e) { - Core::instance()->warn_cache_full(); + EngineCore::instance()->warn_cache_full(); } return e; diff --git a/app/render/plugin/pluginrenderer.cpp b/app/render/plugin/pluginrenderer.cpp index f5660a63e..bfcf75738 100644 --- a/app/render/plugin/pluginrenderer.cpp +++ b/app/render/plugin/pluginrenderer.cpp @@ -41,7 +41,7 @@ #include #include #include "pluginrenderer.h" -#include "core.h" +#include "coreengine.h" #include "undo/undostack.h" #include "pluginSupport/oliveclip.h" #include "pluginSupport/oliveplugininstance.h" @@ -1350,7 +1350,7 @@ static void mark_render_failure(olive::TexturePtr destination) /// Show an error dialog and undo the last operation. Must be called from the GUI thread. static void show_error_dialog_and_undo(const QString &message) { - if (auto *core = olive::Core::instance()) { + if (auto *core = olive::EngineCore::instance()) { if (auto *stack = core->undo_stack()) { if (stack->can_undo()) { stack->undo(); diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 12b26ee3e..11a29d481 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -25,7 +25,6 @@ #include #include "config/config.h" -#include "core.h" #ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND #include "render/backend/dynamicrenderer.h" #endif diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index 080ba49be..3f8857c06 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -26,7 +26,7 @@ #include #include -#include "core.h" +#include "common/xmlutils.h" #include "ofxImageEffect.h" namespace olive diff --git a/app/task/project/import/import.cpp b/app/task/project/import/import.cpp index ba03b3167..d2d7bdf53 100644 --- a/app/task/project/import/import.cpp +++ b/app/task/project/import/import.cpp @@ -25,7 +25,7 @@ #include #include "config/config.h" -#include "core.h" +#include "coreengine.h" #include "node/nodeundo.h" #include "node/project/footage/footage.h" @@ -41,7 +41,7 @@ ProjectImportTask::ProjectImportTask(Folder *folder, filenames_.append(QFileInfo(f)); } - file_count_ = Core::count_files_in_file_list(filenames_); + file_count_ = EngineCore::count_files_in_file_list(filenames_); set_title(tr("Importing %n file(s)", nullptr, file_count_)); } @@ -176,7 +176,7 @@ void ProjectImportTask::validate_image_sequence(Footage *footage, // user just in case... bool is_sequence; - QMetaObject::invokeMethod(Core::instance(), "confirm_image_sequence", + QMetaObject::invokeMethod(EngineCore::instance(), "confirm_image_sequence", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, is_sequence), Q_ARG(QString, footage->filename())); diff --git a/app/task/project/loadotio/loadotio.cpp b/app/task/project/loadotio/loadotio.cpp index c61fedde5..dd883481f 100644 --- a/app/task/project/loadotio/loadotio.cpp +++ b/app/task/project/loadotio/loadotio.cpp @@ -33,7 +33,7 @@ #include #include -#include "core.h" +#include "coreengine.h" #include "node/audio/volume/volume.h" #include "node/block/clip/clip.h" #include "node/block/gap/gap.h" @@ -134,7 +134,7 @@ bool LoadOTIOTask::Run() // Dialog has to be called from the main thread so we pass the list of sequences here. bool accepted = false; QMetaObject::invokeMethod( - Core::instance(), "DialogImportOTIOShow", Qt::BlockingQueuedConnection, + EngineCore::instance(), "show_otio_import_dialog", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, accepted), Q_ARG(QList, timeline_sequnce_map.values())); diff --git a/app/task/project/save/save.cpp b/app/task/project/save/save.cpp index cdf749c43..23aabbf9b 100644 --- a/app/task/project/save/save.cpp +++ b/app/task/project/save/save.cpp @@ -26,7 +26,6 @@ #include #include "common/filefunctions.h" -#include "core.h" #include "node/project/serializer/serializer.h" namespace olive diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 048f42f68..2845632ae 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -26,7 +26,7 @@ #include "common/qtutils.h" #include "common/xmlutils.h" #include "config/config.h" -#include "core.h" +#include "node/project.h" #include "ui/colorcoding.h" namespace olive diff --git a/app/undo/undocommand.cpp b/app/undo/undocommand.cpp index 0782d3385..562b66a18 100644 --- a/app/undo/undocommand.cpp +++ b/app/undo/undocommand.cpp @@ -21,7 +21,7 @@ #include "undocommand.h" -#include "core.h" +#include "node/project.h" namespace olive { diff --git a/worker/workermain.cpp b/worker/workermain.cpp index 37c9679fe..150a47721 100644 --- a/worker/workermain.cpp +++ b/worker/workermain.cpp @@ -40,7 +40,7 @@ #include "common/qtutils.h" #include "config/config.h" -#include "core.h" +#include "coreengine.h" #include "node/factory.h" #include "node/input/multicam/multicamnode.h" #include "node/project/serializer/serializer.h" @@ -128,11 +128,13 @@ public: bool initialize_runtime() { - // Create a minimal Core instance so that code paths calling Core::instance() - // (e.g. ViewerOutput::data for timecode display) do not dereference null. - // The worker is short-lived; leaking this on exit is harmless. - if (!olive::Core::instance()) { - new olive::Core(olive::Core::CoreParams()); + // Create a minimal EngineCore instance so that code paths calling + // EngineCore::instance() (e.g. ViewerOutput::data for timecode display) + // do not dereference null. The worker has no UI, so the plain engine + // core is sufficient. The worker is short-lived; leaking this on exit + // is harmless. + if (!olive::EngineCore::instance()) { + new olive::EngineCore(olive::EngineCore::CoreParams()); } olive::Config::load();