diff --git a/dialogs/advancedvideodialog.cpp b/dialogs/advancedvideodialog.cpp index 15309695b..c4e910d9d 100644 --- a/dialogs/advancedvideodialog.cpp +++ b/dialogs/advancedvideodialog.cpp @@ -33,7 +33,7 @@ extern "C" { } AdvancedVideoDialog::AdvancedVideoDialog(QWidget *parent, - int encoding_codec, + AVCodecID encoding_codec, VideoCodecParams &iparams) : QDialog(parent), params_(iparams) diff --git a/dialogs/advancedvideodialog.h b/dialogs/advancedvideodialog.h index e393c062d..0ff8c1fae 100644 --- a/dialogs/advancedvideodialog.h +++ b/dialogs/advancedvideodialog.h @@ -36,11 +36,29 @@ class AdvancedVideoDialog : public QDialog { Q_OBJECT public: + /** + * @brief AdvancedVideoDialog Constructor + * + * @param parent + * + * QWidget parent. Usually ExportDialog. + * + * @param encoding_codec + * + * The AVCodecID of the selected export codec. + * + * @param iparams + * + * A VideoCodecParams struct containing the extra codec data. + */ AdvancedVideoDialog(QWidget* parent, - int encoding_codec, + AVCodecID encoding_codec, VideoCodecParams& iparams); public slots: + /** + * @brief Overrided accept for saving the UI data into the provided VideoCodecParams struct. + */ virtual void accept() override; private: /** diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index 51180a7ea..b983eca15 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -330,16 +330,16 @@ void ExportDialog::format_changed(int index) { audioGroupbox->setEnabled(audio_enabled); } -void ExportDialog::render_thread_finished() { +void ExportDialog::export_thread_finished() { // Determine if the export succeeded bool succeeded = (progressBar->value() == 100); // If it failed and we didn't cancel it, it must have errored out. Show an error message. - if (!succeeded && !et->WasInterrupted()) { + if (!succeeded && !export_thread_->WasInterrupted()) { QMessageBox::critical( this, tr("Export Failed"), - tr("Export failed - %1").arg(et->GetError()), + tr("Export failed - %1").arg(export_thread_->GetError()), QMessageBox::Ok ); } @@ -358,10 +358,10 @@ void ExportDialog::render_thread_finished() { update_ui(false); // Disconnect cancel button from export thread - disconnect(renderCancel, SIGNAL(clicked(bool)), et, SLOT(Interrupt())); + disconnect(renderCancel, SIGNAL(clicked(bool)), export_thread_, SLOT(Interrupt())); // Free the export thread - et->deleteLater(); + export_thread_->deleteLater(); // If the export succeeded, close the dialog if (succeeded) { @@ -562,12 +562,12 @@ void ExportDialog::StartExport() { } // Create export thread - et = new ExportThread(params, vcodec_params, this); + export_thread_ = new ExportThread(params, vcodec_params, this); // Connect export thread signals/slots - connect(et, SIGNAL(finished()), this, SLOT(render_thread_finished())); - connect(et, SIGNAL(ProgressChanged(int, qint64)), this, SLOT(update_progress_bar(int, qint64))); - connect(renderCancel, SIGNAL(clicked(bool)), et, SLOT(Interrupt())); + connect(export_thread_, SIGNAL(finished()), this, SLOT(export_thread_finished())); + connect(export_thread_, SIGNAL(ProgressChanged(int, qint64)), this, SLOT(update_progress_bar(int, qint64))); + connect(renderCancel, SIGNAL(clicked(bool)), export_thread_, SLOT(Interrupt())); // Close all currently open clips close_active_clips(olive::ActiveSequence.get()); @@ -580,7 +580,7 @@ void ExportDialog::StartExport() { total_export_time_start = QDateTime::currentMSecsSinceEpoch(); - et->start(); + export_thread_->start(); } } @@ -667,7 +667,7 @@ void ExportDialog::comp_type_changed(int) { } void ExportDialog::open_advanced_video_dialog() { - AdvancedVideoDialog avd(this, vcodecCombobox->currentData().toInt(), vcodec_params); + AdvancedVideoDialog avd(this, static_cast(vcodecCombobox->currentData().toInt()), vcodec_params); avd.exec(); } diff --git a/dialogs/exportdialog.h b/dialogs/exportdialog.h index b2ee2e14c..c39c8af2a 100644 --- a/dialogs/exportdialog.h +++ b/dialogs/exportdialog.h @@ -68,42 +68,205 @@ private slots: * Asks the user for the file to save to. */ void StartExport(); + + /** + * @brief Slot for the export thread to update the progress bar's value + * + * @param value + * + * An value between 0 - 100. A percentage of the Sequence that has been exported so far. + * + * @param remaining_ms + * + * The estimated time in milliseconds that it will take to complete the rest of the Sequence. + */ void update_progress_bar(int value, qint64 remaining_ms); - void render_thread_finished(); + + /** + * @brief Slot for the export thread completing (both succeeding and failing) + * + * Runs whenever the thread has finished. Determines whether the thread succeeded or not (and shows an error message + * if not), cleans up the ExportThread object, sets the UI state back to normal. + * + * Connect to ExportThread::finished(). + */ + void export_thread_finished(); + + /** + * @brief Slot for when the video codec changes + * + * Some video codecs require different settings. In the case of that, this function sorts through those. + * + * @param index + * + * Current vcodecCombobox index - its item data contains the AVCodecID. + */ void vcodec_changed(int index); + + /** + * @brief Slot for when the compression type changes + * + * Different UI objects should be displayed for different compression types. + * + * @param index + * + * Unused. + */ void comp_type_changed(int index); + + /** + * @brief Slot to open the Advanced Video Dialog + * + * Opens a dialog for setting more advanced video settings and passes a reference to vcodec_params to it. + */ void open_advanced_video_dialog(); private: + /** + * @brief Function to create UI objects. + */ void setup_ui(); + + /** + * @brief Enables/disables certain UI objects based on the exporting state. + * + * Some UI controls don't need to be set while exporting. This function enables/disables them appropriately. + * + * @param r + * + * TRUE if we're exporting, FALSE if we finished. + */ void prep_ui_for_render(bool r); - QVector format_strings; - ExportThread* et; - + /** + * @brief Retrieves the human-readable name of an AVCodecID and adds it to a QComboBox + * + * Also sets that item's data to the AVCodecID so it can be retrieved directly from the QComboBox. + * + * @param box + * + * The QComboBox to add the item to. + * + * @param codec + * + * The codec to add to the QComboBox. + */ void add_codec_to_combobox(QComboBox* box, enum AVCodecID codec); + /** + * @brief Internal array of human-readable names corresponding to enum ExportFormats + */ + QVector format_strings; + + /** + * @brief Pointer to an ExportThread + * + * Set when exporting starts, and deleted by export_thread_finished() when the thread is complete. + */ + ExportThread* export_thread_; + + /** + * @brief Struct for advanced video codec parameters. + * + * More advanced video encoding parameters to be sent to the ExportThread. These variables are not directly editable + * in this dialog, instead calling open_advanced_video_dialog() will open an AdvancedVideoDialog for setting these + * values directly. vcodec_changed() should also set these to the defaults for that codec where appropriate. + */ VideoCodecParams vcodec_params; + /** + * @brief ComboBox for selecting the time range of the Sequence to export + */ QComboBox* rangeCombobox; + + /** + * @brief SpinBox for the exported video's width + */ QSpinBox* widthSpinbox; + + /** + * @brief SpinBox for the exported video's bitrate + */ QDoubleSpinBox* videobitrateSpinbox; + + /** + * @brief Label for the exported video's bitrate - changes depending on the compression type + */ QLabel* videoBitrateLabel; + + /** + * @brief SpinBox for the exported video's frame rate + */ QDoubleSpinBox* framerateSpinbox; + + /** + * @brief ComboBox for the exported video codec + */ QComboBox* vcodecCombobox; + + /** + * @brief ComboBox for the exported audio's codec + */ QComboBox* acodecCombobox; + + /** + * @brief SpinBox for the exported audio's sample rate + */ QSpinBox* samplingRateSpinbox; + + /** + * @brief SpinBox for the exported audio's bitrate + */ QSpinBox* audiobitrateSpinbox; + + /** + * @brief Progress bar for visually showing the export progress + */ QProgressBar* progressBar; + + /** + * @brief ComboBox for the exported video's format + */ QComboBox* formatCombobox; + + /** + * @brief SpinBox for the exported video's height + */ QSpinBox* heightSpinbox; + + /** + * @brief Export button to trigger the start of an export + */ QPushButton* export_button; + + /** + * @brief Dialog cancel button to close this dialog + */ QPushButton* cancel_button; + + /** + * @brief Cancel button to abort the export before completion + */ QPushButton* renderCancel; + + /** + * @brief GroupBox containing all video-related UI objects + */ QGroupBox* videoGroupbox; + + /** + * @brief GroupBox containing all audio-related UI objects + */ QGroupBox* audioGroupbox; + + /** + * @brief ComboBox for the exported video compression type + */ QComboBox* compressionTypeCombobox; + /** + * @brief Time value set when exporting begins to determine the total duration of the export + */ qint64 total_export_time_start; }; diff --git a/dialogs/loaddialog.cpp b/dialogs/loaddialog.cpp index 92327eabb..d3399f4d5 100644 --- a/dialogs/loaddialog.cpp +++ b/dialogs/loaddialog.cpp @@ -31,7 +31,7 @@ #include "ui/sourcetable.h" #include "ui/mainwindow.h" -LoadDialog::LoadDialog(QWidget *parent, const QString& fn, bool autorecovery, bool clear) : +LoadDialog::LoadDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Loading...")); @@ -46,7 +46,7 @@ LoadDialog::LoadDialog(QWidget *parent, const QString& fn, bool autorecovery, bo layout->addWidget(bar); cancel_button = new QPushButton(tr("Cancel"), this); - connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(cancel())); + connect(cancel_button, SIGNAL(clicked(bool)), this, SIGNAL(cancel())); hboxLayout = new QHBoxLayout(); hboxLayout->addStretch(); @@ -54,27 +54,9 @@ LoadDialog::LoadDialog(QWidget *parent, const QString& fn, bool autorecovery, bo hboxLayout->addStretch(); layout->addLayout(hboxLayout); - - update(); - - lt = new LoadThread(fn, autorecovery, clear); - QObject::connect(lt, SIGNAL(success()), this, SLOT(thread_done())); - QObject::connect(lt, SIGNAL(error()), this, SLOT(die())); - QObject::connect(lt, SIGNAL(report_progress(int)), bar, SLOT(setValue(int))); - lt->start(); } -void LoadDialog::cancel() { - lt->cancel(); - lt->wait(); - die(); -} - -void LoadDialog::die() { - olive::Global->new_project(); - reject(); -} - -void LoadDialog::thread_done() { - accept(); +QProgressBar *LoadDialog::progress_bar() +{ + return bar; } diff --git a/dialogs/loaddialog.h b/dialogs/loaddialog.h index 63586d226..f7b30798a 100644 --- a/dialogs/loaddialog.h +++ b/dialogs/loaddialog.h @@ -28,15 +28,37 @@ #include "project/projectelements.h" #include "project/loadthread.h" +/** + * @brief The LoadDialog class + * + * Shows a modal dialog for loading a project and creates a LoadThread to load it. + */ class LoadDialog : public QDialog { Q_OBJECT public: - LoadDialog(QWidget* parent, const QString& filename, bool autorecovery, bool clear); -private slots: + /** + * @brief LoadDialog Constructor + * + * @param parent + * + * QWidget parent. Usually MainWindow. + * + * @param filename + * + * URL of the project file to load. + * + * @param autorecovery + * + * TRUE if this is an autorecovery project + * + * @param clear + */ + LoadDialog(QWidget* parent); + + QProgressBar* progress_bar(); +signals: void cancel(); - void die(); - void thread_done(); private: QProgressBar* bar; QPushButton* cancel_button; diff --git a/global/global.cpp b/global/global.cpp index 668586dc9..5464ac30d 100644 --- a/global/global.cpp +++ b/global/global.cpp @@ -38,6 +38,8 @@ #include "dialogs/aboutdialog.h" #include "dialogs/speeddialog.h" #include "dialogs/actionsearch.h" +#include "dialogs/loaddialog.h" +#include "project/loadthread.h" #include "timeline/sequence.h" #include "ui/mediaiconservice.h" #include "ui/mainwindow.h" @@ -91,7 +93,7 @@ void OliveGlobal::check_for_autorecovery_file() { if (QFile::exists(autorecovery_filename)) { if (QMessageBox::question(nullptr, tr("Auto-recovery"), tr("Olive didn't close properly and an autorecovery file was detected. Would you like to open it?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { enable_load_project_on_init = false; - open_project_worker(autorecovery_filename, true); + OpenProjectWorker(autorecovery_filename, true); } } autorecovery_timer.setInterval(60000); @@ -164,6 +166,33 @@ void OliveGlobal::SetNativeStyling(QWidget *w) #endif } +void OliveGlobal::LoadProject(const QString &fn, bool autorecovery, bool clear) +{ + // Normally, the user will be closing the previous project to load a new one, but just in case the user + // is importing a new project + + if (clear) { + new_project(); + } + + LoadDialog ld(olive::MainWindow); + + LoadThread* lt = new LoadThread(fn, autorecovery, clear); + connect(&ld, SIGNAL(cancel()), lt, SLOT(cancel())); + connect(lt, SIGNAL(success()), &ld, SLOT(accept())); + connect(lt, SIGNAL(error()), &ld, SLOT(reject())); + connect(lt, SIGNAL(error()), this, SLOT(new_project())); + connect(lt, SIGNAL(report_progress(int)), ld.progress_bar(), SLOT(setValue(int))); + lt->start(); + + ld.exec(); +} + +void OliveGlobal::ImportProject(const QString &fn) +{ + LoadProject(fn, false, false); +} + void OliveGlobal::new_project() { if (can_close_project()) { // clear graph editor @@ -172,8 +201,12 @@ void OliveGlobal::new_project() { // clear effects panel panel_effect_controls->Clear(true); + // clear existing project + olive::Global->set_sequence(nullptr); + panel_footage_viewer->set_media(nullptr); + // clear project contents (footage, sequences, etc.) - panel_project->new_project(); + panel_project->clear(); // clear undo stack olive::UndoStack.clear(); @@ -183,13 +216,16 @@ void OliveGlobal::new_project() { // full update of all panels update_ui(false); + + // set to unmodified + olive::Global->set_modified(false); } } -void OliveGlobal::open_project() { +void OliveGlobal::OpenProject() { QString fn = QFileDialog::getOpenFileName(olive::MainWindow, tr("Open Project..."), "", project_file_filter); if (!fn.isEmpty() && can_close_project()) { - open_project_worker(fn, false); + OpenProjectWorker(fn, false); } } @@ -205,7 +241,7 @@ void OliveGlobal::open_recent(int index) { panel_project->save_recent_projects(); } } else if (can_close_project()) { - open_project_worker(recent_url, false); + OpenProjectWorker(recent_url, false); } } @@ -269,7 +305,7 @@ void OliveGlobal::finished_initialize() { // if a project was set as a command line argument, we load it here if (QFileInfo::exists(olive::ActiveProjectFilename)) { - open_project_worker(olive::ActiveProjectFilename, false); + OpenProjectWorker(olive::ActiveProjectFilename, false); } else { QMessageBox::critical(olive::MainWindow, tr("Missing Project File"), @@ -323,9 +359,9 @@ void OliveGlobal::set_sequence(SequencePtr s) panel_timeline->setFocus(); } -void OliveGlobal::open_project_worker(const QString& fn, bool autorecovery) { +void OliveGlobal::OpenProjectWorker(const QString& fn, bool autorecovery) { update_project_filename(fn); - panel_project->load_project(fn, autorecovery, true); + LoadProject(fn, autorecovery, true); olive::UndoStack.clear(); } diff --git a/global/global.h b/global/global.h index 4c4cc0872..560295f7c 100644 --- a/global/global.h +++ b/global/global.h @@ -193,7 +193,18 @@ public slots: * Confirms whether the current project can be closed, and if so, shows an open file dialog to allow the user to * select a project file and then triggers a project load with it. */ - void open_project(); + void OpenProject(); + + /** + * @brief Import project from file + * + * Imports an Olive project into the current project, effectively merging them. + * + * @param fn + * + * The filename of the project to import. + */ + void ImportProject(const QString& fn); /** * @brief Open recent project from list @@ -327,7 +338,35 @@ private: * beside the original project file so that it does not overwrite the original and so that the user is not working * on the autorecovery project in Olive's application data directory. */ - void open_project_worker(const QString& fn, bool autorecovery); + void OpenProjectWorker(const QString& fn, bool autorecovery); + + /** + * @brief Create a LoadDialog and start a LoadThread to load data from a project + * + * Loads data from an Olive project file creating a LoadDialog to show visual information and a LoadThread to load + * outside of the main/GUI thread. + * + * All project loading functions eventually lead to this one and there's no reason to use it directly. Instead use + * one of the following functions: + * + * * OpenProject() - to check if the current project can be closed and prompt the user for the new project file + * * OpenProjectWorker() - if you already have the filename and wish to close the current project and open it + * * ImportProject() - to import a project file into this one, effectively merging them both + * + * @param fn + * + * The URL of the project file to open + * + * @param autorecovery + * + * TRUE if this file is an autorecovery file, in which case it's loaded slightly differently + * + * @param clear + * + * TRUE if the current project should be closed before opening, FALSE if the project should be imported into the + * currently open one. + */ + void LoadProject(const QString& fn, bool autorecovery, bool clear); /** * @brief File filter used for any file dialogs relating to Olive project files. diff --git a/panels/project.cpp b/panels/project.cpp index 2ef1ec72e..74e051941 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -111,7 +111,7 @@ Project::Project(QWidget *parent) : QPushButton* toolbar_open = new QPushButton(); toolbar_open->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/open.svg"))); toolbar_open->setToolTip("Open Project"); - connect(toolbar_open, SIGNAL(clicked(bool)), olive::Global.get(), SLOT(open_project())); + connect(toolbar_open, SIGNAL(clicked(bool)), olive::Global.get(), SLOT(OpenProject())); toolbar->addWidget(toolbar_open); QPushButton* toolbar_save = new QPushButton(); @@ -724,7 +724,7 @@ void Project::process_file_list(QStringList& files, bool recursive, MediaPtr rep QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { // load the project without clearing the current one - load_project(file, false, false); + olive::Global->ImportProject(file); } @@ -1041,27 +1041,6 @@ void Project::clear() { tree_view->update(); } -void Project::new_project() { - // clear existing project - olive::Global->set_sequence(nullptr); - panel_footage_viewer->set_media(nullptr); - clear(); - olive::Global->set_modified(false); -} - -void Project::load_project(const QString& filename, bool autorecovery, bool clear) { - - // Normally, the user will be closing the previous project to load a new one, but just in case the user - // is importing a new project - - if (clear) { - new_project(); - } - - LoadDialog ld(this, filename, autorecovery, clear); - ld.exec(); -} - void save_marker(QXmlStreamWriter& stream, const Marker& m) { stream.writeStartElement("marker"); stream.writeAttribute("frame", QString::number(m.frame)); diff --git a/panels/project.h b/panels/project.h index 4ee1d779f..ace9e4404 100644 --- a/panels/project.h +++ b/panels/project.h @@ -66,8 +66,6 @@ public: bool reveal_media(Media *media, QModelIndex parent = QModelIndex()); void add_recent_project(QString url); - void new_project(); - void load_project(const QString &filename, bool autorecovery, bool clear); void save_project(bool autorecovery); MediaPtr create_folder_internal(QString name); diff --git a/project/loadthread.h b/project/loadthread.h index 84badc682..be69b9e97 100644 --- a/project/loadthread.h +++ b/project/loadthread.h @@ -37,6 +37,7 @@ class LoadThread : public QThread public: LoadThread(const QString& filename, bool autorecovery, bool clear); void run(); +public slots: void cancel(); signals: void start_question(const QString &title, const QString &text, int buttons); diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 32bbc81df..6a53d5ad0 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -453,7 +453,7 @@ void MainWindow::setup_menus() { new_menu = MenuHelper::create_submenu(file_menu); olive::MenuHelper.make_new_menu(new_menu); - open_project = MenuHelper::create_menu_action(file_menu, "openproj", olive::Global.get(), SLOT(open_project()), QKeySequence("Ctrl+O")); + open_project = MenuHelper::create_menu_action(file_menu, "openproj", olive::Global.get(), SLOT(OpenProject()), QKeySequence("Ctrl+O")); open_recent = MenuHelper::create_submenu(file_menu);