diff --git a/decoders/decoder.cpp b/decoders/decoder.cpp new file mode 100644 index 000000000..8de99a7b3 --- /dev/null +++ b/decoders/decoder.cpp @@ -0,0 +1,6 @@ +#include "decoder.h" + +Decoder::Decoder() +{ + +} diff --git a/decoders/decoder.h b/decoders/decoder.h new file mode 100644 index 000000000..8c2bd04d0 --- /dev/null +++ b/decoders/decoder.h @@ -0,0 +1,11 @@ +#ifndef DECODER_H +#define DECODER_H + + +class Decoder +{ +public: + Decoder(); +}; + +#endif // DECODER_H \ No newline at end of file diff --git a/decoders/ffmpegdecoder.cpp b/decoders/ffmpegdecoder.cpp new file mode 100644 index 000000000..26cf3c579 --- /dev/null +++ b/decoders/ffmpegdecoder.cpp @@ -0,0 +1,6 @@ +#include "ffmpegdecoder.h" + +FFmpegDecoder::FFmpegDecoder() +{ + +} diff --git a/decoders/ffmpegdecoder.h b/decoders/ffmpegdecoder.h new file mode 100644 index 000000000..52f614f40 --- /dev/null +++ b/decoders/ffmpegdecoder.h @@ -0,0 +1,11 @@ +#ifndef FFMPEGDECODER_H +#define FFMPEGDECODER_H + + +class FFmpegDecoder +{ +public: + FFmpegDecoder(); +}; + +#endif // FFMPEGDECODER_H \ No newline at end of file diff --git a/dialogs/aboutdialog.cpp b/dialogs/aboutdialog.cpp index 30eeea00a..c116bfe83 100644 --- a/dialogs/aboutdialog.cpp +++ b/dialogs/aboutdialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/aboutdialog.h b/dialogs/aboutdialog.h index 08ec184e1..d50a1b751 100644 --- a/dialogs/aboutdialog.h +++ b/dialogs/aboutdialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/actionsearch.cpp b/dialogs/actionsearch.cpp index c64968322..6803aea81 100644 --- a/dialogs/actionsearch.cpp +++ b/dialogs/actionsearch.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/actionsearch.h b/dialogs/actionsearch.h index 54688a71e..af82320ad 100644 --- a/dialogs/actionsearch.h +++ b/dialogs/actionsearch.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/advancedvideodialog.cpp b/dialogs/advancedvideodialog.cpp index c4e910d9d..91a0428e2 100644 --- a/dialogs/advancedvideodialog.cpp +++ b/dialogs/advancedvideodialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/advancedvideodialog.h b/dialogs/advancedvideodialog.h index 70cd92142..5ea80ea91 100644 --- a/dialogs/advancedvideodialog.h +++ b/dialogs/advancedvideodialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/autocutsilencedialog.cpp b/dialogs/autocutsilencedialog.cpp index 9280dbe66..e5176bcb2 100644 --- a/dialogs/autocutsilencedialog.cpp +++ b/dialogs/autocutsilencedialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -31,7 +31,7 @@ #include "panels/panels.h" #include "panels/timeline.h" -AutoCutSilenceDialog::AutoCutSilenceDialog(QWidget *parent, QVector clips) : +AutoCutSilenceDialog::AutoCutSilenceDialog(QWidget *parent, QVector clips) : QDialog(parent), clips_(clips) { @@ -124,16 +124,16 @@ void AutoCutSilenceDialog::cut_silence() { // Loop over clips provided to this dialog for (int j=0;jclips.at(clips_.at(j)).get(); + Clip* clip = clips_.at(j); // Check if this clip is an audio footage clip - if (clip->track() >= 0 + if (clip->type() == olive::kTypeAudio && clip->media() != nullptr && clip->media_stream()->preview_done) { // TODO provide warning for preview not being done QVector split_positions; - int clip_start = clip->timeline_in(); + long clip_start = clip->timeline_in(); const FootageStream* ms = clip->media_stream(); long media_length = clip->media_length(); @@ -156,7 +156,7 @@ void AutoCutSilenceDialog::cut_silence() { // read the current sample into the circular array qint8 tmp = 0; - for (int k=start; kaudio_preview.at(k))))); } vols[circular_index] = tmp; @@ -202,13 +202,13 @@ void AutoCutSilenceDialog::cut_silence() { } } - panel_timeline->split_clip_at_positions(ca, clips_.at(j), split_positions); + clip->track()->sequence()->SplitClipAtPositions(ca, clips_.at(j), split_positions); } } if (ca->hasActions()) { - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); } else { delete ca; } diff --git a/dialogs/autocutsilencedialog.h b/dialogs/autocutsilencedialog.h index 6f156679c..37fe57a61 100644 --- a/dialogs/autocutsilencedialog.h +++ b/dialogs/autocutsilencedialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -31,7 +31,7 @@ class AutoCutSilenceDialog : public QDialog { Q_OBJECT public: - AutoCutSilenceDialog(QWidget* parent, QVector clips); + AutoCutSilenceDialog(QWidget* parent, QVector clips); public slots: virtual int exec() override; private slots: @@ -39,7 +39,7 @@ private slots: private: void cut_silence(); - QVector clips_; + QVector clips_; LabelSlider* attack_threshold; LabelSlider* release_threshold; diff --git a/dialogs/clippropertiesdialog.cpp b/dialogs/clippropertiesdialog.cpp index 545ec7ca6..9a5f51f43 100644 --- a/dialogs/clippropertiesdialog.cpp +++ b/dialogs/clippropertiesdialog.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "clippropertiesdialog.h" #include @@ -72,7 +92,7 @@ ClipPropertiesDialog::ClipPropertiesDialog(QWidget *parent, QVector clip } // it's assumed all the clips come from the same sequence - duration_field_->SetFrameRate(first_clip->sequence->frame_rate); + duration_field_->SetFrameRate(first_clip->track()->sequence()->frame_rate); if (all_clips_have_same_duration) { duration_field_->SetDefault(first_clip->length()); @@ -104,7 +124,7 @@ void ClipPropertiesDialog::accept() long clip_duration_rounded = qRound(clip_duration); if (clip->length() != clip_duration_rounded) { - clip->move(ca, + clip->Move(ca, clip->timeline_in(), clip->timeline_in() + clip_duration_rounded, clip->clip_in(), @@ -115,7 +135,7 @@ void ClipPropertiesDialog::accept() } if (ca->hasActions()) { - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); update_ui(false); } else { delete ca; diff --git a/dialogs/clippropertiesdialog.h b/dialogs/clippropertiesdialog.h index 439f870de..0a08d9de8 100644 --- a/dialogs/clippropertiesdialog.h +++ b/dialogs/clippropertiesdialog.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef CLIPPROPERTIESDIALOG_H #define CLIPPROPERTIESDIALOG_H diff --git a/dialogs/debugdialog.cpp b/dialogs/debugdialog.cpp index 6205d661a..817ee452d 100644 --- a/dialogs/debugdialog.cpp +++ b/dialogs/debugdialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/debugdialog.h b/dialogs/debugdialog.h index 6d5797dc0..d6bade443 100644 --- a/dialogs/debugdialog.h +++ b/dialogs/debugdialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/demonotice.cpp b/dialogs/demonotice.cpp index e91570131..818208211 100644 --- a/dialogs/demonotice.cpp +++ b/dialogs/demonotice.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/demonotice.h b/dialogs/demonotice.h index 0407864ab..39f14dc0b 100644 --- a/dialogs/demonotice.h +++ b/dialogs/demonotice.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index db4b69345..797bcf946 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -68,14 +68,15 @@ enum ExportFormats { FORMAT_SIZE }; -ExportDialog::ExportDialog(QWidget *parent) : - QDialog(parent) +ExportDialog::ExportDialog(QWidget *parent, Sequence* sequence) : + QDialog(parent), + sequence_(sequence) { - setWindowTitle(tr("Export \"%1\"").arg(olive::ActiveSequence->name)); + setWindowTitle(tr("Export \"%1\"").arg(sequence->name)); setup_ui(); rangeCombobox->setCurrentIndex(0); - if (olive::ActiveSequence->using_workarea) { + if (sequence->using_workarea) { rangeCombobox->setEnabled(true); rangeCombobox->setCurrentIndex(1); } @@ -109,10 +110,10 @@ ExportDialog::ExportDialog(QWidget *parent) : formatCombobox->setCurrentIndex(FORMAT_MPEG4); // default to sequence's native dimensions - widthSpinbox->setValue(olive::ActiveSequence->width); - heightSpinbox->setValue(olive::ActiveSequence->height); - samplingRateSpinbox->setValue(olive::ActiveSequence->audio_frequency); - framerateSpinbox->setValue(olive::ActiveSequence->frame_rate); + widthSpinbox->setValue(sequence->width); + heightSpinbox->setValue(sequence->height); + samplingRateSpinbox->setValue(sequence->audio_frequency); + framerateSpinbox->setValue(sequence->frame_rate); // set some advanced defaults vcodec_params.threads = 0; @@ -351,8 +352,8 @@ void ExportDialog::export_thread_finished() { prep_ui_for_render(false); // Move OpenGL context back to the sequence viewer - panel_sequence_viewer->viewer_widget->makeCurrent(); - panel_sequence_viewer->viewer_widget->initializeGL(); + panel_sequence_viewer->viewer_widget()->makeCurrent(); + panel_sequence_viewer->viewer_widget()->initializeGL(); // Update the application UI update_ui(false); @@ -537,6 +538,7 @@ void ExportDialog::StartExport() { // Set up export parameters to send to the ExportThread ExportParams params; + params.sequence = sequence_; params.filename = filename; params.video_enabled = videoGroupbox->isChecked(); if (params.video_enabled) { @@ -555,10 +557,10 @@ void ExportDialog::StartExport() { } params.start_frame = 0; - params.end_frame = olive::ActiveSequence->getEndFrame(); // entire sequence + params.end_frame = sequence_->GetEndFrame(); // entire sequence if (rangeCombobox->currentIndex() == 1) { - params.start_frame = qMax(olive::ActiveSequence->workarea_in, params.start_frame); - params.end_frame = qMin(olive::ActiveSequence->workarea_out, params.end_frame); + params.start_frame = qMax(sequence_->workarea_in, params.start_frame); + params.end_frame = qMin(sequence_->workarea_out, params.end_frame); } // Create export thread @@ -572,10 +574,10 @@ void ExportDialog::StartExport() { // Close all effects in effect controls (prevents UI threading issues) panel_effect_controls->Clear(); - olive::Global->set_rendering_state(true); - // Close all currently open clips - close_active_clips(olive::ActiveSequence.get()); + sequence_->Close(); + + olive::Global->set_export_state(true); olive::Global->save_autorecovery_file(); @@ -654,11 +656,11 @@ void ExportDialog::comp_type_changed(int) { case COMPRESSION_TYPE_CBR: case COMPRESSION_TYPE_TARGETBR: videoBitrateLabel->setText(tr("Bitrate (Mbps):")); - videobitrateSpinbox->setValue(qMax(0.5, (double) qRound((0.01528 * olive::ActiveSequence->height) - 4.5))); + videobitrateSpinbox->setValue(qMax(0.5, (double) qRound((0.01528 * sequence_->height) - 4.5))); break; case COMPRESSION_TYPE_CFR: videoBitrateLabel->setText(tr("Quality (CRF):")); - videobitrateSpinbox->setValue(36); + videobitrateSpinbox->setValue(23); videobitrateSpinbox->setMaximum(51); videobitrateSpinbox->setToolTip(tr("Quality Factor:\n\n0 = lossless\n17-18 = visually lossless (compressed, but unnoticeable)\n23 = high quality\n51 = lowest quality possible")); break; diff --git a/dialogs/exportdialog.h b/dialogs/exportdialog.h index bb5a4e486..d76d359f3 100644 --- a/dialogs/exportdialog.h +++ b/dialogs/exportdialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -50,7 +50,7 @@ public: * * QWidget parent. Usually MainWindow. */ - explicit ExportDialog(QWidget *parent); + explicit ExportDialog(QWidget *parent, Sequence *sequence); private slots: /** @@ -270,6 +270,8 @@ private: * @brief Time value set when exporting begins to determine the total duration of the export */ qint64 total_export_time_start; + + Sequence* sequence_; }; #endif // EXPORTDIALOG_H diff --git a/dialogs/loaddialog.cpp b/dialogs/loaddialog.cpp index dfcbb7f5b..198ca3651 100644 --- a/dialogs/loaddialog.cpp +++ b/dialogs/loaddialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/loaddialog.h b/dialogs/loaddialog.h index ea13f515f..24acca8af 100644 --- a/dialogs/loaddialog.h +++ b/dialogs/loaddialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/mediapropertiesdialog.cpp b/dialogs/mediapropertiesdialog.cpp index 3ea790c55..01154ffb6 100644 --- a/dialogs/mediapropertiesdialog.cpp +++ b/dialogs/mediapropertiesdialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -30,6 +30,8 @@ #include #include #include +#include +namespace OCIO = OCIO_NAMESPACE::v1; #include "project/footage.h" #include "project/media.h" @@ -103,7 +105,7 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : // premultiplied alpha mode premultiply_alpha_setting = new QCheckBox(tr("Alpha is Premultiplied"), this); - premultiply_alpha_setting->setChecked(f->alpha_is_premultiplied); + premultiply_alpha_setting->setChecked(f->alpha_is_associated); grid->addWidget(premultiply_alpha_setting, row, 0); row++; @@ -112,12 +114,12 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : interlacing_box = new QComboBox(this); interlacing_box->addItem( tr("Auto (%1)").arg( - get_interlacing_name(f->video_tracks.at(0).video_auto_interlacing) + Footage::get_interlacing_name(f->video_tracks.at(0).video_auto_interlacing) ) ); - interlacing_box->addItem(get_interlacing_name(VIDEO_PROGRESSIVE)); - interlacing_box->addItem(get_interlacing_name(VIDEO_TOP_FIELD_FIRST)); - interlacing_box->addItem(get_interlacing_name(VIDEO_BOTTOM_FIELD_FIRST)); + interlacing_box->addItem(Footage::get_interlacing_name(VIDEO_PROGRESSIVE)); + interlacing_box->addItem(Footage::get_interlacing_name(VIDEO_TOP_FIELD_FIRST)); + interlacing_box->addItem(Footage::get_interlacing_name(VIDEO_BOTTOM_FIELD_FIRST)); interlacing_box->setCurrentIndex( (f->video_tracks.at(0).video_auto_interlacing == f->video_tracks.at(0).video_interlacing) @@ -128,6 +130,28 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : grid->addWidget(interlacing_box, row, 1); row++; + + input_color_space = new QComboBox(this); + + OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + + QString footage_colorspace = f->Colorspace(); + + for (int i=0;igetNumColorSpaces();i++) { + QString colorspace = config->getColorSpaceNameByIndex(i); + + input_color_space->addItem(colorspace); + + if (colorspace == footage_colorspace) { + input_color_space->setCurrentIndex(i); + } + } + + grid->addWidget(new QLabel(tr("Color Space:")), row, 0); + grid->addWidget(input_color_space, row, 1); + + row++; + } name_box = new QLineEdit(item->get_name(), this); @@ -192,9 +216,11 @@ void MediaPropertiesDialog::accept() { } // set premultiplied alpha - f->alpha_is_premultiplied = premultiply_alpha_setting->isChecked(); + f->alpha_is_associated = premultiply_alpha_setting->isChecked(); } + f->SetColorspace(input_color_space->currentText()); + // set name MediaRename* mr = new MediaRename(item, name_box->text()); @@ -206,7 +232,7 @@ void MediaPropertiesDialog::accept() { } ca->appendPost(new UpdateViewer()); - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); QDialog::accept(); } diff --git a/dialogs/mediapropertiesdialog.h b/dialogs/mediapropertiesdialog.h index a13d5e7ec..6e964e6ed 100644 --- a/dialogs/mediapropertiesdialog.h +++ b/dialogs/mediapropertiesdialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -82,6 +82,11 @@ private: * @brief Setting for associated/premultiplied alpha */ QCheckBox* premultiply_alpha_setting; + + /** + * @brief Setting for this media's color space + */ + QComboBox* input_color_space; private slots: /** * @brief Overridden accept function for saving the properties back to the Media class diff --git a/dialogs/newsequencedialog.cpp b/dialogs/newsequencedialog.cpp index 67b3537c0..940c046cb 100644 --- a/dialogs/newsequencedialog.cpp +++ b/dialogs/newsequencedialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -108,8 +108,8 @@ void NewSequenceDialog::accept() { s->audio_layout = AV_CH_LAYOUT_STEREO; ComboAction* ca = new ComboAction(); - panel_project->create_sequence_internal(ca, s, true, nullptr); - olive::UndoStack.push(ca); + olive::project_model.CreateSequence(ca, s, true, nullptr); + olive::undo_stack.push(ca); } else if (existing_item != nullptr) { @@ -128,14 +128,12 @@ void NewSequenceDialog::accept() { esc->audio_layout = AV_CH_LAYOUT_STEREO; ca->append(esc); - for (int i=0;iclips.size();i++) { - ClipPtr c = existing_sequence->clips.at(i); - if (c != nullptr) { - c->refactor_frame_rate(ca, multiplier, true); - } + QVector existing_sequence_clips = existing_sequence->GetAllClips(); + for (int i=0;irefactor_frame_rate(ca, multiplier, true); } - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); } else if (existing_sequence != nullptr) { @@ -235,13 +233,13 @@ void NewSequenceDialog::setup_ui() { videoLayout->addWidget(new QLabel(tr("Width:"), this), 0, 0, 1, 1); width_numeric = new QSpinBox(videoGroupBox); width_numeric->setMaximum(9999); - width_numeric->setValue(olive::CurrentConfig.default_sequence_width); + width_numeric->setValue(olive::config.default_sequence_width); videoLayout->addWidget(width_numeric, 0, 2, 1, 2); videoLayout->addWidget(new QLabel(tr("Height:"), this), 1, 0, 1, 2); height_numeric = new QSpinBox(videoGroupBox); height_numeric->setMaximum(9999); - height_numeric->setValue(olive::CurrentConfig.default_sequence_height); + height_numeric->setValue(olive::config.default_sequence_height); videoLayout->addWidget(height_numeric, 1, 2, 1, 2); videoLayout->addWidget(new QLabel(tr("Frame Rate:"), this), 2, 0, 1, 1); @@ -258,7 +256,7 @@ void NewSequenceDialog::setup_ui() { frame_rate_combobox->addItem("59.94 FPS", 59.94); frame_rate_combobox->addItem("60 FPS", 60.0); for (int i=0;icount();i++) { - if (qFuzzyCompare(frame_rate_combobox->itemData(i).toDouble(), olive::CurrentConfig.default_sequence_framerate)) { + if (qFuzzyCompare(frame_rate_combobox->itemData(i).toDouble(), olive::config.default_sequence_framerate)) { frame_rate_combobox->setCurrentIndex(i); } } @@ -272,8 +270,6 @@ void NewSequenceDialog::setup_ui() { videoLayout->addWidget(new QLabel(tr("Interlacing:"), this), 6, 0, 1, 1); interlacing_combobox = new QComboBox(videoGroupBox); interlacing_combobox->addItem(tr("None (Progressive)")); - // interlacing_combobox->addItem("Upper Field First"); - // interlacing_combobox->addItem("Lower Field First"); videoLayout->addWidget(interlacing_combobox, 6, 2, 1, 2); verticalLayout->addWidget(videoGroupBox); @@ -288,7 +284,7 @@ void NewSequenceDialog::setup_ui() { audio_frequency_combobox = new QComboBox(audioGroupBox); combobox_audio_sample_rates(audio_frequency_combobox); for (int i=0;icount();i++) { - if (audio_frequency_combobox->itemData(i) == olive::CurrentConfig.default_sequence_audio_frequency) { + if (audio_frequency_combobox->itemData(i) == olive::config.default_sequence_audio_frequency) { audio_frequency_combobox->setCurrentIndex(i); } } diff --git a/dialogs/newsequencedialog.h b/dialogs/newsequencedialog.h index 4e5ad8c34..301173b32 100644 --- a/dialogs/newsequencedialog.h +++ b/dialogs/newsequencedialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/preferencesdialog.cpp b/dialogs/preferencesdialog.cpp index 628e1f610..7b1ce1a3f 100644 --- a/dialogs/preferencesdialog.cpp +++ b/dialogs/preferencesdialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -48,6 +48,7 @@ #include "global/config.h" #include "global/path.h" #include "rendering/audio.h" +#include "rendering/pixelformats.h" #include "panels/panels.h" #include "ui/columnedgridlayout.h" #include "ui/mainwindow.h" @@ -89,11 +90,11 @@ PreferencesDialog::PreferencesDialog(QWidget *parent) : // set up default sequence default_sequence.name = tr("Default Sequence"); - default_sequence.width = olive::CurrentConfig.default_sequence_width; - default_sequence.height = olive::CurrentConfig.default_sequence_height; - default_sequence.frame_rate = olive::CurrentConfig.default_sequence_framerate; - default_sequence.audio_frequency = olive::CurrentConfig.default_sequence_audio_frequency; - default_sequence.audio_layout = olive::CurrentConfig.default_sequence_audio_channel_layout; + default_sequence.width = olive::config.default_sequence_width; + default_sequence.height = olive::config.default_sequence_height; + default_sequence.frame_rate = olive::config.default_sequence_framerate; + default_sequence.audio_frequency = olive::config.default_sequence_audio_frequency; + default_sequence.audio_layout = olive::config.default_sequence_audio_channel_layout; } void PreferencesDialog::setup_kbd_shortcut_worker(QMenu* menu, QTreeWidgetItem* parent) { @@ -118,12 +119,28 @@ void PreferencesDialog::setup_kbd_shortcut_worker(QMenu* menu, QTreeWidgetItem* } } -void PreferencesDialog::delete_previews(char type) { - if (type != 't' && type != 'w' && type != 1) return; +void PreferencesDialog::delete_previews(PreviewDeleteTypes type) { + char delete_char = 0; + + switch (type) { + case DELETE_WAVEFORMS: + delete_char = 'w'; + break; + case DELETE_THUMBNAILS: + delete_char = 't'; + break; + case DELETE_BOTH: + delete_char = 1; + break; + case DELETE_NONE: + break; + } + + if (delete_char != 't' && delete_char != 'w' && delete_char != 1) return; QDir preview_path(get_data_path() + "/previews"); - if (type == 1) { + if (delete_char == 1) { // indiscriminately delete everything preview_path.removeRecursively(); } else { @@ -144,13 +161,124 @@ void PreferencesDialog::delete_previews(char type) { // thumbnails will have a 't' towards the end of the filenames, waveforms will have a 'w' // if they match the type of preview we're deleting, remove them - if (preview_file_str.at(identifier_char_index) == type) { + if (preview_file_str.at(identifier_char_index) == delete_char) { QFile::remove(preview_path.filePath(preview_file_str)); } } } } +void PreferencesDialog::populate_ocio_menus(OCIO::ConstConfigRcPtr config) +{ + if (!config) { + + // Just clear everything + ocio_display->clear(); + ocio_default_input->clear(); + ocio_view->clear(); + ocio_look->clear(); + + } else { + + // Get input color spaces for setting the default input color space + ocio_default_input->clear(); + for (int i=0;igetNumColorSpaces();i++) { + QString colorspace = config->getColorSpaceNameByIndex(i); + + ocio_default_input->addItem(colorspace); + + if (colorspace == olive::config.ocio_default_input_colorspace) { + ocio_default_input->setCurrentIndex(i); + } + } + + // Get current display name (if the config is empty, get the current default display) + QString current_display = olive::config.ocio_display; + if (current_display.isEmpty()) { + current_display = config->getDefaultDisplay(); + } + + // Populate the display menu + ocio_display->clear(); + for (int i=0;igetNumDisplays();i++) { + ocio_display->addItem(config->getDisplay(i)); + + // Check if this index is the currently selected + if (config->getDisplay(i) == current_display) { + ocio_display->setCurrentIndex(i); + } + } + + update_ocio_view_menu(config); + + // Populate the look menu + ocio_look->clear(); + ocio_look->addItem(tr("(None)"), QString()); + for (int i=0;igetNumLooks();i++) { + const char* look = config->getLookNameByIndex(i); + + ocio_look->addItem(look, look); + + if (look == olive::config.ocio_look) { + ocio_look->setCurrentIndex(i); + } + } + + } +} + +OCIO::ConstConfigRcPtr PreferencesDialog::TestOCIOConfig(const QString &url) +{ + // Check whether OCIO can load it + OCIO::ConstConfigRcPtr config; + try { + config = OCIO::Config::CreateFromFile(url.toUtf8()); + } catch (OCIO::Exception& e) { + QMessageBox::critical(this, + tr("OpenColorIO Config Error"), + tr("Failed to set OpenColorIO configuration: %1").arg(e.what()), + QMessageBox::Ok); + } + return config; +} + +void PreferencesDialog::update_ocio_view_menu(OCIO::ConstConfigRcPtr config) +{ + + // Get views for the current display set in `ocio_display` + QString display = ocio_display->currentText(); + + // Get current view + QString current_view = olive::config.ocio_view; + if (current_view.isEmpty()) { + current_view = config->getDefaultView(display.toUtf8()); + } + + // Populate the view menu + int ocio_view_count = config->getNumViews(display.toUtf8()); + ocio_view->clear(); + for (int i=0;igetView(display.toUtf8(), i); + + ocio_view->addItem(view); + + if (current_view == view) { + ocio_view->setCurrentIndex(i); + } + } +} + +void PreferencesDialog::update_ocio_config(const QString &s) +{ + OCIO::ConstConfigRcPtr file_config; + + if (!s.isEmpty() && QFileInfo::exists(s)) { + file_config = TestOCIOConfig(s); + } + + populate_ocio_menus(file_config); +} + void PreferencesDialog::AddBoolPair(QCheckBox *ui, bool *value, bool restart_required) { bool_ui.append(ui); @@ -188,6 +316,8 @@ void PreferencesDialog::accept() { bool reinit_audio = false; bool reload_language = false; bool reload_effects = false; + bool reset_ocio_shaders = false; + bool reset_render_threads = false; // Validate whether the specified CSS file exists if (!custom_css_fn->text().isEmpty() && !QFileInfo::exists(custom_css_fn->text())) { @@ -199,11 +329,41 @@ void PreferencesDialog::accept() { return; } - // Validate whether the effects panel should refresh itself - if (olive::CurrentConfig.effect_textbox_lines != effect_textbox_lines_field->value()) { - reload_effects = true; + // Validate whether the chosen OCIO configuration file + if (enable_color_management->isChecked()) { + + // Check whether the file exists + if (!QFileInfo::exists(ocio_config_file->text())) { + + QString msg_title = tr("Invalid OpenColorIO Configuration File"); + QString msg_body; + + if (ocio_config_file->text().isEmpty()) { + msg_body = tr("You must specify an OpenColorIO configuration file if color management is enabled."); + } else { + msg_body = tr("OpenColorIO configuration file '%1' does not exist.").arg(ocio_config_file->text()); + } + + QMessageBox::critical( + this, + msg_title, + msg_body + ); + return; + + } else if (olive::config.ocio_config_path != ocio_config_file->text()) { + + // Check whether OCIO can load it + OCIO::ConstConfigRcPtr file_config = TestOCIOConfig(ocio_config_file->text().toUtf8()); + + if (!file_config) { + return; + } + + } } + // Validate whether one of the bool options requires a restart bool bool_requires_restart = false; for (int i=0;ivalue() - || olive::CurrentConfig.waveform_resolution != waveform_res_spinbox->value() - || olive::CurrentConfig.css_path != custom_css_fn->text() - || olive::CurrentConfig.style != static_cast(ui_style->currentData().toInt())) { + || olive::config.thumbnail_resolution != thumbnail_res_spinbox->value() + || olive::config.waveform_resolution != waveform_res_spinbox->value() + || olive::config.css_path != custom_css_fn->text() + || olive::config.style != static_cast(ui_style->currentData().toInt())) { // any changes to these settings will require a restart - ask the user if we should do one now or later @@ -244,78 +404,107 @@ void PreferencesDialog::accept() { } + + // Everything checks out, start saving settings from the UI to the backend + olive::config.css_path = custom_css_fn->text(); + olive::config.recording_mode = recordingComboBox->currentIndex() + 1; + olive::config.img_seq_formats = imgSeqFormatEdit->text(); + olive::config.upcoming_queue_size = upcoming_queue_spinbox->value(); + olive::config.upcoming_queue_type = upcoming_queue_type->currentIndex(); + olive::config.previous_queue_size = previous_queue_spinbox->value(); + olive::config.previous_queue_type = previous_queue_type->currentIndex(); + // Audio settings may require the audio device to be re-initiated. - if (olive::CurrentConfig.preferred_audio_output != audio_output_devices->currentData().toString() - || olive::CurrentConfig.preferred_audio_input != audio_input_devices->currentData().toString() - || olive::CurrentConfig.audio_rate != audio_sample_rate->currentData().toInt()) { + if (olive::config.preferred_audio_output != audio_output_devices->currentData().toString() + || olive::config.preferred_audio_input != audio_input_devices->currentData().toString() + || olive::config.audio_rate != audio_sample_rate->currentData().toInt()) { reinit_audio = true; } + olive::config.preferred_audio_output = audio_output_devices->currentData().toString(); + olive::config.preferred_audio_input = audio_input_devices->currentData().toString(); + olive::config.audio_rate = audio_sample_rate->currentData().toInt(); + + olive::config.effect_textbox_lines = effect_textbox_lines_field->value(); // see if the language file should be reloaded (not necessary if the app is restarting anyway) if (!restart_after_saving - && olive::CurrentConfig.language_file != language_combobox->currentData().toString()) { + && olive::config.language_file != language_combobox->currentData().toString()) { reload_language = true; } + olive::config.language_file = language_combobox->currentData().toString(); - // save settings from UI to backend - olive::CurrentConfig.css_path = custom_css_fn->text(); - olive::CurrentConfig.recording_mode = recordingComboBox->currentIndex() + 1; - olive::CurrentConfig.img_seq_formats = imgSeqFormatEdit->text(); - olive::CurrentConfig.upcoming_queue_size = upcoming_queue_spinbox->value(); - olive::CurrentConfig.upcoming_queue_type = upcoming_queue_type->currentIndex(); - olive::CurrentConfig.previous_queue_size = previous_queue_spinbox->value(); - olive::CurrentConfig.previous_queue_type = previous_queue_type->currentIndex(); + // Check whether OCIO settings will require a reset of the render threads + if (olive::config.playback_bit_depth != playback_bit_depth->currentIndex() + || olive::config.export_bit_depth != export_bit_depth->currentIndex()) { + reset_render_threads = true; + } + if (olive::config.ocio_config_path != ocio_config_file->text() + || olive::config.ocio_display != ocio_display->currentText() + || olive::config.ocio_view != ocio_view->currentText() + || olive::config.ocio_look != ocio_look->currentData().toString()) { + reset_ocio_shaders = true; + } + if (olive::config.ocio_config_path != ocio_config_file->text()) { + OCIO::SetCurrentConfig(OCIO::Config::CreateFromFile(ocio_config_file->text().toUtf8())); + olive::config.ocio_config_path = ocio_config_file->text(); + } + olive::config.enable_color_management = enable_color_management->isChecked(); + olive::config.playback_bit_depth = playback_bit_depth->currentIndex(); + olive::config.export_bit_depth = export_bit_depth->currentIndex(); + olive::config.ocio_display = ocio_display->currentText(); + olive::config.ocio_default_input_colorspace = ocio_default_input->currentText(); + olive::config.ocio_view = ocio_view->currentText(); - olive::CurrentConfig.preferred_audio_output = audio_output_devices->currentData().toString(); - olive::CurrentConfig.preferred_audio_input = audio_input_devices->currentData().toString(); - olive::CurrentConfig.audio_rate = audio_sample_rate->currentData().toInt(); + // We use data here instead of text because there's a "(None)" option with an empty string + olive::config.ocio_look = ocio_look->currentData().toString(); - olive::CurrentConfig.effect_textbox_lines = effect_textbox_lines_field->value(); - olive::CurrentConfig.language_file = language_combobox->currentData().toString(); - olive::CurrentConfig.default_sequence_width = default_sequence.width; - olive::CurrentConfig.default_sequence_height = default_sequence.height; - olive::CurrentConfig.default_sequence_framerate = default_sequence.frame_rate; - olive::CurrentConfig.default_sequence_audio_frequency = default_sequence.audio_frequency; - olive::CurrentConfig.default_sequence_audio_channel_layout = default_sequence.audio_layout; + // Set default sequence options + olive::config.default_sequence_width = default_sequence.width; + olive::config.default_sequence_height = default_sequence.height; + olive::config.default_sequence_framerate = default_sequence.frame_rate; + olive::config.default_sequence_audio_frequency = default_sequence.audio_frequency; + olive::config.default_sequence_audio_channel_layout = default_sequence.audio_layout; + // Set all bool options for (int i=0;iisChecked(); } - olive::CurrentConfig.style = static_cast(ui_style->currentData().toInt()); + // Set new style + olive::config.style = static_cast(ui_style->currentData().toInt()); - // Check if the thumbnail or waveform icon - if (olive::CurrentConfig.thumbnail_resolution != thumbnail_res_spinbox->value() - || olive::CurrentConfig.waveform_resolution != waveform_res_spinbox->value()) { + // Check if the thumbnail or waveform icon fields have changed, we may need to recreate the previews if so + if (olive::config.thumbnail_resolution != thumbnail_res_spinbox->value() + || olive::config.waveform_resolution != waveform_res_spinbox->value()) { // we're changing the size of thumbnails and waveforms, so let's delete them and regenerate them next start // delete nothing - char delete_match = 0; + PreviewDeleteTypes delete_type = DELETE_NONE; - if (olive::CurrentConfig.thumbnail_resolution != thumbnail_res_spinbox->value()) { + if (olive::config.thumbnail_resolution != thumbnail_res_spinbox->value()) { // delete existing thumbnails - olive::CurrentConfig.thumbnail_resolution = thumbnail_res_spinbox->value(); + olive::config.thumbnail_resolution = thumbnail_res_spinbox->value(); // delete only thumbnails - delete_match = 't'; + delete_type = DELETE_THUMBNAILS; } - if (olive::CurrentConfig.waveform_resolution != waveform_res_spinbox->value()) { + if (olive::config.waveform_resolution != waveform_res_spinbox->value()) { // delete existing waveforms - olive::CurrentConfig.waveform_resolution = waveform_res_spinbox->value(); + olive::config.waveform_resolution = waveform_res_spinbox->value(); // if we're already deleting thumbnails - if (delete_match == 't') { + if (delete_type == DELETE_THUMBNAILS) { // delete all - delete_match = 1; + delete_type = DELETE_BOTH; } else { // just delete waveforms - delete_match = 'w'; + delete_type = DELETE_WAVEFORMS; } } - delete_previews(delete_match); + delete_previews(delete_type); } // Save keyboard shortcuts @@ -323,29 +512,46 @@ void PreferencesDialog::accept() { key_shortcut_fields.at(i)->set_action_shortcut(); } - // Audio settings may require the audio device to be re-initiated. - if (reinit_audio) { - init_audio(); - } - - if (reload_effects) { - panel_effect_controls->Reload(); - } - - // reload language file if it changed - if (reload_language) { - olive::Global->load_translation_from_config(); - } - QDialog::accept(); if (restart_after_saving) { + // since we already ran can_close_project(), bypass checking again by running set_modified(false) olive::Global->set_modified(false); olive::MainWindow->close(); QProcess::startDetached(QApplication::applicationFilePath(), { olive::ActiveProjectFilename }); + } else { + + // Audio settings may require the audio device to be re-initiated. + if (reinit_audio) { + init_audio(); + } + + if (reload_effects) { + panel_effect_controls->Reload(); + } + + // reload language file if it changed + if (reload_language) { + olive::Global->load_translation_from_config(); + } + + if (reset_render_threads) { + if (panel_footage_viewer->seq != nullptr) { + panel_footage_viewer->seq->Close(); + } + panel_footage_viewer->viewer_widget()->get_renderer()->delete_ctx(); + if (panel_sequence_viewer->seq != nullptr) { + panel_sequence_viewer->seq->Close(); + } + panel_sequence_viewer->viewer_widget()->get_renderer()->delete_ctx(); + } else if (reset_ocio_shaders) { + panel_footage_viewer->viewer_widget()->get_renderer()->destroy_ocio(); + panel_sequence_viewer->viewer_widget()->get_renderer()->destroy_ocio(); + } + } } @@ -471,15 +677,30 @@ void PreferencesDialog::browse_css_file() { } } +void PreferencesDialog::browse_ocio_config() +{ + QString fn = QFileDialog::getOpenFileName(this, tr("Browse for OpenColorIO configuration")); + if (!fn.isEmpty()) { + ocio_config_file->setText(fn); + enable_color_management->setChecked(true); + } +} + +void PreferencesDialog::update_ocio_view_menu() +{ + update_ocio_view_menu(OCIO::GetCurrentConfig()); +} + void PreferencesDialog::delete_all_previews() { if (QMessageBox::question(this, tr("Delete All Previews"), tr("Are you sure you want to delete all previews?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - delete_previews(1); + delete_previews(DELETE_BOTH); QMessageBox::information(this, tr("Previews Deleted"), - tr("All previews deleted successfully. You may have to re-open your current project for changes to take effect."), + tr("All previews deleted successfully. You may have to re-open your current project for " + "changes to take effect."), QMessageBox::Ok); } } @@ -528,7 +749,7 @@ void PreferencesDialog::setup_ui() { QString locale_str = locale_file_basename.mid(locale_file_basename.lastIndexOf('_')+1); language_combobox->addItem(QLocale(locale_str).nativeLanguageName(), locale_relative_path); - if (olive::CurrentConfig.language_file == locale_relative_path) { + if (olive::config.language_file == locale_relative_path) { language_combobox->setCurrentIndex(language_combobox->count() - 1); } } @@ -543,8 +764,7 @@ void PreferencesDialog::setup_ui() { general_layout->addWidget(new QLabel(tr("Image sequence formats:"), this), row, 0); imgSeqFormatEdit = new QLineEdit(general_tab); - imgSeqFormatEdit->setText(olive::CurrentConfig.img_seq_formats); - + imgSeqFormatEdit->setText(olive::config.img_seq_formats); general_layout->addWidget(imgSeqFormatEdit, row, 1, 1, 4); row++; @@ -555,7 +775,7 @@ void PreferencesDialog::setup_ui() { thumbnail_res_spinbox = new QSpinBox(this); thumbnail_res_spinbox->setMinimum(0); thumbnail_res_spinbox->setMaximum(INT_MAX); - thumbnail_res_spinbox->setValue(olive::CurrentConfig.thumbnail_resolution); + thumbnail_res_spinbox->setValue(olive::config.thumbnail_resolution); general_layout->addWidget(thumbnail_res_spinbox, row, 1); general_layout->addWidget(new QLabel(tr("Waveform Resolution:"), this), row, 2); @@ -563,7 +783,7 @@ void PreferencesDialog::setup_ui() { waveform_res_spinbox = new QSpinBox(this); waveform_res_spinbox->setMinimum(0); waveform_res_spinbox->setMaximum(INT_MAX); - waveform_res_spinbox->setValue(olive::CurrentConfig.waveform_resolution); + waveform_res_spinbox->setValue(olive::config.waveform_resolution); general_layout->addWidget(waveform_res_spinbox, row, 3); QPushButton* delete_preview_btn = new QPushButton(tr("Delete Previews")); @@ -572,17 +792,27 @@ void PreferencesDialog::setup_ui() { row++; + QHBoxLayout* misc_general = new QHBoxLayout(); + // General -> Use Software Fallbacks When Possible QCheckBox* use_software_fallbacks_checkbox = new QCheckBox(tr("Use Software Fallbacks When Possible")); - AddBoolPair(use_software_fallbacks_checkbox, &olive::CurrentConfig.use_software_fallback, true); - general_layout->addWidget(use_software_fallbacks_checkbox, row, 0, 1, 4); + AddBoolPair(use_software_fallbacks_checkbox, &olive::config.use_software_fallback, true); + misc_general->addWidget(use_software_fallbacks_checkbox); - row++; + // General -> Don't Use Proxies When Exporting + QCheckBox* dont_use_proxies_when_exporting = new QCheckBox(tr("Don't Use Proxies When Exporting")); + dont_use_proxies_when_exporting->setToolTip(tr("Use originals instead of proxies when exporting")); + AddBoolPair(dont_use_proxies_when_exporting, &olive::config.dont_use_proxies_on_export); + misc_general->addWidget(dont_use_proxies_when_exporting); // General -> Default Sequence Settings QPushButton* default_sequence_settings = new QPushButton(tr("Default Sequence Settings")); connect(default_sequence_settings, SIGNAL(clicked(bool)), this, SLOT(edit_default_sequence_settings())); - general_layout->addWidget(default_sequence_settings); + misc_general->addWidget(default_sequence_settings); + + general_layout->addLayout(misc_general, row, 0, 1, 5); + + row++; tabWidget->addTab(general_tab, tr("General")); @@ -593,68 +823,68 @@ void PreferencesDialog::setup_ui() { ColumnedGridLayout* behavior_tab_layout = new ColumnedGridLayout(behavior_tab, 2); QCheckBox* add_default_effects_to_clips = new QCheckBox(tr("Add Default Effects to New Clips")); - AddBoolPair(add_default_effects_to_clips, &olive::CurrentConfig.add_default_effects_to_clips); + AddBoolPair(add_default_effects_to_clips, &olive::config.add_default_effects_to_clips); behavior_tab_layout->Add(add_default_effects_to_clips); QCheckBox* auto_seek_to_beginning = new QCheckBox(tr("Automatically Seek to the Beginning When Playing at the End of a Sequence")); - AddBoolPair(auto_seek_to_beginning, &olive::CurrentConfig.auto_seek_to_beginning); + AddBoolPair(auto_seek_to_beginning, &olive::config.auto_seek_to_beginning); behavior_tab_layout->Add(auto_seek_to_beginning); QCheckBox* selecting_also_seeks = new QCheckBox(tr("Selecting Also Seeks")); - AddBoolPair(selecting_also_seeks, &olive::CurrentConfig.select_also_seeks); + AddBoolPair(selecting_also_seeks, &olive::config.select_also_seeks); behavior_tab_layout->Add(selecting_also_seeks); QCheckBox* edit_tool_also_seeks = new QCheckBox(tr("Edit Tool Also Seeks")); - AddBoolPair(edit_tool_also_seeks, &olive::CurrentConfig.edit_tool_also_seeks); + AddBoolPair(edit_tool_also_seeks, &olive::config.edit_tool_also_seeks); behavior_tab_layout->Add(edit_tool_also_seeks); QCheckBox* edit_tool_selects_links = new QCheckBox(tr("Edit Tool Selects Links")); - AddBoolPair(edit_tool_selects_links, &olive::CurrentConfig.edit_tool_selects_links); + AddBoolPair(edit_tool_selects_links, &olive::config.edit_tool_selects_links); behavior_tab_layout->Add(edit_tool_selects_links); QCheckBox* seek_also_selects = new QCheckBox(tr("Seek Also Selects")); - AddBoolPair(seek_also_selects, &olive::CurrentConfig.seek_also_selects); + AddBoolPair(seek_also_selects, &olive::config.seek_also_selects); behavior_tab_layout->Add(seek_also_selects); QCheckBox* seek_to_end_of_pastes = new QCheckBox(tr("Seek to the End of Pastes")); - AddBoolPair(seek_to_end_of_pastes, &olive::CurrentConfig.paste_seeks); + AddBoolPair(seek_to_end_of_pastes, &olive::config.paste_seeks); behavior_tab_layout->Add(seek_to_end_of_pastes); QCheckBox* scroll_wheel_zooms = new QCheckBox(tr("Scroll Wheel Zooms")); scroll_wheel_zooms->setToolTip(tr("Hold CTRL to toggle this setting")); - AddBoolPair(scroll_wheel_zooms, &olive::CurrentConfig.scroll_zooms); + AddBoolPair(scroll_wheel_zooms, &olive::config.scroll_zooms); behavior_tab_layout->Add(scroll_wheel_zooms); QCheckBox* invert_timeline_scroll_axes = new QCheckBox(tr("Invert Timeline Scroll Axes")); - AddBoolPair(invert_timeline_scroll_axes, &olive::CurrentConfig.invert_timeline_scroll_axes); + AddBoolPair(invert_timeline_scroll_axes, &olive::config.invert_timeline_scroll_axes); behavior_tab_layout->Add(invert_timeline_scroll_axes); QCheckBox* enable_drag_files_to_timeline = new QCheckBox(tr("Enable Drag Files to Timeline")); - AddBoolPair(enable_drag_files_to_timeline, &olive::CurrentConfig.enable_drag_files_to_timeline); + AddBoolPair(enable_drag_files_to_timeline, &olive::config.enable_drag_files_to_timeline); behavior_tab_layout->Add(enable_drag_files_to_timeline); QCheckBox* autoscale_by_default = new QCheckBox(tr("Auto-Scale By Default")); - AddBoolPair(autoscale_by_default, &olive::CurrentConfig.autoscale_by_default); + AddBoolPair(autoscale_by_default, &olive::config.autoscale_by_default); behavior_tab_layout->Add(autoscale_by_default); QCheckBox* enable_seek_to_import = new QCheckBox(tr("Auto-Seek to Imported Clips")); - AddBoolPair(enable_seek_to_import, &olive::CurrentConfig.enable_seek_to_import); + AddBoolPair(enable_seek_to_import, &olive::config.enable_seek_to_import); behavior_tab_layout->Add(enable_seek_to_import); QCheckBox* enable_audio_scrubbing = new QCheckBox(tr("Audio Scrubbing")); - AddBoolPair(enable_audio_scrubbing, &olive::CurrentConfig.enable_audio_scrubbing); + AddBoolPair(enable_audio_scrubbing, &olive::config.enable_audio_scrubbing); behavior_tab_layout->Add(enable_audio_scrubbing); QCheckBox* enable_drop_on_media_to_replace = new QCheckBox(tr("Drop Files on Media to Replace")); - AddBoolPair(enable_drop_on_media_to_replace, &olive::CurrentConfig.drop_on_media_to_replace); + AddBoolPair(enable_drop_on_media_to_replace, &olive::config.drop_on_media_to_replace); behavior_tab_layout->Add(enable_drop_on_media_to_replace); QCheckBox* enable_hover_focus = new QCheckBox(tr("Enable Hover Focus")); - AddBoolPair(enable_hover_focus, &olive::CurrentConfig.hover_focus); + AddBoolPair(enable_hover_focus, &olive::config.hover_focus); behavior_tab_layout->Add(enable_hover_focus); QCheckBox* set_name_and_marker = new QCheckBox(tr("Ask For Name When Setting Marker")); - AddBoolPair(set_name_and_marker, &olive::CurrentConfig.set_name_with_marker); + AddBoolPair(set_name_and_marker, &olive::config.set_name_with_marker); behavior_tab_layout->Add(set_name_and_marker); // Appearance @@ -673,7 +903,7 @@ void PreferencesDialog::setup_ui() { ui_style->addItem(tr("Olive Light"), olive::styling::kOliveDefaultLight); ui_style->addItem(tr("Native"), olive::styling::kNativeDarkIcons); ui_style->addItem(tr("Native (Light Icons)"), olive::styling::kNativeLightIcons); - ui_style->setCurrentIndex(olive::CurrentConfig.style); + ui_style->setCurrentIndex(olive::config.style); appearance_layout->addWidget(ui_style, row, 1, 1, 2); row++; @@ -682,7 +912,7 @@ void PreferencesDialog::setup_ui() { // Native menu styling is only available on Windows. Environments like Ubuntu and Mac use the native menu system by // default QCheckBox* native_menus = new QCheckBox(tr("Use Native Menu Styling")); - AddBoolPair(native_menus, &olive::CurrentConfig.use_native_menu_styling, true); + AddBoolPair(native_menus, &olive::config.use_native_menu_styling, true); appearance_layout->addWidget(native_menus, row, 0, 1, 3); row++; @@ -692,7 +922,7 @@ void PreferencesDialog::setup_ui() { appearance_layout->addWidget(new QLabel(tr("Custom CSS:"), this), row, 0); custom_css_fn = new QLineEdit(general_tab); - custom_css_fn->setText(olive::CurrentConfig.css_path); + custom_css_fn->setText(olive::config.css_path); appearance_layout->addWidget(custom_css_fn, row, 1); QPushButton* custom_css_browse = new QPushButton(tr("Browse"), general_tab); @@ -706,7 +936,7 @@ void PreferencesDialog::setup_ui() { effect_textbox_lines_field = new QSpinBox(general_tab); effect_textbox_lines_field->setMinimum(1); - effect_textbox_lines_field->setValue(olive::CurrentConfig.effect_textbox_lines); + effect_textbox_lines_field->setValue(olive::config.effect_textbox_lines); appearance_layout->addWidget(effect_textbox_lines_field, row, 1, 1, 2); row++; @@ -721,21 +951,21 @@ void PreferencesDialog::setup_ui() { QGridLayout* memory_usage_layout = new QGridLayout(memory_usage_group); memory_usage_layout->addWidget(new QLabel(tr("Upcoming Frame Queue:"), playback_tab), 0, 0); upcoming_queue_spinbox = new QDoubleSpinBox(playback_tab); - upcoming_queue_spinbox->setValue(olive::CurrentConfig.upcoming_queue_size); + upcoming_queue_spinbox->setValue(olive::config.upcoming_queue_size); memory_usage_layout->addWidget(upcoming_queue_spinbox, 0, 1); upcoming_queue_type = new QComboBox(playback_tab); upcoming_queue_type->addItem(tr("frames")); upcoming_queue_type->addItem(tr("seconds")); - upcoming_queue_type->setCurrentIndex(olive::CurrentConfig.upcoming_queue_type); + upcoming_queue_type->setCurrentIndex(olive::config.upcoming_queue_type); memory_usage_layout->addWidget(upcoming_queue_type, 0, 2); memory_usage_layout->addWidget(new QLabel(tr("Previous Frame Queue:"), playback_tab), 1, 0); previous_queue_spinbox = new QDoubleSpinBox(playback_tab); - previous_queue_spinbox->setValue(olive::CurrentConfig.previous_queue_size); + previous_queue_spinbox->setValue(olive::config.previous_queue_size); memory_usage_layout->addWidget(previous_queue_spinbox, 1, 1); previous_queue_type = new QComboBox(playback_tab); previous_queue_type->addItem(tr("frames")); previous_queue_type->addItem(tr("seconds")); - previous_queue_type->setCurrentIndex(olive::CurrentConfig.previous_queue_type); + previous_queue_type->setCurrentIndex(olive::config.previous_queue_type); memory_usage_layout->addWidget(previous_queue_type, 1, 2); playback_tab_layout->addWidget(memory_usage_group); @@ -761,7 +991,7 @@ void PreferencesDialog::setup_ui() { for (int i=0;iaddItem(devs.at(i).deviceName(), devs.at(i).deviceName()); if (!found_preferred_device - && devs.at(i).deviceName() == olive::CurrentConfig.preferred_audio_output) { + && devs.at(i).deviceName() == olive::config.preferred_audio_output) { audio_output_devices->setCurrentIndex(audio_output_devices->count()-1); found_preferred_device = true; } @@ -784,7 +1014,7 @@ void PreferencesDialog::setup_ui() { for (int i=0;iaddItem(devs.at(i).deviceName(), devs.at(i).deviceName()); if (!found_preferred_device - && devs.at(i).deviceName() == olive::CurrentConfig.preferred_audio_input) { + && devs.at(i).deviceName() == olive::config.preferred_audio_input) { audio_input_devices->setCurrentIndex(audio_input_devices->count()-1); found_preferred_device = true; } @@ -801,7 +1031,7 @@ void PreferencesDialog::setup_ui() { audio_sample_rate = new QComboBox(); combobox_audio_sample_rates(audio_sample_rate); for (int i=0;icount();i++) { - if (audio_sample_rate->itemData(i).toInt() == olive::CurrentConfig.audio_rate) { + if (audio_sample_rate->itemData(i).toInt() == olive::config.audio_rate) { audio_sample_rate->setCurrentIndex(i); break; } @@ -817,13 +1047,102 @@ void PreferencesDialog::setup_ui() { recordingComboBox = new QComboBox(general_tab); recordingComboBox->addItem(tr("Mono")); recordingComboBox->addItem(tr("Stereo")); - recordingComboBox->setCurrentIndex(olive::CurrentConfig.recording_mode - 1); + recordingComboBox->setCurrentIndex(olive::config.recording_mode - 1); audio_tab_layout->addWidget(recordingComboBox, row, 1); row++; tabWidget->addTab(audio_tab, tr("Audio")); + // + // COLOR MANAGEMENT + // + + QWidget* color_management_tab = new QWidget(); + + QGridLayout* color_management_layout = new QGridLayout(color_management_tab); + + row = 0; + + // COLOR MANAGEMENT -> Enable Color Management + enable_color_management = new QCheckBox(tr("Enable Color Management")); + enable_color_management->setChecked(olive::config.enable_color_management); + color_management_layout->addWidget(enable_color_management, row, 0); + + row++; + + QGroupBox* opencolorio_groupbox = new QGroupBox(); + QGridLayout* opencolorio_groupbox_layout = new QGridLayout(opencolorio_groupbox); + + // COLOR MANAGEMENT -> OpenColorIO Config File + opencolorio_groupbox_layout->addWidget(new QLabel(tr("OpenColorIO Config File:")), 0, 0); + + ocio_config_file = new QLineEdit(); + ocio_config_file->setText(olive::config.ocio_config_path); + connect(ocio_config_file, SIGNAL(textChanged(const QString &)), this, SLOT(update_ocio_config(const QString&))); + opencolorio_groupbox_layout->addWidget(ocio_config_file, 0, 1, 1, 4); + + QPushButton* ocio_config_browse_btn = new QPushButton(tr("Browse")); + connect(ocio_config_browse_btn, SIGNAL(clicked(bool)), this, SLOT(browse_ocio_config())); + opencolorio_groupbox_layout->addWidget(ocio_config_browse_btn, 0, 5); + + // COLOR MANAGEMENT -> Default Input Color Space + ocio_default_input = new QComboBox(); + opencolorio_groupbox_layout->addWidget(new QLabel(tr("Default Input Color Space:")), 1, 0); + opencolorio_groupbox_layout->addWidget(ocio_default_input, 1, 1, 1, 5); + + // COLOR MANAGEMENT -> Display + ocio_display = new QComboBox(); + connect(ocio_display, SIGNAL(currentIndexChanged(int)), this, SLOT(update_ocio_view_menu())); + opencolorio_groupbox_layout->addWidget(new QLabel(tr("Display:")), 2, 0); + opencolorio_groupbox_layout->addWidget(ocio_display, 2, 1); + + // COLOR MANAGEMENT -> View + ocio_view = new QComboBox(); + opencolorio_groupbox_layout->addWidget(new QLabel(tr("View:")), 2, 2); + opencolorio_groupbox_layout->addWidget(ocio_view, 2, 3); + + // COLOR MANAGEMENT -> Look + ocio_look = new QComboBox(); + opencolorio_groupbox_layout->addWidget(new QLabel(tr("Look:")), 2, 4); + opencolorio_groupbox_layout->addWidget(ocio_look, 2, 5); + + color_management_layout->addWidget(opencolorio_groupbox, row, 0); + + row++; + + // COLOR MANAGEMENT -> Bit Depth + QGroupBox* bit_depth_groupbox = new QGroupBox(tr("Bit Depth")); + QGridLayout* bit_depth_groupbox_layout = new QGridLayout(bit_depth_groupbox); + + // COLOR MANAGEMENT -> Bit Depth -> Playback + playback_bit_depth = new QComboBox(); + for (int i=0;iaddItem(olive::pixel_formats.at(i).name, i); + } + playback_bit_depth->setCurrentIndex(olive::config.playback_bit_depth); + bit_depth_groupbox_layout->addWidget(new QLabel(tr("Playback (Offline):")), 0, 0); + bit_depth_groupbox_layout->addWidget(playback_bit_depth, 0, 1); + + // COLOR MANAGEMENT -> Bit Depth -> Export + export_bit_depth = new QComboBox(); + for (int i=0;iaddItem(olive::pixel_formats.at(i).name, i); + } + export_bit_depth->setCurrentIndex(olive::config.export_bit_depth); + bit_depth_groupbox_layout->addWidget(new QLabel(tr("Export (Online):")), 0, 2); + bit_depth_groupbox_layout->addWidget(export_bit_depth, 0, 3); + + color_management_layout->addWidget(bit_depth_groupbox, row, 0); + + + + //row++; + + populate_ocio_menus(OCIO::GetCurrentConfig()); + + tabWidget->addTab(color_management_tab, tr("Color Management")); + // Shortcuts QWidget* shortcut_tab = new QWidget(this); diff --git a/dialogs/preferencesdialog.h b/dialogs/preferencesdialog.h index f360284ea..721a2b505 100644 --- a/dialogs/preferencesdialog.h +++ b/dialogs/preferencesdialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -33,6 +33,8 @@ #include #include #include +#include +namespace OCIO = OCIO_NAMESPACE::v1; #include "timeline/sequence.h" @@ -108,16 +110,23 @@ private slots: */ void save_shortcut_file(); - /** - * @brief Show a file dialog to browse for an external CSS file to load for styling the application. - */ - void browse_css_file(); - /** * @brief Delete all previews (waveform and thumbnail cache) */ void delete_all_previews(); + // Browse for file functionns + /** + * @brief Show a file dialog to browse for an external CSS file to load for styling the application. + */ + void browse_css_file(); + void browse_ocio_config(); + + // OCIO function + void update_ocio_view_menu(); + void update_ocio_view_menu(OCIO::ConstConfigRcPtr config); + void update_ocio_config(const QString&); + /** * @brief Shows a NewSequenceDialog attached to default_sequence */ @@ -157,6 +166,13 @@ private: */ void setup_kbd_shortcut_worker(QMenu* menu, QTreeWidgetItem* parent); + enum PreviewDeleteTypes { + DELETE_NONE, + DELETE_THUMBNAILS, + DELETE_WAVEFORMS, + DELETE_BOTH + }; + // used to delete previews // type can be: 't' for thumbnails, 'w' for waveforms, or 1 for all /** @@ -164,9 +180,11 @@ private: * * @param type * - * The types of previews to + * The types of previews to delete. */ - void delete_previews(char type); + void delete_previews(PreviewDeleteTypes type); + + void populate_ocio_menus(OCIO::ConstConfigRcPtr config); /** * @brief UI widget for editing the CSS filename @@ -183,6 +201,7 @@ private: */ QComboBox* recordingComboBox; + /** * @brief UI widget for editing keyboard shortcuts */ @@ -243,6 +262,15 @@ private: */ QSpinBox* waveform_res_spinbox; + QCheckBox* enable_color_management; + QLineEdit* ocio_config_file; + QComboBox* ocio_default_input; + QComboBox* ocio_display; + QComboBox* ocio_view; + QComboBox* ocio_look; + QComboBox* playback_bit_depth; + QComboBox* export_bit_depth; + /** * @brief UI widget for selecting the current UI style */ @@ -274,6 +302,19 @@ private: */ QVector key_shortcut_fields; + /** + * @brief Tests an OpenColorIO configuration file to determine whether it's valid and throws a messagebox if not + * + * @param url + * + * URL to the OpenColorIO configuration file. + * + * @return + * + * A OCIO::ConstConfigRcPtr config pointer if the configuration file is valid, nullptr if not. + */ + OCIO::ConstConfigRcPtr TestOCIOConfig(const QString& url); + /** * @brief Add an automated QCheckBox+boolean value pair * diff --git a/dialogs/proxydialog.cpp b/dialogs/proxydialog.cpp index d86044860..acbd77300 100644 --- a/dialogs/proxydialog.cpp +++ b/dialogs/proxydialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/proxydialog.h b/dialogs/proxydialog.h index 6a036d830..a883b2085 100644 --- a/dialogs/proxydialog.h +++ b/dialogs/proxydialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/replaceclipmediadialog.cpp b/dialogs/replaceclipmediadialog.cpp index 8efda92a9..d90452a6e 100644 --- a/dialogs/replaceclipmediadialog.cpp +++ b/dialogs/replaceclipmediadialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -95,7 +95,10 @@ void ReplaceClipMediaDialog::accept() { QMessageBox::Ok ); } else { - if (new_item->get_type() == MEDIA_TYPE_SEQUENCE && olive::ActiveSequence == new_item->to_sequence()) { + + SequencePtr top_sequence = Timeline::GetTopSequence(); + + if (new_item->get_type() == MEDIA_TYPE_SEQUENCE && top_sequence == new_item->to_sequence()) { QMessageBox::critical( this, tr("Active sequence selected"), @@ -109,14 +112,15 @@ void ReplaceClipMediaDialog::accept() { use_same_media_in_points->isChecked() ); - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr && c->media() == media) { + QVector all_clips = top_sequence->GetAllClips(); + for (int i=0;imedia() == media) { rcmc->clips.append(c); } } - olive::UndoStack.push(rcmc); + olive::undo_stack.push(rcmc); QDialog::accept(); } diff --git a/dialogs/replaceclipmediadialog.h b/dialogs/replaceclipmediadialog.h index dd40095be..34fa9b6b1 100644 --- a/dialogs/replaceclipmediadialog.h +++ b/dialogs/replaceclipmediadialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/speeddialog.cpp b/dialogs/speeddialog.cpp index 8a87916bc..9b7cad23e 100644 --- a/dialogs/speeddialog.cpp +++ b/dialogs/speeddialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -33,7 +33,7 @@ #include "panels/timeline.h" #include "undo/undo.h" #include "undo/undostack.h" -#include "effects/effect.h" +#include "nodes/node.h" #include "project/media.h" SpeedDialog::SpeedDialog(QWidget *parent, QVector clips) : QDialog(parent) { @@ -61,7 +61,7 @@ SpeedDialog::SpeedDialog(QWidget *parent, QVector clips) : QDialog(parent grid->addWidget(new QLabel(tr("Duration:"), this), 2, 0); duration = new LabelSlider(this); duration->SetDisplayType(LabelSlider::FrameNumber); - duration->SetFrameRate(olive::ActiveSequence->frame_rate); + duration->SetFrameRate(clips_.first()->track()->sequence()->frame_rate); grid->addWidget(duration, 2, 1); main_layout->addLayout(grid); @@ -103,7 +103,7 @@ int SpeedDialog::exec() { // get default frame rate/percentage clip_percent = c->speed().value; - if (c->track() < 0) { + if (c->type() == olive::kTypeVideo) { bool process_video = true; if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { FootageStream* ms = c->media_stream(); @@ -131,7 +131,7 @@ int SpeedDialog::exec() { enable_frame_rate = true; } - } else { + } else if (c->type() == olive::kTypeAudio) { maintain_pitch->setEnabled(true); if (!multiple_audio) { @@ -192,7 +192,7 @@ void SpeedDialog::percent_update() { Clip* c = clips_.at(i); // get frame rate - if (frame_rate->isEnabled() && c->track() < 0) { + if (frame_rate->isEnabled() && c->type() == olive::kTypeVideo) { double clip_fr = c->media_frame_rate() * percent->value(); if (got_fr) { if (!qIsNaN(fr_val) && !qFuzzyCompare(fr_val, clip_fr)) { @@ -236,7 +236,7 @@ void SpeedDialog::duration_update() { } // get frame rate - if (frame_rate->isEnabled() && c->track() < 0) { + if (frame_rate->isEnabled() && c->type() == olive::kTypeVideo) { double clip_fr = c->media_frame_rate() * clip_pc; if (got_fr) { if (!qIsNaN(fr_val) && !qFuzzyCompare(fr_val, clip_fr)) { @@ -276,7 +276,7 @@ void SpeedDialog::frame_rate_update() { old_pc_val = qSNaN(); } - if (c->track() < 0) { + if (c->type() == olive::kTypeVideo) { // what would the new speed be based on this frame rate double new_clip_speed = frame_rate->value() / c->media_frame_rate(); if (!got_pc_val) { @@ -301,7 +301,7 @@ void SpeedDialog::frame_rate_update() { for (int i=0;itrack() >= 0) { + if (c->type() == olive::kTypeAudio) { long new_clip_len = (qIsNaN(old_pc_val) || qIsNaN(pc_val)) ? c->length() : qRound((c->length() * c->speed().value) / pc_val); @@ -318,15 +318,17 @@ void SpeedDialog::frame_rate_update() { } void set_speed(ComboAction* ca, Clip* c, double speed, bool ripple, long& ep, long& lr) { - panel_timeline->deselect_area(c->timeline_in(), c->timeline_out(), c->track()); + c->track()->DeselectArea(c->timeline_in(), c->timeline_out()); long proposed_out = c->timeline_out(); double multiplier = (c->speed().value / speed); proposed_out = qRound(c->timeline_in() + (c->length() * multiplier)); ca->append(new SetSpeedAction(c, speed)); if (!ripple && proposed_out > c->timeline_out()) { - for (int i=0;isequence->clips.size();i++) { - ClipPtr compare = c->sequence->clips.at(i); + QVector all_clips = c->track()->sequence()->GetAllClips(); + + for (int i=0;itrack() == c->track() && compare->timeline_in() >= c->timeline_out() && compare->timeline_in() < proposed_out) { @@ -336,15 +338,16 @@ void set_speed(ComboAction* ca, Clip* c, double speed, bool ripple, long& ep, lo } ep = qMin(ep, c->timeline_out()); lr = qMax(lr, proposed_out - c->timeline_out()); - c->move(ca, c->timeline_in(), proposed_out, qRound(c->clip_in() * multiplier), c->track()); + c->track()->sequence()->MoveClip(c, + ca, + c->timeline_in(), + proposed_out, + qRound(c->clip_in() * multiplier), + c->track()); c->refactor_frame_rate(ca, multiplier, false); - Selection sel; - sel.in = c->timeline_in(); - sel.out = proposed_out; - sel.track = c->track(); - olive::ActiveSequence->selections.append(sel); + c->track()->SelectArea(c->timeline_in(), proposed_out); } void SpeedDialog::accept() { @@ -357,8 +360,8 @@ void SpeedDialog::accept() { SetClipProperty* reversed_action = new SetClipProperty(kSetClipPropertyReversed); // undoable action for restoring clip selections - SetSelectionsCommand* sel_command = new SetSelectionsCommand(olive::ActiveSequence.get()); - sel_command->old_data = olive::ActiveSequence->selections; + Sequence* sequence = clips_.first()->track()->sequence(); + QVector old_selections = sequence->Selections(); // variables used to calculate ripples long earliest_point = LONG_MAX; @@ -373,7 +376,7 @@ void SpeedDialog::accept() { } // set maintain audio pitch if the user made a selection - if (c->track() >= 0 + if (c->type() == olive::kTypeAudio && maintain_pitch->checkState() != Qt::PartiallyChecked && c->speed().maintain_audio_pitch != maintain_pitch->isChecked()) { audio_pitch_action->AddSetting(c, maintain_pitch->isChecked()); @@ -382,7 +385,12 @@ void SpeedDialog::accept() { // set reverse setting if the user made a selection if (reverse->checkState() != Qt::PartiallyChecked && c->reversed() != reverse->isChecked()) { long new_clip_in = (c->media_length() - (c->length() + c->clip_in())); - c->move(ca, c->timeline_in(), c->timeline_out(), new_clip_in, c->track()); + c->track()->sequence()->MoveClip(c, + ca, + c->timeline_in(), + c->timeline_out(), + new_clip_in, + c->track()); c->set_clip_in(new_clip_in); reversed_action->AddSetting(c, reverse->isChecked()); } @@ -410,7 +418,7 @@ void SpeedDialog::accept() { if (i > 0 && !qFuzzyCompare(cached_speed, c->speed().value)) { can_change_all = false; } - if (c->track() < 0) { + if (c->type() == olive::kTypeVideo) { if (qIsNaN(cached_fr)) { cached_fr = c->media_frame_rate(); } else if (!qFuzzyCompare(cached_fr, c->media_frame_rate())) { @@ -423,7 +431,7 @@ void SpeedDialog::accept() { // make changes for (int i=0;itrack() < 0) { + if (c->type() == olive::kTypeVideo) { set_speed(ca, c, frame_rate->value() / c->media_frame_rate(), ripple->isChecked(), earliest_point, longest_ripple); } else if (can_change_all) { set_speed(ca, c, frame_rate->value() / cached_fr, ripple->isChecked(), earliest_point, longest_ripple); @@ -438,16 +446,15 @@ void SpeedDialog::accept() { } if (ripple->isChecked()) { - ripple_clips(ca, clips_.at(0)->sequence, earliest_point, longest_ripple); + sequence->Ripple(ca, earliest_point, longest_ripple); } - sel_command->new_data = olive::ActiveSequence->selections; - ca->append(sel_command); + ca->append(new SetSelectionsCommand(sequence, old_selections, sequence->Selections())); ca->append(reversed_action); ca->append(audio_pitch_action); - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); update_ui(true); QDialog::accept(); diff --git a/dialogs/speeddialog.h b/dialogs/speeddialog.h index 940c50525..5ef9144e6 100644 --- a/dialogs/speeddialog.h +++ b/dialogs/speeddialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/texteditdialog.cpp b/dialogs/texteditdialog.cpp index e7a5d9fa0..fc5638f1d 100644 --- a/dialogs/texteditdialog.cpp +++ b/dialogs/texteditdialog.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/dialogs/texteditdialog.h b/dialogs/texteditdialog.h index be812dd85..ff3cd8776 100644 --- a/dialogs/texteditdialog.h +++ b/dialogs/texteditdialog.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/effects/effect.cpp b/effects/effect.cpp deleted file mode 100644 index 7aca89711..000000000 --- a/effects/effect.cpp +++ /dev/null @@ -1,1132 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "effect.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "panels/panels.h" -#include "panels/viewer.h" -#include "ui/viewerwidget.h" -#include "ui/collapsiblewidget.h" -#include "panels/project.h" -#include "undo/undo.h" -#include "timeline/sequence.h" -#include "timeline/clip.h" -#include "panels/timeline.h" -#include "panels/effectcontrols.h" -#include "panels/grapheditor.h" -#include "global/debug.h" -#include "global/path.h" -#include "ui/mainwindow.h" -#include "global/math.h" -#include "project/clipboard.h" -#include "global/config.h" -#include "transition.h" -#include "undo/undostack.h" - -#include "effects/internal/transformeffect.h" -#include "effects/internal/texteffect.h" -#include "effects/internal/timecodeeffect.h" -#include "effects/internal/solideffect.h" -#include "effects/internal/audionoiseeffect.h" -#include "effects/internal/toneeffect.h" -#include "effects/internal/volumeeffect.h" -#include "effects/internal/paneffect.h" -#include "effects/internal/shakeeffect.h" -#include "effects/internal/cornerpineffect.h" -#include "effects/internal/vsthost.h" -#include "effects/internal/fillleftrighteffect.h" -#include "effects/internal/frei0reffect.h" -#include "effects/internal/richtexteffect.h" - -QVector effects; - -EffectPtr Effect::Create(Clip* c, const EffectMeta* em) { - if (em->internal >= 0 && em->internal < EFFECT_INTERNAL_COUNT) { - // must be an internal effect - switch (em->internal) { - case EFFECT_INTERNAL_TRANSFORM: return std::make_shared(c, em); - case EFFECT_INTERNAL_TEXT: return std::make_shared(c, em); - case EFFECT_INTERNAL_TIMECODE: return std::make_shared(c, em); - case EFFECT_INTERNAL_SOLID: return std::make_shared(c, em); - case EFFECT_INTERNAL_NOISE: return std::make_shared(c, em); - case EFFECT_INTERNAL_VOLUME: return std::make_shared(c, em); - case EFFECT_INTERNAL_PAN: return std::make_shared(c, em); - case EFFECT_INTERNAL_TONE: return std::make_shared(c, em); - case EFFECT_INTERNAL_SHAKE: return std::make_shared(c, em); - case EFFECT_INTERNAL_CORNERPIN: return std::make_shared(c, em); - case EFFECT_INTERNAL_FILLLEFTRIGHT: return std::make_shared(c, em); - case EFFECT_INTERNAL_VST: return std::make_shared(c, em); -#ifndef NOFREI0R - case EFFECT_INTERNAL_FREI0R: return std::make_shared(c, em); -#endif - case EFFECT_INTERNAL_RICHTEXT: return std::make_shared(c, em); - } - } else if (!em->filename.isEmpty()) { - // load effect from file - return std::make_shared(c, em); - } else { - qCritical() << "Invalid effect data"; - QMessageBox::critical(olive::MainWindow, - QCoreApplication::translate("Effect", "Invalid effect"), - QCoreApplication::translate("Effect", "No candidate for effect '%1'. This effect may be corrupt. Try reinstalling it or Olive.").arg(em->name)); - } - return nullptr; -} - -const EffectMeta* Effect::GetInternalMeta(int internal_id, int type) { - for (int i=0;iname; - - if (!em->filename.isEmpty() && em->internal == -1) { - QFile effect_file(em->filename); - if (effect_file.open(QFile::ReadOnly)) { - QXmlStreamReader reader(&effect_file); - - while (!reader.atEnd()) { - if (reader.name() == "row" && reader.isStartElement()) { - QString row_name; - const QXmlStreamAttributes& attributes = reader.attributes(); - for (int i=0;ifilename << "- ID cannot be empty."; - } else { - EffectField* field = nullptr; - - switch (type) { - case EffectField::EFFECT_FIELD_DOUBLE: - { - - DoubleField* double_field = new DoubleField(row, id); - - for (int i=0;iSetDefault(attr.value().toDouble()); - } else if (attr.name() == "min") { - double_field->SetMinimum(attr.value().toDouble()); - } else if (attr.name() == "max") { - double_field->SetMaximum(attr.value().toDouble()); - } - } - - field = double_field; - } - break; - case EffectField::EFFECT_FIELD_COLOR: - { - QColor color; - - field = new ColorField(row, id); - - for (int i=0;iSetValueAt(0, color); - } - break; - case EffectField::EFFECT_FIELD_STRING: - field = new StringField(row, id); - for (int i=0;iSetValueAt(0, attr.value().toString()); - } - } - break; - case EffectField::EFFECT_FIELD_BOOL: - field = new BoolField(row, id); - for (int i=0;iSetValueAt(0, attr.value() == "1"); - } - } - break; - case EffectField::EFFECT_FIELD_COMBO: - { - ComboField* combo_field = new ComboField(row, id); - int combo_default_index = 0; - for (int i=0;iAddItem(reader.text().toString(), combo_item_count); - combo_item_count++; - } - } - combo_field->SetValueAt(0, combo_default_index); - field = combo_field; - } - break; - case EffectField::EFFECT_FIELD_FONT: - field = new FontField(row, id); - for (int i=0;iSetValueAt(0, attr.value().toString()); - } - } - break; - case EffectField::EFFECT_FIELD_FILE: - field = new FileField(row, id); - for (int i=0;iSetValueAt(0, attr.value().toString()); - } - } - break; - } - } - } - } - } - } else if (reader.name() == "shader" && reader.isStartElement()) { - SetFlags(Flags() | ShaderFlag); - const QXmlStreamAttributes& attributes = reader.attributes(); - for (int i=0;ifilename; - enable_superimpose = false; - } - break; - } - } - }*/ - reader.readNext(); - } - - effect_file.close(); - } else { - qCritical() << "Failed to open effect file" << em->filename; - } - } - } -} - -Effect::~Effect() { - if (isOpen) { - close(); - } - - // Clear graph editor if it's using one of these rows - if (panel_graph_editor != nullptr) { - for (int i=0;iget_row()) { - panel_graph_editor->set_row(nullptr); - break; - } - } - } -} - -void Effect::AddRow(EffectRow *row) -{ - row->setParent(this); - rows.append(row); -} - -void Effect::copy_field_keyframes(EffectPtr e) { - for (int i=0;irows.at(i); - copy_row->SetKeyframingInternal(row->IsKeyframing()); - for (int j=0;jFieldCount();j++) { - // Get field from this (the source) effect - EffectField* field = row->Field(j); - - // Get field from the destination effect - EffectField* copy_field = copy_row->Field(j); - - // Copy keyframes between effects - copy_field->keyframes = field->keyframes; - - // Copy persistet data between effects - copy_field->persistent_data_ = field->persistent_data_; - } - } -} - -EffectRow* Effect::row(int i) { - return rows.at(i); -} - -int Effect::row_count() { - return rows.size(); -} - -EffectGizmo *Effect::add_gizmo(int type) { - EffectGizmo* gizmo = new EffectGizmo(this, type); - gizmos.append(gizmo); - return gizmo; -} - -EffectGizmo *Effect::gizmo(int i) { - return gizmos.at(i); -} - -int Effect::gizmo_count() { - return gizmos.size(); -} - -void Effect::refresh() {} - -void Effect::FieldChanged() { - update_ui(false); -} - -void Effect::delete_self() { - olive::UndoStack.push(new EffectDeleteCommand(this)); - update_ui(true); -} - -void Effect::move_up() { - int index_of_effect = parent_clip->IndexOfEffect(this); - if (index_of_effect == 0) { - return; - } - - MoveEffectCommand* command = new MoveEffectCommand(); - command->clip = parent_clip; - command->from = index_of_effect; - command->to = command->from - 1; - olive::UndoStack.push(command); - panel_effect_controls->Reload(); - panel_sequence_viewer->viewer_widget->frame_update(); -} - -void Effect::move_down() { - int index_of_effect = parent_clip->IndexOfEffect(this); - if (index_of_effect == parent_clip->effects.size()-1) { - return; - } - - MoveEffectCommand* command = new MoveEffectCommand(); - command->clip = parent_clip; - command->from = index_of_effect; - command->to = command->from + 1; - olive::UndoStack.push(command); - panel_effect_controls->Reload(); - panel_sequence_viewer->viewer_widget->frame_update(); -} - -void Effect::save_to_file() { - // save effect settings to file - QString file = QFileDialog::getSaveFileName(olive::MainWindow, - tr("Save Effect Settings"), - QString(), - tr("Effect XML Settings %1").arg("(*.xml)")); - - // if the user picked a file - if (!file.isEmpty()) { - - // ensure file ends with .xml extension - if (!file.endsWith(".xml", Qt::CaseInsensitive)) { - file.append(".xml"); - } - - QFile file_handle(file); - if (file_handle.open(QFile::WriteOnly)) { - - file_handle.write(save_to_string()); - - file_handle.close(); - } else { - QMessageBox::critical(olive::MainWindow, - tr("Save Settings Failed"), - tr("Failed to open \"%1\" for writing.").arg(file), - QMessageBox::Ok); - } - } -} - -void Effect::load_from_file() { - // load effect settings from file - QString file = QFileDialog::getOpenFileName(olive::MainWindow, - tr("Load Effect Settings"), - QString(), - tr("Effect XML Settings %1").arg("(*.xml)")); - - // if the user picked a file - if (!file.isEmpty()) { - QFile file_handle(file); - if (file_handle.open(QFile::ReadOnly)) { - - olive::UndoStack.push(new SetEffectData(this, file_handle.readAll())); - - file_handle.close(); - - update_ui(false); - } else { - QMessageBox::critical(olive::MainWindow, - tr("Load Settings Failed"), - tr("Failed to open \"%1\" for reading.").arg(file), - QMessageBox::Ok); - } - } -} - -bool Effect::AlwaysUpdate() -{ - return false; -} - -bool Effect::IsEnabled() { - return enabled_; -} - -bool Effect::IsExpanded() -{ - return expanded_; -} - -void Effect::SetExpanded(bool e) -{ - expanded_ = e; -} - -void Effect::SetEnabled(bool b) { - enabled_ = b; - emit EnabledChanged(b); -} - -void Effect::load(QXmlStreamReader& stream) { - int row_count = 0; - - QString tag = stream.name().toString(); - - while (!stream.atEnd() && !(stream.name() == tag && stream.isEndElement())) { - stream.readNext(); - if (stream.name() == "row" && stream.isStartElement()) { - if (row_count < rows.size()) { - EffectRow* row = rows.at(row_count); - - while (!stream.atEnd() && !(stream.name() == "row" && stream.isEndElement())) { - stream.readNext(); - - // read field - if (stream.name() == "field" && stream.isStartElement()) { - int field_number = -1; - - // match field using ID - for (int k=0;kFieldCount();l++) { - if (row->Field(l)->id() == attr.value()) { - field_number = l; - break; - } - } - break; - } - } - - if (field_number > -1) { - EffectField* field = row->Field(field_number); - - // get current field value - for (int k=0;kpersistent_data_ = field->ConvertStringToValue(attr.value().toString()); - break; - } - } - - while (!stream.atEnd() && !(stream.name() == "field" && stream.isEndElement())) { - stream.readNext(); - - // read keyframes - if (stream.name() == "key" && stream.isStartElement()) { - row->SetKeyframingInternal(true); - - EffectKeyframe key; - for (int k=0;kConvertStringToValue(attr.value().toString()); - } else if (attr.name() == "frame") { - key.time = attr.value().toLong(); - } else if (attr.name() == "type") { - key.type = attr.value().toInt(); - } else if (attr.name() == "prehx") { - key.pre_handle_x = attr.value().toDouble(); - } else if (attr.name() == "prehy") { - key.pre_handle_y = attr.value().toDouble(); - } else if (attr.name() == "posthx") { - key.post_handle_x = attr.value().toDouble(); - } else if (attr.name() == "posthy") { - key.post_handle_y = attr.value().toDouble(); - } - } - field->keyframes.append(key); - } - } - - field->Changed(); - - } - } - } - - } else { - qCritical() << "Too many rows for effect" << id << ". Project might be corrupt. (Got" << row_count << ", expected <" << rows.size()-1 << ")"; - } - row_count++; - } else if (stream.isStartElement()) { - custom_load(stream); - } - } -} - -void Effect::custom_load(QXmlStreamReader &) {} - -void Effect::save(QXmlStreamWriter& stream) { - stream.writeAttribute("name", meta->category + "/" + meta->name); - stream.writeAttribute("enabled", QString::number(IsEnabled())); - - for (int i=0;iIsSavable()) { - stream.writeStartElement("row"); // row - for (int j=0;jFieldCount();j++) { - EffectField* field = row->Field(j); - - if (!field->id().isEmpty()) { - stream.writeStartElement("field"); // field - stream.writeAttribute("id", field->id()); - stream.writeAttribute("value", field->ConvertValueToString(field->persistent_data_)); - for (int k=0;kkeyframes.size();k++) { - const EffectKeyframe& key = field->keyframes.at(k); - stream.writeStartElement("key"); - stream.writeAttribute("value", field->ConvertValueToString(key.data)); - stream.writeAttribute("frame", QString::number(key.time)); - stream.writeAttribute("type", QString::number(key.type)); - stream.writeAttribute("prehx", QString::number(key.pre_handle_x)); - stream.writeAttribute("prehy", QString::number(key.pre_handle_y)); - stream.writeAttribute("posthx", QString::number(key.post_handle_x)); - stream.writeAttribute("posthy", QString::number(key.post_handle_y)); - stream.writeEndElement(); // key - } - stream.writeEndElement(); // field - } - } - stream.writeEndElement(); // row - } - } -} - -void Effect::load_from_string(const QByteArray &s) { - // clear existing keyframe data - for (int i=0;iSetKeyframingInternal(false); - for (int j=0;jFieldCount();j++) { - EffectField* field = row->Field(j); - field->keyframes.clear(); - } - } - - // write settings with xml writer - QXmlStreamReader stream(s); - - while (!stream.atEnd()) { - stream.readNext(); - - // find the effect opening tag - if (stream.name() == "effect" && stream.isStartElement()) { - - // check the name to see if it matches this effect - const QXmlStreamAttributes& attributes = stream.attributes(); - for (int i=0;ipath.isEmpty() || (vertPath.isEmpty() && fragPath.isEmpty())) return; - QList effects_paths = get_effects_paths(); - const QString& test_fn = vertPath.isEmpty() ? fragPath : vertPath; - for (int i=0;iaddShaderFromSourceFile(QOpenGLShader::Vertex, meta->path + "/" + vertPath)) { - qInfo() << "Vertex shader added successfully"; - } else { - glsl_compiled = false; - qWarning() << "Vertex shader could not be added"; - } - } - if (!fragPath.isEmpty()) { - if (glslProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, meta->path + "/" + fragPath)) { - qInfo() << "Fragment shader added successfully"; - } else { - glsl_compiled = false; - qWarning() << "Fragment shader could not be added"; - } - } - if (glsl_compiled) { - if (glslProgram->link()) { - qInfo() << "Shader program linked successfully"; - } else { - qWarning() << "Shader program failed to link"; - } - } - isOpen = true; - } - } else { - isOpen = true; - } -} - -void Effect::close() { - if (!isOpen) { - qWarning() << "Tried to close an effect that was already closed"; - } - delete_texture(); - if (glslProgram != nullptr) { - delete glslProgram; - glslProgram = nullptr; - } - isOpen = false; -} - -bool Effect::is_glsl_linked() { - return glslProgram != nullptr && glslProgram->isLinked(); -} - -void Effect::startEffect() { - if (!isOpen) { - open(); - qWarning() << "Tried to start a closed effect - opening"; - } - if (olive::CurrentRuntimeConfig.shaders_are_enabled - && (Flags() & Effect::ShaderFlag) - && glslProgram->isLinked()) { - bound = glslProgram->bind(); - } -} - -void Effect::endEffect() { - if (bound) { - glslProgram->release(); - } - bound = false; -} - -int Effect::Flags() -{ - return flags_; -} - -void Effect::SetFlags(int flags) -{ - flags_ = flags; -} - -int Effect::getIterations() { - return iterations; -} - -void Effect::setIterations(int i) { - iterations = i; -} - -void Effect::process_image(double, uint8_t *, uint8_t *, int){} - -EffectPtr Effect::copy(Clip *c) { - EffectPtr copy = Effect::Create(c, meta); - copy->SetEnabled(IsEnabled()); - copy_field_keyframes(copy); - return copy; -} - -void Effect::process_shader(double timecode, GLTextureCoords&, int iteration) { - glslProgram->setUniformValue("resolution", parent_clip->media_width(), parent_clip->media_height()); - glslProgram->setUniformValue("time", GLfloat(timecode)); - glslProgram->setUniformValue("iteration", iteration); - - for (int i=0;iFieldCount();j++) { - EffectField* field = row->Field(j); - if (!field->id().isEmpty()) { - switch (field->type()) { - case EffectField::EFFECT_FIELD_DOUBLE: - { - DoubleField* double_field = static_cast(field); - glslProgram->setUniformValue(double_field->id().toUtf8().constData(), - GLfloat(double_field->GetDoubleAt(timecode))); - } - break; - case EffectField::EFFECT_FIELD_COLOR: - { - ColorField* color_field = static_cast(field); - glslProgram->setUniformValue( - color_field->id().toUtf8().constData(), - GLfloat(color_field->GetColorAt(timecode).redF()), - GLfloat(color_field->GetColorAt(timecode).greenF()), - GLfloat(color_field->GetColorAt(timecode).blueF()) - ); - } - break; - case EffectField::EFFECT_FIELD_BOOL: - glslProgram->setUniformValue(field->id().toUtf8().constData(), field->GetValueAt(timecode).toBool()); - break; - case EffectField::EFFECT_FIELD_COMBO: - glslProgram->setUniformValue(field->id().toUtf8().constData(), field->GetValueAt(timecode).toInt()); - break; - - // can you even send a string to a uniform value? - case EffectField::EFFECT_FIELD_STRING: - case EffectField::EFFECT_FIELD_FONT: - case EffectField::EFFECT_FIELD_FILE: - case EffectField::EFFECT_FIELD_UI: - break; - } - } - } - } -} - -void Effect::process_coords(double, GLTextureCoords&, int) {} - -GLuint Effect::process_superimpose(double timecode) { - bool dimensions_changed = false; - bool redrew_image = false; - - int width = parent_clip->media_width(); - int height = parent_clip->media_height(); - - if (width != img.width() || height != img.height()) { - img = QImage(width, height, QImage::Format_RGBA8888_Premultiplied); - dimensions_changed = true; - } - - if (valueHasChanged(timecode) || dimensions_changed || AlwaysUpdate()) { - redraw(timecode); - redrew_image = true; - } - - if (texture == nullptr || texture->width() != img.width() || texture->height() != img.height()) { - - delete_texture(); - - texture = new QOpenGLTexture(QOpenGLTexture::Target2D); - texture->setSize(img.width(), img.height()); - texture->setFormat(QOpenGLTexture::RGBA8_UNorm); - texture->setMipLevels(texture->maximumMipLevels()); - texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); - texture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); - - redrew_image = true; - - } - - if (redrew_image) { - texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, img.constBits()); - } - - return texture->textureId(); -} - -void Effect::process_audio(double, double, quint8*, int, int) {} - -void Effect::gizmo_draw(double, GLTextureCoords &) {} - -void Effect::gizmo_move(EffectGizmo* gizmo, int x_movement, int y_movement, double timecode, bool done) { - // Loop through each gizmo to find `gizmo` - for (int i=0;ix_field1 != nullptr) { - gizmo_dragging_actions_.append(new KeyframeDataChange(gizmo->x_field1)); - } - if (gizmo->y_field1 != nullptr) { - gizmo_dragging_actions_.append(new KeyframeDataChange(gizmo->y_field1)); - } - if (gizmo->x_field2 != nullptr) { - gizmo_dragging_actions_.append(new KeyframeDataChange(gizmo->x_field2)); - } - if (gizmo->y_field2 != nullptr) { - gizmo_dragging_actions_.append(new KeyframeDataChange(gizmo->y_field2)); - } - } - - // Update the field values - if (gizmo->x_field1 != nullptr) { - gizmo->x_field1->SetValueAt(timecode, - gizmo->x_field1->GetDoubleAt(timecode) + x_movement*gizmo->x_field_multi1); - } - if (gizmo->y_field1 != nullptr) { - gizmo->y_field1->SetValueAt(timecode, - gizmo->y_field1->GetDoubleAt(timecode) + y_movement*gizmo->y_field_multi1); - } - if (gizmo->x_field2 != nullptr) { - gizmo->x_field2->SetValueAt(timecode, - gizmo->x_field2->GetDoubleAt(timecode) + x_movement*gizmo->x_field_multi2); - } - if (gizmo->y_field2 != nullptr) { - gizmo->y_field2->SetValueAt(timecode, - gizmo->y_field2->GetDoubleAt(timecode) + y_movement*gizmo->y_field_multi2); - } - - // If (done && !gizmo_dragging_actions_.isEmpty()), that means the drag just ended and we're going to save - // the new state of the attach fields' keyframes in KeyframeDataChange objects to make the changes undoable - // by the user later. - if (done && !gizmo_dragging_actions_.isEmpty()) { - - // Store all the KeyframeDataChange objects into a ComboAction to send to the undo stack (makes them all - // undoable together rather than having to be undone individually). - ComboAction* ca = new ComboAction(); - - for (int j=0;jSetNewKeyframes(); - - // Add this KeyframeDataChange object to the ComboAction - ca->append(gizmo_dragging_actions_.at(j)); - } - - olive::UndoStack.push(ca); - - gizmo_dragging_actions_.clear(); - } - break; - } - } -} - -void Effect::gizmo_world_to_screen() { - GLfloat view_val[16]; - GLfloat projection_val[16]; - glGetFloatv(GL_MODELVIEW_MATRIX, view_val); - glGetFloatv(GL_PROJECTION_MATRIX, projection_val); - - QMatrix4x4 view_matrix(view_val); - QMatrix4x4 projection_matrix(projection_val); - - for (int i=0;iget_point_count();j++) { - QVector4D screen_pos = QVector4D(g->world_pos[j].x(), g->world_pos[j].y(), 0, 1.0) * (view_matrix * projection_matrix); - - int adjusted_sx1 = qRound(((screen_pos.x()*0.5f)+0.5f)*parent_clip->sequence->width); - int adjusted_sy1 = qRound((1.0f-((screen_pos.y()*0.5f)+0.5f))*parent_clip->sequence->height); - - g->screen_pos[j] = QPoint(adjusted_sx1, adjusted_sy1); - } - } -} - -bool Effect::are_gizmos_enabled() { - return (gizmos.size() > 0); -} - -void Effect::redraw(double) { - /* - // run javascript - QPainter p(&img); - painter_wrapper.img = &img; - painter_wrapper.painter = &p; - - jsEngine.globalObject().setProperty("painter", wrapper_obj); - jsEngine.globalObject().setProperty("width", parent_clip->media_width()); - jsEngine.globalObject().setProperty("height", parent_clip->media_height()); - - for (int i=0;ifieldCount();j++) { - EffectField* field = row->field(j); - if (!field->id.isEmpty()) { - switch (field->type) { - case EFFECT_FIELD_DOUBLE: - jsEngine.globalObject().setProperty(field->id, field->get_double_value(timecode)); - break; - case EFFECT_FIELD_COLOR: - jsEngine.globalObject().setProperty(field->id, field->get_color_value(timecode).name()); - break; - case EFFECT_FIELD_STRING: - jsEngine.globalObject().setProperty(field->id, field->get_string_value(timecode)); - break; - case EFFECT_FIELD_BOOL: - jsEngine.globalObject().setProperty(field->id, field->get_bool_value(timecode)); - break; - case EFFECT_FIELD_COMBO: - jsEngine.globalObject().setProperty(field->id, field->get_combo_index(timecode)); - break; - case EFFECT_FIELD_FONT: - jsEngine.globalObject().setProperty(field->id, field->get_font_name(timecode)); - break; - } - } - } - } - - jsEngine.evaluate(script); - */ -} - -bool Effect::valueHasChanged(double timecode) { - if (cachedValues.size() == 0) { - - for (int i=0;iFieldCount();j++) { - cachedValues.append(crow->Field(j)->GetValueAt(timecode)); - } - } - return true; - - } else { - - bool changed = false; - int index = 0; - for (int i=0;iFieldCount();j++) { - EffectField* field = crow->Field(j); - if (cachedValues.at(index) != field->GetValueAt(timecode)) { - changed = true; - } - cachedValues[index] = field->GetValueAt(timecode); - index++; - } - } - return changed; - - } -} - -void Effect::delete_texture() { - delete texture; - texture = nullptr; -} - -const EffectMeta* get_meta_from_name(const QString& input) { - int split_index = input.indexOf('/'); - QString category; - if (split_index > -1) { - category = input.left(split_index); - } - QString name = input.mid(split_index + 1); - - for (int j=0;j(a) + static_cast(b); - mixed_sample = qMax(qMin(mixed_sample, static_cast(INT16_MAX)), static_cast(INT16_MIN)); - return static_cast(mixed_sample); -} diff --git a/effects/effect.h b/effects/effect.h deleted file mode 100644 index 77c42f51b..000000000 --- a/effects/effect.h +++ /dev/null @@ -1,262 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef EFFECT_H -#define EFFECT_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ui/collapsiblewidget.h" -#include "effectrow.h" -#include "effectgizmo.h" - -class Clip; - -class Effect; -using EffectPtr = std::shared_ptr; - -struct EffectMeta { - QString name; - QString category; - QString filename; - QString path; - QString tooltip; - int internal; - int type; - int subtype; -}; -extern QVector effects; - -double log_volume(double linear); - -enum EffectType { - EFFECT_TYPE_INVALID, - EFFECT_TYPE_VIDEO, - EFFECT_TYPE_AUDIO, - EFFECT_TYPE_EFFECT, - EFFECT_TYPE_TRANSITION -}; - -enum EffectKeyframeType { - EFFECT_KEYFRAME_LINEAR, - EFFECT_KEYFRAME_BEZIER, - EFFECT_KEYFRAME_HOLD -}; - -enum EffectInternal { - EFFECT_INTERNAL_TRANSFORM, - EFFECT_INTERNAL_TEXT, - EFFECT_INTERNAL_SOLID, - EFFECT_INTERNAL_NOISE, - EFFECT_INTERNAL_VOLUME, - EFFECT_INTERNAL_PAN, - EFFECT_INTERNAL_TONE, - EFFECT_INTERNAL_SHAKE, - EFFECT_INTERNAL_TIMECODE, - EFFECT_INTERNAL_MASK, - EFFECT_INTERNAL_FILLLEFTRIGHT, - EFFECT_INTERNAL_VST, - EFFECT_INTERNAL_CORNERPIN, - EFFECT_INTERNAL_FREI0R, - EFFECT_INTERNAL_RICHTEXT, - EFFECT_INTERNAL_COUNT -}; - -struct GLTextureCoords { - int grid_size; - - int vertexTopLeftX; - int vertexTopLeftY; - int vertexTopLeftZ; - int vertexTopRightX; - int vertexTopRightY; - int vertexTopRightZ; - int vertexBottomLeftX; - int vertexBottomLeftY; - int vertexBottomLeftZ; - int vertexBottomRightX; - int vertexBottomRightY; - int vertexBottomRightZ; - - float textureTopLeftX; - float textureTopLeftY; - float textureTopLeftQ; - float textureTopRightX; - float textureTopRightY; - float textureTopRightQ; - float textureBottomRightX; - float textureBottomRightY; - float textureBottomRightQ; - float textureBottomLeftX; - float textureBottomLeftY; - float textureBottomLeftQ; - - int blendmode; - float opacity; -}; - -const EffectMeta* get_meta_from_name(const QString& input); - -qint16 mix_audio_sample(qint16 a, qint16 b); - -class Effect : public QObject { - Q_OBJECT -public: - Effect(Clip *c, const EffectMeta* em); - ~Effect(); - - Clip* parent_clip; - const EffectMeta* meta; - int id; - QString name; - - void AddRow(EffectRow* row); - - EffectRow* row(int i); - int row_count(); - - EffectGizmo* add_gizmo(int type); - EffectGizmo* gizmo(int i); - int gizmo_count(); - - bool IsEnabled(); - bool IsExpanded(); - - virtual void refresh(); - - virtual EffectPtr copy(Clip* c); - void copy_field_keyframes(EffectPtr e); - - virtual void load(QXmlStreamReader& stream); - virtual void custom_load(QXmlStreamReader& stream); - virtual void save(QXmlStreamWriter& stream); - - void load_from_string(const QByteArray &s); - QByteArray save_to_string(); - - // glsl handling - bool is_open(); - void open(); - void close(); - bool is_glsl_linked(); - virtual void startEffect(); - virtual void endEffect(); - - enum VideoEffectFlags { - ShaderFlag = 0x1, - CoordsFlag = 0x2, - SuperimposeFlag = 0x4, - ImageFlag = 0x8 - }; - int Flags(); - void SetFlags(int flags); - - int getIterations(); - void setIterations(int i); - - const char* ffmpeg_filter; - - virtual void process_image(double timecode, uint8_t* input, uint8_t* output, int size); - virtual void process_shader(double timecode, GLTextureCoords&, int iteration); - virtual void process_coords(double timecode, GLTextureCoords& coords, int data); - virtual GLuint process_superimpose(double timecode); - virtual void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count); - - virtual void gizmo_draw(double timecode, GLTextureCoords& coords); - void gizmo_move(EffectGizmo* sender, int x_movement, int y_movement, double timecode, bool done); - void gizmo_world_to_screen(); - bool are_gizmos_enabled(); - - template - T randomNumber() - { - static std::random_device device; - static std::mt19937 generator(device()); - static std::uniform_int_distribution<> distribution(std::numeric_limits::min(), std::numeric_limits::max()); - return distribution(generator); - } - - static EffectPtr Create(Clip *c, const EffectMeta *em); - static const EffectMeta* GetInternalMeta(int internal_id, int type); -public slots: - void FieldChanged(); - void SetEnabled(bool b); - void SetExpanded(bool e); -signals: - void EnabledChanged(bool); -private slots: - void delete_self(); - void move_up(); - void move_down(); - void save_to_file(); - void load_from_file(); -protected: - // glsl effect - QOpenGLShaderProgram* glslProgram; - QString vertPath; - QString fragPath; - - // superimpose effect - QImage img; - QOpenGLTexture* texture; - - // enable effect to update constantly - virtual bool AlwaysUpdate(); - -private: - bool isOpen; - QVector rows; - QVector gizmos; - bool bound; - int iterations; - - bool enabled_; - bool expanded_; - - int flags_; - - QVector gizmo_dragging_actions_; - - // superimpose functions - virtual void redraw(double timecode); - bool valueHasChanged(double timecode); - QVector cachedValues; - void delete_texture(); - void validate_meta_path(); -}; - -#endif // EFFECT_H diff --git a/effects/effectfield.cpp b/effects/effectfield.cpp index e09723088..5968d7577 100644 --- a/effects/effectfield.cpp +++ b/effects/effectfield.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -24,33 +24,23 @@ #include #include "rendering/renderfunctions.h" - #include "global/config.h" - +#include "global/timing.h" #include "effects/effectrow.h" -#include "effects/effect.h" - +#include "nodes/node.h" #include "undo/undo.h" #include "timeline/clip.h" #include "timeline/sequence.h" - #include "global/math.h" - #include "global/debug.h" -EffectField::EffectField(EffectRow* parent, const QString &i, EffectFieldType t) : +EffectField::EffectField(EffectRow* parent, EffectFieldType t) : QObject(parent), type_(t), - id_(i), - enabled_(true), - colspan_(1) + enabled_(true) { // EffectField MUST be created with a parent. Q_ASSERT(parent != nullptr); - Q_ASSERT(!i.isEmpty() || t == EFFECT_FIELD_UI); - - // Add this field to the parent row specified - parent->AddField(this); // Set a very base default value SetValueAt(0, 0); @@ -64,17 +54,6 @@ EffectRow *EffectField::GetParentRow() return static_cast(parent()); } -int EffectField::GetColumnSpan() -{ - return colspan_; -} - -void EffectField::SetColumnSpan(int i) -{ - Q_ASSERT(i >= 1); - colspan_ = i; -} - QVariant EffectField::ConvertStringToValue(const QString &s) { return s; @@ -175,7 +154,9 @@ QVariant EffectField::GetValueAt(double timecode) } else { QColor before_data = keyframes.at(before_keyframe).data.value(); QColor after_data = keyframes.at(after_keyframe).data.value(); - value = QColor(lerp(before_data.red(), after_data.red(), progress), lerp(before_data.green(), after_data.green(), progress), lerp(before_data.blue(), after_data.blue(), progress)); + value = QColor(lerp(before_data.red(), after_data.red(), progress), + lerp(before_data.green(), after_data.green(), progress), + lerp(before_data.blue(), after_data.blue(), progress)); } persistent_data_ = value; break; @@ -234,18 +215,6 @@ void EffectField::SetValueAt(double time, const QVariant &value) emit Changed(); } -double EffectField::Now() -{ - Clip* c = GetParentRow()->GetParentEffect()->parent_clip; - return playhead_to_clip_seconds(c, c->sequence->playhead); -} - -long EffectField::NowInFrames() -{ - Clip* c = GetParentRow()->GetParentEffect()->parent_clip; - return playhead_to_clip_frame(c, c->sequence->playhead); -} - void EffectField::PrepareDataForKeyframing(bool enabled, ComboAction *ca) { if (enabled) { @@ -253,7 +222,7 @@ void EffectField::PrepareDataForKeyframing(bool enabled, ComboAction *ca) // Create keyframe from perpetual data EffectKeyframe key; - key.time = NowInFrames(); + key.time = GetParentRow()->GetParentEffect()->NowInFrames(); key.data = persistent_data_; key.type = EFFECT_KEYFRAME_LINEAR; @@ -266,7 +235,7 @@ void EffectField::PrepareDataForKeyframing(bool enabled, ComboAction *ca) // Convert keyframes to one "perpetual" keyframe // Set first keyframe to whatever the data is now - ca->append(new SetQVariant(&persistent_data_, persistent_data_, GetValueAt(Now()))); + ca->append(new SetQVariant(&persistent_data_, persistent_data_, GetValueAt(GetParentRow()->GetParentEffect()->Now()))); // Delete all keyframes for (int i=0;iGetParentEffect()->parent_clip->sequence->frame_rate); + return (double(frame) / GetParentRow()->GetParentEffect()->parent_clip->track()->sequence()->frame_rate); } long EffectField::SecondsToFrame(double seconds) { - return qRound(seconds * GetParentRow()->GetParentEffect()->parent_clip->sequence->frame_rate); + return qRound(seconds * GetParentRow()->GetParentEffect()->parent_clip->track()->sequence()->frame_rate); } void EffectField::GetKeyframeData(double timecode, int &before, int &after, double &progress) { diff --git a/effects/effectfield.h b/effects/effectfield.h index 9d5dae487..e72e3d685 100644 --- a/effects/effectfield.h +++ b/effects/effectfield.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -26,8 +26,8 @@ #include #include "effects/keyframe.h" -#include "undo/undo.h" #include "undo/undostack.h" +#include "nodes/nodedatatypes.h" class EffectRow; class ComboAction; @@ -56,6 +56,7 @@ class ComboAction; class EffectField : public QObject { Q_OBJECT public: + /** * @brief The EffectFieldType enum * @@ -101,18 +102,11 @@ public: * using the QObject parent/child system to automate memory management. EffectFields are never expected * to change parent during their lifetime. * - * @param i - * - * Field ID. Must be non-empty. Must also be unique within this Effect. Used for saving/loading values into project - * files so that if ordering of fields are changed, or fields are added/removed from Effects later in development, - * saved values in project files will still link with the correct field. Also used as the uniform variable name - * in GLSL shaders. - * * @param t * * The type of data contained within this field. This is expected to be filled by a derived class. */ - EffectField(EffectRow* parent, const QString& i, EffectFieldType t); + EffectField(EffectRow* parent, EffectFieldType t); /** * @brief Get the EffectRow that this field is a member of. @@ -134,15 +128,6 @@ public: */ const EffectFieldType& type(); - /** - * @brief Get the unique identifier of this field set in the constructor - * - * Mostly used for saving/loading or interacting with GLSL-based shader effects (see EffectField() for more details). - * - * @return - */ - const QString& id(); - /** * @brief Get the value of this field at a given timecode * @@ -193,18 +178,6 @@ public: */ void SetValueAt(double time, const QVariant& value); - /** - * @brief Get the current clip/media time - * - * A convenience function that can be plugged into GetValueAt() to get the value wherever the appropriate Sequence's - * playhead it. - * - * @return - * - * Current clip/media time in seconds. - */ - double Now(); - /** * @brief Set up keyframing on this field * @@ -228,30 +201,6 @@ public: */ void PrepareDataForKeyframing(bool enabled, ComboAction* ca); - /** - * @brief Get field's column span - * - * Used whenever constructing the effect's UI. EffectFields are visually laid out in a table, and some widgets can - * be set to take up multiple columns for a better visual flow. - * - * @return - * - * The current column span. - */ - int GetColumnSpan(); - - /** - * @brief Set field's column span - * - * Used whenever constructing the effect's UI. EffectFields are visually laid out in a table, and some widgets can - * be set to take up multiple columns for a better visual flow. - * - * @param i - * - * The column span to set on this field. - */ - void SetColumnSpan(int i); - /** * @brief Convert a value from this field to a string * @@ -432,11 +381,6 @@ private: */ EffectFieldType type_; - /** - * @brief Internal unique identifier for this field set in the constructor. Access with id(). - */ - QString id_; - /** * @brief Used by GetValueAt() to determine whether to use keyframe data or persistent data * @return @@ -492,28 +436,11 @@ private: */ void GetKeyframeData(double timecode, int& before, int& after, double& d); - /** - * @brief Retrieve the current clip as a frame number - * - * Same as Now() but retrieves the value as a frame number (in the appropriate Sequence's frame rate) instead of - * seconds. - * - * @return - * - * The current clip time in frames - */ - long NowInFrames(); - /** * @brief Internal enabled value */ bool enabled_; - /** - * @brief Internal column span value - */ - int colspan_; - }; #endif // EFFECTFIELD_H diff --git a/effects/effectfields.h b/effects/effectfields.h index 1a6e1777a..dacd3b7d4 100644 --- a/effects/effectfields.h +++ b/effects/effectfields.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef EFFECTFIELDS_H #define EFFECTFIELDS_H diff --git a/effects/effectgizmo.cpp b/effects/effectgizmo.cpp index 1c71afdc5..db810016c 100644 --- a/effects/effectgizmo.cpp +++ b/effects/effectgizmo.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -22,9 +22,9 @@ #include "ui/labelslider.h" #include "effects/fields/doublefield.h" -#include "effects/effect.h" +#include "nodes/node.h" -EffectGizmo::EffectGizmo(Effect *parent, int type) : +EffectGizmo::EffectGizmo(Node *parent, int type) : QObject(parent), x_field1(nullptr), x_field_multi1(1.0), diff --git a/effects/effectgizmo.h b/effects/effectgizmo.h index 30332b21d..aea73fcf2 100644 --- a/effects/effectgizmo.h +++ b/effects/effectgizmo.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -27,25 +27,26 @@ enum GizmoType { GIZMO_TYPE_TARGET }; -#define GIZMO_DOT_SIZE 2.5 -#define GIZMO_TARGET_SIZE 5.0 +#define GIZMO_DOT_SIZE 2.5f +#define GIZMO_TARGET_SIZE 5.0f #include #include #include #include #include +#include #include class DoubleField; -class Effect; +class Node; class EffectGizmo : public QObject { Q_OBJECT public: - EffectGizmo(Effect* parent, int type); + EffectGizmo(Node* parent, int type); - QVector world_pos; + QVector world_pos; QVector screen_pos; DoubleField* x_field1; diff --git a/effects/effectloaders.cpp b/effects/effectloaders.cpp index 83fdc5b64..ae04cce81 100644 --- a/effects/effectloaders.cpp +++ b/effects/effectloaders.cpp @@ -1,149 +1,106 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #include "effectloaders.h" -#include "effects/effect.h" +#include +#include +#include + +#include "nodes/node.h" #include "effects/transition.h" #include "global/path.h" #include "panels/panels.h" #include "panels/effectcontrols.h" #include "global/config.h" -#include -#include -#include -#include +#include "effects/internal/transformeffect.h" +#include "effects/internal/texteffect.h" +#include "effects/internal/timecodeeffect.h" +#include "effects/internal/solideffect.h" +#include "effects/internal/audionoiseeffect.h" +#include "effects/internal/toneeffect.h" +#include "effects/internal/volumeeffect.h" +#include "effects/internal/paneffect.h" +#include "effects/internal/shakeeffect.h" +#include "effects/internal/cornerpineffect.h" +#include "effects/internal/vsthost.h" +#include "effects/internal/fillleftrighteffect.h" +#include "effects/internal/richtexteffect.h" -#ifndef NOFREI0R -#include -typedef void (*f0rGetPluginInfo)(f0r_plugin_info_t* info); -#endif +#include "effects/internal/crossdissolvetransition.h" +#include "effects/internal/linearfadetransition.h" +#include "effects/internal/logarithmicfadetransition.h" +#include "effects/internal/exponentialfadetransition.h" + +#include "nodes/nodes/nodemedia.h" +#include "nodes/nodes/nodeimageoutput.h" +#include "nodes/nodes/nodeshader.h" + +QMutex olive::effects_loaded; void load_internal_effects() { - if (!olive::CurrentRuntimeConfig.shaders_are_enabled) { + if (!olive::runtime_config.shaders_are_enabled) { qWarning() << "Shaders are disabled, some effects may be nonfunctional"; } - EffectMeta em; + olive::node_library.resize(kInvalidNode); + olive::node_library.fill(nullptr); - // load internal effects - em.path = ":/internalshaders"; - - em.type = EFFECT_TYPE_EFFECT; - em.subtype = EFFECT_TYPE_AUDIO; - - em.name = "Volume"; - em.internal = EFFECT_INTERNAL_VOLUME; - effects.append(em); - - em.name = "Pan"; - em.internal = EFFECT_INTERNAL_PAN; - effects.append(em); - - em.name = "VST Plugin 2.x"; - em.internal = EFFECT_INTERNAL_VST; - effects.append(em); - - em.name = "Tone"; - em.internal = EFFECT_INTERNAL_TONE; - effects.append(em); - - em.name = "Noise"; - em.internal = EFFECT_INTERNAL_NOISE; - effects.append(em); - - em.name = "Fill Left/Right"; - em.internal = EFFECT_INTERNAL_FILLLEFTRIGHT; - effects.append(em); - - em.subtype = EFFECT_TYPE_VIDEO; - - em.name = "Transform"; - em.category = "Distort"; - em.internal = EFFECT_INTERNAL_TRANSFORM; - effects.append(em); - - em.name = "Corner Pin"; - em.internal = EFFECT_INTERNAL_CORNERPIN; - effects.append(em); - - /*em.name = "Mask"; - em.internal = EFFECT_INTERNAL_MASK; - effects.append(em);*/ - - em.name = "Shake"; - em.internal = EFFECT_INTERNAL_SHAKE; - effects.append(em); - - em.name = "Text"; - em.category = "Render"; - em.internal = EFFECT_INTERNAL_TEXT; - effects.append(em); - - em.name = "Rich Text"; - em.category = "Render"; - em.internal = EFFECT_INTERNAL_RICHTEXT; - effects.append(em); - - em.name = "Timecode"; - em.internal = EFFECT_INTERNAL_TIMECODE; - effects.append(em); - - em.name = "Solid"; - em.internal = EFFECT_INTERNAL_SOLID; - effects.append(em); - - // internal transitions - em.type = EFFECT_TYPE_TRANSITION; - em.category = ""; - - em.name = "Cross Dissolve"; - em.internal = TRANSITION_INTERNAL_CROSSDISSOLVE; - effects.append(em); - - em.subtype = EFFECT_TYPE_AUDIO; - - em.name = "Linear Fade"; - em.internal = TRANSITION_INTERNAL_LINEARFADE; - effects.append(em); - - em.name = "Exponential Fade"; - em.internal = TRANSITION_INTERNAL_EXPONENTIALFADE; - effects.append(em); - - em.name = "Logarithmic Fade"; - em.internal = TRANSITION_INTERNAL_LOGARITHMICFADE; - effects.append(em); + olive::node_library[kTransformEffect] = std::make_shared(nullptr); + olive::node_library[kTextInput] = std::make_shared(nullptr); + olive::node_library[kSolidInput] = std::make_shared(nullptr); + olive::node_library[kNoiseInput] = std::make_shared(nullptr); + olive::node_library[kVolumeEffect] = std::make_shared(nullptr); + olive::node_library[kPanEffect] = std::make_shared(nullptr); + olive::node_library[kToneInput] = std::make_shared(nullptr); + olive::node_library[kShakeEffect] = std::make_shared(nullptr); + olive::node_library[kTimecodeEffect] = std::make_shared(nullptr); + olive::node_library[kFillLeftRightEffect] = std::make_shared(nullptr); + olive::node_library[kVstEffect] = std::make_shared(nullptr); + olive::node_library[kCornerPinEffect] = std::make_shared(nullptr); + olive::node_library[kRichTextInput] = std::make_shared(nullptr); + olive::node_library[kMediaInput] = std::make_shared(nullptr); + olive::node_library[kImageOutput] = std::make_shared(nullptr); + olive::node_library[kCrossDissolveTransition] = std::make_shared(nullptr); + olive::node_library[kLinearFadeTransition] = std::make_shared(nullptr); + olive::node_library[kExponentialFadeTransition] = std::make_shared(nullptr); + olive::node_library[kLogarithmicFadeTransition] = std::make_shared(nullptr); } void load_shader_effects_worker(const QString& effects_path) { QDir effects_dir(effects_path); if (effects_dir.exists()) { - QList entries = effects_dir.entryList(QStringList("*.xml"), QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); + + QList entries = effects_dir.entryList({"*.xml"}, + QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); + for (int i=0;i(nullptr, + effect_name, + effect_id, + effect_cat, + file_url)); } else { qCritical() << "Invalid effect found in" << entries.at(i); } @@ -201,86 +157,14 @@ void EffectInit::StartLoading() { init_thread->start(); } -#ifndef NOFREI0R -void load_frei0r_effects_worker(const QString& dir, EffectMeta& em, QVector& loaded_names) { - QDir search_dir(dir); - if (search_dir.exists()) { - QList entry_list = search_dir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); - for (int j=0;j(effect.resolve("f0r_get_plugin_info")); - if (get_info_func != nullptr) { - f0r_plugin_info_t info; - get_info_func(&info); - - if (!loaded_names.contains(info.name) - && info.plugin_type == F0R_PLUGIN_TYPE_FILTER - && info.color_model == F0R_COLOR_MODEL_RGBA8888) { - em.name = info.name; - em.path = dir; - em.filename = entry_list.at(j); - em.tooltip = QString("%1\n%2\n%3\n%4").arg(em.name, info.author, info.explanation, em.filename); - - loaded_names.append(em.name); - - effects.append(em); - } - } - effect.unload(); - } - } - } - } -} - -void load_frei0r_effects() { - QList effect_dirs = get_effects_paths(); - - // add defined paths for frei0r plugins on unix -#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX) || defined(__HAIKU__) - effect_dirs.prepend("/usr/lib/frei0r-1"); - effect_dirs.prepend("/usr/local/lib/frei0r-1"); - effect_dirs.prepend(QDir::homePath() + "/.frei0r-1/lib"); -#endif - - QString env_path(qgetenv("FREI0R_PATH")); - if (!env_path.isEmpty()) effect_dirs.append(env_path); - - QVector loaded_names; - - // search for paths - EffectMeta em; - em.category = "Frei0r"; - em.type = EFFECT_TYPE_EFFECT; - em.subtype = EFFECT_TYPE_VIDEO; - em.internal = EFFECT_INTERNAL_FREI0R; - - for (int i=0;ieffects_loaded.lock(); + olive::effects_loaded.lock(); } void EffectInit::run() { qInfo() << "Initializing effects..."; load_internal_effects(); load_shader_effects(); -#ifndef NOFREI0R - load_frei0r_effects(); -#endif - panel_effect_controls->effects_loaded.unlock(); + olive::effects_loaded.unlock(); qInfo() << "Finished initializing effects"; } diff --git a/effects/effectloaders.h b/effects/effectloaders.h index e4a3da21e..ea7294358 100644 --- a/effects/effectloaders.h +++ b/effects/effectloaders.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -23,6 +23,11 @@ #include #include +#include + +namespace olive { + extern QMutex effects_loaded; +} /** * @brief The EffectInit class diff --git a/effects/effectrow.cpp b/effects/effectrow.cpp index 912d25ad2..274ce3479 100644 --- a/effects/effectrow.cpp +++ b/effects/effectrow.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -32,17 +32,23 @@ #include "panels/effectcontrols.h" #include "panels/viewer.h" #include "panels/grapheditor.h" -#include "effect.h" +#include "nodes/node.h" #include "ui/viewerwidget.h" #include "ui/keyframenavigator.h" #include "ui/clickablelabel.h" -EffectRow::EffectRow(Effect *parent, const QString &n, bool savable, bool keyframable) : +EffectRow::EffectRow(Node *parent, + const QString &id, + const QString &name, + bool savable, + bool keyframable) : QObject(parent), - name_(n), + id_(id), + name_(name), keyframable_(keyframable), keyframing_(false), - savable_(savable) + savable_(savable), + output_type_(olive::nodes::kInvalid) { Q_ASSERT(parent != nullptr); @@ -52,15 +58,67 @@ EffectRow::EffectRow(Effect *parent, const QString &n, bool savable, bool keyfra void EffectRow::AddField(EffectField *field) { field->setParent(this); + + connect(field, SIGNAL(Clicked()), this, SIGNAL(Clicked())); + connect(field, SIGNAL(Changed()), this, SIGNAL(Changed())); + fields_.append(field); } +void EffectRow::AddAcceptedNodeInput(olive::nodes::DataType type) +{ + Q_ASSERT(output_type_ == olive::nodes::kInvalid); + + accepted_inputs_.append(type); +} + +void EffectRow::ConnectEdge(EffectRow *output, EffectRow *input) +{ + // Make sure one is an output and one is an input + Q_ASSERT(output->IsNodeInput() != input->IsNodeInput()); + + // Swap them if necessary + if (input->IsNodeOutput()) { + EffectRow* temp = output; + output = input; + input = temp; + } + + // Inputs can only have one edge, so we disconnect it here if there is one + if (!input->node_edges_.isEmpty()) { + DisconnectEdge(input->node_edges_.first()); + } + + NodeEdgePtr edge = std::make_shared(output, input); + + output->node_edges_.append(edge); + input->node_edges_.append(edge); + + emit output->EdgesChanged(); +} + +void EffectRow::DisconnectEdge(NodeEdgePtr edge) +{ + EffectRow* output = edge->output(); + EffectRow* input = edge->input(); + + output->node_edges_.removeAll(edge); + input->node_edges_.removeAll(edge); + + emit output->EdgesChanged(); +} + +QVector EffectRow::edges() +{ + return node_edges_; +} + bool EffectRow::IsKeyframing() { return keyframing_; } void EffectRow::SetKeyframingInternal(bool b) { - if (GetParentEffect()->meta->type != EFFECT_TYPE_TRANSITION) { + if (GetParentEffect()->type() != EFFECT_TYPE_TRANSITION) { keyframing_ = b; emit KeyframingSetChanged(keyframing_); } @@ -76,6 +134,58 @@ bool EffectRow::IsKeyframable() return keyframable_; } +QVariant EffectRow::GetValueAt(double timecode) +{ + Q_ASSERT(FieldCount() == 1); + + return Field(0)->GetValueAt(timecode); +} + +void EffectRow::SetValueAt(double timecode, const QVariant &value) +{ + Q_ASSERT(FieldCount() == 1); + + Field(0)->SetValueAt(timecode, value); +} + +void EffectRow::SetEnabled(bool enabled) +{ + for (int i=0;iSetEnabled(enabled); + } +} + +void EffectRow::SetOutputDataType(olive::nodes::DataType type) +{ + Q_ASSERT(accepted_inputs_.isEmpty()); + + output_type_ = type; +} + +bool EffectRow::CanAcceptDataType(olive::nodes::DataType type) +{ + if (!IsNodeInput()) { + return false; + } + + return accepted_inputs_.contains(type); +} + +olive::nodes::DataType EffectRow::OutputDataType() +{ + return output_type_; +} + +bool EffectRow::IsNodeInput() +{ + return !accepted_inputs_.isEmpty(); +} + +bool EffectRow::IsNodeOutput() +{ + return output_type_ != olive::nodes::kInvalid; +} + void EffectRow::SetKeyframingEnabled(bool enabled) { if (enabled == keyframing_) { return; @@ -93,7 +203,7 @@ void EffectRow::SetKeyframingEnabled(bool enabled) { Field(i)->PrepareDataForKeyframing(true, ca); } - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); update_ui(false); @@ -116,7 +226,7 @@ void EffectRow::SetKeyframingEnabled(bool enabled) { // Disable keyframing setting on this row ca->append(new SetIsKeyframing(this, false)); - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); update_ui(false); @@ -131,7 +241,7 @@ void EffectRow::SetKeyframingEnabled(bool enabled) { void EffectRow::GoToPreviousKeyframe() { long key = LONG_MIN; Clip* c = GetParentEffect()->parent_clip; - long sequence_playhead = c->sequence->playhead; + long sequence_playhead = c->track()->sequence()->playhead; // Used to convert clip frame number to sequence frame number long time_adjustment = c->timeline_in() - c->clip_in(); @@ -158,7 +268,7 @@ void EffectRow::GoToPreviousKeyframe() { void EffectRow::ToggleKeyframe() { Clip* c = GetParentEffect()->parent_clip; - long sequence_playhead = c->sequence->playhead; + long sequence_playhead = c->track()->sequence()->playhead; // Used to convert clip frame number to sequence frame number long time_adjustment = c->timeline_in() - c->clip_in(); @@ -222,7 +332,7 @@ void EffectRow::ToggleKeyframe() { } - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); update_ui(false); } @@ -233,7 +343,7 @@ void EffectRow::GoToNextKeyframe() { EffectField* f = Field(i); for (int j=0;jkeyframes.size();j++) { long comp = f->keyframes.at(j).time - c->clip_in() + c->timeline_in(); - if (comp > olive::ActiveSequence->playhead) { + if (comp > c->track()->sequence()->playhead) { key = qMin(comp, key); } } @@ -251,7 +361,7 @@ void EffectRow::SetKeyframeOnAllFields(ComboAction* ca) { KeyframeDataChange* kdc = new KeyframeDataChange(field); - field->SetValueAt(field->Now(), field->GetValueAt(field->Now())); + field->SetValueAt(GetParentEffect()->Now(), field->GetValueAt(GetParentEffect()->Now())); kdc->SetNewKeyframes(); ca->append(kdc); @@ -260,9 +370,9 @@ void EffectRow::SetKeyframeOnAllFields(ComboAction* ca) { panel_effect_controls->update_keyframes(); } -Effect *EffectRow::GetParentEffect() +Node *EffectRow::GetParentEffect() { - return static_cast(parent()); + return static_cast(parent()); } const QString &EffectRow::name() { diff --git a/effects/effectrow.h b/effects/effectrow.h index 723a368ab..5288cd08e 100644 --- a/effects/effectrow.h +++ b/effects/effectrow.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -24,7 +24,7 @@ #include #include -class Effect; +class Node; class QGridLayout; class EffectField; class QLabel; @@ -35,6 +35,7 @@ class KeyframeNavigator; class ClickableLabel; #include "effectfields.h" +#include "nodes/nodeedge.h" /** * @brief The EffectRow class @@ -51,6 +52,7 @@ class ClickableLabel; class EffectRow : public QObject { Q_OBJECT public: + /** * @brief EffectRow Constructor * @@ -60,7 +62,14 @@ public: * EffectRow and automatically frees it through the QObject parent/child system. EffectRows are never intended to * change parents throughout their lifetimes. * - * @param n + * @param id + * + * Field ID. Must be non-empty. Must also be unique within this Effect. Used for saving/loading values into project + * files so that if ordering of fields are changed, or fields are added/removed from Effects later in development, + * saved values in project files will still link with the correct field. Also used as the uniform variable name + * in GLSL shaders. + * + * @param name * * Row name. This is not used as an internal identifier, it's just for the user interface, so it can be translated * with no issue. @@ -75,19 +84,11 @@ public: * Whether keyframing can be enabled on this row or not. This is true by default. Some values you may want to prevent * the user from keyframing (e.g. the filename of a VST plugin), which can be done by setting this to false. */ - EffectRow(Effect* parent, const QString& n, bool savable = true, bool keyframable = true); - - /** - * @brief Add a field to this row - * - * Ownership of the EffectField is transferred to this row and the row will free its memory. In the Effect's UI, this - * will add the field to an additional column. - * - * @param Field - * - * The field to add to this row. - */ - void AddField(EffectField* Field); + EffectRow(Node* parent, + const QString& id, + const QString& name, + bool savable = true, + bool keyframable = true); /** * @brief Retrieve the EffectField at this index. Must be less than FieldCount(). @@ -126,7 +127,7 @@ public: * * @return The parent Effect object that this row is attached to. */ - Effect* GetParentEffect(); + Node* GetParentEffect(); /** * @brief Return the row's name @@ -135,6 +136,15 @@ public: */ const QString& name(); + /** + * @brief Get the unique identifier of this field set in the constructor + * + * Mostly used for saving/loading or interacting with GLSL-based shader effects (see EffectField() for more details). + * + * @return + */ + const QString& id(); + /** * @brief Get whether this row is keyframing or not * @@ -163,6 +173,150 @@ public: * @return True if this row can be keyframed. This value is set in the constructor. */ bool IsKeyframable(); + + /** + * @brief Get value at a given timecode + * + * Functions as a wrapper for EffectField::GetValueAt(). + * + * The default function is to return the result of the first EffectField on this EffectRow which should be sufficient + * for most input types. If this EffectRow has more or less than one EffectField, you must override this function in a + * derived class to provide the correct field <-> row coordination or else it will trigger an abort. + * + * @param timecode + * + * Timecode to get the value at + * + * @return + * + * The value of the first EffectField at the given timecode + */ + virtual QVariant GetValueAt(double timecode); + + /** + * @brief SetValueAt + * + * Functions as a wrapper for EffectField::SetValueAt(). + * + * The default function is to call the function of the first EffectField on this EffectRow which should be sufficient + * for most input types. If this EffectRow has more or less than one EffectField, you must override this function in a + * derived class to provide the correct field <-> row coordination or else it will trigger an abort. + * + * @param timecode + * + * Timecode to set the value at + * + * @param value + * + * Value to set at this timecode + */ + virtual void SetValueAt(double timecode, const QVariant& value); + + /** + * @brief Sets the enabled state on all EffectField objects on this row to enabled + */ + void SetEnabled(bool enabled); + + /** + * @brief Check if nodes can be connected to this as an input. + * + * Connecting is enabled by adding an accepted node input using AddNodeInput(). + * + * @return + * + * TRUE if nodes can be connected as an input. + */ + bool IsNodeInput(); + + /** + * @brief Check if nodes can be connected to this as an output + * + * Connecting is enabled by setting an output data type in SetOutputDataType(). + * + * @return + * + * TRUE if nodes can be connected as an output + */ + bool IsNodeOutput(); + + /** + * @brief Set output data type + * + * Set the type of data this row outputs to type + */ + void SetOutputDataType(olive::nodes::DataType type); + + /** + * @brief Check if this row can accept the data type specified in type + * + * @return + * + * TRUE if this row can accept this data type. If this row is not an input, this function will always return FALSE. + */ + bool CanAcceptDataType(olive::nodes::DataType type); + + /** + * @brief Get this row's output data type + * + * @return + * + * If this is not an output, this will always return olive::nodes::kInvalid + */ + olive::nodes::DataType OutputDataType(); + + /** + * @brief Adds a node data type that can be accepted by this input + * + * Allows this input to take a node connection from a data type specified by type. An input can take several data + * types. + * + * @param type + * + * The data type to add + */ + void AddAcceptedNodeInput(olive::nodes::DataType type); + + /** + * @brief Connect two node sockets together + * + * For outputs, the maximum amount of edges is unlimited and this will continually add to a list of edges. For inputs, + * there can only be one edge and adding a second will destroy the first. + * + * @param edge + * + * The edge to add (output) or replace the current edge (input). + */ + static void ConnectEdge(EffectRow* output, EffectRow* input); + + /** + * @brief Disconnect an edge + * + * Disconnects two nodes and destroys the edge object connecting them together + * + * @param edge + * + * Edge to be removed + */ + static void DisconnectEdge(NodeEdgePtr edge); + + /** + * @brief Get a list of references to all edges currently connected to this row + */ + QVector edges(); + +protected: + /** + * @brief Add a field to this row + * + * Ownership of the EffectField is transferred to this row and the row will free its memory. In the Effect's UI, this + * will add the field to an additional column. + * + * @param Field + * + * The field to add to this row. + */ + void AddField(EffectField* Field); + public slots: /** * @brief Go to previous keyframe @@ -199,6 +353,7 @@ public slots: */ void FocusRow(); signals: + /** * @brief Keyframing setting changed signal * @@ -209,6 +364,28 @@ signals: * True if keyframing was enabled, false if keyframing was disabled. */ void KeyframingSetChanged(bool); + + /** + * @brief Changed signal + * + * Wrapper for EffectField::Changed(). + */ + void Changed(); + + /** + * @brief Clicked signal + * + * Wrapper for EffectField::Clicked(). + */ + void Clicked(); + + /** + * @brief Edges changed signal + * + * Signal emitted any time an edge is connected or disconnected from this row + */ + void EdgesChanged(); + private slots: /** * @brief Set keyframing enabled state @@ -222,6 +399,12 @@ private slots: */ void SetKeyframingEnabled(bool); private: + + /** + * @brief Internal unique identifier for this field set in the constructor. Access with id(). + */ + QString id_; + /** * @brief Internal variable for the row's name * @@ -257,6 +440,27 @@ private: * get freed automatically. */ QVector fields_; + + /** + * @brief Internal array of accepted node data types. + * + * Is mutally-exclusive with accepted_outputs_, i.e. you cannot have values added to this and also a value set in + * accepted_outputs_. + */ + QVector accepted_inputs_; + + /** + * @brief Internal value for what kind of data this row outputs + * + * Is mutally-exclusive with accepted_inputs_, i.e. you cannot have values added to it and also a value set in + * this. + */ + olive::nodes::DataType output_type_; + + /** + * @brief Internal array of node edges. Access with AddEdge() and RemoveEdge(). + */ + QVector node_edges_; }; #endif // EFFECTROW_H diff --git a/effects/fields/boolfield.cpp b/effects/fields/boolfield.cpp index 7fc7c2a4d..ac3be8ea6 100644 --- a/effects/fields/boolfield.cpp +++ b/effects/fields/boolfield.cpp @@ -1,9 +1,32 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "boolfield.h" #include -BoolField::BoolField(EffectRow *parent, const QString &id) : - EffectField(parent, id, EFFECT_FIELD_BOOL) +#include "nodes/node.h" +#include "undo/undo.h" + +BoolField::BoolField(EffectRow *parent) : + EffectField(parent, EffectField::EFFECT_FIELD_BOOL) {} bool BoolField::GetBoolAt(double timecode) @@ -69,8 +92,8 @@ void BoolField::UpdateFromWidget(bool b) { KeyframeDataChange* kdc = new KeyframeDataChange(this); - SetValueAt(Now(), b); + SetValueAt(GetParentRow()->GetParentEffect()->Now(), b); kdc->SetNewKeyframes(); - olive::UndoStack.push(kdc); + olive::undo_stack.push(kdc); } diff --git a/effects/fields/boolfield.h b/effects/fields/boolfield.h index d84e24c95..1e8e891ae 100644 --- a/effects/fields/boolfield.h +++ b/effects/fields/boolfield.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef BOOLFIELD_H #define BOOLFIELD_H @@ -15,7 +35,7 @@ public: /** * @brief Reimplementation of EffectField::EffectField(). */ - BoolField(EffectRow* parent, const QString& id); + BoolField(EffectRow* parent); /** * @brief Get the boolean value at a given timecode diff --git a/effects/fields/buttonfield.cpp b/effects/fields/buttonfield.cpp index 23cc860af..2023105f3 100644 --- a/effects/fields/buttonfield.cpp +++ b/effects/fields/buttonfield.cpp @@ -1,9 +1,29 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "buttonfield.h" #include ButtonField::ButtonField(EffectRow *parent, const QString &string) : - EffectField(parent, nullptr, EFFECT_FIELD_UI), + EffectField(parent, EffectField::EFFECT_FIELD_UI), button_text_(string) {} diff --git a/effects/fields/buttonfield.h b/effects/fields/buttonfield.h index 2f481af30..0cea15459 100644 --- a/effects/fields/buttonfield.h +++ b/effects/fields/buttonfield.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef BUTTONFIELD_H #define BUTTONFIELD_H diff --git a/effects/fields/colorfield.cpp b/effects/fields/colorfield.cpp index a70135af4..5702b4582 100644 --- a/effects/fields/colorfield.cpp +++ b/effects/fields/colorfield.cpp @@ -1,11 +1,33 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "colorfield.h" #include #include "ui/colorbutton.h" +#include "nodes/node.h" +#include "undo/undo.h" -ColorField::ColorField(EffectRow* parent, const QString& id) : - EffectField(parent, id, EFFECT_FIELD_COLOR) +ColorField::ColorField(EffectRow* parent) : + EffectField(parent, EffectField::EFFECT_FIELD_COLOR) {} QColor ColorField::GetColorAt(double timecode) @@ -44,8 +66,8 @@ void ColorField::UpdateFromWidget(const QColor& c) { KeyframeDataChange* kdc = new KeyframeDataChange(this); - SetValueAt(Now(), c); + SetValueAt(GetParentRow()->GetParentEffect()->Now(), c); kdc->SetNewKeyframes(); - olive::UndoStack.push(kdc); + olive::undo_stack.push(kdc); } diff --git a/effects/fields/colorfield.h b/effects/fields/colorfield.h index c37427f31..235e831dc 100644 --- a/effects/fields/colorfield.h +++ b/effects/fields/colorfield.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef COLORFIELD_H #define COLORFIELD_H @@ -15,7 +35,7 @@ public: /** * @brief Reimplementation of EffectField::EffectField(). */ - ColorField(EffectRow* parent, const QString& id); + ColorField(EffectRow* parent); /** * @brief Get the color value at a given timecode diff --git a/effects/fields/combofield.cpp b/effects/fields/combofield.cpp index ad43bb4f5..c9542a000 100644 --- a/effects/fields/combofield.cpp +++ b/effects/fields/combofield.cpp @@ -1,11 +1,33 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "combofield.h" #include +#include "nodes/node.h" #include "ui/comboboxex.h" +#include "undo/undo.h" -ComboField::ComboField(EffectRow* parent, const QString& id) : - EffectField(parent, id, EFFECT_FIELD_COMBO) +ComboField::ComboField(EffectRow* parent) : + EffectField(parent, EffectField::EFFECT_FIELD_COMBO) {} void ComboField::AddItem(const QString &text, const QVariant &data) @@ -64,8 +86,8 @@ void ComboField::UpdateFromWidget(int index) { KeyframeDataChange* kdc = new KeyframeDataChange(this); - SetValueAt(Now(), items_.at(index).data); + SetValueAt(GetParentRow()->GetParentEffect()->Now(), items_.at(index).data); kdc->SetNewKeyframes(); - olive::UndoStack.push(kdc); + olive::undo_stack.push(kdc); } diff --git a/effects/fields/combofield.h b/effects/fields/combofield.h index 64b7cc7fe..2ba63c400 100644 --- a/effects/fields/combofield.h +++ b/effects/fields/combofield.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef COMBOFIELD_H #define COMBOFIELD_H @@ -28,7 +48,7 @@ public: /** * @brief Reimplementation of EffectField::EffectField(). */ - ComboField(EffectRow* parent, const QString& id); + ComboField(EffectRow* parent); /** * @brief Add an item to this ComboField diff --git a/effects/fields/doublefield.cpp b/effects/fields/doublefield.cpp index ecbadc827..14ef3ec15 100644 --- a/effects/fields/doublefield.cpp +++ b/effects/fields/doublefield.cpp @@ -1,9 +1,30 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "doublefield.h" -#include "effects/effectrow.h" +#include "nodes/node.h" +#include "undo/undo.h" -DoubleField::DoubleField(EffectRow* parent, const QString& id) : - EffectField(parent, id, EFFECT_FIELD_DOUBLE), +DoubleField::DoubleField(EffectRow* parent) : + EffectField(parent, EffectField::EFFECT_FIELD_DOUBLE), min_(qSNaN()), max_(qSNaN()), default_(0), @@ -118,12 +139,12 @@ void DoubleField::UpdateFromWidget(double d) kdc_ = new KeyframeDataChange(this); } - SetValueAt(Now(), d); + SetValueAt(GetParentRow()->GetParentEffect()->Now(), d); if (!ls->IsDragging() && kdc_ != nullptr) { kdc_->SetNewKeyframes(); - olive::UndoStack.push(kdc_); + olive::undo_stack.push(kdc_); kdc_ = nullptr; } diff --git a/effects/fields/doublefield.h b/effects/fields/doublefield.h index 90d1351a8..11734244e 100644 --- a/effects/fields/doublefield.h +++ b/effects/fields/doublefield.h @@ -1,9 +1,31 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef DOUBLEFIELD_H #define DOUBLEFIELD_H #include "../effectfield.h" #include "ui/labelslider.h" +class KeyframeDataChange; + /** * @brief The DoubleField class * @@ -17,7 +39,7 @@ public: /** * @brief Reimplementation of EffectField::EffectField(). */ - DoubleField(EffectRow* parent, const QString& id); + DoubleField(EffectRow* parent); /** * @brief Get double value at timecode diff --git a/effects/fields/filefield.cpp b/effects/fields/filefield.cpp index 577a695f2..cc7f7a244 100644 --- a/effects/fields/filefield.cpp +++ b/effects/fields/filefield.cpp @@ -1,11 +1,33 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "filefield.h" #include #include "ui/embeddedfilechooser.h" +#include "nodes/node.h" +#include "undo/undo.h" -FileField::FileField(EffectRow* parent, const QString &id) : - EffectField(parent, id, EFFECT_FIELD_FILE) +FileField::FileField(EffectRow* parent) : + EffectField(parent, EffectField::EFFECT_FIELD_FILE) { // Set default value to an empty string SetValueAt(0, ""); @@ -39,8 +61,8 @@ void FileField::UpdateFromWidget(const QString &s) { KeyframeDataChange* kdc = new KeyframeDataChange(this); - SetValueAt(Now(), s); + SetValueAt(GetParentRow()->GetParentEffect()->Now(), s); kdc->SetNewKeyframes(); - olive::UndoStack.push(kdc); + olive::undo_stack.push(kdc); } diff --git a/effects/fields/filefield.h b/effects/fields/filefield.h index f0717f9c0..3752fd3f7 100644 --- a/effects/fields/filefield.h +++ b/effects/fields/filefield.h @@ -1,10 +1,30 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef FILEFIELD_H #define FILEFIELD_H #include "../effectfield.h" /** - * @brief The FileField class + * @brief The FileInput class * * An EffectField derivative that produces filenames in string and uses an EmbeddedFileChooser * as its visual representation. @@ -16,7 +36,7 @@ public: /** * @brief Reimplementation of EffectField::EffectField(). */ - FileField(EffectRow* parent, const QString& id); + FileField(EffectRow* parent); /** * @brief Get the filename at the given timecode diff --git a/effects/fields/fontfield.cpp b/effects/fields/fontfield.cpp index 8ee06877a..24e51615a 100644 --- a/effects/fields/fontfield.cpp +++ b/effects/fields/fontfield.cpp @@ -1,14 +1,36 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "fontfield.h" #include #include #include "ui/comboboxex.h" +#include "nodes/node.h" +#include "undo/undo.h" -// NOTE/TODO: This shares a lot of similarity with ComboField, and could probably be a derived class of it +// NOTE/TODO: This shares a lot of similarity with ComboInput, and could probably be a derived class of it -FontField::FontField(EffectRow* parent, const QString &id) : - EffectField(parent, id, EFFECT_FIELD_FONT) +FontField::FontField(EffectRow* parent) : + EffectField(parent, EffectField::EFFECT_FIELD_FONT) { font_list = QFontDatabase().families(); @@ -66,8 +88,8 @@ void FontField::UpdateFromWidget(const QString& s) { KeyframeDataChange* kdc = new KeyframeDataChange(this); - SetValueAt(Now(), s); + SetValueAt(GetParentRow()->GetParentEffect()->Now(), s); kdc->SetNewKeyframes(); - olive::UndoStack.push(kdc); + olive::undo_stack.push(kdc); } diff --git a/effects/fields/fontfield.h b/effects/fields/fontfield.h index b34b122d5..6abd7c6c2 100644 --- a/effects/fields/fontfield.h +++ b/effects/fields/fontfield.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef FONTFIELD_H #define FONTFIELD_H @@ -17,7 +37,7 @@ public: /** * @brief Reimplementation of EffectField::EffectField(). */ - FontField(EffectRow* parent, const QString& id); + FontField(EffectRow* parent); /** * @brief Get the font family name at the given timecode diff --git a/effects/fields/labelfield.cpp b/effects/fields/labelfield.cpp index b3eccfa6d..45306699a 100644 --- a/effects/fields/labelfield.cpp +++ b/effects/fields/labelfield.cpp @@ -1,9 +1,29 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "labelfield.h" #include LabelField::LabelField(EffectRow *parent, const QString &string) : - EffectField(parent, nullptr, EFFECT_FIELD_UI), + EffectField(parent, EffectField::EFFECT_FIELD_UI), label_text_(string) {} diff --git a/effects/fields/labelfield.h b/effects/fields/labelfield.h index 64aa78ddb..dba2912ff 100644 --- a/effects/fields/labelfield.h +++ b/effects/fields/labelfield.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef LABELFIELD_H #define LABELFIELD_H diff --git a/effects/fields/stringfield.cpp b/effects/fields/stringfield.cpp index 7d59d3391..6da53cd93 100644 --- a/effects/fields/stringfield.cpp +++ b/effects/fields/stringfield.cpp @@ -1,13 +1,35 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "stringfield.h" #include #include +#include "nodes/node.h" #include "ui/texteditex.h" #include "global/config.h" +#include "undo/undo.h" -StringField::StringField(EffectRow* parent, const QString& id, bool rich_text) : - EffectField(parent, id, EFFECT_FIELD_STRING), +StringField::StringField(EffectRow* parent, bool rich_text) : + EffectField(parent, EffectField::EFFECT_FIELD_STRING), rich_text_(rich_text) { // Set default value to an empty string @@ -31,7 +53,7 @@ QWidget *StringField::CreateWidget(QWidget *existing) text_edit->setUndoRedoEnabled(true); // the "2" is because the height needs one extra pixel of padding on the top and the bottom - text_edit->setTextHeight(qCeil(text_edit->fontMetrics().lineSpacing()*olive::CurrentConfig.effect_textbox_lines + text_edit->setTextHeight(qCeil(text_edit->fontMetrics().lineSpacing()*olive::config.effect_textbox_lines + text_edit->document()->documentMargin() + text_edit->document()->documentMargin() + 2)); @@ -72,8 +94,8 @@ void StringField::UpdateFromWidget(const QString &s) { KeyframeDataChange* kdc = new KeyframeDataChange(this); - SetValueAt(Now(), s); + SetValueAt(GetParentRow()->GetParentEffect()->Now(), s); kdc->SetNewKeyframes(); - olive::UndoStack.push(kdc); + olive::undo_stack.push(kdc); } diff --git a/effects/fields/stringfield.h b/effects/fields/stringfield.h index d8ee9f360..4124285bb 100644 --- a/effects/fields/stringfield.h +++ b/effects/fields/stringfield.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef STRINGFIELD_H #define STRINGFIELD_H @@ -19,7 +39,7 @@ public: * Provides a setting for whether this StringField - and its attached TextEditEx objects - should operate in rich * text or plain text mode, defaulting to rich text mode. */ - StringField(EffectRow* parent, const QString& id, bool rich_text = true); + StringField(EffectRow* parent, bool rich_text = true); /** * @brief Get the string at the given timecode diff --git a/effects/internal/audionoiseeffect.cpp b/effects/internal/audionoiseeffect.cpp index 3f51bcf21..128921978 100644 --- a/effects/internal/audionoiseeffect.cpp +++ b/effects/internal/audionoiseeffect.cpp @@ -1,61 +1,94 @@ -/* - * Olive. Olive is a free non-linear video editor for Windows, macOS, and Linux. - * Copyright (C) 2018 {{ organization }} - * - * 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 . - */ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "audionoiseeffect.h" #include #include -AudioNoiseEffect::AudioNoiseEffect(Clip* c, const EffectMeta *em) : Effect(c, em) { - EffectRow* amount_row = new EffectRow(this, tr("Amount")); - amount_val = new DoubleField(amount_row, "amount"); +AudioNoiseEffect::AudioNoiseEffect(Clip* c) : Node(c) { + amount_val = new DoubleInput(this, "amount", tr("Amount")); amount_val->SetMinimum(0); amount_val->SetDefault(20); amount_val->SetMaximum(100); - EffectRow* mix_row = new EffectRow(this, tr("Mix")); - mix_val = new BoolField(mix_row, "mix"); + mix_val = new BoolInput(this, "mix", tr("Mix")); mix_val->SetValueAt(0, true); } -void AudioNoiseEffect::process_audio(double timecode_start, double timecode_end, quint8 *samples, int nb_bytes, int) { - double interval = (timecode_end - timecode_start)/nb_bytes; - for (int i=0;i(c); +} + +void AudioNoiseEffect::process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) { + + Q_UNUSED(type) + + double interval = (timecode_end - timecode_start)/nb_samples; + for (int i=0;irandomNumber(); - qint16 right_noise_sample = this->randomNumber(); - // set noise volume - double vol = log_volume( amount_val->GetDoubleAt(timecode)*0.01 ); - left_noise_sample *= vol; - right_noise_sample *= vol; + float vol = log_volume( amount_val->GetDoubleAt(timecode)*0.01 ); - // mix with source audio - if (mix_val->GetBoolAt(timecode)) { - qint16 left_sample = static_cast (((samples[i+1] & 0xFF) << 8) | (samples[i] & 0xFF)); - qint16 right_sample = static_cast (((samples[i+3] & 0xFF) << 8) | (samples[i+2] & 0xFF)); - left_noise_sample = mix_audio_sample(left_noise_sample, left_sample); - right_noise_sample = mix_audio_sample(right_noise_sample, right_sample); + for (int j=0;jrandomFloat() * vol; + + // mix with source audio + if (mix_val->GetBoolAt(timecode)) { + samples[j][i] += noise_sample; + } else { + samples[j][i] = noise_sample; + } } - - samples[i+3] = static_cast (right_noise_sample >> 8); - samples[i+2] = static_cast (right_noise_sample); - samples[i+1] = static_cast (left_noise_sample >> 8); - samples[i] = static_cast (left_noise_sample); } } diff --git a/effects/internal/audionoiseeffect.h b/effects/internal/audionoiseeffect.h index dab29be78..d950080b5 100644 --- a/effects/internal/audionoiseeffect.h +++ b/effects/internal/audionoiseeffect.h @@ -1,36 +1,49 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef AUDIONOISEEFFECT_H #define AUDIONOISEEFFECT_H -#include "effects/effect.h" +#include "nodes/node.h" -class AudioNoiseEffect : public Effect { +class AudioNoiseEffect : public Node { Q_OBJECT public: - AudioNoiseEffect(Clip* c, const EffectMeta* em); - void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count); + AudioNoiseEffect(Clip* c); - DoubleField* amount_val; - BoolField* mix_val; + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) override; + + DoubleInput* amount_val; + BoolInput* mix_val; }; #endif // AUDIONOISEEFFECT_H diff --git a/effects/internal/blending.frag b/effects/internal/blending.frag deleted file mode 100644 index c8d14d593..000000000 --- a/effects/internal/blending.frag +++ /dev/null @@ -1,207 +0,0 @@ -#version 110 - -const int BLEND_MODE_ADD = 0; -const int BLEND_MODE_AVERAGE = 1; -const int BLEND_MODE_COLORBURN = 2; -const int BLEND_MODE_COLORDODGE = 3; -const int BLEND_MODE_DARKEN = 4; -const int BLEND_MODE_DIFFERENCE = 5; -const int BLEND_MODE_EXCLUSION = 6; -const int BLEND_MODE_GLOW = 7; -const int BLEND_MODE_HARDLIGHT = 8; -const int BLEND_MODE_HARDMIX = 9; -const int BLEND_MODE_LIGHTEN = 10; -const int BLEND_MODE_LINEARBURN = 11; -const int BLEND_MODE_LINEARDODGE = 12; -const int BLEND_MODE_LINEARLIGHT = 13; -const int BLEND_MODE_MULTIPLY = 14; -const int BLEND_MODE_NEGATION = 15; -const int BLEND_MODE_NORMAL = 16; -const int BLEND_MODE_OVERLAY = 17; -const int BLEND_MODE_PHOENIX = 18; -const int BLEND_MODE_PINLIGHT = 19; -const int BLEND_MODE_REFLECT = 20; -const int BLEND_MODE_SCREEN = 21; -const int BLEND_MODE_SOFTLIGHT = 22; -const int BLEND_MODE_SUBSTRACT = 23; -const int BLEND_MODE_SUBTRACT = 24; -const int BLEND_MODE_VIVIDLIGHT = 25; - -uniform sampler2D background; -uniform sampler2D foreground; - -uniform int blendmode; -uniform float opacity; - -varying vec2 vTexCoord; - -// adapted from https://github.com/jamieowen/glsl-blend -// and http://www.deepskycolors.com/archivo/2010/04/21/formulas-for-Photoshop-blending-modes.html - -// float blending functions -float blend_color_burn(float base, float blend) { - return (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0); -} - -float blend_color_dodge(float base, float blend) { - return (blend==1.0)?blend:min(base/(1.0-blend),1.0); -} - -float blend_vivid_light(float base, float blend) { - return (blend<0.5)?blend_color_burn(base,(2.0*blend)):blend_color_dodge(base,(2.0*(blend-0.5))); -} - -vec3 blend_vivid_light(vec3 base, vec3 blend) { - return vec3(blend_vivid_light(base.r,blend.r),blend_vivid_light(base.g,blend.g),blend_vivid_light(base.b,blend.b)); -} - -float blend_hard_mix(float base, float blend) { - return (blend_vivid_light(base,blend)<0.5)?0.0:1.0; -} - -float blend_lighten(float base, float blend) { - return max(blend,base); -} - -float blend_overlay(float base, float blend) { - return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend)); -} - -vec3 blend_overlay(vec3 base, vec3 blend) { - return vec3(blend_overlay(base.r,blend.r),blend_overlay(base.g,blend.g),blend_overlay(base.b,blend.b)); -} - -float blend_darken(float base, float blend) { - return min(blend, base); -} - -float blend_linear_burn(float base, float blend) { - return max(base+blend-1.0,0.0); -} - -vec3 blend_linear_burn(vec3 base, vec3 blend) { - return max(base+blend-vec3(1.0),vec3(0.0)); -} - -float blend_linear_dodge(float base, float blend) { - return min(base+blend,1.0); -} - -vec3 blend_linear_dodge(vec3 base, vec3 blend) { - return min(base+blend,vec3(1.0)); -} - -float blend_linear_light(float base, float blend) { - return blend<0.5?blend_linear_burn(base,(2.0*blend)):blend_linear_dodge(base,(2.0*(blend-0.5))); -} - -float blend_pin_light(float base, float blend) { - return (blend<0.5)?blend_darken(base,(2.0*blend)):blend_lighten(base,(2.0*(blend-0.5))); -} - -float blend_reflect(float base, float blend) { - return (blend==1.0)?blend:min(base*base/(1.0-blend),1.0); -} - -vec3 blend_reflect(vec3 base, vec3 blend) { - return vec3(blend_reflect(base.r,blend.r),blend_reflect(base.g,blend.g),blend_reflect(base.b,blend.b)); -} - -float blend_screen(float base, float blend) { - return 1.0-((1.0-base)*(1.0-blend)); -} - -float blend_substract(float base, float blend) { - return max(base+blend-1.0,0.0); -} - -float blend_soft_light(float base, float blend) { - return (blend<0.5)?(2.0*base*blend+base*base*(1.0-2.0*blend)):(sqrt(base)*(2.0*blend-1.0)+2.0*base*(1.0-blend)); -} - -// RGB blending function, alpha is handled below -vec3 blend(vec3 base, vec3 blend) { - if (blendmode == BLEND_MODE_AVERAGE) { - return (base+blend)/2.0; - } else if (blendmode == BLEND_MODE_COLORBURN) { - return vec3(blend_color_burn(base.r, blend.r), blend_color_burn(base.g, blend.g), blend_color_burn(base.b, blend.b)); - } else if (blendmode == BLEND_MODE_COLORDODGE) { - return vec3(blend_color_dodge(base.r, blend.r), blend_color_dodge(base.g, blend.g), blend_color_dodge(base.b, blend.b)); - } else if (blendmode == BLEND_MODE_DARKEN) { - return vec3(blend_darken(base.r, blend.r), blend_darken(base.g, blend.g), blend_darken(base.b, blend.b)); - } else if (blendmode == BLEND_MODE_DIFFERENCE) { - return abs(base-blend); - } else if (blendmode == BLEND_MODE_EXCLUSION) { - return base+blend-2.0*base*blend; - } else if (blendmode == BLEND_MODE_GLOW) { - return blend_reflect(blend, base); - } else if (blendmode == BLEND_MODE_HARDLIGHT) { - return blend_overlay(blend,base); - } else if (blendmode == BLEND_MODE_HARDMIX) { - return vec3(blend_hard_mix(base.r,blend.r),blend_hard_mix(base.g,blend.g),blend_hard_mix(base.b,blend.b)); - } else if (blendmode == BLEND_MODE_LIGHTEN) { - return vec3(blend_lighten(base.r,blend.r),blend_lighten(base.g,blend.g),blend_lighten(base.b,blend.b)); - } else if (blendmode == BLEND_MODE_LINEARBURN || blendmode == BLEND_MODE_SUBTRACT) { - return blend_linear_burn(base, blend); - } else if (blendmode == BLEND_MODE_LINEARDODGE || blendmode == BLEND_MODE_ADD) { - return blend_linear_dodge(base, blend); - } else if (blendmode == BLEND_MODE_LINEARLIGHT) { - return vec3(blend_linear_light(base.r,blend.r),blend_linear_light(base.g,blend.g),blend_linear_light(base.b,blend.b)); - } else if (blendmode == BLEND_MODE_MULTIPLY) { - return (base * blend); - } else if (blendmode == BLEND_MODE_NEGATION) { - return vec3(1.0)-abs(vec3(1.0)-base-blend); - } else if (blendmode == BLEND_MODE_OVERLAY) { - return blend_overlay(base, blend); - } else if (blendmode == BLEND_MODE_PHOENIX) { - return min(base,blend)-max(base,blend)+vec3(1.0); - } else if (blendmode == BLEND_MODE_PINLIGHT) { - return vec3(blend_pin_light(base.r,blend.r),blend_pin_light(base.g,blend.g),blend_pin_light(base.b,blend.b)); - } else if (blendmode == BLEND_MODE_REFLECT) { - return blend_reflect(base, blend); - } else if (blendmode == BLEND_MODE_SCREEN) { - return vec3(blend_screen(base.r,blend.r),blend_screen(base.g,blend.g),blend_screen(base.b,blend.b)); - } else if (blendmode == BLEND_MODE_SUBSTRACT) { - return max(base+blend-vec3(1.0),vec3(0.0)); - } else if (blendmode == BLEND_MODE_SOFTLIGHT) { - return vec3(blend_soft_light(base.r,blend.r),blend_soft_light(base.g,blend.g),blend_soft_light(base.b,blend.b)); - } else if (blendmode == BLEND_MODE_VIVIDLIGHT) { - return vec3(blend_vivid_light(base.r,blend.r),blend_vivid_light(base.g,blend.g),blend_vivid_light(base.b,blend.b)); - } else { - return blend; - } -} - -void main(void) { - vec4 bg_color = texture2D(background, vTexCoord); - vec4 fg_color = texture2D(foreground, vTexCoord); - - // blend textures together - vec3 composite = blend(bg_color.rgb, fg_color.rgb); - - // add foreground and background alpha's together - // float alpha_opac = fg_color.a*opacity; - - if (blendmode == BLEND_MODE_OVERLAY - || blendmode == BLEND_MODE_LIGHTEN - || blendmode == BLEND_MODE_SCREEN - || blendmode == BLEND_MODE_COLORDODGE - || blendmode == BLEND_MODE_LINEARDODGE - || blendmode == BLEND_MODE_ADD - || blendmode == BLEND_MODE_SOFTLIGHT - || blendmode == BLEND_MODE_NEGATION - || blendmode == BLEND_MODE_AVERAGE - || blendmode == BLEND_MODE_REFLECT - || blendmode == BLEND_MODE_EXCLUSION - || blendmode == BLEND_MODE_DIFFERENCE) { - composite *= fg_color.a; - } - - vec4 full_composite = vec4(composite + bg_color.rgb*(1.0-fg_color.a), bg_color.a + fg_color.a); - // vec4 full_composite = vec4(mix(bg_color.rgb, composite, alpha_opac), bg_color.a + alpha_opac); - - full_composite = mix(bg_color, full_composite, opacity); - - // output to color - gl_FragColor = full_composite; -} \ No newline at end of file diff --git a/effects/internal/cornerpineffect.cpp b/effects/internal/cornerpineffect.cpp index db802503b..2e5f93281 100644 --- a/effects/internal/cornerpineffect.cpp +++ b/effects/internal/cornerpineffect.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -24,74 +24,93 @@ #include "timeline/clip.h" #include "global/debug.h" -CornerPinEffect::CornerPinEffect(Clip* c, const EffectMeta *em) : Effect(c, em) { - SetFlags(Effect::CoordsFlag | Effect::ShaderFlag); +CornerPinEffect::CornerPinEffect(Clip* c) : Node(c) { + SetFlags(Node::CoordsFlag | Node::ShaderFlag); - EffectRow* top_left = new EffectRow(this, tr("Top Left")); - top_left_x = new DoubleField(top_left, "topleftx"); - top_left_y = new DoubleField(top_left, "toplefty"); + top_left = new Vec2Input(this, "topleft", tr("Top Left")); - EffectRow* top_right = new EffectRow(this, tr("Top Right")); - top_right_x = new DoubleField(top_right, "toprightx"); - top_right_y = new DoubleField(top_right, "toprighty"); + top_right = new Vec2Input(this, "topright", tr("Top Right")); - EffectRow* bottom_left = new EffectRow(this, tr("Bottom Left")); - bottom_left_x = new DoubleField(bottom_left, "bottomleftx"); - bottom_left_y = new DoubleField(bottom_left, "bottomlefty"); + bottom_left = new Vec2Input(this, "bottomleft", tr("Bottom Left")); - EffectRow* bottom_right = new EffectRow(this, tr("Bottom Right")); - bottom_right_x = new DoubleField(bottom_right, "bottomrightx"); - bottom_right_y = new DoubleField(bottom_right, "bottomrighty"); + bottom_right = new Vec2Input(this, "bottomright", tr("Bottom Right")); - EffectRow* perspective_row = new EffectRow(this, tr("Perspective")); - perspective = new BoolField(perspective_row, "perspective"); + perspective = new BoolInput(this, "perspective", tr("Perspective")); perspective->SetValueAt(0, true); top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); - top_left_gizmo->x_field1 = top_left_x; - top_left_gizmo->y_field1 = top_left_y; + top_left_gizmo->x_field1 = static_cast(top_left->Field(0)); + top_left_gizmo->y_field1 = static_cast(top_left->Field(1)); top_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); - top_right_gizmo->x_field1 = top_right_x; - top_right_gizmo->y_field1 = top_right_y; + top_right_gizmo->x_field1 = static_cast(top_right->Field(0)); + top_right_gizmo->y_field1 = static_cast(top_right->Field(1)); bottom_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); - bottom_left_gizmo->x_field1 = bottom_left_x; - bottom_left_gizmo->y_field1 = bottom_left_y; + bottom_left_gizmo->x_field1 = static_cast(bottom_left->Field(0)); + bottom_left_gizmo->y_field1 = static_cast(bottom_left->Field(1)); bottom_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); - bottom_right_gizmo->x_field1 = bottom_right_x; - bottom_right_gizmo->y_field1 = bottom_right_y; + bottom_right_gizmo->x_field1 = static_cast(bottom_right->Field(0)); + bottom_right_gizmo->y_field1 = static_cast(bottom_right->Field(1)); - vertPath = "cornerpin.vert"; - fragPath = "cornerpin.frag"; + shader_vert_path_ = "cornerpin.vert"; + shader_frag_path_ = "cornerpin.frag"; +} + +QString CornerPinEffect::name() +{ + return tr("Corner Pin"); +} + +QString CornerPinEffect::id() +{ + return "org.olivevideoeditor.Olive.cornerpin"; +} + +QString CornerPinEffect::category() +{ + return tr("Distort"); +} + +QString CornerPinEffect::description() +{ + return tr("Distort/warp this clip by pinning each of its four corners."); +} + +EffectType CornerPinEffect::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType CornerPinEffect::subtype() +{ + return olive::kTypeVideo; +} + +NodePtr CornerPinEffect::Create(Clip *c) +{ + return std::make_shared(c); } void CornerPinEffect::process_coords(double timecode, GLTextureCoords &coords, int) { - coords.vertexTopLeftX += top_left_x->GetDoubleAt(timecode); - coords.vertexTopLeftY += top_left_y->GetDoubleAt(timecode); - - coords.vertexTopRightX += top_right_x->GetDoubleAt(timecode); - coords.vertexTopRightY += top_right_y->GetDoubleAt(timecode); - - coords.vertexBottomLeftX += bottom_left_x->GetDoubleAt(timecode); - coords.vertexBottomLeftY += bottom_left_y->GetDoubleAt(timecode); - - coords.vertexBottomRightX += bottom_right_x->GetDoubleAt(timecode); - coords.vertexBottomRightY += bottom_right_y->GetDoubleAt(timecode); + coords.vertex_top_left += top_left->GetVector2DAt(timecode); + coords.vertex_top_right += top_right->GetVector2DAt(timecode); + coords.vertex_bottom_left += bottom_left->GetVector2DAt(timecode); + coords.vertex_bottom_right += bottom_right->GetVector2DAt(timecode); } void CornerPinEffect::process_shader(double timecode, GLTextureCoords &coords, int) { - glslProgram->setUniformValue("p0", GLfloat(coords.vertexBottomLeftX), GLfloat(coords.vertexBottomLeftY)); - glslProgram->setUniformValue("p1", GLfloat(coords.vertexBottomRightX), GLfloat(coords.vertexBottomRightY)); - glslProgram->setUniformValue("p2", GLfloat(coords.vertexTopLeftX), GLfloat(coords.vertexTopLeftY)); - glslProgram->setUniformValue("p3", GLfloat(coords.vertexTopRightX), GLfloat(coords.vertexTopRightY)); - glslProgram->setUniformValue("perspective", perspective->GetBoolAt(timecode)); + shader_program_->setUniformValue("p0", coords.vertex_bottom_left.x(), coords.vertex_bottom_left.y()); + shader_program_->setUniformValue("p1", coords.vertex_bottom_right.x(), coords.vertex_bottom_right.y()); + shader_program_->setUniformValue("p2", coords.vertex_top_left.x(), coords.vertex_top_left.y()); + shader_program_->setUniformValue("p3", coords.vertex_top_right.x(), coords.vertex_top_right.y()); + shader_program_->setUniformValue("perspective", perspective->GetBoolAt(timecode)); } void CornerPinEffect::gizmo_draw(double, GLTextureCoords &coords) { - top_left_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); - top_right_gizmo->world_pos[0] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); - bottom_right_gizmo->world_pos[0] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); - bottom_left_gizmo->world_pos[0] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); + top_left_gizmo->world_pos[0] = coords.vertex_top_left; + top_right_gizmo->world_pos[0] = coords.vertex_top_right; + bottom_right_gizmo->world_pos[0] = coords.vertex_bottom_right; + bottom_left_gizmo->world_pos[0] = coords.vertex_bottom_left; } diff --git a/effects/internal/cornerpineffect.h b/effects/internal/cornerpineffect.h index ffee7311b..9226d7c40 100644 --- a/effects/internal/cornerpineffect.h +++ b/effects/internal/cornerpineffect.h @@ -1,45 +1,51 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef CORNERPINEFFECT_H #define CORNERPINEFFECT_H -#include "effects/effect.h" +#include "nodes/node.h" -class CornerPinEffect : public Effect { +class CornerPinEffect : public Node { Q_OBJECT public: - CornerPinEffect(Clip* c, const EffectMeta* em); + CornerPinEffect(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString category() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + void process_coords(double timecode, GLTextureCoords& coords, int data); void process_shader(double timecode, GLTextureCoords& coords, int iterations); void gizmo_draw(double timecode, GLTextureCoords& coords); private: - DoubleField* top_left_x; - DoubleField* top_left_y; - DoubleField* top_right_x; - DoubleField* top_right_y; - DoubleField* bottom_left_x; - DoubleField* bottom_left_y; - DoubleField* bottom_right_x; - DoubleField* bottom_right_y; - BoolField* perspective; + Vec2Input* top_left; + Vec2Input* top_right; + Vec2Input* bottom_left; + Vec2Input* bottom_right; + + BoolInput* perspective; EffectGizmo* top_left_gizmo; EffectGizmo* top_right_gizmo; diff --git a/effects/internal/crossdissolvetransition.cpp b/effects/internal/crossdissolvetransition.cpp index a90fbbeff..dc895acea 100644 --- a/effects/internal/crossdissolvetransition.cpp +++ b/effects/internal/crossdissolvetransition.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -22,13 +22,49 @@ #include -CrossDissolveTransition::CrossDissolveTransition(Clip* c, Clip* s, const EffectMeta* em) : Transition(c, s, em) { - SetFlags(Effect::CoordsFlag); +CrossDissolveTransition::CrossDissolveTransition(Clip* c) : Transition(c) { + SetFlags(Node::CoordsFlag); +} + +QString CrossDissolveTransition::name() +{ + return tr("Cross Dissolve"); +} + +QString CrossDissolveTransition::id() +{ + return "org.olivevideoeditor.Olive.crossdissolve"; +} + +QString CrossDissolveTransition::category() +{ + return tr("Dissolves"); +} + +QString CrossDissolveTransition::description() +{ + return tr("Dissolve clips evenly."); +} + +EffectType CrossDissolveTransition::type() +{ + return EFFECT_TYPE_TRANSITION; +} + +olive::TrackType CrossDissolveTransition::subtype() +{ + return olive::kTypeVideo; +} + +NodePtr CrossDissolveTransition::Create(Clip *c) +{ + return std::make_shared(c); } void CrossDissolveTransition::process_coords(double progress, GLTextureCoords& coords, int data) { - if (!(data == kTransitionClosing && secondary_clip != nullptr)) { - if (data == kTransitionClosing) progress = 1.0 - progress; + if (data == kTransitionClosing) { + coords.opacity *= (1.0 - progress); + } else { coords.opacity *= progress; } } diff --git a/effects/internal/crossdissolvetransition.h b/effects/internal/crossdissolvetransition.h index 42c91492e..153665def 100644 --- a/effects/internal/crossdissolvetransition.h +++ b/effects/internal/crossdissolvetransition.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -25,8 +25,17 @@ class CrossDissolveTransition : public Transition { public: - CrossDissolveTransition(Clip *c, Clip *s, const EffectMeta* em); - void process_coords(double timecode, GLTextureCoords &, int data); + CrossDissolveTransition(Clip *c); + + virtual QString name() override; + virtual QString id() override; + virtual QString category() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void process_coords(double timecode, GLTextureCoords &, int data) override; }; #endif // CROSSDISSOLVETRANSITION_H diff --git a/effects/internal/cubetransition.h b/effects/internal/cubetransition.h index fb3bdc512..b1dbe0607 100644 --- a/effects/internal/cubetransition.h +++ b/effects/internal/cubetransition.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -25,8 +25,8 @@ class CubeTransition : public Transition { public: - CubeTransition(Clip* c, Clip* s, const EffectMeta* em); - void process_coords(double timecode, GLTextureCoords &, int data); + CubeTransition(Clip* c, Clip* s); + void process_coords(double timecode, GLTextureCoords &, int data); }; #endif // CUBETRANSITION_H diff --git a/effects/internal/dropshadoweffect.cpp b/effects/internal/dropshadoweffect.cpp index c7c0d61b6..934a76b04 100644 --- a/effects/internal/dropshadoweffect.cpp +++ b/effects/internal/dropshadoweffect.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "dropshadoweffect.h" DropShadowEffect::DropShadowEffect() { diff --git a/effects/internal/dropshadoweffect.h b/effects/internal/dropshadoweffect.h index 704053c59..5492f09a3 100644 --- a/effects/internal/dropshadoweffect.h +++ b/effects/internal/dropshadoweffect.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef DROPSHADOWEFFECT_H #define DROPSHADOWEFFECT_H @@ -8,4 +28,4 @@ public: DropShadowEffect(); }; -#endif // DROPSHADOWEFFECT_H \ No newline at end of file +#endif // DROPSHADOWEFFECT_H diff --git a/effects/internal/exponentialfadetransition.cpp b/effects/internal/exponentialfadetransition.cpp index ca7c367ca..8c5aae400 100644 --- a/effects/internal/exponentialfadetransition.cpp +++ b/effects/internal/exponentialfadetransition.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -22,34 +22,62 @@ #include -ExponentialFadeTransition::ExponentialFadeTransition(Clip* c, Clip* s, const EffectMeta* em) : Transition(c, s, em) {} - -void ExponentialFadeTransition::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int type) { - double interval = (timecode_end-timecode_start)/nb_bytes; - - for (int i=0;i> 8); - samples[i] = (quint8) samp; - } +ExponentialFadeTransition::ExponentialFadeTransition(Clip* c) : + Transition(c) +{ +} + +QString ExponentialFadeTransition::name() +{ + return tr("Exponential Fade"); +} + +QString ExponentialFadeTransition::id() +{ + return "org.olivevideoeditor.Olive.exponentialfade"; +} + +QString ExponentialFadeTransition::description() +{ + return tr("An exponential audio fade that starts slow and ends fast."); +} + +EffectType ExponentialFadeTransition::type() +{ + return EFFECT_TYPE_TRANSITION; +} + +olive::TrackType ExponentialFadeTransition::subtype() +{ + return olive::kTypeAudio; +} + +NodePtr ExponentialFadeTransition::Create(Clip *c) +{ + return std::make_shared(c); +} + +void ExponentialFadeTransition::process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) { + double interval = (timecode_end-timecode_start)/nb_samples; + + for (int i=0;i. + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -25,8 +25,21 @@ class ExponentialFadeTransition : public Transition { public: - ExponentialFadeTransition(Clip* c, Clip* s, const EffectMeta* em); - void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count); + ExponentialFadeTransition(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) override; }; #endif // LINEARFADETRANSITION_H diff --git a/effects/internal/fillleftrighteffect.cpp b/effects/internal/fillleftrighteffect.cpp index ff0ebb68d..141583124 100644 --- a/effects/internal/fillleftrighteffect.cpp +++ b/effects/internal/fillleftrighteffect.cpp @@ -1,44 +1,84 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #include "fillleftrighteffect.h" -#define FILL_TYPE_LEFT 0 -#define FILL_TYPE_RIGHT 1 +enum FillType { + FILL_TYPE_LEFT, + FILL_TYPE_RIGHT +}; -FillLeftRightEffect::FillLeftRightEffect(Clip* c, const EffectMeta *em) : Effect(c, em) { - EffectRow* type_row = new EffectRow(this, tr("Type")); - fill_type = new ComboField(type_row, "type"); +FillLeftRightEffect::FillLeftRightEffect(Clip* c) : Node(c) { + fill_type = new ComboInput(this, "type", tr("Type")); fill_type->AddItem(tr("Fill Left with Right"), FILL_TYPE_LEFT); fill_type->AddItem(tr("Fill Right with Left"), FILL_TYPE_RIGHT); } -void FillLeftRightEffect::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int) { - double interval = (timecode_end-timecode_start)/nb_bytes; - for (int i=0;iGetValueAt(timecode_start+(interval*i)) == FILL_TYPE_LEFT) { - samples[i+1] = samples[i+3]; - samples[i] = samples[i+2]; - } else { - samples[i+3] = samples[i+1]; - samples[i+2] = samples[i]; +QString FillLeftRightEffect::name() +{ + return tr("Fill Left/Right"); +} + +QString FillLeftRightEffect::id() +{ + return "org.olivevideoeditor.Olive.fillleftright"; +} + +QString FillLeftRightEffect::description() +{ + return tr("Replaces either the left or right channel with the other"); +} + +EffectType FillLeftRightEffect::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType FillLeftRightEffect::subtype() +{ + return olive::kTypeAudio; +} + +NodePtr FillLeftRightEffect::Create(Clip *c) +{ + return std::make_shared(c); +} + +void FillLeftRightEffect::process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) { + + Q_UNUSED(type) + + double interval = (timecode_end-timecode_start)/nb_samples; + + if (channel_count == 2) { + for (int i=0;iGetValueAt(timecode_start+(interval*i)) == FILL_TYPE_LEFT) { + samples[0][i] = samples[1][i]; + } else { + samples[1][i] = samples[0][i]; + } } } } diff --git a/effects/internal/fillleftrighteffect.h b/effects/internal/fillleftrighteffect.h index 9300bcb72..1acd1de54 100644 --- a/effects/internal/fillleftrighteffect.h +++ b/effects/internal/fillleftrighteffect.h @@ -1,35 +1,48 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef FILLLEFTRIGHTEFFECT_H #define FILLLEFTRIGHTEFFECT_H -#include "effects/effect.h" +#include "nodes/node.h" -class FillLeftRightEffect : public Effect { +class FillLeftRightEffect : public Node { Q_OBJECT public: - FillLeftRightEffect(Clip* c, const EffectMeta* em); - void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count); + FillLeftRightEffect(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) override; private: - ComboField* fill_type; + ComboInput* fill_type; }; #endif // FILLLEFTRIGHTEFFECT_H diff --git a/effects/internal/internalshaders.qrc b/effects/internal/internalshaders.qrc index 39b1a3aee..ae51e0b27 100644 --- a/effects/internal/internalshaders.qrc +++ b/effects/internal/internalshaders.qrc @@ -1,6 +1,5 @@ - blending.frag common.vert cornerpin.frag cornerpin.vert diff --git a/effects/internal/linearfadetransition.cpp b/effects/internal/linearfadetransition.cpp index 0c3917928..e89e311dc 100644 --- a/effects/internal/linearfadetransition.cpp +++ b/effects/internal/linearfadetransition.cpp @@ -1,43 +1,79 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #include "linearfadetransition.h" -LinearFadeTransition::LinearFadeTransition(Clip* c, Clip* s, const EffectMeta* em) : Transition(c, s, em) {} +LinearFadeTransition::LinearFadeTransition(Clip* c) : Transition(c) {} -void LinearFadeTransition::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int type) { - double interval = (timecode_end-timecode_start)/nb_bytes; - - for (int i=0;i> 8); - samples[i] = (quint8) samp; - } +QString LinearFadeTransition::name() +{ + return tr("Linear Fade"); +} + +QString LinearFadeTransition::id() +{ + return "org.olivevideoeditor.Olive.linearfade"; +} + +QString LinearFadeTransition::description() +{ + return tr("An linear audio fade that fades evenly at a constant rate."); +} + +EffectType LinearFadeTransition::type() +{ + return EFFECT_TYPE_TRANSITION; +} + +olive::TrackType LinearFadeTransition::subtype() +{ + return olive::kTypeAudio; +} + +NodePtr LinearFadeTransition::Create(Clip *c) +{ + return std::make_shared(c); +} + +void LinearFadeTransition::process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) { + double interval = (timecode_end-timecode_start)/nb_samples; + + for (int i=0;i. + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -25,8 +25,21 @@ class LinearFadeTransition : public Transition { public: - LinearFadeTransition(Clip* c, Clip* s, const EffectMeta* em); - void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count); + LinearFadeTransition(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) override; }; #endif // LINEARFADETRANSITION_H diff --git a/effects/internal/logarithmicfadetransition.cpp b/effects/internal/logarithmicfadetransition.cpp index e4b3bd664..79fc82875 100644 --- a/effects/internal/logarithmicfadetransition.cpp +++ b/effects/internal/logarithmicfadetransition.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -22,24 +22,62 @@ #include -LogarithmicFadeTransition::LogarithmicFadeTransition(Clip* c, Clip* s, const EffectMeta* em) : Transition(c, s, em) {} - -void LogarithmicFadeTransition::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int type) { - double interval = (timecode_end-timecode_start)/nb_bytes; - - for (int i=0;i> 8); - samples[i] = (quint8) samp; - } +LogarithmicFadeTransition::LogarithmicFadeTransition(Clip* c) : + Transition(c) +{ +} + +QString LogarithmicFadeTransition::name() +{ + return tr("Logarithmic Fade"); +} + +QString LogarithmicFadeTransition::id() +{ + return "org.olivevideoeditor.Olive.logarithmicfade"; +} + +QString LogarithmicFadeTransition::description() +{ + return tr("An logarithmic audio fade that starts fast and ends slow."); +} + +EffectType LogarithmicFadeTransition::type() +{ + return EFFECT_TYPE_TRANSITION; +} + +olive::TrackType LogarithmicFadeTransition::subtype() +{ + return olive::kTypeAudio; +} + +NodePtr LogarithmicFadeTransition::Create(Clip *c) +{ + return std::make_shared(c); +} + +void LogarithmicFadeTransition::process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) { + double interval = (timecode_end-timecode_start)/nb_samples; + + for (int i=0;i. + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -25,8 +25,21 @@ class LogarithmicFadeTransition : public Transition { public: - LogarithmicFadeTransition(Clip* c, Clip* s, const EffectMeta* em); - void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count); + LogarithmicFadeTransition(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip* c) override; + + virtual void process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) override; }; #endif // LOGARITHMICFADETRANSITION_H diff --git a/effects/internal/paneffect.cpp b/effects/internal/paneffect.cpp index 987d6528f..223933f8a 100644 --- a/effects/internal/paneffect.cpp +++ b/effects/internal/paneffect.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -28,34 +28,69 @@ #include "ui/labelslider.h" #include "ui/collapsiblewidget.h" -PanEffect::PanEffect(Clip* c, const EffectMeta *em) : Effect(c, em) { - EffectRow* pan_row = new EffectRow(this, tr("Pan")); - pan_val = new DoubleField(pan_row, "pan"); +PanEffect::PanEffect(Clip* c) : Node(c) { + pan_val = new DoubleInput(this, "pan", tr("Pan")); pan_val->SetMinimum(-100); pan_val->SetDefault(0); pan_val->SetMaximum(100); } -void PanEffect::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int) { - double interval = (timecode_end - timecode_start)/nb_bytes; - for (int i=0;i(c); +} + +void PanEffect::process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) { + + Q_UNUSED(type) + + // This has no effect on mono sources + if (channel_count < 2) { + return; + } + + double interval = (timecode_end - timecode_start)/nb_samples; + + for (int i=0;iGetDoubleAt(timecode_start+(interval*i)); double pval = log_volume(qAbs(pan_field_val)*0.01); - qint16 left_sample = qint16(((samples[i+1] & 0xFF) << 8) | (samples[i] & 0xFF)); - qint16 right_sample = qint16(((samples[i+3] & 0xFF) << 8) | (samples[i+2] & 0xFF)); - if (pan_field_val < 0) { // affect right channel - right_sample *= (1.0-pval); + samples[1][i] *= (1.0-pval); } else { // affect left channel - left_sample *= (1.0-pval); + samples[0][i] *= (1.0-pval); } - - samples[i+3] = quint8(right_sample >> 8); - samples[i+2] = quint8(right_sample); - samples[i+1] = quint8(left_sample >> 8); - samples[i] = quint8(left_sample); } } diff --git a/effects/internal/paneffect.h b/effects/internal/paneffect.h index 5e767e04e..ac6ea6034 100644 --- a/effects/internal/paneffect.h +++ b/effects/internal/paneffect.h @@ -1,35 +1,48 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef PANEFFECT_H #define PANEFFECT_H -#include "effects/effect.h" +#include "nodes/node.h" -class PanEffect : public Effect { +class PanEffect : public Node { Q_OBJECT public: - PanEffect(Clip* c, const EffectMeta* em); - void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count); + PanEffect(Clip* c); - DoubleField* pan_val; + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) override; + + DoubleInput* pan_val; }; #endif // PANEFFECT_H diff --git a/effects/internal/richtexteffect.cpp b/effects/internal/richtexteffect.cpp index a648d1776..6bc3ca4a9 100644 --- a/effects/internal/richtexteffect.cpp +++ b/effects/internal/richtexteffect.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "richtexteffect.h" #include @@ -14,65 +34,43 @@ enum AutoscrollDirection { SCROLL_RIGHT, }; -RichTextEffect::RichTextEffect(Clip *c, const EffectMeta *em) : - Effect(c, em) +RichTextEffect::RichTextEffect(Clip *c) : + Node(c) { - SetFlags(Effect::SuperimposeFlag); + SetFlags(Node::SuperimposeFlag); - EffectRow* text_row = new EffectRow(this, tr("Text")); - text_val = new StringField(text_row, "text"); - text_val->SetColumnSpan(2); + text_val = new StringInput(this, "text", tr("Text")); - EffectRow* padding_row = new EffectRow(this, tr("Padding")); - padding_field = new DoubleField(padding_row, "padding"); - padding_field->SetColumnSpan(2); + padding_field = new DoubleInput(this, "padding", tr("Padding")); - EffectRow* position_row = new EffectRow(this, tr("Position")); - position_x = new DoubleField(position_row, "posx"); - position_y = new DoubleField(position_row, "posy"); + position = new Vec2Input(this, "pos", tr("Position")); - EffectRow* vertical_align_row = new EffectRow(this, tr("Vertical Align:")); - vertical_align = new ComboField(vertical_align_row, "valign"); + vertical_align = new ComboInput(this, "valign", tr("Vertical Align:")); vertical_align->AddItem(tr("Top"), Qt::AlignTop); vertical_align->AddItem(tr("Center"), Qt::AlignCenter); vertical_align->AddItem(tr("Bottom"), Qt::AlignBottom); vertical_align->SetValueAt(0, Qt::AlignCenter); - vertical_align->SetColumnSpan(2); - EffectRow* autoscroll_row = new EffectRow(this, tr("Auto-Scroll")); - autoscroll = new ComboField(autoscroll_row, "autoscroll"); + autoscroll = new ComboInput(this, "autoscroll", tr("Auto-Scroll")); autoscroll->AddItem(tr("Off"), SCROLL_OFF); autoscroll->AddItem(tr("Up"), SCROLL_UP); autoscroll->AddItem(tr("Down"), SCROLL_DOWN); autoscroll->AddItem(tr("Left"), SCROLL_LEFT); autoscroll->AddItem(tr("Right"), SCROLL_RIGHT); - autoscroll->SetColumnSpan(2); - EffectRow* shadow_row = new EffectRow(this, tr("Shadow")); - shadow_bool = new BoolField(shadow_row, "shadow"); - shadow_bool->SetColumnSpan(2); + shadow_bool = new BoolInput(this, "shadow", tr("Shadow")); - EffectRow* shadow_color_row = new EffectRow(this, tr("Shadow Color")); - shadow_color = new ColorField(shadow_color_row, "shadowcolor"); - shadow_color->SetColumnSpan(2); + shadow_color = new ColorInput(this, "shadowcolor", tr("Shadow Color")); - EffectRow* shadow_angle_row = new EffectRow(this, tr("Shadow Angle")); - shadow_angle = new DoubleField(shadow_angle_row, "shadowangle"); - shadow_angle->SetColumnSpan(2); + shadow_angle = new DoubleInput(this, "shadowangle", tr("Shadow Angle")); - EffectRow* shadow_distance_row = new EffectRow(this, tr("Shadow Distance")); - shadow_distance = new DoubleField(shadow_distance_row, "shadowdistance"); - shadow_distance->SetColumnSpan(2); + shadow_distance = new DoubleInput(this, "shadowdistance", tr("Shadow Distance")); shadow_distance->SetMinimum(0); - EffectRow* shadow_softness_row = new EffectRow(this, tr("Shadow Softness")); - shadow_softness = new DoubleField(shadow_softness_row, "shadowsoftness"); - shadow_softness->SetColumnSpan(2); + shadow_softness = new DoubleInput(this, "shadowsoftness", tr("Shadow Softness")); shadow_softness->SetMinimum(0); - EffectRow* shadow_opacity_row = new EffectRow(this, tr("Shadow Opacity")); - shadow_opacity = new DoubleField(shadow_opacity_row, "shadowopacity"); - shadow_opacity->SetColumnSpan(2); + shadow_opacity = new DoubleInput(this, "shadowopacity", tr("Shadow Opacity")); shadow_opacity->SetMinimum(0); shadow_opacity->SetMaximum(100); @@ -84,6 +82,41 @@ RichTextEffect::RichTextEffect(Clip *c, const EffectMeta *em) : ""); } +QString RichTextEffect::name() +{ + return tr("Rich Text"); +} + +QString RichTextEffect::id() +{ + return "org.olivevideoeditor.Olive.richtext"; +} + +QString RichTextEffect::category() +{ + return tr("Render"); +} + +QString RichTextEffect::description() +{ + return tr("Render formatted rich text over a clip."); +} + +EffectType RichTextEffect::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType RichTextEffect::subtype() +{ + return olive::kTypeVideo; +} + +NodePtr RichTextEffect::Create(Clip *c) +{ + return std::make_shared(c); +} + void RichTextEffect::redraw(double timecode) { QPainter p(&img); @@ -100,8 +133,8 @@ void RichTextEffect::redraw(double timecode) td.setHtml(text_val->GetStringAt(timecode)); td.setTextWidth(width); - int translate_x = qRound(position_x->GetDoubleAt(timecode) + padding); - int translate_y = qRound(position_y->GetDoubleAt(timecode) + padding); + QPoint translation = position->GetVector2DAt(timecode).toPoint(); + translation += {padding, padding}; int doc_height = qRound(td.size().height()); @@ -118,9 +151,9 @@ void RichTextEffect::redraw(double timecode) // If we're not auto-scrolling the vertical direction, respect the vertical alignment if (vertical_align->GetValueAt(timecode).toInt() == Qt::AlignCenter) { - translate_y += height / 2 - doc_height / 2; + translation.setY(translation.y() + height / 2 - doc_height / 2); } else if (vertical_align->GetValueAt(timecode).toInt() == Qt::AlignBottom) { - translate_y += height - doc_height; + translation.setY(translation.y() + height - doc_height); } // Check if we are autoscrolling @@ -131,7 +164,7 @@ void RichTextEffect::redraw(double timecode) } int doc_width = qRound(td.size().width()); - translate_x += qRound(-doc_width + (img.width() + doc_width) * scroll_progress); + translation.setX(translation.x() + qRound(-doc_width + (img.width() + doc_width) * scroll_progress)); } } else if (auto_scroll_dir == SCROLL_UP || auto_scroll_dir == SCROLL_DOWN) { @@ -142,13 +175,13 @@ void RichTextEffect::redraw(double timecode) scroll_progress = 1.0 - scroll_progress; } - translate_y += qRound(-doc_height + (img.height() + doc_height)*scroll_progress); + translation.setY(translation.y() + qRound(-doc_height + (img.height() + doc_height)*scroll_progress)); } QRect clip_rect = img.rect(); - clip_rect.translate(-translate_x, -translate_y); - p.translate(translate_x, translate_y); + clip_rect.translate(-translation); + p.translate(translation); img.fill(Qt::transparent); @@ -188,5 +221,5 @@ void RichTextEffect::redraw(double timecode) bool RichTextEffect::AlwaysUpdate() { - return autoscroll->GetValueAt(autoscroll->Now()).toInt() != SCROLL_OFF; + return autoscroll->GetValueAt(Now()).toInt() != SCROLL_OFF; } diff --git a/effects/internal/richtexteffect.h b/effects/internal/richtexteffect.h index 2a8a7978b..8944d802c 100644 --- a/effects/internal/richtexteffect.h +++ b/effects/internal/richtexteffect.h @@ -1,29 +1,58 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef RICHTEXTEFFECT_H #define RICHTEXTEFFECT_H -#include "effects/effect.h" +#include "nodes/node.h" -class RichTextEffect : public Effect { +class RichTextEffect : public Node { Q_OBJECT public: - RichTextEffect(Clip* c, const EffectMeta *em); - void redraw(double timecode); + RichTextEffect(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString category() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void redraw(double timecode) override; + protected: virtual bool AlwaysUpdate() override; private: - StringField* text_val; - DoubleField* padding_field; - DoubleField* position_x; - DoubleField* position_y; - ComboField* vertical_align; - ComboField* autoscroll; + StringInput* text_val; + DoubleInput* padding_field; + Vec2Input* position; + ComboInput* vertical_align; + ComboInput* autoscroll; - BoolField* shadow_bool; - DoubleField* shadow_angle; - DoubleField* shadow_distance; - ColorField* shadow_color; - DoubleField* shadow_softness; - DoubleField* shadow_opacity; + BoolInput* shadow_bool; + DoubleInput* shadow_angle; + DoubleInput* shadow_distance; + ColorInput* shadow_color; + DoubleInput* shadow_softness; + DoubleInput* shadow_opacity; }; #endif // RICHTEXTEFFECT_H diff --git a/effects/internal/shakeeffect.cpp b/effects/internal/shakeeffect.cpp index 6467e2bb5..7d623dd19 100644 --- a/effects/internal/shakeeffect.cpp +++ b/effects/internal/shakeeffect.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -32,21 +32,18 @@ #include "panels/timeline.h" #include "global/debug.h" -ShakeEffect::ShakeEffect(Clip* c, const EffectMeta *em) : Effect(c, em) { - SetFlags(Effect::CoordsFlag); +ShakeEffect::ShakeEffect(Clip* c) : Node(c) { + SetFlags(Node::CoordsFlag); - EffectRow* intensity_row = new EffectRow(this, tr("Intensity")); - intensity_val = new DoubleField(intensity_row, "intensity"); + intensity_val = new DoubleInput(this, "intensity", tr("Intensity")); intensity_val->SetMinimum(0); intensity_val->SetDefault(25); - EffectRow* rotation_row = new EffectRow(this, tr("Rotation")); - rotation_val = new DoubleField(rotation_row, "rotation"); + rotation_val = new DoubleInput(this, "rotation", tr("Rotation")); rotation_val->SetMinimum(0); rotation_val->SetDefault(10); - EffectRow* frequency_row = new EffectRow(this, tr("Frequency")); - frequency_val = new DoubleField(frequency_row, "frequency"); + frequency_val = new DoubleInput(this, "frequency", tr("Frequency")); frequency_val->SetMinimum(0); frequency_val->SetDefault(5); @@ -78,15 +75,42 @@ void ShakeEffect::process_coords(double timecode, GLTextureCoords& coords, int) yoff *= multiplier; rotoff *= rotmult; - coords.vertexTopLeftX += xoff; - coords.vertexTopRightX += xoff; - coords.vertexBottomLeftX += xoff; - coords.vertexBottomRightX += xoff; + coords.matrix.translate(xoff, yoff, 0.0); - coords.vertexTopLeftY += yoff; - coords.vertexTopRightY += yoff; - coords.vertexBottomLeftY += yoff; - coords.vertexBottomRightY += yoff; + coords.matrix.rotate(QQuaternion::fromEulerAngles(0.0f, 0.0f, rotoff)); +} - glRotatef(rotoff, 0, 0, 1); +QString ShakeEffect::name() +{ + return tr("Shake"); +} + +QString ShakeEffect::id() +{ + return "org.olivevideoeditor.Olive.shake"; +} + +QString ShakeEffect::category() +{ + return tr("Distort"); +} + +QString ShakeEffect::description() +{ + return tr("Simulate a camera shake movement."); +} + +EffectType ShakeEffect::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType ShakeEffect::subtype() +{ + return olive::kTypeVideo; +} + +NodePtr ShakeEffect::Create(Clip *c) +{ + return std::make_shared(c); } diff --git a/effects/internal/shakeeffect.h b/effects/internal/shakeeffect.h index b199f493b..a9e6e9829 100644 --- a/effects/internal/shakeeffect.h +++ b/effects/internal/shakeeffect.h @@ -1,39 +1,47 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef SHAKEEFFECT_H #define SHAKEEFFECT_H -#include "effects/effect.h" +#include "nodes/node.h" #define RANDOM_VAL_SIZE 30 -class ShakeEffect : public Effect { +class ShakeEffect : public Node { Q_OBJECT public: - ShakeEffect(Clip* c, const EffectMeta* em); - void process_coords(double timecode, GLTextureCoords& coords, int data); + ShakeEffect(Clip* c); + virtual void process_coords(double timecode, GLTextureCoords& coords, int data) override; - DoubleField* intensity_val; - DoubleField* rotation_val; - DoubleField* frequency_val; + virtual QString name() override; + virtual QString id() override; + virtual QString category() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + DoubleInput* intensity_val; + DoubleInput* rotation_val; + DoubleInput* frequency_val; private: double random_vals[RANDOM_VAL_SIZE]; }; diff --git a/effects/internal/solideffect.cpp b/effects/internal/solideffect.cpp index db6f7434c..452ea1e93 100644 --- a/effects/internal/solideffect.cpp +++ b/effects/internal/solideffect.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -34,30 +34,26 @@ const int SMPTE_BARS = 7; const int SMPTE_STRIP_COUNT = 3; const int SMPTE_LOWER_BARS = 4; -SolidEffect::SolidEffect(Clip* c, const EffectMeta* em) : - Effect(c, em) +SolidEffect::SolidEffect(Clip* c) : + Node(c) { - SetFlags(Effect::SuperimposeFlag); + SetFlags(Node::SuperimposeFlag); // Field for solid type - EffectRow* type_row = new EffectRow(this, tr("Type")); - solid_type = new ComboField(type_row, "type"); + solid_type = new ComboInput(this, "type", tr("Type")); solid_type->AddItem(tr("Solid Color"), SOLID_TYPE_COLOR); solid_type->AddItem(tr("SMPTE Bars"), SOLID_TYPE_BARS); solid_type->AddItem(tr("Checkerboard"), SOLID_TYPE_CHECKERBOARD); - EffectRow* opacity_row = new EffectRow(this, tr("Opacity")); - opacity_field = new DoubleField(opacity_row, "opacity"); + opacity_field = new DoubleInput(this, "opacity", tr("Opacity")); opacity_field->SetMinimum(0); opacity_field->SetDefault(100); opacity_field->SetMaximum(100); - EffectRow* solid_color_row = new EffectRow(this, tr("Color")); - solid_color_field = new ColorField(solid_color_row, "color"); + solid_color_field = new ColorInput(this, "color", tr("Color")); solid_color_field->SetValueAt(0, QColor(Qt::red)); - EffectRow* checkerboard_size = new EffectRow(this, tr("Checkerboard Size")); - checkerboard_size_field = new DoubleField(checkerboard_size, "checker_size"); + checkerboard_size_field = new DoubleInput(this, "checker_size", tr("Checkerboard Size")); checkerboard_size_field->SetMinimum(1); checkerboard_size_field->SetDefault(10); @@ -73,6 +69,41 @@ SolidEffect::SolidEffect(Clip* c, const EffectMeta* em) : fragPath = ":/shaders/solideffect.frag";*/ } +QString SolidEffect::name() +{ + return tr("Solid"); +} + +QString SolidEffect::id() +{ + return "org.olivevideoeditor.Olive.solid"; +} + +QString SolidEffect::category() +{ + return tr("Render"); +} + +QString SolidEffect::description() +{ + return tr("Render a solid color over this clip."); +} + +EffectType SolidEffect::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType SolidEffect::subtype() +{ + return olive::kTypeVideo; +} + +NodePtr SolidEffect::Create(Clip *c) +{ + return std::make_shared(c); +} + void SolidEffect::redraw(double timecode) { int w = img.width(); int h = img.height(); diff --git a/effects/internal/solideffect.h b/effects/internal/solideffect.h index 26843fe71..f1bd50c1a 100644 --- a/effects/internal/solideffect.h +++ b/effects/internal/solideffect.h @@ -1,31 +1,31 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef SOLIDEFFECT_H #define SOLIDEFFECT_H -#include "effects/effect.h" +#include "nodes/node.h" #include -class SolidEffect : public Effect { +class SolidEffect : public Node { Q_OBJECT public: enum SolidType { @@ -34,17 +34,26 @@ public: SOLID_TYPE_CHECKERBOARD }; - SolidEffect(Clip* c, const EffectMeta *em); - virtual void redraw(double timecode); + SolidEffect(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString category() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void redraw(double timecode) override; void SetType(SolidType type); private slots: void ui_update(const QVariant &d); private: - ComboField* solid_type; - ColorField* solid_color_field; - DoubleField* opacity_field; - DoubleField* checkerboard_size_field; + ComboInput* solid_type; + ColorInput* solid_color_field; + DoubleInput* opacity_field; + DoubleInput* checkerboard_size_field; }; #endif // SOLIDEFFECT_H diff --git a/effects/internal/texteffect.cpp b/effects/internal/texteffect.cpp index 279f406d9..b32262f9c 100644 --- a/effects/internal/texteffect.cpp +++ b/effects/internal/texteffect.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -39,94 +39,60 @@ #include "timeline/sequence.h" #include "ui/comboboxex.h" #include "ui/colorbutton.h" -#include "ui/fontcombobox.h" #include "ui/blur.h" #include "global/config.h" -TextEffect::TextEffect(Clip* c, const EffectMeta* em) : - Effect(c, em) +TextEffect::TextEffect(Clip* c) : + Node(c) { - SetFlags(Effect::SuperimposeFlag); + SetFlags(Node::SuperimposeFlag); - EffectRow* text_field = new EffectRow(this, tr("Text")); - text_val = new StringField(text_field, "text", false); - text_val->SetColumnSpan(2); + text_val = new StringInput(this, "text", tr("Text"), false); - EffectRow* font_row = new EffectRow(this, tr("Font")); - set_font_combobox = new FontField(font_row, "font"); - set_font_combobox->SetColumnSpan(2); + set_font_combobox = new FontInput(this, "font", tr("Font")); - EffectRow* size_row = new EffectRow(this, tr("Size")); - size_val = new DoubleField(size_row, "size"); + size_val = new DoubleInput(this, "size", tr("Size")); size_val->SetMinimum(0); - size_val->SetColumnSpan(2); - EffectRow* color_row = new EffectRow(this, tr("Color")); - set_color_button = new ColorField(color_row, "color"); - set_color_button->SetColumnSpan(2); + set_color_button = new ColorInput(this, "color", tr("Color")); - EffectRow* alignment_row = new EffectRow(this, tr("Alignment")); - halign_field = new ComboField(alignment_row, "halign"); + halign_field = new ComboInput(this, "halign", tr("Horizontal Alignment")); halign_field->AddItem(tr("Left"), Qt::AlignLeft); halign_field->AddItem(tr("Center"), Qt::AlignHCenter); halign_field->AddItem(tr("Right"), Qt::AlignRight); halign_field->AddItem(tr("Justify"), Qt::AlignJustify); - valign_field = new ComboField(alignment_row, "valign"); + valign_field = new ComboInput(this, "valign", tr("Vertical Alignment")); valign_field->AddItem(tr("Top"), Qt::AlignTop); valign_field->AddItem(tr("Center"), Qt::AlignVCenter); valign_field->AddItem(tr("Bottom"), Qt::AlignBottom); - EffectRow* word_wrap_row = new EffectRow(this, tr("Word Wrap")); - word_wrap_field = new BoolField(word_wrap_row, "wordwrap"); - word_wrap_field->SetColumnSpan(2); + word_wrap_field = new BoolInput(this, "wordwrap", tr("Word Wrap")); - EffectRow* padding_row = new EffectRow(this, tr("Padding")); - padding_field = new DoubleField(padding_row, "padding"); - padding_field->SetColumnSpan(2); + padding_field = new DoubleInput(this, "padding", tr("Padding")); - EffectRow* position_row = new EffectRow(this, tr("Position")); - position_x = new DoubleField(position_row, "posx"); - position_y = new DoubleField(position_row, "posy"); + position = new Vec2Input(this, "pos", tr("Position")); - EffectRow* outline_row = new EffectRow(this, tr("Outline")); - outline_bool = new BoolField(outline_row, "outline"); - outline_bool->SetColumnSpan(2); + outline_bool = new BoolInput(this, "outline", tr("Outline")); - EffectRow* outline_color_row = new EffectRow(this, tr("Outline Color")); - outline_color = new ColorField(outline_color_row, "outlinecolor"); - outline_color->SetColumnSpan(2); + outline_color = new ColorInput(this, "outlinecolor", tr("Outline Color")); - EffectRow* outline_width_row = new EffectRow(this, tr("Outline Width")); - outline_width = new DoubleField(outline_width_row, "outlinewidth"); - outline_width->SetColumnSpan(2); + outline_width = new DoubleInput(this, "outlinewidth", tr("Outline Width")); outline_width->SetMinimum(0); - EffectRow* shadow_row = new EffectRow(this, tr("Shadow")); - shadow_bool = new BoolField(shadow_row, "shadow"); - shadow_bool->SetColumnSpan(2); + shadow_bool = new BoolInput(this, "shadow", tr("Shadow")); - EffectRow* shadow_color_row = new EffectRow(this, tr("Shadow Color")); - shadow_color = new ColorField(shadow_color_row, "shadowcolor"); - shadow_color->SetColumnSpan(2); + shadow_color = new ColorInput(this, "shadowcolor", tr("Shadow Color")); - EffectRow* shadow_angle_row = new EffectRow(this, tr("Shadow Angle")); - shadow_angle = new DoubleField(shadow_angle_row, "shadowangle"); - shadow_angle->SetColumnSpan(2); + shadow_angle = new DoubleInput(this, "shadowangle", tr("Shadow Angle")); - EffectRow* shadow_distance_row = new EffectRow(this, tr("Shadow Distance")); - shadow_distance = new DoubleField(shadow_distance_row, "shadowdistance"); - shadow_distance->SetColumnSpan(2); + shadow_distance = new DoubleInput(this, "shadowdistance", tr("Shadow Distance")); shadow_distance->SetMinimum(0); - EffectRow* shadow_softness_row = new EffectRow(this, tr("Shadow Softness")); - shadow_softness = new DoubleField(shadow_softness_row, "shadowsoftness"); - shadow_softness->SetColumnSpan(2); + shadow_softness = new DoubleInput(this, "shadowsoftness", tr("Shadow Softness")); shadow_softness->SetMinimum(0); - EffectRow* shadow_opacity_row = new EffectRow(this, tr("Shadow Opacity")); - shadow_opacity = new DoubleField(shadow_opacity_row, "shadowopacity"); - shadow_opacity->SetColumnSpan(2); + shadow_opacity = new DoubleInput(this, "shadowopacity", tr("Shadow Opacity")); shadow_opacity->SetMinimum(0); shadow_opacity->SetMaximum(100); @@ -151,8 +117,43 @@ TextEffect::TextEffect(Clip* c, const EffectMeta* em) : connect(shadow_bool, SIGNAL(Toggled(bool)), this, SLOT(shadow_enable(bool))); connect(outline_bool, SIGNAL(Toggled(bool)), this, SLOT(outline_enable(bool))); - vertPath = "common.vert"; - fragPath = "dropshadow.frag"; + shader_vert_path_ = "common.vert"; + shader_frag_path_ = "dropshadow.frag"; +} + +QString TextEffect::name() +{ + return tr("Text"); +} + +QString TextEffect::id() +{ + return "org.olivevideoeditor.Olive.text"; +} + +QString TextEffect::category() +{ + return tr("Render"); +} + +QString TextEffect::description() +{ + return tr("Generate simple text over this clip"); +} + +EffectType TextEffect::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType TextEffect::subtype() +{ + return olive::kTypeVideo; +} + +NodePtr TextEffect::Create(Clip *c) +{ + return std::make_shared(c); } void TextEffect::redraw(double timecode) { @@ -257,7 +258,7 @@ void TextEffect::redraw(double timecode) { path.addText(text_x, text_y, font, lines.at(i)); } - path.translate(position_x->GetDoubleAt(timecode) + padding, position_y->GetDoubleAt(timecode) + padding); + path.translate(position->GetVector2DAt(timecode).toPointF() + QPointF(padding, padding)); // draw software shadow if (shadow_bool->GetBoolAt(timecode)) { diff --git a/effects/internal/texteffect.h b/effects/internal/texteffect.h index 05b9286a8..de4076616 100644 --- a/effects/internal/texteffect.h +++ b/effects/internal/texteffect.h @@ -1,63 +1,71 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef TEXTEFFECT_H #define TEXTEFFECT_H -#include "effects/effect.h" +#include "nodes/node.h" #include #include -class TextEffect : public Effect { +class TextEffect : public Node { Q_OBJECT public: - TextEffect(Clip* c, const EffectMeta *em); - void redraw(double timecode); + TextEffect(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString category() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void redraw(double timecode) override; private slots: void outline_enable(bool); void shadow_enable(bool); private: QFont font; - StringField* text_val; - DoubleField* size_val; - ColorField* set_color_button; - FontField* set_font_combobox; - ComboField* halign_field; - ComboField* valign_field; - BoolField* word_wrap_field; - DoubleField* padding_field; - DoubleField* position_x; - DoubleField* position_y; + StringInput* text_val; + DoubleInput* size_val; + ColorInput* set_color_button; + FontInput* set_font_combobox; + ComboInput* halign_field; + ComboInput* valign_field; + BoolInput* word_wrap_field; + DoubleInput* padding_field; + Vec2Input* position; - BoolField* outline_bool; - DoubleField* outline_width; - ColorField* outline_color; + BoolInput* outline_bool; + DoubleInput* outline_width; + ColorInput* outline_color; - BoolField* shadow_bool; - DoubleField* shadow_angle; - DoubleField* shadow_distance; - ColorField* shadow_color; - DoubleField* shadow_softness; - DoubleField* shadow_opacity; + BoolInput* shadow_bool; + DoubleInput* shadow_angle; + DoubleInput* shadow_distance; + ColorInput* shadow_color; + DoubleInput* shadow_softness; + DoubleInput* shadow_opacity; }; diff --git a/effects/internal/timecodeeffect.cpp b/effects/internal/timecodeeffect.cpp index 4de3e3497..32a892cdf 100644 --- a/effects/internal/timecodeeffect.cpp +++ b/effects/internal/timecodeeffect.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -37,66 +37,90 @@ #include "ui/collapsiblewidget.h" #include "timeline/clip.h" #include "timeline/sequence.h" -#include "panels/viewer.h" +#include "global/timing.h" #include "ui/comboboxex.h" #include "ui/colorbutton.h" -#include "ui/fontcombobox.h" #include "global/config.h" -TimecodeEffect::TimecodeEffect(Clip* c, const EffectMeta* em) : - Effect(c, em) +TimecodeEffect::TimecodeEffect(Clip* c) : + Node(c) { - SetFlags(Effect::SuperimposeFlag); + SetFlags(Node::SuperimposeFlag); - EffectRow* tc_row = new EffectRow(this, tr("Timecode")); - tc_select = new ComboField(tc_row, "tc_selector"); + tc_select = new ComboInput(this, "tc_selector", tr("Timecode")); tc_select->AddItem(tr("Sequence"), true); tc_select->AddItem(tr("Media"), false); tc_select->SetValueAt(0, true); - EffectRow* scale_row = new EffectRow(this, tr("Scale")); - scale_val = new DoubleField(scale_row, "scale"); - scale_val->SetColumnSpan(2); + scale_val = new DoubleInput(this, "scale", tr("Scale")); scale_val->SetMinimum(1); scale_val->SetDefault(100); scale_val->SetMaximum(1000); - EffectRow* color_row = new EffectRow(this, tr("Color")); - color_val = new ColorField(color_row, "color"); - color_val->SetColumnSpan(2); + color_val = new ColorInput(this, "color", tr("Color")); color_val->SetValueAt(0, QColor(Qt::white)); - EffectRow* color_bg_row = new EffectRow(this, tr("Background Color")); - color_bg_val = new ColorField(color_bg_row, "bgcolor"); - color_bg_val->SetColumnSpan(2); + color_bg_val = new ColorInput(this, "bgcolor", tr("Background Color")); color_bg_val->SetValueAt(0, QColor(Qt::black)); - EffectRow* bg_alpha_row = new EffectRow(this, tr("Background Opacity")); - bg_alpha = new DoubleField(bg_alpha_row, "bgalpha"); - bg_alpha->SetColumnSpan(2); + bg_alpha = new DoubleInput(this, "bgalpha", tr("Background Opacity")); bg_alpha->SetMinimum(0); bg_alpha->SetDefault(50); bg_alpha->SetMaximum(100); - EffectRow* offset_row = new EffectRow(this, tr("Offset")); - offset_x_val = new DoubleField(offset_row, "offsetx"); - offset_y_val = new DoubleField(offset_row, "offsety"); + offset_val = new Vec2Input(this, "offset", tr("Offset")); + offset_val->SetDefault(0); - EffectRow* prepent_text_row = new EffectRow(this, tr("Prepend")); - prepend_text = new StringField(prepent_text_row, "prepend", false); - prepend_text->SetColumnSpan(2); + prepend_text = new StringInput(this, "prepend", tr("Prepend"), false); +} + +QString TimecodeEffect::name() +{ + return tr("Timecode"); +} + +QString TimecodeEffect::id() +{ + return "org.olivevideoeditor.Olive.timecode"; +} + +QString TimecodeEffect::category() +{ + return tr("Render"); +} + +QString TimecodeEffect::description() +{ + return tr("Render the media or sequence timecode on this clip."); +} + +EffectType TimecodeEffect::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType TimecodeEffect::subtype() +{ + return olive::kTypeVideo; +} + +NodePtr TimecodeEffect::Create(Clip *c) +{ + return std::make_shared(c); } void TimecodeEffect::redraw(double timecode) { + Sequence* sequence = parent_clip->track()->sequence(); + if (tc_select->GetValueAt(timecode).toBool()) { - display_timecode = prepend_text->GetStringAt(timecode) + frame_to_timecode(olive::ActiveSequence->playhead, - olive::CurrentConfig.timecode_view, - olive::ActiveSequence->frame_rate); + display_timecode = prepend_text->GetStringAt(timecode) + frame_to_timecode(sequence->playhead, + olive::config.timecode_view, + sequence->frame_rate); } else { double media_rate = parent_clip->media_frame_rate(); display_timecode = prepend_text->GetStringAt(timecode) + frame_to_timecode(qRound(timecode * media_rate), - olive::CurrentConfig.timecode_view, + olive::config.timecode_view, media_rate); } img.fill(Qt::transparent); @@ -115,18 +139,17 @@ void TimecodeEffect::redraw(double timecode) { QPainterPath path; - int text_x, text_y, rect_y, offset_x, offset_y; + int text_x, text_y, rect_y; int text_height = fm.height(); int text_width = fm.width(display_timecode); QColor background_color = color_bg_val->GetColorAt(timecode); int alpha_val = qCeil(bg_alpha->GetDoubleAt(timecode)*2.55); background_color.setAlpha(alpha_val); - offset_x = int(offset_x_val->GetDoubleAt(timecode)); - offset_y = int(offset_y_val->GetDoubleAt(timecode)); + QVector2D offset = offset_val->GetVector2DAt(timecode); - text_x = offset_x + (width/2) - (text_width/2); - text_y = offset_y + height - height/10; + text_x = offset.x() + (width/2) - (text_width/2); + text_y = offset.y() + height - height/10; rect_y = text_y + fm.descent() - text_height; path.addText(text_x, text_y, font, display_timecode); diff --git a/effects/internal/timecodeeffect.h b/effects/internal/timecodeeffect.h index d51565940..71051d421 100644 --- a/effects/internal/timecodeeffect.h +++ b/effects/internal/timecodeeffect.h @@ -1,44 +1,52 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef TIMECODEEFFECT_H #define TIMECODEEFFECT_H -#include "effects/effect.h" +#include "nodes/node.h" #include #include -class TimecodeEffect : public Effect { +class TimecodeEffect : public Node { Q_OBJECT public: - TimecodeEffect(Clip* c, const EffectMeta *em); - void redraw(double timecode); - DoubleField* scale_val; - ColorField* color_val; - ColorField* color_bg_val; - DoubleField* bg_alpha; - DoubleField* offset_x_val; - DoubleField* offset_y_val; - StringField* prepend_text; - ComboField* tc_select; + TimecodeEffect(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString category() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void redraw(double timecode) override; + DoubleInput* scale_val; + ColorInput* color_val; + ColorInput* color_bg_val; + DoubleInput* bg_alpha; + Vec2Input* offset_val; + StringInput* prepend_text; + ComboInput* tc_select; protected: virtual bool AlwaysUpdate() override; diff --git a/effects/internal/toneeffect.cpp b/effects/internal/toneeffect.cpp index a1758f86e..b0b66fa46 100644 --- a/effects/internal/toneeffect.cpp +++ b/effects/internal/toneeffect.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -27,51 +27,88 @@ #include "timeline/clip.h" #include "timeline/sequence.h" -ToneEffect::ToneEffect(Clip* c, const EffectMeta *em) : Effect(c, em), sinX(INT_MIN) { - EffectRow* type_row = new EffectRow(this, tr("Type")); - type_val = new ComboField(type_row, "type"); +ToneEffect::ToneEffect(Clip* c) : Node(c), sinX(INT_MIN) { + type_val = new ComboInput(this, "type", tr("Type")); type_val->AddItem(tr("Sine"), TONE_TYPE_SINE); - EffectRow* frequency_row = new EffectRow(this, tr("Frequency")); - freq_val = new DoubleField(frequency_row, "frequency"); + freq_val = new DoubleInput(this, "frequency", tr("Frequency")); freq_val->SetMinimum(20); freq_val->SetMaximum(20000); freq_val->SetDefault(1000); - EffectRow* amount_row = new EffectRow(this, tr("Amount")); - amount_val = new DoubleField(amount_row, "amount"); + amount_val = new DoubleInput(this, "amount", tr("Amount")); amount_val->SetMinimum(0); amount_val->SetMaximum(100); amount_val->SetDefault(25); - EffectRow* mix_row = new EffectRow(this, tr("Mix")); - mix_val = new BoolField(mix_row, "mix"); + mix_val = new BoolInput(this, "mix", tr("Mix")); mix_val->SetValueAt(0, true); } -void ToneEffect::process_audio(double timecode_start, double timecode_end, quint8 *samples, int nb_bytes, int) { - double interval = (timecode_end - timecode_start)/nb_bytes; - for (int i=0;i(c); +} + +void ToneEffect::process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) { + + Q_UNUSED(type) + + double interval = (timecode_end - timecode_start)/nb_samples; + + for (int i=0;iGetDoubleAt(timecode)) + /parent_clip->track()->sequence()->audio_frequency) + *log_volume(amount_val->GetDoubleAt(timecode)*0.01); - qint16 left_tone_sample = qint16(qRound(qSin((2*M_PI*sinX*freq_val->GetDoubleAt(timecode)) - /parent_clip->sequence->audio_frequency) - *log_volume(amount_val->GetDoubleAt(timecode)*0.01)*INT16_MAX)); - qint16 right_tone_sample = left_tone_sample; + for (int j=0;jGetBoolAt(timecode)) { + + // mix with source audio + samples[j][i] += tone_sample; + + } else { + + // replace source audio + samples[j][i] = tone_sample; + + } - // mix with source audio - if (mix_val->GetBoolAt(timecode)) { - qint16 left_sample = qint16(((samples[i+1] & 0xFF) << 8) | (samples[i] & 0xFF)); - qint16 right_sample = qint16(((samples[i+3] & 0xFF) << 8) | (samples[i+2] & 0xFF)); - left_tone_sample = mix_audio_sample(left_tone_sample, left_sample); - right_tone_sample = mix_audio_sample(right_tone_sample, right_sample); } - samples[i+3] = quint8(right_tone_sample >> 8); - samples[i+2] = quint8(right_tone_sample); - samples[i+1] = quint8(left_tone_sample >> 8); - samples[i] = quint8(left_tone_sample); - sinX++; } } diff --git a/effects/internal/toneeffect.h b/effects/internal/toneeffect.h index a14f49a98..ddb723d79 100644 --- a/effects/internal/toneeffect.h +++ b/effects/internal/toneeffect.h @@ -1,39 +1,53 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef TONEEFFECT_H #define TONEEFFECT_H -#include "effects/effect.h" +#include "nodes/node.h" -class ToneEffect : public Effect { +class ToneEffect : public Node { Q_OBJECT public: - ToneEffect(Clip* c, const EffectMeta* em); - void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count); + ToneEffect(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) override; - ComboField* type_val; - DoubleField* freq_val; - DoubleField* amount_val; - BoolField* mix_val; private: + ComboInput* type_val; + DoubleInput* freq_val; + DoubleInput* amount_val; + BoolInput* mix_val; + int sinX; }; diff --git a/effects/internal/transformeffect.cpp b/effects/internal/transformeffect.cpp index 03c219dcd..1e04afc13 100644 --- a/effects/internal/transformeffect.cpp +++ b/effects/internal/transformeffect.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -43,122 +43,131 @@ #include "panels/viewer.h" #include "ui/viewerwidget.h" -TransformEffect::TransformEffect(Clip* c, const EffectMeta* em) : Effect(c, em) { - SetFlags(Effect::CoordsFlag); +TransformEffect::TransformEffect(Clip* c) : Node(c) { + SetFlags(Node::CoordsFlag); - EffectRow* position_row = new EffectRow(this, tr("Position")); + position = new Vec2Input(this, "pos", tr("Position")); - position_x = new DoubleField(position_row, "posx"); // position X - position_y = new DoubleField(position_row, "posy"); // position Y + scale = new Vec2Input(this, "scale", tr("Scale")); + scale->SetMinimum(0); + scale->SetDefault(100); - EffectRow* scale_row = new EffectRow(this, tr("Scale")); + uniform_scale_field = new BoolInput(this, "uniformscale", tr("Uniform Scale")); + connect(uniform_scale_field, SIGNAL(Toggled(bool)), scale, SLOT(SetSingleValueMode(bool))); + uniform_scale_field->SetValueAt(0, true); - // scale X (and Y is uniform scale is selected) - scale_x = new DoubleField(scale_row, "scalex"); - scale_x->SetMinimum(0); + rotation = new DoubleInput(this, "rotation", tr("Rotation")); - // scale Y (disabled if uniform scale is selected) - scale_y = new DoubleField(scale_row, "scaley"); - scale_y->SetMinimum(0); - - EffectRow* uniform_scale_row = new EffectRow(this, tr("Uniform Scale")); - - uniform_scale_field = new BoolField(uniform_scale_row, "uniformscale"); // uniform scale option - - EffectRow* rotation_row = new EffectRow(this, tr("Rotation")); - - rotation = new DoubleField(rotation_row, "rotation"); - - EffectRow* anchor_point_row = new EffectRow(this, tr("Anchor Point")); - - anchor_x_box = new DoubleField(anchor_point_row, "anchorx"); // anchor point X - anchor_y_box = new DoubleField(anchor_point_row, "anchory"); // anchor point Y - - EffectRow* opacity_row = new EffectRow(this, tr("Opacity")); + anchor_point = new Vec2Input(this, "anchor", tr("Anchor Point")); + anchor_point->SetDefault(0); // opacity - opacity = new DoubleField(opacity_row, "opacity"); + opacity = new DoubleInput(this, "opacity", tr("Opacity")); opacity->SetMinimum(0); opacity->SetMaximum(100); + opacity->SetDefault(100); - EffectRow* blend_mode_row = new EffectRow(this, tr("Blend Mode")); - - // blend mode - blend_mode_box = new ComboField(blend_mode_row, "blendmode"); - blend_mode_box->SetColumnSpan(2); - blend_mode_box->AddItem(tr("Normal"), ""); + // TEMP - Create matrix output + EffectRow* matrix_output = new EffectRow(this, "matrix", "Matrix", false, false); + matrix_output->SetOutputDataType(olive::nodes::kMatrix); // set up gizmos top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); top_left_gizmo->set_cursor(Qt::SizeFDiagCursor); - top_left_gizmo->x_field1 = scale_x; + top_left_gizmo->x_field1 = static_cast(scale->Field(0)); top_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); top_center_gizmo->set_cursor(Qt::SizeVerCursor); - top_center_gizmo->y_field1 = scale_x; + top_center_gizmo->y_field1 = static_cast(scale->Field(0)); top_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); top_right_gizmo->set_cursor(Qt::SizeBDiagCursor); - top_right_gizmo->x_field1 = scale_x; + top_right_gizmo->x_field1 = static_cast(scale->Field(0)); bottom_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); bottom_left_gizmo->set_cursor(Qt::SizeBDiagCursor); - bottom_left_gizmo->x_field1 = scale_x; + bottom_left_gizmo->x_field1 = static_cast(scale->Field(0)); bottom_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); bottom_center_gizmo->set_cursor(Qt::SizeVerCursor); - bottom_center_gizmo->y_field1 = scale_x; + bottom_center_gizmo->y_field1 = static_cast(scale->Field(0)); bottom_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); bottom_right_gizmo->set_cursor(Qt::SizeFDiagCursor); - bottom_right_gizmo->x_field1 = scale_x; + bottom_right_gizmo->x_field1 = static_cast(scale->Field(0)); left_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); left_center_gizmo->set_cursor(Qt::SizeHorCursor); - left_center_gizmo->x_field1 = scale_x; + left_center_gizmo->x_field1 = static_cast(scale->Field(0)); right_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); right_center_gizmo->set_cursor(Qt::SizeHorCursor); - right_center_gizmo->x_field1 = scale_x; + right_center_gizmo->x_field1 = static_cast(scale->Field(0)); anchor_gizmo = add_gizmo(GIZMO_TYPE_TARGET); anchor_gizmo->set_cursor(Qt::SizeAllCursor); - anchor_gizmo->x_field1 = anchor_x_box; - anchor_gizmo->y_field1 = anchor_y_box; - anchor_gizmo->x_field2 = position_x; - anchor_gizmo->y_field2 = position_y; + anchor_gizmo->x_field1 = static_cast(anchor_point->Field(0)); + anchor_gizmo->y_field1 = static_cast(anchor_point->Field(1)); + anchor_gizmo->x_field2 = static_cast(position->Field(0)); + anchor_gizmo->y_field2 = static_cast(position->Field(1)); rotate_gizmo = add_gizmo(GIZMO_TYPE_DOT); rotate_gizmo->color = Qt::green; rotate_gizmo->set_cursor(Qt::SizeAllCursor); - rotate_gizmo->x_field1 = rotation; + rotate_gizmo->x_field1 = static_cast(rotation->Field(0)); rect_gizmo = add_gizmo(GIZMO_TYPE_POLY); - rect_gizmo->x_field1 = position_x; - rect_gizmo->y_field1 = position_y; + rect_gizmo->x_field1 = static_cast(position->Field(0)); + rect_gizmo->y_field1 = static_cast(position->Field(1)); connect(uniform_scale_field, SIGNAL(Toggled(bool)), this, SLOT(toggle_uniform_scale(bool))); - // set defaults - uniform_scale_field->SetValueAt(0, true); - blend_mode_box->SetValueAt(0, ""); - anchor_x_box->SetDefault(0); - anchor_y_box->SetDefault(0); - opacity->SetDefault(100); - scale_x->SetDefault(100); - scale_y->SetDefault(100); - refresh(); } +QString TransformEffect::name() +{ + return tr("Transform"); +} + +QString TransformEffect::id() +{ + return "org.olivevideoeditor.Olive.transform"; +} + +QString TransformEffect::category() +{ + return tr("Distort"); +} + +QString TransformEffect::description() +{ + return tr("Transform the position, scale, and rotation of this clip."); +} + +EffectType TransformEffect::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType TransformEffect::subtype() +{ + return olive::kTypeVideo; +} + +NodePtr TransformEffect::Create(Clip *c) +{ + return std::make_shared(c); +} + void TransformEffect::refresh() { - if (parent_clip != nullptr && parent_clip->sequence != nullptr) { + if (parent_clip != nullptr && parent_clip->track()->sequence() != nullptr) { - position_x->SetDefault(parent_clip->sequence->width/2); - position_y->SetDefault(parent_clip->sequence->height/2); + position->SetDefault({parent_clip->track()->sequence()->width*0.5, + parent_clip->track()->sequence()->height*0.5}); - double x_percent_multipler = 200.0 / parent_clip->sequence->width; - double y_percent_multipler = 200.0 / parent_clip->sequence->height; + double x_percent_multipler = 200.0 / parent_clip->track()->sequence()->width; + double y_percent_multipler = 200.0 / parent_clip->track()->sequence()->height; top_left_gizmo->x_field_multi1 = -x_percent_multipler; top_left_gizmo->y_field_multi1 = -y_percent_multipler; @@ -178,7 +187,10 @@ void TransformEffect::refresh() { } void TransformEffect::toggle_uniform_scale(bool enabled) { - scale_y->SetEnabled(!enabled); + scale->SetSingleValueMode(enabled); + + DoubleField* scale_x = static_cast(scale->Field(0)); + DoubleField* scale_y = static_cast(scale->Field(1)); top_center_gizmo->y_field1 = enabled ? scale_x : scale_y; bottom_center_gizmo->y_field1 = enabled ? scale_x : scale_y; @@ -190,51 +202,55 @@ void TransformEffect::toggle_uniform_scale(bool enabled) { void TransformEffect::process_coords(double timecode, GLTextureCoords& coords, int) { // position - glTranslated(position_x->GetDoubleAt(timecode)-(parent_clip->sequence->width/2), - position_y->GetDoubleAt(timecode)-(parent_clip->sequence->height/2), - 0); + coords.matrix.translate(position->GetVector2DAt(timecode) + - QVector2D(parent_clip->track()->sequence()->width*0.5f, + parent_clip->track()->sequence()->height*0.5f)); // anchor point - int anchor_x_offset = qRound(anchor_x_box->GetDoubleAt(timecode)); - int anchor_y_offset = qRound(anchor_y_box->GetDoubleAt(timecode)); - coords.vertexTopLeftX -= anchor_x_offset; - coords.vertexTopRightX -= anchor_x_offset; - coords.vertexBottomLeftX -= anchor_x_offset; - coords.vertexBottomRightX -= anchor_x_offset; - coords.vertexTopLeftY -= anchor_y_offset; - coords.vertexTopRightY -= anchor_y_offset; - coords.vertexBottomLeftY -= anchor_y_offset; - coords.vertexBottomRightY -= anchor_y_offset; + QVector2D anchor_val = anchor_point->GetVector2DAt(timecode); + + coords.vertex_top_left -= anchor_val; + coords.vertex_top_right -= anchor_val; + coords.vertex_bottom_left -= anchor_val; + coords.vertex_bottom_right -= anchor_val; // rotation - glRotated(rotation->GetDoubleAt(timecode), 0, 0, 1); + coords.matrix.rotate(QQuaternion::fromEulerAngles(0, 0, float(rotation->GetDoubleAt(timecode)))); // scale - double sx = scale_x->GetDoubleAt(timecode)*0.01; - double sy = (uniform_scale_field->GetBoolAt(timecode)) ? sx : scale_y->GetDoubleAt(timecode)*0.01; - glScaled(sx, sy, 1); - - // blend mode - coords.blendmode = blend_mode_box->GetValueAt(timecode).toInt(); + coords.matrix.scale(scale->GetVector2DAt(timecode)*0.01f); // opacity coords.opacity *= float(opacity->GetDoubleAt(timecode)*0.01); } +QVector3D LerpVector3D(const QVector3D& a, const QVector3D& b, float t) { + return QVector3D( + float_lerp(a.x(), b.x(), t), + float_lerp(a.y(), b.y(), t), + float_lerp(a.z(), b.z(), t) + ); +} + void TransformEffect::gizmo_draw(double, GLTextureCoords& coords) { - top_left_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); - top_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexTopLeftX, coords.vertexTopRightX, 0.5), lerp(coords.vertexTopLeftY, coords.vertexTopRightY, 0.5)); - top_right_gizmo->world_pos[0] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); - right_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexTopRightX, coords.vertexBottomRightX, 0.5), lerp(coords.vertexTopRightY, coords.vertexBottomRightY, 0.5)); - bottom_right_gizmo->world_pos[0] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); - bottom_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexBottomRightX, coords.vertexBottomLeftX, 0.5), lerp(coords.vertexBottomRightY, coords.vertexBottomLeftY, 0.5)); - bottom_left_gizmo->world_pos[0] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); - left_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexBottomLeftX, coords.vertexTopLeftX, 0.5), lerp(coords.vertexBottomLeftY, coords.vertexTopLeftY, 0.5)); + top_left_gizmo->world_pos[0] = coords.vertex_top_left; + top_right_gizmo->world_pos[0] = coords.vertex_top_right; + bottom_right_gizmo->world_pos[0] = coords.vertex_bottom_right; + bottom_left_gizmo->world_pos[0] = coords.vertex_bottom_left; + + top_center_gizmo->world_pos[0] = LerpVector3D(coords.vertex_top_left, coords.vertex_top_right, 0.5); + right_center_gizmo->world_pos[0] = LerpVector3D(coords.vertex_top_right, coords.vertex_bottom_right, 0.5); + bottom_center_gizmo->world_pos[0] = LerpVector3D(coords.vertex_bottom_right, coords.vertex_bottom_left, 0.5); + left_center_gizmo->world_pos[0] = LerpVector3D(coords.vertex_bottom_left, coords.vertex_top_left, 0.5); - rotate_gizmo->world_pos[0] = QPoint(lerp(top_center_gizmo->world_pos[0].x(), bottom_center_gizmo->world_pos[0].x(), -0.1), lerp(top_center_gizmo->world_pos[0].y(), bottom_center_gizmo->world_pos[0].y(), -0.1)); + rotate_gizmo->world_pos[0] = QVector3D( + float_lerp(top_center_gizmo->world_pos[0].x(), bottom_center_gizmo->world_pos[0].x(), 0.5f), + float_lerp(top_center_gizmo->world_pos[0].y(), bottom_center_gizmo->world_pos[0].y(), -0.1f), + 0.0f + ); - rect_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); - rect_gizmo->world_pos[1] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); - rect_gizmo->world_pos[2] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); - rect_gizmo->world_pos[3] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); + rect_gizmo->world_pos[0] = coords.vertex_top_left; + rect_gizmo->world_pos[1] = coords.vertex_top_right; + rect_gizmo->world_pos[2] = coords.vertex_bottom_right; + rect_gizmo->world_pos[3] = coords.vertex_bottom_left; } diff --git a/effects/internal/transformeffect.h b/effects/internal/transformeffect.h index 43aad2b26..2ead33e31 100644 --- a/effects/internal/transformeffect.h +++ b/effects/internal/transformeffect.h @@ -1,49 +1,56 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef TRANSFORMEFFECT_H #define TRANSFORMEFFECT_H -#include "effects/effect.h" +#include "nodes/node.h" -class TransformEffect : public Effect { +class TransformEffect : public Node { Q_OBJECT public: - TransformEffect(Clip* c, const EffectMeta* em); - void refresh(); - void process_coords(double timecode, GLTextureCoords& coords, int data); + TransformEffect(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString category() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void refresh() override; + virtual void process_coords(double timecode, GLTextureCoords& coords, int data) override; + + virtual void gizmo_draw(double timecode, GLTextureCoords& coords) override; - void gizmo_draw(double timecode, GLTextureCoords& coords); public slots: void toggle_uniform_scale(bool enabled); + private: - DoubleField* position_x; - DoubleField* position_y; - DoubleField* scale_x; - DoubleField* scale_y; - BoolField* uniform_scale_field; - DoubleField* rotation; - DoubleField* anchor_x_box; - DoubleField* anchor_y_box; - DoubleField* opacity; - ComboField* blend_mode_box; + Vec2Input* position; + Vec2Input* scale; + BoolInput* uniform_scale_field; + DoubleInput* rotation; + Vec2Input* anchor_point; + DoubleInput* opacity; EffectGizmo* top_left_gizmo; EffectGizmo* top_center_gizmo; diff --git a/effects/internal/voideffect.cpp b/effects/internal/voideffect.cpp index bf3192cd6..f5eb5a8d2 100644 --- a/effects/internal/voideffect.cpp +++ b/effects/internal/voideffect.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -27,25 +27,55 @@ #include "ui/collapsiblewidget.h" #include "global/debug.h" -VoidEffect::VoidEffect(Clip* c, const QString& n) : Effect(c, nullptr) { - QString display_name; - if (n.isEmpty()) { - display_name = tr("(unknown)"); - } else { - display_name = n; +VoidEffect::VoidEffect(Clip* c, const QString& n, const QString& id) : + Node(c), + display_name_(n), + id_(id) +{ + if (display_name_.isEmpty()) { + display_name_ = tr("(unknown)"); } - EffectRow* row = new EffectRow(this, tr("Missing Effect"), false, false); - new LabelField(row, display_name); - - name = display_name; - - void_meta.type = EFFECT_TYPE_EFFECT; - meta = &void_meta; + new LabelWidget(this, tr("Missing Effect"), display_name_); } -EffectPtr VoidEffect::copy(Clip* c) { - EffectPtr copy = std::make_shared(c, name); +QString VoidEffect::name() +{ + return display_name_; +} + +QString VoidEffect::id() +{ + return id_; +} + +QString VoidEffect::description() +{ + return QString(); +} + +EffectType VoidEffect::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType VoidEffect::subtype() +{ + return olive::kTypeVideo; +} + +bool VoidEffect::IsCreatable() +{ + return false; +} + +NodePtr VoidEffect::Create(Clip *) +{ + return nullptr; +} + +NodePtr VoidEffect::copy(Clip* c) { + NodePtr copy = std::make_shared(c, display_name_, id_); copy->SetEnabled(IsEnabled()); copy_field_keyframes(copy); return copy; @@ -54,7 +84,7 @@ EffectPtr VoidEffect::copy(Clip* c) { void VoidEffect::load(QXmlStreamReader &stream) { QString tag = stream.name().toString(); - QXmlStreamWriter writer(&bytes); + QXmlStreamWriter writer(&bytes_); // copy XML from reader to writer while (!stream.atEnd() && !(stream.name() == tag && stream.isEndElement())) { @@ -76,18 +106,18 @@ void VoidEffect::load(QXmlStreamReader &stream) { } void VoidEffect::save(QXmlStreamWriter &stream) { - if (!name.isEmpty()) { - stream.writeAttribute("name", name); + if (!display_name_.isEmpty()) { + stream.writeAttribute("name", display_name_); stream.writeAttribute("enabled", QString::number(IsEnabled())); // force xml writer to expand tag, ignored when loading stream.writeStartElement("void"); stream.writeEndElement(); - if (!bytes.isEmpty()) { + if (!bytes_.isEmpty()) { // write stored data QIODevice* device = stream.device(); - device->write(bytes); + device->write(bytes_); } } } diff --git a/effects/internal/voideffect.h b/effects/internal/voideffect.h index a0c49c7ba..e551f4ae0 100644 --- a/effects/internal/voideffect.h +++ b/effects/internal/voideffect.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -27,19 +27,28 @@ * isn't lost if the user saves over the project. */ -#include "effects/effect.h" +#include "nodes/node.h" -class VoidEffect : public Effect { +class VoidEffect : public Node { Q_OBJECT public: - VoidEffect(Clip* c, const QString& n); + VoidEffect(Clip* c, const QString& n, const QString &id); + + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual bool IsCreatable() override; + virtual NodePtr Create(Clip *c) override; + virtual NodePtr copy(Clip* c) override; - virtual EffectPtr copy(Clip* c) override; virtual void load(QXmlStreamReader &stream) override; virtual void save(QXmlStreamWriter &stream) override; private: - QByteArray bytes; - EffectMeta void_meta; + QByteArray bytes_; + QString display_name_; + QString id_; }; #endif // VOIDEFFECT_H diff --git a/effects/internal/volumeeffect.cpp b/effects/internal/volumeeffect.cpp index e01d5241c..96c66b676 100644 --- a/effects/internal/volumeeffect.cpp +++ b/effects/internal/volumeeffect.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -28,41 +28,63 @@ #include "ui/labelslider.h" #include "ui/collapsiblewidget.h" -VolumeEffect::VolumeEffect(Clip* c, const EffectMeta *em) : Effect(c, em) { - EffectRow* volume_row = new EffectRow(this, tr("Volume")); - volume_val = new DoubleField(volume_row, "volume"); +VolumeEffect::VolumeEffect(Clip* c) : Node(c) { + volume_val = new DoubleInput(this, "volume", tr("Volume")); // set defaults volume_val->SetDefault(1); volume_val->SetDisplayType(LabelSlider::Decibel); } -void VolumeEffect::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int) { - double interval = (timecode_end-timecode_start)/nb_bytes; - for (int i=0;i(c); +} + +void VolumeEffect::process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) { + + Q_UNUSED(type) + + double interval = (timecode_end-timecode_start)/nb_samples; + + for (int i=0;iGetDoubleAt(timecode_start+(interval*i)); - qint32 right_samp = qint16(((samples[i+3] & 0xFF) << 8) | (samples[i+2] & 0xFF)); - qint32 left_samp = qint16(((samples[i+1] & 0xFF) << 8) | (samples[i] & 0xFF)); + for (int j=0;j INT16_MAX) { - left_samp = INT16_MAX; - } else if (left_samp < INT16_MIN) { - left_samp = INT16_MIN; } - - if (right_samp > INT16_MAX) { - right_samp = INT16_MAX; - } else if (right_samp < INT16_MIN) { - right_samp = INT16_MIN; - } - - samples[i+3] = (quint8) (right_samp >> 8); - samples[i+2] = (quint8) right_samp; - samples[i+1] = (quint8) (left_samp >> 8); - samples[i] = (quint8) left_samp; } } diff --git a/effects/internal/volumeeffect.h b/effects/internal/volumeeffect.h index 80e0adc70..d8cdbf81f 100644 --- a/effects/internal/volumeeffect.h +++ b/effects/internal/volumeeffect.h @@ -1,35 +1,49 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef VOLUMEEFFECT_H #define VOLUMEEFFECT_H -#include "effects/effect.h" +#include "nodes/node.h" -class VolumeEffect : public Effect { +class VolumeEffect : public Node { Q_OBJECT public: - VolumeEffect(Clip* c, const EffectMeta* em); - void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count); + VolumeEffect(Clip* c); - DoubleField* volume_val; + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) override; + +private: + DoubleInput* volume_val; }; #endif // VOLUMEEFFECT_H diff --git a/effects/internal/vsthost.cpp b/effects/internal/vsthost.cpp index 2958832f1..c4d9f2aff 100644 --- a/effects/internal/vsthost.cpp +++ b/effects/internal/vsthost.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -46,7 +46,6 @@ class NSWindow; #endif #define BLOCK_SIZE 512 -#define CHANNEL_COUNT 2 struct VSTRect { int16_t top; @@ -212,15 +211,6 @@ bool VSTHost::canPluginDo(char *canDoString) { return (dispatcher(plugin, effCanDo, 0, 0, static_cast(canDoString), 0.0f) > 0); } -void VSTHost::processAudio(long numFrames) { - // Always reset the output array before processing. - for (int i=0;iprocessReplacing(plugin, inputs, outputs, numFrames); -} - void VSTHost::CreateDialogIfNull() { if (dialog == nullptr) { @@ -237,74 +227,84 @@ void VSTHost::send_data_cache_to_plugin() dispatcher(plugin, effSetChunk, 0, int32_t(data_cache.size()), static_cast(data_cache.data()), 0); } -VSTHost::VSTHost(Clip* c, const EffectMeta *em) : - Effect(c, em), +VSTHost::VSTHost(Clip* c) : + Node(c), plugin(nullptr), - dialog(nullptr) + dialog(nullptr), + input_cache(BLOCK_SIZE), + output_cache(BLOCK_SIZE) { plugin = nullptr; - inputs = new float* [CHANNEL_COUNT]; - outputs = new float* [CHANNEL_COUNT]; - for(int channel = 0; channel < CHANNEL_COUNT; channel++) { - inputs[channel] = new float[BLOCK_SIZE]; - outputs[channel] = new float[BLOCK_SIZE]; - } - - EffectRow* file_row = new EffectRow(this, tr("Plugin"), true, false); - file_field = new FileField(file_row, "filename"); + file_field = new FileInput(this, "filename", tr("Plugin"), true, false); connect(file_field, SIGNAL(Changed()), this, SLOT(change_plugin()), Qt::QueuedConnection); - EffectRow* interface_row = new EffectRow(this, tr("Interface"), false, false); - - show_interface_btn = new ButtonField(interface_row, tr("Show")); + show_interface_btn = new ButtonWidget(this, tr("Interface"), tr("Show")); show_interface_btn->SetCheckable(true); show_interface_btn->SetEnabled(false); connect(show_interface_btn, SIGNAL(Toggled(bool)), this, SLOT(show_interface(bool))); } VSTHost::~VSTHost() { - for(int channel = 0; channel < CHANNEL_COUNT; channel++) { - delete [] inputs[channel]; - delete [] outputs[channel]; - } - delete [] outputs; - delete [] inputs; - freePlugin(); } -void VSTHost::process_audio(double, double, quint8* samples, int nb_bytes, int) { +QString VSTHost::name() +{ + return tr("VST Plugin 2.x"); +} + +QString VSTHost::id() +{ + return "org.olivevideoeditor.Olive.vst2x"; +} + +QString VSTHost::description() +{ + return tr("Use a VST 2.x plugin on this clip's audio."); +} + +EffectType VSTHost::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType VSTHost::subtype() +{ + return olive::kTypeAudio; +} + +NodePtr VSTHost::Create(Clip *c) +{ + return std::make_shared(c); +} + +void VSTHost::process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) { if (plugin != nullptr) { - int interval = BLOCK_SIZE*4; - for (int i=0;i>2; - inputs[0][index] = float(left_sample) / float(INT16_MAX); - inputs[1][index] = float(right_sample) / float(INT16_MAX); + for (int i=0;i>2); + plugin->processReplacing(plugin, input_cache.data(), output_cache.data(), sample_size); - // convert back to int16 - for (int j=i;j>2; - - qint16 left_sample = qint16(qRound(outputs[0][index] * INT16_MAX)); - qint16 right_sample = qint16(qRound(outputs[1][index] * INT16_MAX)); - - samples[j+3] = quint8(right_sample >> 8); - samples[j+2] = quint8(right_sample); - samples[j+1] = quint8(left_sample >> 8); - samples[j] = quint8(left_sample); + // Copy output cache back to samples + for (int j=0;jSetEnabled(plugin != nullptr); } + +SampleCache::SampleCache(int block_size) : + block_size_(block_size), + channel_count_(0), + array_(nullptr) +{ +} + +SampleCache::~SampleCache() +{ + destroy(); +} + +void SampleCache::Create(int channels) +{ + if (channel_count_ != channels) { + + if (channel_count_ > 0) { + destroy(); + } + + channel_count_ = channels; + + array_ = new float* [channel_count_]; + for (int i=0;i. + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -24,28 +24,57 @@ #include #include -#include "effects/effect.h" +#include "nodes/node.h" #include "include/vestige.h" // Plugin's dispatcher function typedef intptr_t (*dispatcherFuncPtr)(AEffect *effect, int32_t opCode, int32_t index, int32_t value, void *ptr, float opt); -class VSTHost : public Effect { +class SampleCache { +public: + SampleCache(int block_size); + ~SampleCache(); + + void Create(int channels); + void SetZero(); + + float** data(); +private: + int channel_count_; + int block_size_; + float** array_; + void destroy(); +}; + +class VSTHost : public Node { Q_OBJECT public: - VSTHost(Clip* c, const EffectMeta* em); - ~VSTHost(); - void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count); + VSTHost(Clip* c); + virtual ~VSTHost() override; - void custom_load(QXmlStreamReader& stream); - void save(QXmlStreamWriter& stream); + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; + + virtual void process_audio(double timecode_start, + double timecode_end, + float **samples, + int nb_samples, + int channel_count, + int type) override; + + virtual void custom_load(QXmlStreamReader& stream) override; + virtual void save(QXmlStreamWriter& stream) override; private slots: void show_interface(bool show); void uncheck_show_button(); void change_plugin(); private: - FileField* file_field; - ButtonField* show_interface_btn; + FileInput* file_field; + ButtonWidget* show_interface_btn; void loadPlugin(); void freePlugin(); @@ -57,12 +86,11 @@ private: void resumePlugin(); void suspendPlugin(); bool canPluginDo(char *canDoString); - void processAudio(long numFrames); void CreateDialogIfNull(); - float** inputs; - float** outputs; QDialog* dialog; QByteArray data_cache; + SampleCache input_cache; + SampleCache output_cache; void send_data_cache_to_plugin(); diff --git a/effects/keyframe.cpp b/effects/keyframe.cpp index d73518cc3..870de3123 100644 --- a/effects/keyframe.cpp +++ b/effects/keyframe.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -60,7 +60,7 @@ void delete_keyframes(QVector& selected_key_fields, QVector for (int i=0;iappend(new KeyframeDelete(fields.at(i), key_indices.at(i))); } - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); selected_keys.clear(); selected_key_fields.clear(); update_ui(false); diff --git a/effects/keyframe.h b/effects/keyframe.h index 241d28ef8..4a076bc34 100644 --- a/effects/keyframe.h +++ b/effects/keyframe.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/effects/shaders/boxblur.frag b/effects/shaders/boxblur.frag index 3da0380bc..a9479c135 100644 --- a/effects/shaders/boxblur.frag +++ b/effects/shaders/boxblur.frag @@ -1,16 +1,10 @@ -#version 110 - -uniform sampler2D image; - uniform float radius; -uniform vec2 resolution; uniform bool horiz_blur; uniform bool vert_blur; uniform int iteration; +uniform vec2 resolution; -varying vec2 vTexCoord; - -void main(void) { +vec4 process(vec4 col) { float rad = ceil(radius); float divider = 1.0 / rad; @@ -19,15 +13,15 @@ void main(void) { if (iteration == 0 && horiz_blur && !radius_is_zero) { for (float x=-rad+0.5;x<=rad;x+=2.0) { - color += texture2D(image, (vec2(gl_FragCoord.x+x, gl_FragCoord.y))/resolution)*(divider); + color += texture2D(texture, vec2(gl_FragCoord.x+x, gl_FragCoord.y)/resolution)*(divider); } - gl_FragColor = color; + return color; } else if (iteration == 1 && vert_blur && !radius_is_zero) { for (float x=-rad+0.5;x<=rad;x+=2.0) { - color += texture2D(image, (vec2(gl_FragCoord.x, gl_FragCoord.y+x))/resolution)*(divider); + color += texture2D(texture, vec2(gl_FragCoord.x, gl_FragCoord.y+x)/resolution)*(divider); } - gl_FragColor = color; + return color; } else { - gl_FragColor = texture2D(image, vTexCoord); + return col; } -} \ No newline at end of file +} diff --git a/effects/shaders/boxblur.xml b/effects/shaders/boxblur.xml index a6c0cb491..53140ed99 100644 --- a/effects/shaders/boxblur.xml +++ b/effects/shaders/boxblur.xml @@ -1,13 +1,7 @@ - - - - - - - - - - - + + + + + \ No newline at end of file diff --git a/effects/shaders/bulge.frag b/effects/shaders/bulge.frag index 3569eaa4d..68b55b2ca 100644 --- a/effects/shaders/bulge.frag +++ b/effects/shaders/bulge.frag @@ -1,7 +1,3 @@ -#version 120 - -uniform sampler2D tex0; -varying vec2 vTexCoord; const float PI = 3.1415926535; uniform vec2 resolution; @@ -18,15 +14,15 @@ vec2 distort(vec2 p, vec2 offset) { return 0.5 * (p + 1.0); } -void main(void) { +vec4 process(vec4 col) { vec2 offset = vec2(xoff/resolution.x, yoff/resolution.y); - vec2 xy = 2.0 * vTexCoord - 1.0 - offset; + vec2 xy = 2.0 * v_texcoord - 1.0 - offset; vec2 uv; float d = length(xy); uv = distort(xy, offset); if (uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0) { - gl_FragColor = texture2D(tex0, uv); + return texture2D(texture, uv); } else { - discard; + return vec4(0.0); } } \ No newline at end of file diff --git a/effects/shaders/chromaticaberration.frag b/effects/shaders/chromaticaberration.frag index ff9ddfa96..1b795f348 100644 --- a/effects/shaders/chromaticaberration.frag +++ b/effects/shaders/chromaticaberration.frag @@ -1,22 +1,17 @@ -#version 110 - -uniform vec2 resolution; - -uniform sampler2D tex; -varying vec2 vTexCoord; - uniform float red_amount; uniform float green_amount; uniform float blue_amount; -void main(void) { +uniform vec2 resolution; + +vec4 process(vec4 col) { vec2 rOffset = vec2(red_amount*0.01)/resolution; vec2 gOffset = vec2(green_amount*0.01)/resolution; vec2 bOffset = vec2(blue_amount*0.01)/resolution; - vec4 rValue = texture2D(tex, vTexCoord - rOffset); - vec4 gValue = texture2D(tex, vTexCoord - gOffset); - vec4 bValue = texture2D(tex, vTexCoord - bOffset); + vec4 rValue = texture2D(texture, v_texcoord - rOffset); + vec4 gValue = texture2D(texture, v_texcoord - gOffset); + vec4 bValue = texture2D(texture, v_texcoord - bOffset); - gl_FragColor = vec4(rValue.r, gValue.g, bValue.b, texture2D(tex, vTexCoord).a); + return vec4(rValue.r, gValue.g, bValue.b, col.a); } \ No newline at end of file diff --git a/effects/shaders/crop.frag b/effects/shaders/crop.frag index 0cfbeeba8..509596b34 100644 --- a/effects/shaders/crop.frag +++ b/effects/shaders/crop.frag @@ -1,43 +1,47 @@ -#version 110 - uniform float left; uniform float top; uniform float right; uniform float bottom; uniform float feather; uniform bool invert; +uniform vec2 resolution; uniform mediump float amount_val; -uniform sampler2D myTexture; -varying vec2 vTexCoord; -void main(void) { - vec4 textureColor = texture2D(myTexture, vec2(vTexCoord.x, vTexCoord.y)); - float alpha = textureColor.a; +vec4 process(vec4 col) { + float alpha = 1.0; + + if (feather == 0.0) { - if (vTexCoord.x < (left*0.01) || vTexCoord.y < (top*0.01) || vTexCoord.x > (1.0-(right*0.01)) || vTexCoord.y > (1.0-(bottom*0.01))) { + if (v_texcoord.x < (left*0.01) || v_texcoord.y < (top*0.01) || v_texcoord.x > (1.0-(right*0.01)) || v_texcoord.y > (1.0-(bottom*0.01))) { alpha = 0.0; } } else { - float f = pow(2.0, 10.0-(feather*0.1)); - if (left > 0.0) alpha = alpha * clamp(((vTexCoord.x+(0.5/f))-(left*0.01))*f, 0.0, 1.0); // left - if (top > 0.0) alpha = alpha * clamp(((vTexCoord.y+(0.5/f))-(top*0.01))*f, 0.0, 1.0); // top - if (right > 0.0) alpha = alpha * clamp((((1.0-vTexCoord.x)+(0.5/f))-(right*0.01))*f, 0.0, 1.0); // right - if (bottom > 0.0) alpha = alpha * clamp((((1.0-vTexCoord.y)+(0.5/f))-(bottom*0.01))*f, 0.0, 1.0); // bottom + + float f = pow(2.0, 10.0-(feather*0.25)); + + if (left > 0.0) { + alpha *= clamp(((resolution.x*v_texcoord.x+(0.5/f))-(left*0.01*resolution.x))*f, 0.0, 1.0); // left + } + + if (top > 0.0) { + alpha *= clamp(((resolution.y*v_texcoord.y+(0.5/f))-(top*0.01*resolution.y))*f, 0.0, 1.0); // top + } + + if (right > 0.0) { + alpha *= clamp(((resolution.x*(1.0-v_texcoord.x)+(0.5/f))-(right*0.01*resolution.x))*f, 0.0, 1.0); // right + } + + if (bottom > 0.0) { + alpha *= clamp(((resolution.y*(1.0-v_texcoord.y)+(0.5/f))-(bottom*0.01*resolution.y))*f, 0.0, 1.0); // bottom + } + + } - if (invert) - {gl_FragColor = vec4( - textureColor.r*(1.0 - alpha), - textureColor.g*(1.0 - alpha), - textureColor.b*(1.0 - alpha), - 1.0 - alpha - );} - else{ - gl_FragColor = vec4( - textureColor.r*(alpha), - textureColor.g*(alpha), - textureColor.b*(alpha), - alpha - );} + if (invert) { + return col * (1.0-alpha); + } else { + return col * alpha; + } } \ No newline at end of file diff --git a/effects/shaders/directionalblur.frag b/effects/shaders/directionalblur.frag index e04d350ed..86c8ac079 100644 --- a/effects/shaders/directionalblur.frag +++ b/effects/shaders/directionalblur.frag @@ -1,15 +1,11 @@ -#version 110 - #define M_PI 3.1415926535897932384626433832795 -uniform sampler2D image; - uniform float angle; // degrees uniform float length; uniform vec2 resolution; -void main(void) { +vec4 process(vec4 col) { if (length > 0.0) { float ceillen = ceil(length); float radians = (angle*M_PI)/180.0; @@ -21,10 +17,10 @@ void main(void) { for (float i=-ceillen+0.5;i<=ceillen;i+=2.0) { float y = sin_angle * i; float x = cos_angle * i; - color += texture2D(image, vec2(gl_FragCoord.x+x, gl_FragCoord.y+y)/resolution)*(divider); + color += texture2D(texture, vec2(gl_FragCoord.x+x, gl_FragCoord.y+y)/resolution)*(divider); } - gl_FragColor = color; + return color; } else { - gl_FragColor = texture2D(image, gl_FragCoord.xy/resolution); + return col; } } \ No newline at end of file diff --git a/effects/shaders/dropshadow.xml.disabled b/effects/shaders/dropshadow.xml.disabled deleted file mode 100644 index 9a5278d4d..000000000 --- a/effects/shaders/dropshadow.xml.disabled +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/effects/shaders/flip.frag b/effects/shaders/flip.frag index 6a55cc8ca..6bdb05a9c 100644 --- a/effects/shaders/flip.frag +++ b/effects/shaders/flip.frag @@ -1,23 +1,14 @@ -#version 110 - uniform bool horiz; uniform bool vert; -uniform sampler2D myTexture; -varying vec2 vTexCoord; +vec4 process(vec4 col) { + float x = v_texcoord.x; + float y = v_texcoord.y; -void main(void) { - float x = vTexCoord.x; - float y = vTexCoord.y; + if (!horiz && !vert) return col; if (horiz) x = 1.0 - x; if (vert) y = 1.0 - y; - vec4 textureColor = texture2D(myTexture, vec2(x, y)); - gl_FragColor = vec4( - textureColor.r, - textureColor.g, - textureColor.b, - textureColor.a - ); + return texture2D(texture, vec2(x, y)); } \ No newline at end of file diff --git a/effects/shaders/gaussianblur.frag b/effects/shaders/gaussianblur.frag index 720671d99..e1d08a367 100644 --- a/effects/shaders/gaussianblur.frag +++ b/effects/shaders/gaussianblur.frag @@ -1,18 +1,11 @@ -#version 110 - #define M_PI 3.1415926535897932384626433832795 -uniform sampler2D image; - -//uniform float radius; uniform float sigma; uniform vec2 resolution; uniform bool horiz_blur; uniform bool vert_blur; uniform int iteration; -varying vec2 vTexCoord; - float gaussian(float x, float sigma) { return (1.0/(sigma*sqrt(2.0*M_PI)))*exp(-0.5*pow(x/sigma, 2.0)); } @@ -21,7 +14,7 @@ float gaussian2(float x, float y, float sigma) { return (1.0/(pow(sigma, 2.0)*2.0*M_PI))*exp(-0.5*((pow(x, 2.0) + pow(y, 2.0))/pow(sigma, 2.0))); } -void main(void) { +vec4 process(vec4 col) { float rad = ceil(3.0 * sigma); float sum = 0.0; @@ -39,16 +32,16 @@ void main(void) { if (iteration == 0 && horiz_blur && !radius_is_zero) { for (float x=-rad+0.5;x<=rad;x+=2.0) { float weight = (gaussian2(x, 0.0, sigma)/sum); - color += texture2D(image, (vec2(gl_FragCoord.x+x, gl_FragCoord.y))/resolution)*(weight); + color += texture2D(texture, vec2(gl_FragCoord.x+x, gl_FragCoord.y)/resolution)*(weight); } - gl_FragColor = color; + return color; } else if (iteration == 1 && vert_blur && !radius_is_zero) { for (float x=-rad+0.5;x<=rad;x+=2.0) { float weight = (gaussian2(0.0, x, sigma)/sum); - color += texture2D(image, (vec2(gl_FragCoord.x, gl_FragCoord.y+x))/resolution)*(weight); + color += texture2D(texture, vec2(gl_FragCoord.x, gl_FragCoord.y+x)/resolution)*(weight); } - gl_FragColor = color; + return color; } else { - gl_FragColor = texture2D(image, vTexCoord); + return col; } } \ No newline at end of file diff --git a/effects/shaders/invert.frag b/effects/shaders/invert.frag deleted file mode 100644 index 7ad7178a7..000000000 --- a/effects/shaders/invert.frag +++ /dev/null @@ -1,13 +0,0 @@ -#version 110 - -uniform float amount; - -uniform sampler2D myTexture; -varying vec2 vTexCoord; - -void main(void) { - vec4 textureColor = texture2D(myTexture, vTexCoord); - float amount_val = amount * 0.01; - vec3 col = textureColor.rgb+((vec3(1.0)-textureColor.rgb-textureColor.rgb)*vec3(amount_val)); - gl_FragColor = vec4(col, textureColor.a); -} \ No newline at end of file diff --git a/effects/shaders/chromakey.frag b/effects/shaders/need checking/chromakey.frag similarity index 90% rename from effects/shaders/chromakey.frag rename to effects/shaders/need checking/chromakey.frag index 6e088f961..e24cbb1cf 100644 --- a/effects/shaders/chromakey.frag +++ b/effects/shaders/need checking/chromakey.frag @@ -4,9 +4,6 @@ feel free to use or modify at will*/ /*the following three functions convert RGB into YCbCr in the same manner as in JPEG images*/ -uniform sampler2D tex; -varying vec2 vTexCoord; - uniform vec3 key_color; uniform float tola; uniform float tolb; @@ -42,13 +39,11 @@ float colorclose(float Cb_p,float Cr_p,float Cb_key,float Cr_key,float tola,floa if (temp < tolb) {return ((temp-tola)/(tolb-tola));} return (1.0); } - -void main(void) { + +vec4 process(vec4 texture_color) { float cb_key = rgb2cb(key_color); float cr_key = rgb2cr(key_color); - vec4 texture_color = texture2D(tex, vTexCoord); - float cb = rgb2cb(texture_color.rgb); float cr = rgb2cr(texture_color.rgb); float mask = colorclose(cb, cr, cb_key, cr_key, (tola/100.0), (tolb/100.0)); @@ -68,5 +63,5 @@ void main(void) { } else if (mode == 2) { // original } - gl_FragColor = texture_color; + return texture_color; } diff --git a/effects/shaders/chromakey.xml b/effects/shaders/need checking/chromakey.xml similarity index 100% rename from effects/shaders/chromakey.xml rename to effects/shaders/need checking/chromakey.xml diff --git a/effects/shaders/colorcorrection.frag b/effects/shaders/need checking/colorcorrection.frag similarity index 100% rename from effects/shaders/colorcorrection.frag rename to effects/shaders/need checking/colorcorrection.frag diff --git a/effects/shaders/colorcorrection.xml b/effects/shaders/need checking/colorcorrection.xml similarity index 100% rename from effects/shaders/colorcorrection.xml rename to effects/shaders/need checking/colorcorrection.xml diff --git a/effects/shaders/colorsel.frag b/effects/shaders/need checking/colorsel.frag similarity index 89% rename from effects/shaders/colorsel.frag rename to effects/shaders/need checking/colorsel.frag index 903e4a0f8..dc4b7f280 100644 --- a/effects/shaders/colorsel.frag +++ b/effects/shaders/need checking/colorsel.frag @@ -2,10 +2,6 @@ Based on Edward Cannon's Simple Chroma Key (adaptation by Olive Team) RGB to HSV based on MattKC's toonify source code Feel free to modify and use at will */ -#version 110 - -uniform sampler2D tex; -varying vec2 vTexCoord; uniform float loc; uniform float hic; @@ -57,8 +53,7 @@ bool isNotIncreasingSequence(float a, float b, float c) { return (c < b || a > b); } -void main(void) { - vec4 texture_color = texture2D(tex,vTexCoord); +vec4 process(vec4 texture_color) { vec3 color = texture_color.rgb; float toCheck = 0.0; @@ -79,5 +74,5 @@ void main(void) { } texture_color.a = isNotIncreasingSequence(loc, toCheck, hic) ? (invert ? texture_color.a : 0.0) : (invert ? 0.0 : texture_color.a); texture_color.rgb *= texture_color.a; - gl_FragColor = texture_color; + return texture_color; } diff --git a/effects/shaders/colorsel.xml b/effects/shaders/need checking/colorsel.xml similarity index 100% rename from effects/shaders/colorsel.xml rename to effects/shaders/need checking/colorsel.xml diff --git a/effects/shaders/common.frag b/effects/shaders/need checking/common.frag similarity index 100% rename from effects/shaders/common.frag rename to effects/shaders/need checking/common.frag diff --git a/effects/shaders/common.vert b/effects/shaders/need checking/common.vert similarity index 100% rename from effects/shaders/common.vert rename to effects/shaders/need checking/common.vert diff --git a/effects/shaders/crossstitch.frag b/effects/shaders/need checking/crossstitch.frag similarity index 100% rename from effects/shaders/crossstitch.frag rename to effects/shaders/need checking/crossstitch.frag diff --git a/effects/shaders/crossstitch.xml b/effects/shaders/need checking/crossstitch.xml similarity index 100% rename from effects/shaders/crossstitch.xml rename to effects/shaders/need checking/crossstitch.xml diff --git a/effects/shaders/emboss.frag b/effects/shaders/need checking/emboss.frag similarity index 100% rename from effects/shaders/emboss.frag rename to effects/shaders/need checking/emboss.frag diff --git a/effects/shaders/emboss.xml b/effects/shaders/need checking/emboss.xml similarity index 100% rename from effects/shaders/emboss.xml rename to effects/shaders/need checking/emboss.xml diff --git a/effects/shaders/findedges.frag b/effects/shaders/need checking/findedges.frag similarity index 100% rename from effects/shaders/findedges.frag rename to effects/shaders/need checking/findedges.frag diff --git a/effects/shaders/findedges.xml.disabled b/effects/shaders/need checking/findedges.xml.disabled similarity index 100% rename from effects/shaders/findedges.xml.disabled rename to effects/shaders/need checking/findedges.xml.disabled diff --git a/effects/shaders/fisheye.frag b/effects/shaders/need checking/fisheye.frag similarity index 67% rename from effects/shaders/fisheye.frag rename to effects/shaders/need checking/fisheye.frag index 18689ccc8..a8b57b7a1 100644 --- a/effects/shaders/fisheye.frag +++ b/effects/shaders/need checking/fisheye.frag @@ -1,15 +1,12 @@ -#version 120 -uniform sampler2D tex0; -varying vec2 vTexCoord; #define M_PI 3.1415926535897932384626433832795 uniform float size; -void main(void) { +vec4 process(vec4 col) { float maxFactor = 2.0 - (size * 0.01); vec2 uv; - vec2 xy = 2.0 * vTexCoord - 1.0; + vec2 xy = 2.0 * v_texcoord - 1.0; float d = length(xy); if (d < (2.0-maxFactor)) { d = length(xy * maxFactor); @@ -20,8 +17,7 @@ void main(void) { uv.x = r * cos(phi) + 0.5; uv.y = r * sin(phi) + 0.5; } else { - uv = vTexCoord; + uv = v_texcoord; } - vec4 c = texture2D(tex0, uv); - gl_FragColor = c; + return texture2D(texture, uv); } \ No newline at end of file diff --git a/effects/shaders/fisheye.xml b/effects/shaders/need checking/fisheye.xml similarity index 100% rename from effects/shaders/fisheye.xml rename to effects/shaders/need checking/fisheye.xml diff --git a/effects/shaders/huesatbri.frag b/effects/shaders/need checking/huesatbri.frag similarity index 77% rename from effects/shaders/huesatbri.frag rename to effects/shaders/need checking/huesatbri.frag index 21f0fcecb..0582fa501 100644 --- a/effects/shaders/huesatbri.frag +++ b/effects/shaders/need checking/huesatbri.frag @@ -1,12 +1,7 @@ -#version 110 - uniform float hue; uniform float saturation; uniform float brightness; -uniform sampler2D myTexture; -varying vec2 vTexCoord; - vec3 rgb2hsv(vec3 c) { vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); @@ -23,9 +18,7 @@ vec3 hsv2rgb(vec3 c) { return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); } -void main(void) { - vec4 tex_color = texture2D(myTexture, vTexCoord); - +vec4 process(vec4 tex_color) { vec3 hsv = rgb2hsv(tex_color.rgb); hsv.r += (hue/360.0); hsv.g *= (saturation*0.01); @@ -33,10 +26,8 @@ void main(void) { vec3 rgb = hsv2rgb(hsv); - gl_FragColor = vec4( - rgb.r, - rgb.g, - rgb.b, + return vec4( + rgb.rgb, tex_color.a ); } \ No newline at end of file diff --git a/effects/shaders/huesatbri.xml b/effects/shaders/need checking/huesatbri.xml similarity index 100% rename from effects/shaders/huesatbri.xml rename to effects/shaders/need checking/huesatbri.xml diff --git a/effects/shaders/need checking/invert.frag b/effects/shaders/need checking/invert.frag new file mode 100644 index 000000000..bcfa87612 --- /dev/null +++ b/effects/shaders/need checking/invert.frag @@ -0,0 +1,7 @@ +uniform float amount; + +vec4 process(vec4 col) { + float amount_val = amount * 0.01; + vec3 color = col.rgb+((vec3(1.0)-col.rgb-col.rgb)*vec3(amount_val)); + return vec4(color, col.a); +} \ No newline at end of file diff --git a/effects/shaders/invert.xml b/effects/shaders/need checking/invert.xml similarity index 100% rename from effects/shaders/invert.xml rename to effects/shaders/need checking/invert.xml diff --git a/effects/shaders/lumakey.frag b/effects/shaders/need checking/lumakey.frag similarity index 100% rename from effects/shaders/lumakey.frag rename to effects/shaders/need checking/lumakey.frag diff --git a/effects/shaders/lumakey.xml b/effects/shaders/need checking/lumakey.xml similarity index 100% rename from effects/shaders/lumakey.xml rename to effects/shaders/need checking/lumakey.xml diff --git a/effects/shaders/posterize.frag b/effects/shaders/need checking/posterize.frag similarity index 59% rename from effects/shaders/posterize.frag rename to effects/shaders/need checking/posterize.frag index a3144450b..85ee2c1dc 100644 --- a/effects/shaders/posterize.frag +++ b/effects/shaders/need checking/posterize.frag @@ -1,19 +1,13 @@ -#version 110 - -uniform sampler2D sceneTex; // 0 uniform float gamma_cent; // 0.6 uniform float numColors; // 8.0 -varying vec2 vTexCoord; - -void main() { +vec4 process(vec4 color) { float gamma = gamma_cent*0.01; - vec4 color = texture2D(sceneTex, vTexCoord); vec3 c = color.rgb; c = pow(c, vec3(gamma, gamma, gamma)); c = c * numColors; c = floor(c); c = c / numColors; c = pow(c, vec3(1.0/gamma)); - gl_FragColor = vec4(c, color.a); + return vec4(c, color.a); } \ No newline at end of file diff --git a/effects/shaders/posterize.xml b/effects/shaders/need checking/posterize.xml similarity index 100% rename from effects/shaders/posterize.xml rename to effects/shaders/need checking/posterize.xml diff --git a/effects/shaders/toonify.frag b/effects/shaders/need checking/toonify.frag similarity index 100% rename from effects/shaders/toonify.frag rename to effects/shaders/need checking/toonify.frag diff --git a/effects/shaders/toonify.xml b/effects/shaders/need checking/toonify.xml similarity index 100% rename from effects/shaders/toonify.xml rename to effects/shaders/need checking/toonify.xml diff --git a/effects/shaders/need checking/volumetriclight.frag b/effects/shaders/need checking/volumetriclight.frag new file mode 100644 index 000000000..bf27e17d7 --- /dev/null +++ b/effects/shaders/need checking/volumetriclight.frag @@ -0,0 +1,15 @@ +uniform float amount; + +uniform vec2 resolution; // screen resolution + +vec4 process(vec4 col) { + float alpha = texture2D(texture, v_texcoord)[3]; + + vec3 p = gl_FragCoord.xyz/vec3(resolution, 1.0)-.5; + vec4 color = texture2D(texture,.5+(p.xy*=.992)); + vec3 o = color.rgb; + for (float i=0.;i 0.0) { vec2 distance = vec2((gl_FragCoord.x - (resolution.x/2.0) - center_x), (gl_FragCoord.y - (resolution.y/2.0) - center_y)); @@ -28,10 +25,10 @@ void main(void) { for (float i=-limit+0.5;i<=limit;i+=2.0) { float y = sin_angle * i; float x = cos_angle * i; - color += texture2D(image, vec2(gl_FragCoord.x+x, gl_FragCoord.y+y)/resolution)*(divider); + color += texture2D(texture, vec2(gl_FragCoord.x+x, gl_FragCoord.y+y)/resolution)*(divider); } - gl_FragColor = color; + return color; } else { - gl_FragColor = texture2D(image, gl_FragCoord.xy/resolution); + return col; } } \ No newline at end of file diff --git a/effects/shaders/ripple.frag b/effects/shaders/ripple.frag index 03c91a5ce..b9fc951f0 100644 --- a/effects/shaders/ripple.frag +++ b/effects/shaders/ripple.frag @@ -1,7 +1,3 @@ -#version 110 - -varying vec2 vTexCoord; - uniform float speed; uniform float intensity; uniform float frequency; @@ -12,10 +8,9 @@ uniform bool stretch; uniform vec2 resolution; // Screen resolution uniform float time; // time in seconds -uniform sampler2D tex0; // scene buffer -void main(void) { - vec2 texCoord = vTexCoord; +vec4 process(vec4 col) { + vec2 texCoord = v_texcoord; vec2 center = vec2(1.0); if (!stretch) { @@ -29,6 +24,6 @@ void main(void) { vec2 p = 2.0 * texCoord - center; float len = length(p); - vec2 uv = vTexCoord + (p/len)*cos((frequency*0.01)*(len*12.0-real_time*(speed*0.05)))*(intensity*0.0005); - gl_FragColor = texture2D(tex0,uv); + vec2 uv = v_texcoord + (p/len)*cos((frequency*0.01)*(len*12.0-real_time*(speed*0.05)))*(intensity*0.0005); + return texture2D(texture, uv); } \ No newline at end of file diff --git a/effects/shaders/sphere.frag b/effects/shaders/sphere.frag index 2445b37a4..6496dd5b3 100644 --- a/effects/shaders/sphere.frag +++ b/effects/shaders/sphere.frag @@ -1,9 +1,4 @@ -#version 110 - -varying vec2 vTexCoord; - uniform vec2 resolution; // Screen resolution -uniform sampler2D tex0; // scene buffer uniform float xoff; uniform float yoff; @@ -12,8 +7,8 @@ uniform bool tile; uniform bool hide_edges; uniform bool stretch; -void main(void) { - vec2 texCoord = vTexCoord; +vec4 process(vec4 col) { + vec2 texCoord = v_texcoord; vec2 offset = vec2(1.0); @@ -28,7 +23,7 @@ void main(void) { } vec2 p = 2.0 * texCoord - offset; - vec2 adj_tc = 2.0 * vTexCoord - 1.0; + vec2 adj_tc = 2.0 * v_texcoord - 1.0; float r = dot(p,p); if (r > 1.0) discard; float f = (1.0-sqrt(1.0-r))/(r); @@ -39,7 +34,7 @@ void main(void) { uv.x = mod(uv.x, 1.0); uv.y = mod(uv.y, 1.0); } else if (hide_edges && (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)) { - discard; + return vec4(0.0); } - gl_FragColor = vec4(texture2D(tex0,uv)); -} \ No newline at end of file + return vec4(texture2D(texture,uv)); +} diff --git a/effects/shaders/swirl.frag b/effects/shaders/swirl.frag index 00411f61f..2a3b0f569 100644 --- a/effects/shaders/swirl.frag +++ b/effects/shaders/swirl.frag @@ -1,5 +1,3 @@ -#version 110 - // Swirl effect parameters uniform float radius; uniform float angle; @@ -7,13 +5,10 @@ uniform float center_x; uniform float center_y; uniform vec2 resolution; -uniform sampler2D myTexture; -varying vec2 vTexCoord; - -void main(void) { +vec4 process(vec4 col) { vec2 center = vec2((resolution.x*0.5)+center_x, (resolution.y*0.5)+center_y); - vec2 uv = vTexCoord.st; + vec2 uv = v_texcoord.st; vec2 tc = uv * resolution; tc -= center; @@ -26,5 +21,6 @@ void main(void) { tc = vec2(dot(tc, vec2(c, -s)), dot(tc, vec2(s, c))); } tc += center; - gl_FragColor = texture2D(myTexture, tc / resolution); + + return texture2D(texture, tc / resolution); } \ No newline at end of file diff --git a/effects/shaders/tile.frag b/effects/shaders/tile.frag index 5c076b647..c7194a246 100644 --- a/effects/shaders/tile.frag +++ b/effects/shaders/tile.frag @@ -1,33 +1,14 @@ -#version 110 - -varying vec2 vTexCoord; -uniform sampler2D tex0; // scene buffer - uniform float scale; uniform float centerx; uniform float centery; uniform bool mirrorx; uniform bool mirrory; -void main(void) { - vec2 texCoord = vTexCoord; - - /*vec2 coord = vec2( - mod(vTexCoord.x*tilecount, 1.0), - mod(vTexCoord.y*tilecount, 1.0) - ); - - if (mirrorx && mod(vTexCoord.x*tilecount, 2.0) > 1.0) { - coord.x = 1.0 - coord.x; - } - - if (mirrory && mod(vTexCoord.y*tilecount, 2.0) > 1.0) { - coord.y = 1.0 - coord.y; - }*/ - +vec4 process(vec4 col) { + float adj_scale = scale*0.01; - vec2 scaled_coords = (vTexCoord/adj_scale); + vec2 scaled_coords = (v_texcoord/adj_scale); vec2 coord = scaled_coords-vec2(0.5/adj_scale, 0.5/adj_scale)+vec2(0.5, 0.5)+vec2(-centerx*0.01, -centery*0.01); vec2 modcoord = mod(coord, 1.0); @@ -39,5 +20,6 @@ void main(void) { modcoord.y = 1.0 - modcoord.y; } - gl_FragColor = vec4(texture2D(tex0, modcoord)); + return vec4(texture2D(texture, modcoord)); + } \ No newline at end of file diff --git a/effects/shaders/vignette.frag b/effects/shaders/vignette.frag index ae218dfc8..d73404f2b 100644 --- a/effects/shaders/vignette.frag +++ b/effects/shaders/vignette.frag @@ -1,6 +1,3 @@ -#version 110 - -uniform sampler2D sceneTex; // 0 uniform float lensRadiusX; uniform float lensRadiusY; uniform float centerX; @@ -10,14 +7,11 @@ uniform vec2 resolution; uniform bool invert; // uniform vec2 lensRadius; // 0.45, 0.38 -varying vec2 vTexCoord; - -void main(void) { +vec4 process(vec4 c) { if (lensRadiusX == 0.0) { - discard; + return vec4(0.0); } - vec4 c = texture2D(sceneTex, vTexCoord); - vec2 vignetteCoord = vTexCoord; + vec2 vignetteCoord = v_texcoord; if (circular) { float ar = (resolution.x/resolution.y); vignetteCoord.x *= ar; @@ -31,5 +25,5 @@ void main(void) { else {c *= smoothstep(size, size*0.99*(1.0-lensRadiusY*0.01), dist);} - gl_FragColor = c; -} \ No newline at end of file + return c; +} diff --git a/effects/shaders/volumetriclight.frag b/effects/shaders/volumetriclight.frag deleted file mode 100644 index cfeeb9252..000000000 --- a/effects/shaders/volumetriclight.frag +++ /dev/null @@ -1,19 +0,0 @@ -#version 110 - -uniform float amount; - -uniform sampler2D tex0; -uniform vec2 resolution; // screen resolution -varying vec2 vTexCoord; -#define T texture2D(tex0,.5+(p.xy*=.992)) - -void main() { - float alpha = texture2D(tex0, vTexCoord)[3]; - - vec3 p = gl_FragCoord.xyz/vec3(resolution, 1.0)-.5; - vec3 o = T.rgb; - for (float i=0.;i 1.0 || x < 0.0 || x > 1.0) { - discard; + return vec4(0.0); } else { - vec4 textureColor = texture2D(myTexture, vec2(x, y)); - gl_FragColor = vec4( - textureColor.r, - textureColor.g, - textureColor.b, - textureColor.a - ); + return texture2D(texture, vec2(x, y)); } } \ No newline at end of file diff --git a/effects/transition.cpp b/effects/transition.cpp index d3a9d40cf..af25547ad 100644 --- a/effects/transition.cpp +++ b/effects/transition.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -24,8 +24,7 @@ #include "timeline/clip.h" #include "timeline/sequence.h" #include "global/debug.h" - -#include "project/clipboard.h" +#include "global/clipboard.h" #include "effects/internal/crossdissolvetransition.h" #include "effects/internal/linearfadetransition.h" @@ -41,28 +40,34 @@ #include #include -Transition::Transition(Clip *c, Clip *s, const EffectMeta* em) : - Effect(c, em), - secondary_clip(s) +Transition::Transition(Clip *c) : + Node(c), + secondary_clip(nullptr) { - EffectRow* length_row = new EffectRow(this, tr("Length"), false, false); - length_field = new DoubleField(length_row, "length"); + length_field = new DoubleInput(this, "length", tr("Length"), false, false); length_field->SetDefault(30); length_field->SetMinimum(1); length_field->SetDisplayType(LabelSlider::FrameNumber); - length_field->SetFrameRate(parent_clip->sequence == nullptr ? - parent_clip->cached_frame_rate() : parent_clip->sequence->frame_rate); + + if (parent_clip != nullptr) { + length_field->SetFrameRate(parent_clip->track()->sequence() == nullptr ? + parent_clip->cached_frame_rate() : parent_clip->track()->sequence()->frame_rate); + } connect(length_field, SIGNAL(Changed()), this, SLOT(UpdateMaximumLength())); } -TransitionPtr Transition::copy(Clip *c, Clip *s) { - return Transition::Create(c, s, meta, get_true_length()); +NodePtr Transition::copy(Clip *c) { + NodePtr node = Node::copy(c); + + static_cast(node.get())->set_length(get_true_length()); + + return node; } void Transition::save(QXmlStreamWriter &stream) { stream.writeAttribute("length", QString::number(get_true_length())); - Effect::save(stream); + Node::save(stream); } void Transition::set_length(int l) { @@ -98,17 +103,18 @@ Clip* Transition::get_closed_clip() { return nullptr; } -TransitionPtr Transition::CreateFromMeta(Clip* c, Clip* s, const EffectMeta* em) { +/* +TransitionPtr Transition::CreateFromMeta(Clip* c, Clip* s) { if (!em->filename.isEmpty()) { // load effect from file return TransitionPtr(new Transition(c, s, em)); } else if (em->internal >= 0 && em->internal < TRANSITION_INTERNAL_COUNT) { // must be an internal effect switch (em->internal) { - case TRANSITION_INTERNAL_CROSSDISSOLVE: return TransitionPtr(new CrossDissolveTransition(c, s, em)); - case TRANSITION_INTERNAL_LINEARFADE: return TransitionPtr(new LinearFadeTransition(c, s, em)); - case TRANSITION_INTERNAL_EXPONENTIALFADE: return TransitionPtr(new ExponentialFadeTransition(c, s, em)); - case TRANSITION_INTERNAL_LOGARITHMICFADE: return TransitionPtr(new LogarithmicFadeTransition(c, s, em)); + case kCrossDissolveTransition: return TransitionPtr(new CrossDissolveTransition(c, s, em)); + case kLinearFadeTransition: return TransitionPtr(new LinearFadeTransition(c, s, em)); + case kExponentialFadeTransition: return TransitionPtr(new ExponentialFadeTransition(c, s, em)); + case kLogarithmicFadeTransition: return TransitionPtr(new LogarithmicFadeTransition(c, s, em)); //case TRANSITION_INTERNAL_CUBE: return TransitionPtr(new CubeTransition(c, s, em)); } } else { @@ -120,6 +126,7 @@ TransitionPtr Transition::CreateFromMeta(Clip* c, Clip* s, const EffectMeta* em) } return nullptr; } +*/ void Transition::UpdateMaximumLength() { @@ -156,14 +163,3 @@ long Transition::GetMaximumEmptySpaceOnClip(Clip *c) return maximum_transition_length; } - -TransitionPtr Transition::Create(Clip* c, Clip* s, const EffectMeta* em, long length) { - TransitionPtr t(CreateFromMeta(c, s, em)); - if (t != nullptr) { - if (length > 0) { - t->set_length(length); - } - return t; - } - return nullptr; -} diff --git a/effects/transition.h b/effects/transition.h index 8895d9dd7..7be393c5c 100644 --- a/effects/transition.h +++ b/effects/transition.h @@ -1,27 +1,28 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef TRANSITION_H #define TRANSITION_H -#include "effect.h" +#include "nodes/node.h" +#include "nodes/inputs.h" enum TransitionType { kTransitionNone, @@ -29,23 +30,16 @@ enum TransitionType { kTransitionClosing }; -enum TransitionInternal { - TRANSITION_INTERNAL_CROSSDISSOLVE, - TRANSITION_INTERNAL_LINEARFADE, - TRANSITION_INTERNAL_EXPONENTIALFADE, - TRANSITION_INTERNAL_LOGARITHMICFADE, - //TRANSITION_INTERNAL_CUBE, - TRANSITION_INTERNAL_COUNT -}; - class Transition; using TransitionPtr = std::shared_ptr; -class Transition : public Effect { +class Transition : public Node { Q_OBJECT public: - Transition(Clip* c, Clip* s, const EffectMeta* em); - virtual TransitionPtr copy(Clip* c, Clip* s); + Transition(Clip* c); + + virtual NodePtr copy(Clip* c) override; + Clip* secondary_clip; virtual void save(QXmlStreamWriter& stream) override; @@ -57,11 +51,9 @@ public: Clip* get_opened_clip(); Clip* get_closed_clip(); - static TransitionPtr Create(Clip* c, Clip* s, const EffectMeta* em, long length = 0); - static TransitionPtr CreateFromMeta(Clip *c, Clip *s, const EffectMeta* em); private: - DoubleField* length_field; + DoubleInput* length_field; private slots: void UpdateMaximumLength(); diff --git a/global/clipboard.cpp b/global/clipboard.cpp new file mode 100644 index 000000000..22d1c4bfc --- /dev/null +++ b/global/clipboard.cpp @@ -0,0 +1,104 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "clipboard.h" + +#include "timeline/clip.h" +#include "nodes/node.h" +#include "effects/transition.h" +#include "undo/undo.h" + +Clipboard::Clipboard() : + type_(CLIPBOARD_TYPE_CLIP) +{ +} + +void Clipboard::Append(VoidPtr obj) +{ + clipboard_.append(obj); +} + +void Clipboard::Insert(int pos, VoidPtr obj) +{ + clipboard_.insert(pos, obj); +} + +void Clipboard::RemoveAt(int pos) +{ + clipboard_.removeAt(pos); +} + +void Clipboard::Clear() +{ + clipboard_.clear(); + clipboard_transitions_.clear(); +} + +int Clipboard::Count() +{ + return clipboard_.size(); +} + +VoidPtr Clipboard::Get(int i) +{ + return clipboard_.at(i); +} + +void Clipboard::SetType(Clipboard::Type type) +{ + if (type == type_) { + return; + } + + Clear(); + type_ = type; +} + +bool Clipboard::IsEmpty() +{ + return clipboard_.isEmpty(); +} + +Clipboard::Type Clipboard::type() +{ + return type_; +} + +bool Clipboard::DeleteClipsWithMedia(ComboAction *ca, Media *m) +{ + if (type_ != CLIPBOARD_TYPE_CLIP) { + return false; + } + + int delete_count = 0; + + for (int i=0;i(clipboard_.at(i)).get(); + + if (c->media() == m) { + ca->append(new RemoveClipsFromClipboard(i-delete_count)); + delete_count++; + } + } + + return (delete_count > 0); +} + +Clipboard olive::clipboard; diff --git a/global/clipboard.h b/global/clipboard.h new file mode 100644 index 000000000..be3190c2d --- /dev/null +++ b/global/clipboard.h @@ -0,0 +1,62 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef CLIPBOARD_H +#define CLIPBOARD_H + +#include + +#include "effects/transition.h" +#include "project/media.h" + +using VoidPtr = std::shared_ptr; + +class Clipboard { +public: + enum Type { + CLIPBOARD_TYPE_CLIP, + CLIPBOARD_TYPE_EFFECT + }; + + Clipboard(); + void Append(VoidPtr obj); + void Insert(int pos, VoidPtr obj); + void RemoveAt(int pos); + void Clear(); + int Count(); + VoidPtr Get(int i); + void SetType(Type type); + bool IsEmpty(); + Type type(); + + bool DeleteClipsWithMedia(ComboAction* ca, Media* m); + +private: + Type type_; + + QVector clipboard_transitions_; + QVector clipboard_; +}; + +namespace olive { +extern Clipboard clipboard; +} + +#endif // CLIPBOARD_H diff --git a/global/config.cpp b/global/config.cpp index 06c53833e..917bc66cc 100644 --- a/global/config.cpp +++ b/global/config.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -29,12 +29,11 @@ #include "debug.h" -Config olive::CurrentConfig; -RuntimeConfig olive::CurrentRuntimeConfig; +Config olive::config; +RuntimeConfig olive::runtime_config; Config::Config() - : show_track_lines(true), - scroll_zooms(false), + : scroll_zooms(false), edit_tool_selects_links(false), edit_tool_also_seeks(false), select_also_seeks(false), @@ -72,6 +71,7 @@ Config::Config() thumbnail_resolution(120), add_default_effects_to_clips(true), invert_timeline_scroll_axes(true), + enable_color_management(false), style(olive::styling::kOliveDefaultDark), use_native_menu_styling(true), default_sequence_width(1920), @@ -79,6 +79,10 @@ Config::Config() default_sequence_framerate(29.97), default_sequence_audio_frequency(48000), default_sequence_audio_channel_layout(3), + playback_bit_depth(olive::PIX_FMT_RGBA16F), + export_bit_depth(olive::PIX_FMT_RGBA32F), + dont_use_proxies_on_export(true), + maximum_recent_projects(10), locked_panels(false) {} @@ -90,10 +94,7 @@ void Config::load(QString path) { while (!stream.atEnd()) { stream.readNext(); if (stream.isStartElement()) { - if (stream.name() == "ShowTrackLines") { - stream.readNext(); - show_track_lines = (stream.text() == "1"); - } else if (stream.name() == "ScrollZooms") { + if (stream.name() == "ScrollZooms") { stream.readNext(); scroll_zooms = (stream.text() == "1"); } else if (stream.name() == "InvertTimelineScrollAxes") { @@ -219,6 +220,24 @@ void Config::load(QString path) { } else if (stream.name() == "AddDefaultEffectsToClips") { stream.readNext(); add_default_effects_to_clips = (stream.text() == "1"); + } else if (stream.name() == "EnableColorManagement") { + stream.readNext(); + enable_color_management = (stream.text() == "1"); + } else if (stream.name() == "OCIOConfigPath") { + stream.readNext(); + ocio_config_path = stream.text().toString(); + } else if (stream.name() == "OCIODisplay") { + stream.readNext(); + ocio_display = stream.text().toString(); + } else if (stream.name() == "OCIOView") { + stream.readNext(); + ocio_view = stream.text().toString(); + } else if (stream.name() == "OCIOLook") { + stream.readNext(); + ocio_look = stream.text().toString(); + } else if (stream.name() == "OCIODefaultInput") { + stream.readNext(); + ocio_default_input_colorspace = stream.text().toString(); } else if (stream.name() == "Style") { stream.readNext(); style = static_cast(stream.text().toInt()); @@ -240,6 +259,15 @@ void Config::load(QString path) { } else if (stream.name() == "DefaultSequenceAudioLayout") { stream.readNext(); default_sequence_audio_channel_layout = stream.text().toInt(); + } else if (stream.name() == "PlaybackBitDepth") { + stream.readNext(); + playback_bit_depth = stream.text().toInt(); + } else if (stream.name() == "ExportBitDepth") { + stream.readNext(); + export_bit_depth = stream.text().toInt(); + } else if (stream.name() == "DontUseProxiesOnExport") { + stream.readNext(); + dont_use_proxies_on_export = (stream.text() == "1"); } else if (stream.name() == "LockedPanels") { stream.readNext(); locked_panels = (stream.text() == "1"); @@ -267,7 +295,6 @@ void Config::save(QString path) { stream.writeStartElement("Configuration"); // configuration stream.writeTextElement("Version", QString::number(olive::kSaveVersion)); - stream.writeTextElement("ShowTrackLines", QString::number(show_track_lines)); stream.writeTextElement("ScrollZooms", QString::number(scroll_zooms)); stream.writeTextElement("InvertTimelineScrollAxes", QString::number(invert_timeline_scroll_axes)); stream.writeTextElement("EditToolSelectsLinks", QString::number(edit_tool_selects_links)); @@ -292,7 +319,7 @@ void Config::save(QString path) { stream.writeTextElement("HoverFocus", QString::number(hover_focus)); stream.writeTextElement("ProjectViewType", QString::number(project_view_type)); stream.writeTextElement("SetNameWithMarker", QString::number(set_name_with_marker)); - stream.writeTextElement("ShowProjectToolbar", QString::number(panel_project->IsToolbarVisible())); + stream.writeTextElement("ShowProjectToolbar", QString::number(panel_project.first()->IsToolbarVisible())); stream.writeTextElement("PreviousFrameQueueSize", QString::number(previous_queue_size)); stream.writeTextElement("PreviousFrameQueueType", QString::number(previous_queue_type)); stream.writeTextElement("UpcomingFrameQueueSize", QString::number(upcoming_queue_size)); @@ -310,6 +337,12 @@ void Config::save(QString path) { stream.writeTextElement("ThumbnailResolution", QString::number(thumbnail_resolution)); stream.writeTextElement("WaveformResolution", QString::number(waveform_resolution)); stream.writeTextElement("AddDefaultEffectsToClips", QString::number(add_default_effects_to_clips)); + stream.writeTextElement("EnableColorManagement", QString::number(enable_color_management)); + stream.writeTextElement("OCIOConfigPath", ocio_config_path); + stream.writeTextElement("OCIODisplay", ocio_display); + stream.writeTextElement("OCIOView", ocio_view); + stream.writeTextElement("OCIOLook", ocio_look); + stream.writeTextElement("OCIODefaultInput", ocio_default_input_colorspace); stream.writeTextElement("Style", QString::number(style)); stream.writeTextElement("NativeMenuStyling", QString::number(use_native_menu_styling)); stream.writeTextElement("DefaultSequenceWidth", QString::number(default_sequence_width)); @@ -317,6 +350,9 @@ void Config::save(QString path) { stream.writeTextElement("DefaultSequenceFrameRate", QString::number(default_sequence_framerate)); stream.writeTextElement("DefaultSequenceAudioFrequency", QString::number(default_sequence_audio_frequency)); stream.writeTextElement("DefaultSequenceAudioLayout", QString::number(default_sequence_audio_channel_layout)); + stream.writeTextElement("PlaybackBitDepth", QString::number(playback_bit_depth)); + stream.writeTextElement("ExportBitDepth", QString::number(export_bit_depth)); + stream.writeTextElement("DontUseProxiesOnExport", QString::number(dont_use_proxies_on_export)); stream.writeTextElement("LockedPanels", QString::number(locked_panels)); stream.writeEndElement(); // configuration @@ -325,6 +361,5 @@ void Config::save(QString path) { } RuntimeConfig::RuntimeConfig() : - shaders_are_enabled(true), - disable_blending(false) + shaders_are_enabled(true) {} diff --git a/global/config.h b/global/config.h index 6fffe5f51..293bcdcca 100644 --- a/global/config.h +++ b/global/config.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -24,6 +24,7 @@ #include #include "ui/styling.h" +#include "timeline/timelinetools.h" namespace olive { /** @@ -159,13 +160,6 @@ struct Config { */ Config(); - /** - * @brief Show track lines - * - * **TRUE** if the Timeline should show lines between tracks. - */ - bool show_track_lines; - /** * @brief The scroll wheel zooms rather than scrolls * @@ -519,6 +513,48 @@ struct Config { */ bool invert_timeline_scroll_axes; + /** + * @brief Enable color managemennt + * + * **TRUE** if color management through OpenColorIO should be enabled + */ + bool enable_color_management; + + /** + * @brief Path to OpenColorIO configuration file + * + * Used if Config::enable_color_management is true. + */ + QString ocio_config_path; + + /** + * @brief OpenColorIO Display + * + * Used if Config::enable_color_management is true + */ + QString ocio_display; + + /** + * @brief OpenColorIO View + * + * Used if Config::enable_color_management is true + */ + QString ocio_view; + + /** + * @brief OpenColorIO Look + * + * Used if Config::enable_color_management is true + */ + QString ocio_look; + + /** + * @brief OpenColorIO Default Input Colorspace + * + * The colorspace to default to if no colorspace can be determined from the filename or a manual setting. + */ + QString ocio_default_input_colorspace; + /** * @brief Style to use when theming Olive. * @@ -558,6 +594,26 @@ struct Config { */ int default_sequence_audio_channel_layout; + /** + * @brief Playback bit depth (an index of olive::rendering::bit_depths) + */ + int playback_bit_depth; + + /** + * @brief Export bit depth (an index of olive::rendering::bit_depths) + */ + int export_bit_depth; + + /** + * @brief Don't use proxies on export (use originals instead) + */ + bool dont_use_proxies_on_export; + + /** + * @brief The maximum amount of recent projects stored in the Open Recent list + */ + int maximum_recent_projects; + /** * @brief Sets whether panels should load locked or not */ @@ -607,25 +663,18 @@ struct RuntimeConfig { */ bool shaders_are_enabled; - /** - * @brief Disable blending modes - * - * Some users had difficulty utilizing blending modes (provided by shaders). Set this to **TRUE** to bypass - * shader-based blending modes and utilize standard (less versatile) OpenGL blending instead. - */ - bool disable_blending; - /** * @brief Load an external translation file * * Overrides Config::language_file and sets the path to a language file to use. */ QString external_translation_file; + }; namespace olive { -extern Config CurrentConfig; -extern RuntimeConfig CurrentRuntimeConfig; +extern Config config; +extern RuntimeConfig runtime_config; } #endif // CONFIG_H diff --git a/global/crossplatformlib.cpp b/global/crossplatformlib.cpp new file mode 100644 index 000000000..b6f7a2655 --- /dev/null +++ b/global/crossplatformlib.cpp @@ -0,0 +1,64 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "crossplatformlib.h" + +#include + +ModulePtr LibLoad(const QString &filename) { +#ifdef _WIN32 + LPCWSTR dll_fn_w = reinterpret_cast(filename.utf16()); + return LoadLibrary(dll_fn_w); +#elif defined(__linux__) || defined(__APPLE__) || defined(__HAIKU__) + return dlopen(filename.toUtf8(), RTLD_LAZY); +#else + qWarning() << "Olive doesn't know how to open dynamic libraries on this platform, external libraries will not be functional"; + return nullptr; +#endif +} + +QStringList LibFilter() { +#ifdef _WIN32 + return QStringList("*.dll"); +#elif defined(__linux__) || defined(__APPLE__) || defined(__HAIKU__) + return {"*.so", "*.dylib"}; +#endif +} + +#ifdef __APPLE__ +CFBundleRef BundleLoad(const QString &filename) { + CFStringRef bundle_str = CFStringCreateWithCString(NULL, filename.toUtf8(), kCFStringEncodingUTF8); + CFURLRef bundle_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundle_str, kCFURLPOSIXPathStyle, true); + CFBundleRef bundle = NULL; + if (bundle_url != NULL) { + bundle = CFBundleCreate(kCFAllocatorDefault, bundle_url); + } else { + qCritical() << "Failed to create VST URL"; + } + CFRelease(bundle_url); + CFRelease(bundle_str); + return bundle; +} + +void BundleClose(CFBundleRef bundle) { + CFBundleUnloadExecutable(bundle); + CFRelease(bundle); +} +#endif diff --git a/global/crossplatformlib.h b/global/crossplatformlib.h new file mode 100644 index 000000000..068f49604 --- /dev/null +++ b/global/crossplatformlib.h @@ -0,0 +1,49 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef CROSSPLATFORMLIB_H +#define CROSSPLATFORMLIB_H + +#include + +#ifdef _WIN32 + #include + #define LibAddress GetProcAddress + #define LibClose FreeModule + #define ModulePtr HMODULE +#elif defined(__linux__) || defined(__APPLE__) || defined(__HAIKU__) + #include + #define LibAddress dlsym + #define LibClose dlclose + #define ModulePtr void* +#endif + +ModulePtr LibLoad(const QString& filename); +QStringList LibFilter(); + +#ifdef __APPLE__ +#include +class NSWindow; + +CFBundleRef BundleLoad(const QString& filename); +void BundleClose(CFBundleRef bundle); +#endif + +#endif // CROSSPLATFORMLIB_H diff --git a/global/debug.cpp b/global/debug.cpp index 87d892f2c..d9aee5e21 100644 --- a/global/debug.cpp +++ b/global/debug.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/global/debug.h b/global/debug.h index 791a12cc6..b6b4be132 100644 --- a/global/debug.h +++ b/global/debug.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/global/global.cpp b/global/global.cpp index f956662ae..cad59f386 100644 --- a/global/global.cpp +++ b/global/global.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -30,6 +30,8 @@ #include "panels/panels.h" #include "global/path.h" #include "global/config.h" +#include "global/timing.h" +#include "global/clipboard.h" #include "rendering/audio.h" #include "dialogs/demonotice.h" #include "dialogs/preferencesdialog.h" @@ -38,12 +40,16 @@ #include "dialogs/aboutdialog.h" #include "dialogs/speeddialog.h" #include "dialogs/actionsearch.h" +#include "dialogs/newsequencedialog.h" #include "dialogs/loaddialog.h" #include "dialogs/autocutsilencedialog.h" +#include "effects/effectloaders.h" #include "project/loadthread.h" +#include "project/savethread.h" #include "timeline/sequence.h" #include "ui/mediaiconservice.h" #include "ui/mainwindow.h" +#include "ui/menu.h" #include "ui/updatenotification.h" #include "undo/undostack.h" @@ -52,7 +58,8 @@ QString olive::ActiveProjectFilename; QString olive::AppName; OliveGlobal::OliveGlobal() : - changed_since_last_autorecovery(false) + changed_since_last_autorecovery(false), + rendering_(false) { // sets current app name QString version_id; @@ -108,8 +115,13 @@ void OliveGlobal::check_for_autorecovery_file() { } } -void OliveGlobal::set_rendering_state(bool rendering) { - audio_rendering = rendering; +bool OliveGlobal::is_exporting() +{ + return rendering_; +} + +void OliveGlobal::set_export_state(bool rendering) { + rendering_ = rendering; if (rendering) { autorecovery_timer.stop(); } else { @@ -138,12 +150,12 @@ QString OliveGlobal::get_recent_project_list_file() { } void OliveGlobal::load_translation_from_config() { - QString language_file = olive::CurrentRuntimeConfig.external_translation_file.isEmpty() ? - olive::CurrentConfig.language_file : - olive::CurrentRuntimeConfig.external_translation_file; + QString language_file = olive::runtime_config.external_translation_file.isEmpty() ? + olive::config.language_file : + olive::runtime_config.external_translation_file; // clear runtime language file so if the user sets a different language, we won't load it next time - olive::CurrentRuntimeConfig.external_translation_file.clear(); + olive::runtime_config.external_translation_file.clear(); // remove current translation if there is one QApplication::removeTranslator(translator.get()); @@ -169,16 +181,71 @@ void OliveGlobal::SetNativeStyling(QWidget *w) w->setStyleSheet(""); w->setPalette(w->style()->standardPalette()); w->setStyle(QStyleFactory::create("windowsvista")); +#else + Q_UNUSED(w) #endif } +void OliveGlobal::add_recent_project(const QString &url) +{ + bool found = false; + for (int i=0;i olive::config.maximum_recent_projects) { + recent_projects.removeLast(); + } + } + save_recent_projects(); +} + +void OliveGlobal::load_recent_projects() +{ + QFile f(get_recent_project_list_file()); + if (f.exists() && f.open(QFile::ReadOnly | QFile::Text)) { + QTextStream text_stream(&f); + while (true) { + QString line = text_stream.readLine(); + if (line.isNull()) { + break; + } else { + recent_projects.append(line); + } + } + f.close(); + } +} + +int OliveGlobal::recent_project_count() +{ + return recent_projects.size(); +} + +const QString &OliveGlobal::recent_project(int index) +{ + return recent_projects.at(index); +} + +const QString &OliveGlobal::get_autorecovery_filename() +{ + return autorecovery_filename; +} + void OliveGlobal::LoadProject(const QString &fn, bool autorecovery) { // QSortFilterProxyModels are not thread-safe, and as we'll be loading in another thread, leaving it connected // can cause glitches in its presentation. Therefore for the duration of the loading process, we disconnect it, // and reconnect it later once the loading is complete. - panel_project->DisconnectFilterToModel(); + for (int i=0;iDisconnectFilterToModel(); + } LoadDialog ld(olive::MainWindow); @@ -192,7 +259,9 @@ void OliveGlobal::LoadProject(const QString &fn, bool autorecovery) connect(lt, SIGNAL(report_progress(int)), &ld, SLOT(setValue(int))); lt->start(); - panel_project->ConnectFilterToModel(); + for (int i=0;iConnectFilterToModel(); + } } void OliveGlobal::ClearProject() @@ -204,14 +273,20 @@ void OliveGlobal::ClearProject() panel_effect_controls->Clear(true); // clear existing project - olive::Global->set_sequence(nullptr); + Timeline::CloseAll(); panel_footage_viewer->set_media(nullptr); + // delete sequences first because it's important to close all the clips before deleting the media + QVector sequences = olive::project_model.GetAllSequences(); + for (int i=0;iset_sequence(nullptr); + } + // clear project contents (footage, sequences, etc.) - panel_project->clear(); + olive::project_model.clear(); // clear undo stack - olive::UndoStack.clear(); + olive::undo_stack.clear(); // empty current project filename update_project_filename(""); @@ -220,7 +295,195 @@ void OliveGlobal::ClearProject() update_ui(false); // set to unmodified - olive::Global->set_modified(false); + set_modified(false); +} + +void OliveGlobal::save_recent_projects() +{ + // save to file + QFile f(get_recent_project_list_file()); + if (f.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) { + QTextStream out(&f); + for (int i=0;i 0) { + out << "\n"; + } + out << recent_projects.at(i); + } + f.close(); + } else { + qWarning() << "Could not save recent projects"; + } +} + +void OliveGlobal::PasteInternal(Sequence *s, bool insert) +{ + if (s == nullptr) { + return; + } + + if (!olive::clipboard.IsEmpty()) { + if (olive::clipboard.type() == Clipboard::CLIPBOARD_TYPE_CLIP) { + ComboAction* ca = new ComboAction(); + + // create copies and delete areas that we'll be pasting to + QVector delete_areas; + QVector original_clips; + QVector pasted_clips; + long paste_start = LONG_MAX; + long paste_end = LONG_MIN; + + for (int i=0;i(olive::clipboard.Get(i)); + + // create copy of clip and offset by playhead + ClipPtr cc = c->copy(s->GetTrackList(c->track()->type())->TrackAt(c->track()->Index())); + + // convert frame rates + cc->set_timeline_in(rescale_frame_number(cc->timeline_in(), c->cached_frame_rate(), s->frame_rate)); + cc->set_timeline_out(rescale_frame_number(cc->timeline_out(), c->cached_frame_rate(), s->frame_rate)); + cc->set_clip_in(rescale_frame_number(cc->clip_in(), c->cached_frame_rate(), s->frame_rate)); + + cc->set_timeline_in(cc->timeline_in() + s->playhead); + cc->set_timeline_out(cc->timeline_out() + s->playhead); + cc->set_track(c->track()); + + paste_start = qMin(paste_start, cc->timeline_in()); + paste_end = qMax(paste_end, cc->timeline_out()); + + original_clips.append(c.get()); + pasted_clips.append(cc); + + if (!insert) { + delete_areas.append(Selection(cc->timeline_in(), cc->timeline_out(), c->track())); + } + } + if (insert) { + s->SplitAllClipsAtPoint(ca, s->playhead); + s->Ripple(ca, paste_start, paste_end - paste_start); + } else { + s->DeleteAreas(ca, delete_areas, false); + } + + // correct linked clips + olive::timeline::RelinkClips(original_clips, pasted_clips); + + ca->append(new AddClipCommand(pasted_clips)); + + olive::undo_stack.push(ca); + + update_ui(true); + + if (olive::config.paste_seeks) { + panel_sequence_viewer->seek(paste_end); + } + + } else if (olive::clipboard.type() == Clipboard::CLIPBOARD_TYPE_EFFECT) { + ComboAction* ca = new ComboAction(); + + bool replace = false; + bool skip = false; + bool ask_conflict = true; + + QVector selected_clips = s->SelectedClips(); + + for (int i=0;i(olive::clipboard.Get(j)); + if (c->type() == e->subtype()) { + int found = -1; + if (ask_conflict) { + replace = false; + skip = false; + } + for (int k=0;keffects.size();k++) { + if (c->effects.at(k)->id() == e->id()) { + found = k; + break; + } + } + if (found >= 0 && ask_conflict) { + QMessageBox box(olive::MainWindow); + box.setWindowTitle(tr("Effect already exists")); + box.setText(tr("Clip '%1' already contains a '%2' effect. " + "Would you like to replace it with the pasted one or add it as a separate effect?") + .arg(c->name(), e->name())); + box.setIcon(QMessageBox::Icon::Question); + + box.addButton(tr("Add"), QMessageBox::YesRole); + QPushButton* replace_button = box.addButton(tr("Replace"), QMessageBox::NoRole); + QPushButton* skip_button = box.addButton(tr("Skip"), QMessageBox::RejectRole); + + QCheckBox* future_box = new QCheckBox(tr("Do this for all conflicts found"), &box); + box.setCheckBox(future_box); + + box.exec(); + + if (box.clickedButton() == replace_button) { + replace = true; + } else if (box.clickedButton() == skip_button) { + skip = true; + } + ask_conflict = !future_box->isChecked(); + } + + if (found >= 0 && skip) { + // do nothing + } else if (found >= 0 && replace) { + ca->append(new EffectDeleteCommand(c->effects.at(found).get())); + + ca->append(new AddEffectCommand(c, e->copy(c), kInvalidNode, found)); + } else { + ca->append(new AddEffectCommand(c, e->copy(c), kInvalidNode)); + } + } + } + } + if (ca->hasActions()) { + ca->appendPost(new ReloadEffectsCommand()); + olive::undo_stack.push(ca); + } else { + delete ca; + } + update_ui(true); + } + } +} + +void OliveGlobal::EffectMenuAction(QAction *q) +{ + ComboAction* ca = new ComboAction(); + + NodeType node_type = static_cast(q->data().toInt()); + Node* n = olive::node_library[node_type].get(); + + for (int i=0;itype() == n->subtype()) { + if (n->type() == EFFECT_TYPE_TRANSITION) { + if (c->opening_transition == nullptr) { + ca->append(new AddTransitionCommand(c, + nullptr, + nullptr, + node_type, + olive::config.default_transition_length)); + } + if (c->closing_transition == nullptr) { + ca->append(new AddTransitionCommand(nullptr, + c, + nullptr, + node_type, + olive::config.default_transition_length)); + } + } else { + ca->append(new AddEffectCommand(c, nullptr, node_type)); + } + } + } + olive::undo_stack.push(ca); + update_ui(true); } void OliveGlobal::ImportProject(const QString &fn) @@ -251,7 +514,7 @@ void OliveGlobal::open_recent(int index) { tr("The project '%1' no longer exists. Would you like to remove it from the recent projects list?").arg(recent_url), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { recent_projects.removeAt(index); - panel_project->save_recent_projects(); + save_recent_projects(); } } else if (can_close_project()) { OpenProjectWorker(recent_url, false); @@ -265,7 +528,7 @@ bool OliveGlobal::save_project_as() { fn += ".ove"; } update_project_filename(fn); - panel_project->save_project(false); + olive::Save(false); return true; } return false; @@ -275,7 +538,7 @@ bool OliveGlobal::save_project() { if (olive::ActiveProjectFilename.isEmpty()) { return save_project_as(); } else { - panel_project->save_project(false); + olive::Save(false); return true; } } @@ -301,9 +564,36 @@ bool OliveGlobal::can_close_project() { return true; } +void OliveGlobal::open_new_sequence_dialog() +{ + NewSequenceDialog nsd(olive::MainWindow); + nsd.set_sequence_name(olive::project_model.GetNextSequenceName()); + nsd.exec(); +} + +void OliveGlobal::open_import_dialog() +{ + QFileDialog fd(olive::MainWindow, tr("Import media..."), "", tr("All Files") + " (*)"); + fd.setFileMode(QFileDialog::ExistingFiles); + + if (fd.exec()) { + QStringList files = fd.selectedFiles(); + + Media* parent = nullptr; + for (int i=0;ifocused()) { + parent = panel_project.at(i)->get_selected_folder(); + break; + } + } + + olive::project_model.process_file_list(files, false, nullptr, parent); + } +} + void OliveGlobal::open_export_dialog() { if (CheckForActiveSequence()) { - ExportDialog e(olive::MainWindow); + ExportDialog e(olive::MainWindow, Timeline::GetTopSequence().get()); e.exec(); } } @@ -339,7 +629,7 @@ void OliveGlobal::finished_initialize() { void OliveGlobal::save_autorecovery_file() { if (changed_since_last_autorecovery) { - panel_project->save_project(true); + olive::Save(true); changed_since_last_autorecovery = false; @@ -355,27 +645,28 @@ void OliveGlobal::open_preferences() { pd.exec(); } -void OliveGlobal::set_sequence(SequencePtr s) +void OliveGlobal::PrimarySequenceChanged() { panel_graph_editor->set_row(nullptr); panel_effect_controls->Clear(true); +} - olive::ActiveSequence = s; - panel_sequence_viewer->set_main_sequence(); - panel_timeline->update_sequence(); - panel_timeline->setFocus(); +void OliveGlobal::clear_recent_projects() +{ + recent_projects.clear(); + save_recent_projects(); } void OliveGlobal::OpenProjectWorker(QString fn, bool autorecovery) { ClearProject(); update_project_filename(fn); LoadProject(fn, autorecovery); - olive::UndoStack.clear(); + olive::undo_stack.clear(); } bool OliveGlobal::CheckForActiveSequence(bool show_msg) { - if (olive::ActiveSequence == nullptr) { + if (Timeline::GetTopSequence() == nullptr) { if (show_msg) { QMessageBox::information(olive::MainWindow, @@ -389,32 +680,99 @@ bool OliveGlobal::CheckForActiveSequence(bool show_msg) return true; } +void OliveGlobal::ShowEffectMenu(EffectType type, olive::TrackType subtype, const QVector selected_clips) +{ + effect_menu_selected_clips = selected_clips; + + olive::effects_loaded.lock(); + + Menu effects_menu(olive::MainWindow); + effects_menu.setToolTipsVisible(true); + + for (int i=0;itype() == type && node->subtype() == subtype) { + QAction* action = new QAction(&effects_menu); + action->setText(node->name()); + action->setData(i); + if (!node->description().isEmpty()) { + action->setToolTip(node->description()); + } + + QMenu* parent = &effects_menu; + if (!node->category().isEmpty()) { + bool found = false; + for (int j=0;jmenu() != nullptr) { + if (action->menu()->title() == node->category()) { + parent = action->menu(); + found = true; + break; + } + } + } + if (!found) { + parent = new Menu(&effects_menu); + parent->setToolTipsVisible(true); + parent->setTitle(node->category()); + + bool found = false; + for (int i=0;itext() > node->category()) { + effects_menu.insertMenu(comp_action, parent); + found = true; + break; + } + } + if (!found) effects_menu.addMenu(parent); + } + } + + bool found = false; + for (int i=0;iactions().size();i++) { + QAction* comp_action = parent->actions().at(i); + if (comp_action->text() > action->text()) { + parent->insertAction(comp_action, action); + found = true; + break; + } + } + if (!found) parent->addAction(action); + } + } + + olive::effects_loaded.unlock(); + + connect(&effects_menu, SIGNAL(triggered(QAction*)), this, SLOT(EffectMenuAction(QAction*))); + effects_menu.exec(QCursor::pos()); +} + void OliveGlobal::undo() { // workaround to prevent crash (and also users should never need to do this) - if (!panel_timeline->importing) { - olive::UndoStack.undo(); + if (!Timeline::IsImporting()) { + olive::undo_stack.undo(); update_ui(true); } } void OliveGlobal::redo() { // workaround to prevent crash (and also users should never need to do this) - if (!panel_timeline->importing) { - olive::UndoStack.redo(); + if (!Timeline::IsImporting()) { + olive::undo_stack.redo(); update_ui(true); } } void OliveGlobal::paste() { - if (olive::ActiveSequence != nullptr) { - panel_timeline->paste(false); - } + PasteInternal(Timeline::GetTopSequence().get(), false); } void OliveGlobal::paste_insert() { - if (olive::ActiveSequence != nullptr) { - panel_timeline->paste(true); - } + PasteInternal(Timeline::GetTopSequence().get(), true); } void OliveGlobal::open_about_dialog() { @@ -427,9 +785,9 @@ void OliveGlobal::open_debug_log() { } void OliveGlobal::open_speed_dialog() { - if (olive::ActiveSequence != nullptr) { + if (Timeline::GetTopSequence() != nullptr) { - QVector selected_clips = olive::ActiveSequence->SelectedClips(); + QVector selected_clips = Timeline::GetTopSequence()->SelectedClips(); if (!selected_clips.isEmpty()) { SpeedDialog s(olive::MainWindow, selected_clips); @@ -441,7 +799,7 @@ void OliveGlobal::open_speed_dialog() { void OliveGlobal::open_autocut_silence_dialog() { if (CheckForActiveSequence()) { - QVector selected_clips = olive::ActiveSequence->SelectedClipIndexes(); + QVector selected_clips = Timeline::GetTopSequence()->SelectedClips(); if (selected_clips.isEmpty()) { QMessageBox::critical(olive::MainWindow, @@ -457,7 +815,7 @@ void OliveGlobal::open_autocut_silence_dialog() { } void OliveGlobal::clear_undo_stack() { - olive::UndoStack.clear(); + olive::undo_stack.clear(); } void OliveGlobal::open_action_search() { diff --git a/global/global.h b/global/global.h index 95fbe3fc0..458c41c6c 100644 --- a/global/global.h +++ b/global/global.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -34,23 +34,23 @@ * A resource for various global functions used throughout Olive. */ class OliveGlobal : public QObject { - Q_OBJECT + Q_OBJECT public: - /** + /** * @brief OliveGlobal Constructor * * Creates Olive Global object. Also sets some default runtime settings and the application name. */ - OliveGlobal(); + OliveGlobal(); - /** + /** * @brief Returns the file dialog filter used when interfacing with Olive project files. * * @return The file filter string used by QFileDialog to limit the files shown to Olive (*.ove) files. */ - const QString& get_project_file_filter(); + const QString& get_project_file_filter(); - /** + /** * @brief Change the current active project filename * * Triggered to change the current active project filename. Call this before calling any internal project @@ -63,18 +63,28 @@ public: * The URL of the project file to work with. Can be an empty string, in which case Olive will treat the project * as an unsaved project. */ - void update_project_filename(const QString& s); + void update_project_filename(const QString& s); - /** + /** * @brief Check whether an auto-recovery file exists and ask the user if they want to load it. * * Usually called on initialization. Checks if an auto-recovery file exists (meaning the last session of Olive * didn't close correctly). If it finds one, asks the user if they want to load it. If so, loads the auto-recovery * project. */ - void check_for_autorecovery_file(); + void check_for_autorecovery_file(); - /** + /** + * @brief Get whether the project is currently being rendered or not. Useful for determining whether to treat the + * render as online or offline. + * + * @return + * + * TRUE if the project is being exported, FALSE if not. + */ + bool is_exporting(); + + /** * @brief Set the application state depending on if the user is exporting a video * * Some background functions shouldn't run while Olive is exporting a video. This function will disable/enable them @@ -90,9 +100,9 @@ public: * * **TRUE** if Olive is about to export a video. **FALSE** if Olive has finished exporting. */ - void set_rendering_state(bool rendering); + void set_export_state(bool rendering); - /** + /** * @brief Set the application's "modified" state * * Primarily controls whether the application prompts the user to save the project upon closing or not. Also @@ -103,9 +113,9 @@ public: * * TRUE if the project has been modified, FALSE if it has not. */ - void set_modified(bool modified); + void set_modified(bool modified); - /** + /** * @brief Get application's current "modified" state * * Currently just a wrapper around MainWindow::isWindowModified(), but use this instead in case it changes. @@ -115,9 +125,9 @@ public: * * TRUE if the project has been modified since the last save. */ - bool is_modified(); + bool is_modified(); - /** + /** * @brief Set a project to load just after launching * * Called by main() if Olive was called with a project file as a running argument. Sets up Olive to load the @@ -127,49 +137,109 @@ public: * * The URL of the project file to load. */ - void load_project_on_launch(const QString& s); + void load_project_on_launch(const QString& s); - /** + /** * @brief Retrieves the URL of the config file containing the autorecovery projects * @return The URL as a string */ - QString get_recent_project_list_file(); + QString get_recent_project_list_file(); - /** + /** * @brief (Re)load translation file from olive::config */ - void load_translation_from_config(); + void load_translation_from_config(); - /** + /** * @brief Set native UI styling on a given widget * * @param w * * The widget to set styling on. */ - static void SetNativeStyling(QWidget* w); + static void SetNativeStyling(QWidget* w); + + /** + * @brief Adds a project URL to the recent projects list + * + * @param url + * + * The project URL to add + */ + void add_recent_project(const QString& url); + + /** + * @brief Load recent projects from file + * + * Should be called on application startup. + */ + void load_recent_projects(); + + /** + * @brief Total count of recent projects + * + * @return + * + * Number of recent projects in the list + */ + int recent_project_count(); + + /** + * @brief Get the recent project at a given index + * + * @param index + * + * @return + * + * The recent project at index + */ + const QString& recent_project(int index); + + /** + * @brief Retrieves the filename of the autorecovery file to save to during this session + * + * @return + * + * A URL pointing to the autorecovery file + */ + const QString& get_autorecovery_filename(); + + /** + * @brief Returns whether a Sequence is currently active or not, and optionally displays a messagebox if not + * + * Checks whether a Sequence is active and can display a messagebox if not to inform users to make one active in + * order to perform said action. + * + * @return + * + * TRUE if there is an active Sequence, FALSE if not. + */ + bool CheckForActiveSequence(bool show_msg = true); + + + void ShowEffectMenu(EffectType type, olive::TrackType subtype, const QVector selected_clips); public slots: - /** + /** * @brief Undo user's last action */ - void undo(); + void undo(); - /** + /** * @brief Redo user's last action */ - void redo(); + void redo(); - /** + /** * @brief Paste contents of clipboard * * Pastes contents of clipboard. Seeing as several types of data can be copied into the clipboard, this * function will automatically determine what type of data is in the clipboard and paste it in the correct * location (e.g. clip data will go to the Timeline, effect data will go to Effect Controls). */ - void paste(); + void paste(); - /** + /** * @brief Paste contents of clipboard, making space for it when possible * * Pastes contents of clipboard (same as paste()). If the clipboard contains clip data, the clips are cut at the @@ -177,25 +247,25 @@ public slots: * semi-non-destructive as a result (as opposed to paste() overwriting clips). If the clipboard contains effect * data, the functionality is identical to paste(). */ - void paste_insert(); + void paste_insert(); - /** + /** * @brief Create new project. * * Confirms whether the current project can be closed, and if so, clears all current project data and resets * program state. Standard `File > New` behavior. */ - void new_project(); + void new_project(); - /** + /** * @brief Open a project from file. * * 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 OpenProject(); + void OpenProject(); - /** + /** * @brief Import project from file * * Imports an Olive project into the current project, effectively merging them. @@ -204,9 +274,9 @@ public slots: * * The filename of the project to import. */ - void ImportProject(const QString& fn); + void ImportProject(const QString& fn); - /** + /** * @brief Open recent project from list * * Triggers a project load from the internal recent projects list. @@ -215,9 +285,9 @@ public slots: * * Index in the list of the project file to load */ - void open_recent(int index); + void open_recent(int index); - /** + /** * @brief Shows a save file dialog and saves the project as the resulting filename * * Shows a save file dialog for the user to save their current project as a different filename from the current @@ -227,9 +297,9 @@ public slots: * if a user is closing an unsaved project, clicks "Yes" to save, we know if they actually saved or not and won't * continue closing the project if they didn't. */ - bool save_project_as(); + bool save_project_as(); - /** + /** * @brief Saves the current project to file * * If the project has been saved already, this function will overwrite the project file with the current project @@ -239,9 +309,9 @@ public slots: * value of save_project_as(). Useful if the user closing an unsaved project, clicks "Yes" to save, we know if they * actually saved or not and won't continue closing the project if they didn't. */ - bool save_project(); + bool save_project(); - /** + /** * @brief Determine whether the current project can be closed. * * Queried any time the current project is going to be closed (e.g. starting a new project, loading a project, @@ -253,81 +323,93 @@ public slots: * returns **TRUE**. If it does and the user clicks YES, this returns the result of save_project(). If the user * clicks NO, this returns **TRUE**. If the user clicks CANCEL, this returns **FALSE**. */ - bool can_close_project(); + bool can_close_project(); - /** + /** + * @brief Opens the NewSequenceDialog to create a new Sequence + */ + void open_new_sequence_dialog(); + + /** + * @brief Open a file dialog for importing files into the project + */ + void open_import_dialog(); + + /** * @brief Open the Export dialog to trigger an export of the current sequence. */ - void open_export_dialog(); + void open_export_dialog(); - /** + /** * @brief Open the About Olive dialog. */ - void open_about_dialog(); + void open_about_dialog(); - /** + /** * @brief Open the Debug Log window. */ - void open_debug_log(); + void open_debug_log(); - /** + /** * @brief Open the Speed/Duration dialog. */ - void open_speed_dialog(); + void open_speed_dialog(); - /** + /** * @brief Open the auto-cut silence dialog. */ - void open_autocut_silence_dialog(); + void open_autocut_silence_dialog(); - /** + /** * @brief Open the Action Search overlay. */ - void open_action_search(); + void open_action_search(); - /** + /** * @brief Clears the current undo stack. * * Clears all current commands in the undo stack. Mostly used for debugging. */ - void clear_undo_stack(); + void clear_undo_stack(); - /** + /** * @brief Function called when Olive has finished starting up * * Sets up some last things for Olive that must be run after Olive has completed initialization. If a project was * loaded as a command line argument, it's loaded here. */ - void finished_initialize(); + void finished_initialize(); - /** + /** * @brief Save an auto-recovery file of the current project. * * Call this function to save the current state of the project as an auto-recovery project. Called regularly by * `autorecovery_timer`. */ - void save_autorecovery_file(); + void save_autorecovery_file(); - /** + /** * @brief Opens the Preferences dialog */ - void open_preferences(); + void open_preferences(); - /** - * @brief Set the current active Sequence + /** + * @brief Clear the recent projects list * - * Call this to change the active Sequence (e.g. when the user double clicks a Sequence in the Project panel). - * This will affect panel_timeline, panel_sequence_viewer, and panel_effect_controls and can then be retrieved - * using olive::ActiveSequence. - * - * @param s - * - * The Sequence to set as the active Sequence. + * Also saves the cleared recent projects to the config file making it permanent. */ - void set_sequence(SequencePtr s); + void clear_recent_projects(); + + /** + * @brief Slot for when the primary sequence has changed. + * + * Usually by opening a sequence or bringing a corresponding + * Timeline widget on top. + */ + void PrimarySequenceChanged(); private: - /** + /** * @brief Internal function to handle loading a project from file * * Start loading a project. Doesn't check if the current project can be closed, doesn't check if the project exists. @@ -343,21 +425,9 @@ 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 OpenProjectWorker(QString fn, bool autorecovery); + void OpenProjectWorker(QString fn, bool autorecovery); - /** - * @brief Returns whether a Sequence is currently active or not, and optionally displays a messagebox if not - * - * Checks whether a Sequence is active and can display a messagebox if not to inform users to make one active in - * order to perform said action. - * - * @return - * - * TRUE if there is an active Sequence, FALSE if not. - */ - bool CheckForActiveSequence(bool show_msg = true); - - /** + /** * @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 @@ -383,37 +453,49 @@ private: * 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); + void LoadProject(const QString& fn, bool autorecovery); - /** + /** * @brief Indiscriminately clear the project without prompting the user * * Will clear the entire project without prompting to save. This is dangerous, use new_project() instead for * anything initiated by the user. */ - void ClearProject(); + void ClearProject(); - /** + /** + * @brief Saves current recent project list to the configuration file + * + * This should be called whenever the recent projects change so the changes can be persistent. + */ + void save_recent_projects(); + + /** + * @brief Internal pasting function + */ + void PasteInternal(Sequence* s, bool insert); + + /** * @brief File filter used for any file dialogs relating to Olive project files. */ - QString project_file_filter; + QString project_file_filter; - /** + /** * @brief Regular interval to save an auto-recovery project. */ - QTimer autorecovery_timer; + QTimer autorecovery_timer; - /** + /** * @brief Internal variable set to **TRUE** by main() if a project file was set as an argument */ - bool enable_load_project_on_init; + bool enable_load_project_on_init; - /** + /** * @brief Internal translator object that interfaces with the currently loaded language file */ - std::unique_ptr translator; + std::unique_ptr translator; - /** + /** * @brief Internal variable for whether the project has changed since the last autorecovery * * Set by set_modified(), which should be called alongside any change made to the project file and is "unset" when @@ -421,30 +503,57 @@ private: * prevents an autorecovery file saving multiple times if the project hasn't actually changed since the last * autorecovery, but still hasn't been saved into the original file yet. */ - bool changed_since_last_autorecovery; + bool changed_since_last_autorecovery; + + /** + * @brief Internal variable for rendering state (set by set_rendering_state() and accessed by is_rendering() ). + */ + bool rendering_; + + /** + * @brief Internal variable for the filename to the autorecovery project file + */ + QString autorecovery_filename; + + /** + * @brief Internal list of recent projects + */ + QStringList recent_projects; + + /** + * @brief Internal array of selected clips that a menu created by ShowEffectMenu will act on + */ + QVector effect_menu_selected_clips; private slots: - + /** + * @brief Receiver for a menu initiated by ShowEffectMenu + * + * Adds the selected effect/node to the clips in + * + * @param q + */ + void EffectMenuAction(QAction* q); }; namespace olive { - /** +/** * @brief Object resource for various global functions used throughout Olive */ - extern std::unique_ptr Global; +extern std::unique_ptr Global; - /** +/** * @brief Currently active project filename * * Filename for the currently active project. Empty means the file has not * been saved yet. */ - extern QString ActiveProjectFilename; +extern QString ActiveProjectFilename; - /** +/** * @brief Current application name */ - extern QString AppName; +extern QString AppName; } #endif // OLIVEGLOBAL_H diff --git a/global/math.cpp b/global/math.cpp index fe19b229b..c11a79d4b 100644 --- a/global/math.cpp +++ b/global/math.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -99,3 +99,9 @@ QRect fit_size_into_rect(const QRect &r, int width, int height) return QRect(r.x(), r.y() + (r.height() / 2 - new_height / 2), r.width(), new_height); } } + +template +const T &clamp(const T &val, T &min, T &max) +{ + return qMax(qMin(max, val), min); +} diff --git a/global/math.h b/global/math.h index ed71f3981..721e4453e 100644 --- a/global/math.h +++ b/global/math.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -32,10 +32,17 @@ double cubic_from_t(double a, double b, double c, double d, double t); double cubic_t_from_x(double x_target, double a, double b, double c, double d); double solveCubicBezier(double p0, double p1, double p2, double p3, double x); +template +const T& clamp(const T& val, T& min, T& max); + QRect fit_size_into_rect(const QRect& r, int width, int height); // decibel conversion functions double amplitude_to_db(double amplitude); double db_to_amplitude(double db); +// frame <-> pixel conversion functions +int getScreenPointFromFrame(double zoom, long frame); +long getFrameFromScreenPoint(double zoom, int x); + #endif // MATH_H diff --git a/global/path.cpp b/global/path.cpp index 95403bb6a..3c0eebb93 100644 --- a/global/path.cpp +++ b/global/path.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/global/path.h b/global/path.h index 09313ec11..f3ea5a659 100644 --- a/global/path.h +++ b/global/path.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/global/timing.cpp b/global/timing.cpp new file mode 100644 index 000000000..f87cacb70 --- /dev/null +++ b/global/timing.cpp @@ -0,0 +1,169 @@ +#include "timing.h" + +#include "timeline/sequence.h" +#include "timeline/clip.h" +#include "global/config.h" + +double get_timecode(Clip* c, long playhead) { + return double(playhead_to_clip_frame(c, playhead))/c->track()->sequence()->frame_rate; +} + +long rescale_frame_number(long framenumber, double source_frame_rate, double target_frame_rate) { + return qRound((double(framenumber)/source_frame_rate)*target_frame_rate); +} + +long playhead_to_clip_frame(Clip* c, long playhead) { + return (qMax(0L, playhead - c->timeline_in(true)) + c->clip_in(true)); +} + +double playhead_to_clip_seconds(Clip* c, long playhead) { + // returns time in seconds + long clip_frame = playhead_to_clip_frame(c, playhead); + + if (c->reversed()) { + clip_frame = c->media_length() - clip_frame - 1; + } + + double secs = (double(clip_frame)/c->track()->sequence()->frame_rate)*c->speed().value; + if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { + secs *= c->media()->to_footage()->speed; + } + + return secs; +} + +int64_t seconds_to_timestamp(Clip *c, double seconds) { + return qRound64(seconds * av_q2d(av_inv_q(c->time_base()))); +} + +int64_t playhead_to_timestamp(Clip* c, long playhead) { + return seconds_to_timestamp(c, playhead_to_clip_seconds(c, playhead)); +} + +bool frame_rate_is_droppable(double rate) { + return (qFuzzyCompare(rate, 23.976) + || qFuzzyCompare(rate, 29.97) + || qFuzzyCompare(rate, 59.94)); +} + +long timecode_to_frame(const QString& s, int view, double frame_rate) { + QList list = s.split(QRegExp("[:;]")); + + if (view == olive::kTimecodeFrames || (list.size() == 1 && view != olive::kTimecodeMilliseconds)) { + return s.toLong(); + } + + int frRound = qRound(frame_rate); + int hours, minutes, seconds, frames; + + if (view == olive::kTimecodeMilliseconds) { + long milliseconds = s.toLong(); + + hours = milliseconds/3600000; + milliseconds -= (hours*3600000); + minutes = milliseconds/60000; + milliseconds -= (minutes*60000); + seconds = milliseconds/1000; + milliseconds -= (seconds*1000); + frames = qRound64((milliseconds*0.001)*frame_rate); + + seconds = qRound64(seconds * frame_rate); + minutes = qRound64(minutes * frame_rate * 60); + hours = qRound64(hours * frame_rate * 3600); + } else { + hours = ((list.size() > 0) ? list.at(0).toInt() : 0) * frRound * 3600; + minutes = ((list.size() > 1) ? list.at(1).toInt() : 0) * frRound * 60; + seconds = ((list.size() > 2) ? list.at(2).toInt() : 0) * frRound; + frames = (list.size() > 3) ? list.at(3).toInt() : 0; + } + + int f = (frames + seconds + minutes + hours); + + if ((view == olive::kTimecodeDrop || view == olive::kTimecodeMilliseconds) && frame_rate_is_droppable(frame_rate)) { + // return drop + int d; + int m; + + int dropFrames = qRound(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate + int framesPer10Minutes = qRound(frame_rate * 60 * 10); //Number of frames per ten minutes + int framesPerMinute = (qRound(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames + + d = f / framesPer10Minutes; + f -= dropFrames*9*d; + + m = f % framesPer10Minutes; + + if (m > dropFrames) { + f -= (dropFrames * ((m - dropFrames) / framesPerMinute)); + } + } + + // return non-drop + return f; +} + +QString frame_to_timecode(long f, int view, double frame_rate) { + if (view == olive::kTimecodeFrames) { + return QString::number(f); + } + + // return timecode + int hours = 0; + int mins = 0; + int secs = 0; + int frames = 0; + QString token = ":"; + + if ((view == olive::kTimecodeDrop || view == olive::kTimecodeMilliseconds) && frame_rate_is_droppable(frame_rate)) { + //CONVERT A FRAME NUMBER TO DROP FRAME TIMECODE + //Code by David Heidelberger, adapted from Andrew Duncan, further adapted for Olive by Olive Team + //Given an int called framenumber and a double called framerate + //Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off. + + int d; + int m; + + int dropFrames = qRound(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate + int framesPerHour = qRound(frame_rate*60*60); //Number of frqRound64ames in an hour + int framesPer24Hours = framesPerHour*24; //Number of frames in a day - timecode rolls over after 24 hours + int framesPer10Minutes = qRound(frame_rate * 60 * 10); //Number of frames per ten minutes + int framesPerMinute = (qRound(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames + + //If framenumber is greater than 24 hrs, next operation will rollover clock + f = f % framesPer24Hours; // % is the modulus operator, which returns a remainder. a % b = the remainder of a/b + + d = f / framesPer10Minutes; // \ means integer division, which is a/b without a remainder. Some languages you could use floor(a/b) + m = f % framesPer10Minutes; + + //In the original post, the next line read m>1, which only worked for 29.97. Jean-Baptiste Mardelle correctly pointed out that m should be compared to dropFrames. + if (m > dropFrames) { + f = f + (dropFrames*9*d) + dropFrames * ((m - dropFrames) / framesPerMinute); + } else { + f = f + dropFrames*9*d; + } + + int frRound = qRound(frame_rate); + frames = f % frRound; + secs = (f / frRound) % 60; + mins = ((f / frRound) / 60) % 60; + hours = (((f / frRound) / 60) / 60); + + token = ";"; + } else { + // non-drop timecode + + int int_fps = qRound(frame_rate); + hours = f / (3600 * int_fps); + mins = f / (60*int_fps) % 60; + secs = f/int_fps % 60; + frames = f%int_fps; + } + if (view == olive::kTimecodeMilliseconds) { + return QString::number((hours*3600000)+(mins*60000)+(secs*1000)+qCeil(frames*1000/frame_rate)); + } + return QString(QString::number(hours).rightJustified(2, '0') + + ":" + QString::number(mins).rightJustified(2, '0') + + ":" + QString::number(secs).rightJustified(2, '0') + + token + QString::number(frames).rightJustified(2, '0') + ); +} diff --git a/global/timing.h b/global/timing.h new file mode 100644 index 000000000..1744b8c92 --- /dev/null +++ b/global/timing.h @@ -0,0 +1,137 @@ +#ifndef TIMING_H +#define TIMING_H + +#include +#include + +class Clip; + +/** + * @brief Get timecode + * + * Get the current clip/media time from the Timeline playhead in seconds. For instance if the playhead was at the start + * of a clip (whose in point wasn't trimmed), this would be 0.0 as it's the start of the clip/media; + * + * @param c + * + * Clip to get the timecode of + * + * @param playhead + * + * Sequence playhead to convert to a clip/media timecode + * + * @return + * + * Timecode in seconds + */ +double get_timecode(Clip *c, long playhead); + +/** + * @brief Rescale a frame number between two frame rates + * + * Converts a frame number from one frame rate to its equivalent in another frame rate + * + * @param framenumber + * + * The frame number to convert + * + * @param source_frame_rate + * + * Frame rate that the frame number is currently in + * + * @param target_frame_rate + * + * Frame rate to convert to + * + * @return + * + * Rescaled frame number + */ +long rescale_frame_number(long framenumber, double source_frame_rate, double target_frame_rate); + +/** + * @brief Convert playhead frame number to a clip frame number + * + * Converts a Timeline playhead to a the current clip's frame. Equivalent to + * `PLAYHEAD - CLIP_TIMELINE_IN + CLIP_MEDIA_IN`. All keyframes are in clip frames. + * + * @param c + * + * The clip to get the current frame number of + * + * @param playhead + * + * The current Timeline frame number + * + * @return + * + * The curren frame number of the clip at `playhead` + */ +long playhead_to_clip_frame(Clip* c, long playhead); + +/** + * @brief Converts the playhead to clip seconds + * + * Get the current timecode at the playhead in terms of clip seconds. + * + * FIXME: Possible duplicate of get_timecode()? Will need to research this more. + * + * @param c + * + * Clip to return clip seconds of. + * + * @param playhead + * + * Current Timeline playhead to convert to clip seconds + * + * @return + * + * Clip time in seconds + */ +double playhead_to_clip_seconds(Clip *c, long playhead); + +/** + * @brief Convert seconds to FFmpeg timestamp + * + * Used for interaction with FFmpeg, converts seconds in a floating-point value to a timestamp in AVStream->time_base + * units. + * + * @param c + * + * Clip to get timestamp of + * + * @param seconds + * + * Clip time in seconds + * + * @return + * + * An FFmpeg-compatible timestamp in AVStream->time_base units. + */ +int64_t seconds_to_timestamp(Clip* c, double seconds); + +/** + * @brief Convert Timeline playhead to FFmpeg timestamp + * + * Used for interaction with FFmpeg, converts the Timeline playhead to a timestamp in AVStream->time_base + * units. + * + * @param c + * + * Clip to get timestamp of + * + * @param playhead + * + * Timeline playhead to convert to a timestamp + * + * @return + * + * An FFmpeg-compatible timestamp in AVStream->time_base units. + */ +int64_t playhead_to_timestamp(Clip *c, long playhead); + +bool frame_rate_is_droppable(double rate); +long timecode_to_frame(const QString& s, int view, double frame_rate); +QString frame_to_timecode(long f, int view, double frame_rate); + +#endif // TIMING_H diff --git a/main.cpp b/main.cpp index 18b1adb67..294c34b2c 100644 --- a/main.cpp +++ b/main.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -24,6 +24,7 @@ #include "global/config.h" #include "global/global.h" #include "panels/timeline.h" +#include "rendering/pixelformats.h" #include "ui/mediaiconservice.h" #include "ui/mainwindow.h" @@ -58,7 +59,6 @@ int main(int argc, char *argv[]) { "\t-f, --fullscreen\tStart in full screen mode\n" "\t--disable-shaders\tDisable OpenGL shaders (for debugging)\n" "\t--no-debug\t\tDisable internal debug log and output directly to console\n" - "\t--disable-blend-modes\tDisable shader-based blending for older GPUs\n" "\t--translation \tSet an external language file to use\n" "\n" "Environment Variables:\n" @@ -70,15 +70,13 @@ int main(int argc, char *argv[]) { } else if (!strcmp(argv[i], "--fullscreen") || !strcmp(argv[i], "-f")) { launch_fullscreen = true; } else if (!strcmp(argv[i], "--disable-shaders")) { - olive::CurrentRuntimeConfig.shaders_are_enabled = false; + olive::runtime_config.shaders_are_enabled = false; } else if (!strcmp(argv[i], "--no-debug")) { use_internal_logger = false; - } else if (!strcmp(argv[i], "--disable-blend-modes")) { - olive::CurrentRuntimeConfig.disable_blending = true; } else if (!strcmp(argv[i], "--translation")) { if (i + 1 < argc && argv[i + 1][0] != '-') { // load translation file - olive::CurrentRuntimeConfig.external_translation_file = argv[i + 1]; + olive::runtime_config.external_translation_file = argv[i + 1]; i++; } else { @@ -112,7 +110,9 @@ int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); QSurfaceFormat format; + format.setVersion(3, 2); format.setDepthBufferSize(24); + format.setProfile(QSurfaceFormat::CoreProfile); QSurfaceFormat::setDefaultFormat(format); QApplication a(argc, argv); @@ -135,6 +135,9 @@ int main(int argc, char *argv[]) { // multiply track height constants by the current DPI scale olive::timeline::MultiplyTrackSizesByDPI(); + // set up rendering bit depths + olive::InitializePixelFormats(); + // connect main window's first paint to global's init finished function QObject::connect(&w, SIGNAL(finished_first_paint()), olive::Global.get(), SLOT(finished_initialize()), Qt::QueuedConnection); @@ -147,5 +150,5 @@ int main(int argc, char *argv[]) { w.showMaximized(); } - return a.exec(); + return a.exec(); } diff --git a/nodes/inputs.h b/nodes/inputs.h new file mode 100644 index 000000000..796cbaffb --- /dev/null +++ b/nodes/inputs.h @@ -0,0 +1,15 @@ +#ifndef INPUTS_H +#define INPUTS_H + +#include "inputs/vecinput.h" +#include "inputs/boolinput.h" +#include "inputs/comboinput.h" +#include "inputs/colorinput.h" +#include "inputs/stringinput.h" +#include "inputs/fileinput.h" +#include "inputs/fontinput.h" + +#include "widgets/labelwidget.h" +#include "widgets/buttonwidget.h" + +#endif // INPUTS_H diff --git a/nodes/inputs/boolinput.cpp b/nodes/inputs/boolinput.cpp new file mode 100644 index 000000000..aa98181d6 --- /dev/null +++ b/nodes/inputs/boolinput.cpp @@ -0,0 +1,16 @@ +#include "boolinput.h" + +BoolInput::BoolInput(Node* parent, const QString& id, const QString& name, bool savable, bool keyframable) : + EffectRow(parent, id, name, savable, keyframable) +{ + BoolField* bool_field = new BoolField(this); + connect(bool_field, SIGNAL(Toggled(bool)), this, SIGNAL(Toggled(bool))); + AddField(bool_field); + + AddAcceptedNodeInput(olive::nodes::kBoolean); +} + +bool BoolInput::GetBoolAt(double timecode) +{ + return static_cast(Field(0))->GetBoolAt(timecode); +} diff --git a/nodes/inputs/boolinput.h b/nodes/inputs/boolinput.h new file mode 100644 index 000000000..e10660fb3 --- /dev/null +++ b/nodes/inputs/boolinput.h @@ -0,0 +1,36 @@ +#ifndef BOOLINPUT_H +#define BOOLINPUT_H + +#include "effects/effectrow.h" + +class BoolInput : public EffectRow +{ + Q_OBJECT +public: + BoolInput(Node* parent, const QString& id, const QString& name, bool savable = true, bool keyframable = true); + + /** + * @brief Get the boolean value at a given timecode + * + * A wrapper for BoolField::GetBoolAt(). + * + * @param timecode + * + * The timecode to retrieve the value at + * + * @return + * + * The boolean value at this timecode + */ + bool GetBoolAt(double timecode); + +signals: + /** + * @brief Emitted whenever the UI widget's boolean value has changed + * + * Wrapper for BoolField::Toggled(). + */ + void Toggled(bool); +}; + +#endif // BOOLINPUT_H diff --git a/nodes/inputs/colorinput.cpp b/nodes/inputs/colorinput.cpp new file mode 100644 index 000000000..ade47dbd2 --- /dev/null +++ b/nodes/inputs/colorinput.cpp @@ -0,0 +1,14 @@ +#include "colorinput.h" + +ColorInput::ColorInput(Node* parent, const QString& id, const QString& name, bool savable, bool keyframable) : + EffectRow(parent, id, name, savable, keyframable) +{ + AddField(new ColorField(this)); + + AddAcceptedNodeInput(olive::nodes::kColor); +} + +QColor ColorInput::GetColorAt(double timecode) +{ + return static_cast(Field(0))->GetColorAt(timecode); +} diff --git a/nodes/inputs/colorinput.h b/nodes/inputs/colorinput.h new file mode 100644 index 000000000..486f18e2e --- /dev/null +++ b/nodes/inputs/colorinput.h @@ -0,0 +1,28 @@ +#ifndef COLORINPUT_H +#define COLORINPUT_H + +#include "effects/effectrow.h" + +class ColorInput : public EffectRow +{ + Q_OBJECT +public: + ColorInput(Node* parent, const QString& id, const QString& name, bool savable = true, bool keyframable = true); + + /** + * @brief Get the color value at a given timecode + * + * Wrapper for ColorField::GetColorAt(). + * + * @param timecode + * + * The timecode to retrieve the color at + * + * @return + * + * The color value at this timecode + */ + QColor GetColorAt(double timecode); +}; + +#endif // COLORINPUT_H diff --git a/nodes/inputs/comboinput.cpp b/nodes/inputs/comboinput.cpp new file mode 100644 index 000000000..395ff6ebf --- /dev/null +++ b/nodes/inputs/comboinput.cpp @@ -0,0 +1,16 @@ +#include "comboinput.h" + +ComboInput::ComboInput(Node* parent, const QString& id, const QString& name, bool savable, bool keyframable) : + EffectRow(parent, id, name, savable, keyframable) +{ + ComboField* combo_field = new ComboField(this); + connect(combo_field, SIGNAL(DataChanged(const QVariant&)), this, SIGNAL(DataChanged(const QVariant&))); + AddField(combo_field); + + AddAcceptedNodeInput(olive::nodes::kCombo); +} + +void ComboInput::AddItem(const QString &text, const QVariant &data) +{ + static_cast(Field(0))->AddItem(text, data); +} diff --git a/nodes/inputs/comboinput.h b/nodes/inputs/comboinput.h new file mode 100644 index 000000000..25dbc4e91 --- /dev/null +++ b/nodes/inputs/comboinput.h @@ -0,0 +1,36 @@ +#ifndef COMBOINPUT_H +#define COMBOINPUT_H + +#include "effects/effectrow.h" + +class ComboInput : public EffectRow +{ + Q_OBJECT +public: + ComboInput(Node* parent, const QString& id, const QString& name, bool savable = true, bool keyframable = true); + + /** + * @brief Add an item to this ComboInput + * + * Wrapper for ComboField::AddItem. + * + * @param text + * + * The text to show at this index. + * + * @param data + * + * The data to be retrieved at this index. + */ + void AddItem(const QString& text, const QVariant& data); + +signals: + /** + * @brief Signal emitted whenever a connected widget's data gets changed + * + * Wrapper for ComboField::DataChanged. + */ + void DataChanged(const QVariant&); +}; + +#endif // COMBOINPUT_H diff --git a/nodes/inputs/fileinput.cpp b/nodes/inputs/fileinput.cpp new file mode 100644 index 000000000..4f8d5c808 --- /dev/null +++ b/nodes/inputs/fileinput.cpp @@ -0,0 +1,14 @@ +#include "fileinput.h" + +FileInput::FileInput(Node* parent, const QString& id, const QString& name, bool savable, bool keyframable) : + EffectRow(parent, id, name, savable, keyframable) +{ + AddField(new FileField(this)); + + AddAcceptedNodeInput(olive::nodes::kFile); +} + +QString FileInput::GetFileAt(double timecode) +{ + return static_cast(Field(0))->GetFileAt(timecode); +} diff --git a/nodes/inputs/fileinput.h b/nodes/inputs/fileinput.h new file mode 100644 index 000000000..1793e5b97 --- /dev/null +++ b/nodes/inputs/fileinput.h @@ -0,0 +1,28 @@ +#ifndef FILEINPUT_H +#define FILEINPUT_H + +#include "effects/effectrow.h" + +class FileInput : public EffectRow +{ + Q_OBJECT +public: + FileInput(Node* parent, const QString& id, const QString& name, bool savable = true, bool keyframable = true); + + /** + * @brief Get the filename at the given timecode + * + * A convenience function, equivalent to GetValueAt(timecode).toString() + * + * @param timecode + * + * The timecode to retrieve the filename at + * + * @return + * + * The filename at this timecode + */ + QString GetFileAt(double timecode); +}; + +#endif // FILEINPUT_H diff --git a/nodes/inputs/fontinput.cpp b/nodes/inputs/fontinput.cpp new file mode 100644 index 000000000..ec9ba7077 --- /dev/null +++ b/nodes/inputs/fontinput.cpp @@ -0,0 +1,14 @@ +#include "fontinput.h" + +FontInput::FontInput(Node* parent, const QString& id, const QString& name, bool savable, bool keyframable) : + EffectRow(parent, id, name, savable, keyframable) +{ + AddField(new FontField(this)); + + AddAcceptedNodeInput(olive::nodes::kFont); +} + +QString FontInput::GetFontAt(double timecode) +{ + return static_cast(Field(0))->GetFontAt(timecode); +} diff --git a/nodes/inputs/fontinput.h b/nodes/inputs/fontinput.h new file mode 100644 index 000000000..cfcb34c55 --- /dev/null +++ b/nodes/inputs/fontinput.h @@ -0,0 +1,28 @@ +#ifndef FONTINPUT_H +#define FONTINPUT_H + +#include "effects/effectrow.h" + +class FontInput : public EffectRow +{ + Q_OBJECT +public: + FontInput(Node* parent, const QString& id, const QString& name, bool savable = true, bool keyframable = true); + + /** + * @brief Get the font family name at the given timecode + * + * Wrapper for FontField::GetFontAt(). + * + * @param timecode + * + * The timecode to retrieve the font family name at + * + * @return + * + * The font family name at this timecode + */ + QString GetFontAt(double timecode); +}; + +#endif // FONTINPUT_H diff --git a/nodes/inputs/stringinput.cpp b/nodes/inputs/stringinput.cpp new file mode 100644 index 000000000..121051ea8 --- /dev/null +++ b/nodes/inputs/stringinput.cpp @@ -0,0 +1,14 @@ +#include "stringinput.h" + +StringInput::StringInput(Node* parent, const QString& id, const QString& name, bool rich_text, bool savable, bool keyframable) : + EffectRow(parent, id, name, savable, keyframable) +{ + AddField(new StringField(this, rich_text)); + + AddAcceptedNodeInput(olive::nodes::kString); +} + +QString StringInput::GetStringAt(double timecode) +{ + return static_cast(Field(0))->GetStringAt(timecode); +} diff --git a/nodes/inputs/stringinput.h b/nodes/inputs/stringinput.h new file mode 100644 index 000000000..d738d2fe3 --- /dev/null +++ b/nodes/inputs/stringinput.h @@ -0,0 +1,33 @@ +#ifndef STRINGINPUT_H +#define STRINGINPUT_H + +#include "effects/effectrow.h" + +class StringInput : public EffectRow +{ + Q_OBJECT +public: + StringInput(Node* parent, + const QString& id, + const QString& name, + bool rich_text = true, + bool savable = true, + bool keyframable = true); + + /** + * @brief Get the string at the given timecode + * + * Wrappre for StringField::GetStringAt(). + * + * @param timecode + * + * The timecode to retrieve the string at + * + * @return + * + * The string at this timecode + */ + QString GetStringAt(double timecode); +}; + +#endif // STRINGINPUT_H diff --git a/nodes/inputs/vecinput.cpp b/nodes/inputs/vecinput.cpp new file mode 100644 index 000000000..f99666c6b --- /dev/null +++ b/nodes/inputs/vecinput.cpp @@ -0,0 +1,195 @@ +#include "vecinput.h" + +#include +#include +#include + +VecInput::VecInput(Node* parent, const QString& id, const QString& name, int values, bool savable, bool keyframable) : + EffectRow(parent, id, name, savable, keyframable), + single_value_mode_(false), + values_(values) +{ + Q_ASSERT(values_ >= 1 && values_ <= 4); + + for (int i=0;i 1) { + AddAcceptedNodeInput(olive::nodes::kVec2); + } + if (values > 2) { + AddAcceptedNodeInput(olive::nodes::kVec3); + } + if (values > 3) { + AddAcceptedNodeInput(olive::nodes::kVec4); + } +} + +void VecInput::SetMinimum(double minimum) +{ + for (int i=0;i(Field(i))->SetMinimum(minimum); + } +} + +void VecInput::SetMaximum(double maximum) +{ + for (int i=0;i(Field(i))->SetMaximum(maximum); + } +} + +void VecInput::SetDefault(double def) +{ + for (int i=0;i(Field(i))->SetDefault(def); + } +} + +void VecInput::SetDefault(const QVector &def) +{ + for (int i=0;i(Field(i))->SetDefault(def.at(i)); + } +} + +void VecInput::SetDisplayType(LabelSlider::DisplayType type) +{ + for (int i=0;i(Field(i))->SetDisplayType(type); + } +} + +void VecInput::SetFrameRate(const double &rate) +{ + for (int i=0;i(Field(i))->SetFrameRate(rate); + } +} + +void VecInput::SetSingleValueMode(bool on) +{ + single_value_mode_ = on; + + // Disable all fields except the first + for (int i=1;iSetEnabled(!single_value_mode_); + } +} + +DoubleInput::DoubleInput(Node *parent, const QString &id, const QString &name, bool savable, bool keyframable) : + VecInput(parent, id, name, 1, savable, keyframable) +{ +} + +double DoubleInput::GetDoubleAt(double timecode) +{ + return static_cast(Field(0))->GetDoubleAt(timecode); +} + +Vec2Input::Vec2Input(Node *parent, const QString &id, const QString &name, bool savable, bool keyframable) : + VecInput(parent, id, name, 2, savable, keyframable) +{ +} + +QVector2D Vec2Input::GetVector2DAt(double timecode) +{ + return GetValueAt(timecode).value(); +} + +QVariant Vec2Input::GetValueAt(double timecode) +{ + QVector2D vec2; + vec2.setX(static_cast(Field(0))->GetDoubleAt(timecode)); + + if (single_value_mode_) { + vec2.setY(static_cast(Field(0))->GetDoubleAt(timecode)); + } else { + vec2.setY(static_cast(Field(1))->GetDoubleAt(timecode)); + } + + return vec2; +} + +void Vec2Input::SetValueAt(double timecode, const QVariant &value) +{ + QVector2D vec2 = value.value(); + + static_cast(Field(0))->SetValueAt(timecode, vec2.x()); + static_cast(Field(1))->SetValueAt(timecode, vec2.y()); +} + +Vec3Input::Vec3Input(Node *parent, const QString &id, const QString &name, bool savable, bool keyframable) : + VecInput(parent, id, name, 3, savable, keyframable) +{ +} + +QVector3D Vec3Input::GetVector3DAt(double timecode) +{ + return GetValueAt(timecode).value(); +} + +QVariant Vec3Input::GetValueAt(double timecode) +{ + QVector3D vec3; + vec3.setX(static_cast(Field(0))->GetDoubleAt(timecode)); + + if (single_value_mode_) { + vec3.setY(static_cast(Field(0))->GetDoubleAt(timecode)); + vec3.setZ(static_cast(Field(0))->GetDoubleAt(timecode)); + } else { + vec3.setY(static_cast(Field(1))->GetDoubleAt(timecode)); + vec3.setZ(static_cast(Field(2))->GetDoubleAt(timecode)); + } + + return vec3; +} + +void Vec3Input::SetValueAt(double timecode, const QVariant &value) +{ + QVector3D vec3 = value.value(); + + static_cast(Field(0))->SetValueAt(timecode, vec3.x()); + static_cast(Field(1))->SetValueAt(timecode, vec3.y()); + static_cast(Field(2))->SetValueAt(timecode, vec3.z()); +} + +Vec4Input::Vec4Input(Node *parent, const QString &id, const QString &name, bool savable, bool keyframable) : + VecInput(parent, id, name, 4, savable, keyframable) +{ +} + +QVector4D Vec4Input::GetVector4DAt(double timecode) +{ + return GetValueAt(timecode).value(); +} + +QVariant Vec4Input::GetValueAt(double timecode) +{ + QVector4D vec4; + vec4.setX(static_cast(Field(0))->GetDoubleAt(timecode)); + + if (single_value_mode_) { + vec4.setY(static_cast(Field(0))->GetDoubleAt(timecode)); + vec4.setZ(static_cast(Field(0))->GetDoubleAt(timecode)); + vec4.setZ(static_cast(Field(0))->GetDoubleAt(timecode)); + } else { + vec4.setY(static_cast(Field(1))->GetDoubleAt(timecode)); + vec4.setZ(static_cast(Field(2))->GetDoubleAt(timecode)); + vec4.setW(static_cast(Field(3))->GetDoubleAt(timecode)); + } + + return vec4; +} + +void Vec4Input::SetValueAt(double timecode, const QVariant &value) +{ + QVector4D vec4 = value.value(); + + static_cast(Field(0))->SetValueAt(timecode, vec4.x()); + static_cast(Field(1))->SetValueAt(timecode, vec4.y()); + static_cast(Field(2))->SetValueAt(timecode, vec4.z()); + static_cast(Field(3))->SetValueAt(timecode, vec4.w()); +} diff --git a/nodes/inputs/vecinput.h b/nodes/inputs/vecinput.h new file mode 100644 index 000000000..7fda0aaca --- /dev/null +++ b/nodes/inputs/vecinput.h @@ -0,0 +1,78 @@ +#ifndef VEC2INPUT_H +#define VEC2INPUT_H + +#include + +#include "effects/effectrow.h" + +class VecInput : public EffectRow +{ + Q_OBJECT +public: + VecInput(Node* parent, const QString& id, const QString& name, int values, bool savable = true, bool keyframable = true); + + void SetMinimum(double minimum); + void SetMaximum(double maximum); + void SetDefault(double def); + + void SetDefault(const QVector &def); + + /** + * @brief Sets the UI display type to a member of LabelSlider::DisplayType. + */ + void SetDisplayType(LabelSlider::DisplayType type); + + /** + * @brief For a timecode-based display type, sets the frame rate to be used for the displayed timecode + * + * \see SetDisplayType() and LabelSlider::SetFrameRate(). + */ + void SetFrameRate(const double& rate); + +protected: + bool single_value_mode_; + +public slots: + void SetSingleValueMode(bool on); + +private: + + int values_; +}; + +class DoubleInput : public VecInput +{ +public: + DoubleInput(Node* parent, const QString& id, const QString& name, bool savable = true, bool keyframable = true); + + double GetDoubleAt(double timecode); +}; + +class Vec2Input : public VecInput { +public: + Vec2Input(Node* parent, const QString& id, const QString& name, bool savable = true, bool keyframable = true); + + QVector2D GetVector2DAt(double timecode); + virtual QVariant GetValueAt(double timecode) override; + virtual void SetValueAt(double timecode, const QVariant &value) override; +}; + +class Vec3Input : public VecInput { +public: + Vec3Input(Node* parent, const QString& id, const QString& name, bool savable = true, bool keyframable = true); + + QVector3D GetVector3DAt(double timecode); + virtual QVariant GetValueAt(double timecode) override; + virtual void SetValueAt(double timecode, const QVariant &value) override; +}; + +class Vec4Input : public VecInput { +public: + Vec4Input(Node* parent, const QString& id, const QString& name, bool savable = true, bool keyframable = true); + + QVector4D GetVector4DAt(double timecode); + virtual QVariant GetValueAt(double timecode) override; + virtual void SetValueAt(double timecode, const QVariant &value) override; +}; + +#endif // VEC2INPUT_H diff --git a/nodes/node.cpp b/nodes/node.cpp new file mode 100644 index 000000000..17c11c6c6 --- /dev/null +++ b/nodes/node.cpp @@ -0,0 +1,943 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "node.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "panels/panels.h" +#include "panels/viewer.h" +#include "ui/viewerwidget.h" +#include "ui/collapsiblewidget.h" +#include "panels/project.h" +#include "undo/undo.h" +#include "timeline/sequence.h" +#include "timeline/clip.h" +#include "panels/timeline.h" +#include "panels/effectcontrols.h" +#include "panels/grapheditor.h" +#include "global/debug.h" +#include "global/path.h" +#include "ui/mainwindow.h" +#include "ui/menu.h" +#include "global/math.h" +#include "global/clipboard.h" +#include "global/config.h" +#include "effects/transition.h" +#include "undo/undostack.h" +#include "rendering/shadergenerators.h" +#include "global/timing.h" +#include "nodes/nodes.h" +#include "effects/effectloaders.h" + +QVector olive::node_library; + +Node::Node(Clip* c) : + parent_clip(c), + flags_(0), + shader_program_(nullptr), + texture(0), + tex_width_(0), + tex_height_(0), + isOpen(false), + bound(false), + iterations(1), + enabled_(true), + expanded_(true), + texture_ctx(nullptr) +{ +} + +Node::~Node() { + if (isOpen) { + close(); + } + + // Clear graph editor if it's using one of these rows + if (panel_graph_editor != nullptr) { + for (int i=0;iget_row()) { + panel_graph_editor->set_row(nullptr); + break; + } + } + } +} + +QString Node::category() +{ + return QString(); +} + +bool Node::IsCreatable() +{ + return true; +} + +void Node::AddRow(EffectRow *row) +{ + row->setParent(this); + rows.append(row); +} + +int Node::IndexOfRow(EffectRow *row) +{ + return rows.indexOf(row); +} + +void Node::copy_field_keyframes(NodePtr e) { + for (int i=0;irows.at(i); + copy_row->SetKeyframingInternal(row->IsKeyframing()); + for (int j=0;jFieldCount();j++) { + // Get field from this (the source) effect + EffectField* field = row->Field(j); + + // Get field from the destination effect + EffectField* copy_field = copy_row->Field(j); + + // Copy keyframes between effects + copy_field->keyframes = field->keyframes; + + // Copy persistet data between effects + copy_field->persistent_data_ = field->persistent_data_; + } + } +} + +EffectRow* Node::row(int i) { + return rows.at(i); +} + +int Node::row_count() { + return rows.size(); +} + +EffectGizmo *Node::add_gizmo(int type) { + EffectGizmo* gizmo = new EffectGizmo(this, type); + gizmos.append(gizmo); + return gizmo; +} + +EffectGizmo *Node::gizmo(int i) { + return gizmos.at(i); +} + +int Node::gizmo_count() { + return gizmos.size(); +} + +QVector Node::GetAllEdges() +{ + QVector edges; + + for (int i=0;iedges()); + } + + return edges; +} + +void Node::refresh() {} + +void Node::FieldChanged() { + // Update the UI if a field has been modified, but don't both if this effect is inactive + if (parent_clip != nullptr) { + update_ui(false); + } +} + +void Node::delete_self() { + olive::undo_stack.push(new EffectDeleteCommand(this)); + update_ui(true); +} + +void Node::move_up() { + int index_of_effect = parent_clip->IndexOfEffect(this); + if (index_of_effect == 0) { + return; + } + + MoveEffectCommand* command = new MoveEffectCommand(); + command->clip = parent_clip; + command->from = index_of_effect; + command->to = command->from - 1; + olive::undo_stack.push(command); + panel_effect_controls->Reload(); + panel_sequence_viewer->viewer_widget()->frame_update(); +} + +void Node::move_down() { + int index_of_effect = parent_clip->IndexOfEffect(this); + if (index_of_effect == parent_clip->effects.size()-1) { + return; + } + + MoveEffectCommand* command = new MoveEffectCommand(); + command->clip = parent_clip; + command->from = index_of_effect; + command->to = command->from + 1; + olive::undo_stack.push(command); + panel_effect_controls->Reload(); + panel_sequence_viewer->viewer_widget()->frame_update(); +} + +void Node::save_to_file() { + // save effect settings to file + QString file = QFileDialog::getSaveFileName(olive::MainWindow, + tr("Save Effect Settings"), + QString(), + tr("Effect XML Settings %1").arg("(*.xml)")); + + // if the user picked a file + if (!file.isEmpty()) { + + // ensure file ends with .xml extension + if (!file.endsWith(".xml", Qt::CaseInsensitive)) { + file.append(".xml"); + } + + QFile file_handle(file); + if (file_handle.open(QFile::WriteOnly)) { + + file_handle.write(save_to_string()); + + file_handle.close(); + } else { + QMessageBox::critical(olive::MainWindow, + tr("Save Settings Failed"), + tr("Failed to open \"%1\" for writing.").arg(file), + QMessageBox::Ok); + } + } +} + +void Node::load_from_file() { + // load effect settings from file + QString file = QFileDialog::getOpenFileName(olive::MainWindow, + tr("Load Effect Settings"), + QString(), + tr("Effect XML Settings %1").arg("(*.xml)")); + + // if the user picked a file + if (!file.isEmpty()) { + QFile file_handle(file); + if (file_handle.open(QFile::ReadOnly)) { + + olive::undo_stack.push(new SetEffectData(this, file_handle.readAll())); + + file_handle.close(); + + update_ui(false); + } else { + QMessageBox::critical(olive::MainWindow, + tr("Load Settings Failed"), + tr("Failed to open \"%1\" for reading.").arg(file), + QMessageBox::Ok); + } + } +} + +bool Node::AlwaysUpdate() +{ + return false; +} + +bool Node::IsEnabled() { + return enabled_; +} + +bool Node::IsExpanded() +{ + return expanded_; +} + +void Node::SetExpanded(bool e) +{ + expanded_ = e; +} + +void Node::SetPos(const QPointF &pos) +{ + pos_ = pos; +} + +void Node::SetEnabled(bool b) { + enabled_ = b; + emit EnabledChanged(b); +} + +void Node::load(QXmlStreamReader& stream) { + /* + int row_count = 0; + + QString tag = stream.name().toString(); + + while (!stream.atEnd() && !(stream.name() == tag && stream.isEndElement())) { + stream.readNext(); + if (stream.name() == "row" && stream.isStartElement()) { + if (row_count < rows.size()) { + EffectRow* row = rows.at(row_count); + + while (!stream.atEnd() && !(stream.name() == "row" && stream.isEndElement())) { + stream.readNext(); + + // read field + if (stream.name() == "field" && stream.isStartElement()) { + int field_number = -1; + + // match field using ID + for (int k=0;kFieldCount();l++) { + if (row->Field(l)->id() == attr.value()) { + field_number = l; + break; + } + } + break; + } + } + + if (field_number > -1) { + EffectField* field = row->Field(field_number); + + // get current field value + for (int k=0;kpersistent_data_ = field->ConvertStringToValue(attr.value().toString()); + break; + } + } + + while (!stream.atEnd() && !(stream.name() == "field" && stream.isEndElement())) { + stream.readNext(); + + // read keyframes + if (stream.name() == "key" && stream.isStartElement()) { + row->SetKeyframingInternal(true); + + EffectKeyframe key; + for (int k=0;kConvertStringToValue(attr.value().toString()); + } else if (attr.name() == "frame") { + key.time = attr.value().toLong(); + } else if (attr.name() == "type") { + key.type = attr.value().toInt(); + } else if (attr.name() == "prehx") { + key.pre_handle_x = attr.value().toDouble(); + } else if (attr.name() == "prehy") { + key.pre_handle_y = attr.value().toDouble(); + } else if (attr.name() == "posthx") { + key.post_handle_x = attr.value().toDouble(); + } else if (attr.name() == "posthy") { + key.post_handle_y = attr.value().toDouble(); + } + } + field->keyframes.append(key); + } + } + + field->Changed(); + + } + } + } + + } else { + qCritical() << "Too many rows for effect" << id << ". Project might be corrupt. (Got" << row_count << ", expected <" << rows.size()-1 << ")"; + } + row_count++; + } else if (stream.isStartElement()) { + custom_load(stream); + } + } + */ +} + +void Node::custom_load(QXmlStreamReader &) {} + +void Node::save(QXmlStreamWriter& stream) { + /* + stream.writeAttribute("name", meta->category + "/" + meta->name); + stream.writeAttribute("enabled", QString::number(IsEnabled())); + + for (int i=0;iIsSavable()) { + stream.writeStartElement("row"); // row + for (int j=0;jFieldCount();j++) { + EffectField* field = row->Field(j); + + if (!field->id().isEmpty()) { + stream.writeStartElement("field"); // field + stream.writeAttribute("id", field->id()); + stream.writeAttribute("value", field->ConvertValueToString(field->persistent_data_)); + for (int k=0;kkeyframes.size();k++) { + const EffectKeyframe& key = field->keyframes.at(k); + stream.writeStartElement("key"); + stream.writeAttribute("value", field->ConvertValueToString(key.data)); + stream.writeAttribute("frame", QString::number(key.time)); + stream.writeAttribute("type", QString::number(key.type)); + stream.writeAttribute("prehx", QString::number(key.pre_handle_x)); + stream.writeAttribute("prehy", QString::number(key.pre_handle_y)); + stream.writeAttribute("posthx", QString::number(key.post_handle_x)); + stream.writeAttribute("posthy", QString::number(key.post_handle_y)); + stream.writeEndElement(); // key + } + stream.writeEndElement(); // field + } + } + stream.writeEndElement(); // row + } + } + */ +} + +void Node::load_from_string(const QByteArray &s) { + // clear existing keyframe data + for (int i=0;iSetKeyframingInternal(false); + for (int j=0;jFieldCount();j++) { + EffectField* field = row->Field(j); + field->keyframes.clear(); + } + } + + // write settings with xml writer + QXmlStreamReader stream(s); + + bool found_id = false; + + while (!stream.atEnd()) { + stream.readNext(); + + // find the effect opening tag + if (stream.name() == "effect" && stream.isStartElement()) { + + // check the name to see if it matches this effect + const QXmlStreamAttributes& attributes = stream.attributes(); + for (int i=0;ipath.isEmpty() || (shader_vert_path_.isEmpty() && shader_frag_path_.isEmpty())) return; + QList effects_paths = get_effects_paths(); + const QString& test_fn = shader_vert_path_.isEmpty() ? shader_frag_path_ : shader_vert_path_; + for (int i=0;iisLinked(); +} + +QOpenGLShaderProgram *Node::GetShaderPipeline() +{ + return shader_program_.get(); +} + +int Node::Flags() +{ + return flags_; +} + +void Node::SetFlags(int flags) +{ + flags_ = flags; +} + +int Node::getIterations() { + return iterations; +} + +void Node::setIterations(int i) { + iterations = i; +} + +const QPointF &Node::pos() +{ + return pos_; +} + +void Node::process_image(double, uint8_t *, uint8_t *, int){} + +NodePtr Node::copy(Clip *c) { + NodePtr copy = Create(c); + copy->SetEnabled(IsEnabled()); + copy_field_keyframes(copy); + return copy; +} + +void Node::process_shader(double timecode, GLTextureCoords&, int iteration) { + /* + shader_program_->bind(); + + shader_program_->setUniformValue("resolution", parent_clip->media_width(), parent_clip->media_height()); + shader_program_->setUniformValue("time", GLfloat(timecode)); + shader_program_->setUniformValue("iteration", iteration); + + for (int i=0;iFieldCount();j++) { + EffectField* field = row->Field(j); + if (!field->id().isEmpty()) { + switch (field->type()) { + case EffectField::EFFECT_FIELD_DOUBLE: + { + DoubleField* double_field = static_cast(field); + shader_program_->setUniformValue(double_field->id().toUtf8().constData(), + GLfloat(double_field->GetDoubleAt(timecode))); + } + break; + case EffectField::EFFECT_FIELD_COLOR: + { + ColorField* color_field = static_cast(field); + shader_program_->setUniformValue( + color_field->id().toUtf8().constData(), + GLfloat(color_field->GetColorAt(timecode).redF()), + GLfloat(color_field->GetColorAt(timecode).greenF()), + GLfloat(color_field->GetColorAt(timecode).blueF()) + ); + } + break; + case EffectField::EFFECT_FIELD_BOOL: + shader_program_->setUniformValue(field->id().toUtf8().constData(), field->GetValueAt(timecode).toBool()); + break; + case EffectField::EFFECT_FIELD_COMBO: + shader_program_->setUniformValue(field->id().toUtf8().constData(), field->GetValueAt(timecode).toInt()); + break; + + // can you even send a string to a uniform value? + case EffectField::EFFECT_FIELD_STRING: + case EffectField::EFFECT_FIELD_FONT: + case EffectField::EFFECT_FIELD_FILE: + case EffectField::EFFECT_FIELD_UI: + break; + } + } + } + } + + shader_program_->release(); + */ +} + +void Node::process_coords(double, GLTextureCoords&, int) {} + +GLuint Node::process_superimpose(QOpenGLContext* ctx, double timecode) { + bool dimensions_changed = false; + bool redrew_image = false; + + int width = parent_clip->media_width(); + int height = parent_clip->media_height(); + + if (width != img.width() || height != img.height()) { + img = QImage(width, height, QImage::Format_RGBA8888_Premultiplied); + dimensions_changed = true; + } + + if (valueHasChanged(timecode) || dimensions_changed || AlwaysUpdate()) { + redraw(timecode); + redrew_image = true; + } + + QOpenGLFunctions* f = ctx->functions(); + + if (texture == 0 || tex_width_ != img.width() || tex_height_ != img.height()) { + delete_texture(); + + tex_width_ = img.width(); + tex_height_ = img.height(); + + // create texture object + f->glGenTextures(1, &texture); + + f->glBindTexture(GL_TEXTURE_2D, texture); + + // set texture filtering to bilinear + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + f->glTexImage2D( + GL_TEXTURE_2D, 0, GL_RGBA8, tex_width_, tex_height_, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.constBits() + ); + + f->glBindTexture(GL_TEXTURE_2D, 0); + + redrew_image = false; + } + + if (redrew_image) { + f->glBindTexture(GL_TEXTURE_2D, texture); + + f->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width_, tex_height_, GL_RGBA, GL_UNSIGNED_BYTE, img.constBits()); + + f->glBindTexture(GL_TEXTURE_2D, 0); + } + + return texture; +} + +void Node::process_audio(double, double, float **, int, int, int) {} + +void Node::gizmo_draw(double, GLTextureCoords &) {} + +void Node::gizmo_move(EffectGizmo* gizmo, int x_movement, int y_movement, double timecode, bool done) { + // Loop through each gizmo to find `gizmo` + for (int i=0;ix_field1 != nullptr) { + gizmo_dragging_actions_.append(new KeyframeDataChange(gizmo->x_field1)); + } + if (gizmo->y_field1 != nullptr) { + gizmo_dragging_actions_.append(new KeyframeDataChange(gizmo->y_field1)); + } + if (gizmo->x_field2 != nullptr) { + gizmo_dragging_actions_.append(new KeyframeDataChange(gizmo->x_field2)); + } + if (gizmo->y_field2 != nullptr) { + gizmo_dragging_actions_.append(new KeyframeDataChange(gizmo->y_field2)); + } + } + + // Update the field values + if (gizmo->x_field1 != nullptr) { + gizmo->x_field1->SetValueAt(timecode, + gizmo->x_field1->GetDoubleAt(timecode) + x_movement*gizmo->x_field_multi1); + } + if (gizmo->y_field1 != nullptr) { + gizmo->y_field1->SetValueAt(timecode, + gizmo->y_field1->GetDoubleAt(timecode) + y_movement*gizmo->y_field_multi1); + } + if (gizmo->x_field2 != nullptr) { + gizmo->x_field2->SetValueAt(timecode, + gizmo->x_field2->GetDoubleAt(timecode) + x_movement*gizmo->x_field_multi2); + } + if (gizmo->y_field2 != nullptr) { + gizmo->y_field2->SetValueAt(timecode, + gizmo->y_field2->GetDoubleAt(timecode) + y_movement*gizmo->y_field_multi2); + } + + // If (done && !gizmo_dragging_actions_.isEmpty()), that means the drag just ended and we're going to save + // the new state of the attach fields' keyframes in KeyframeDataChange objects to make the changes undoable + // by the user later. + if (done && !gizmo_dragging_actions_.isEmpty()) { + + // Store all the KeyframeDataChange objects into a ComboAction to send to the undo stack (makes them all + // undoable together rather than having to be undone individually). + ComboAction* ca = new ComboAction(); + + for (int j=0;jSetNewKeyframes(); + + // Add this KeyframeDataChange object to the ComboAction + ca->append(gizmo_dragging_actions_.at(j)); + } + + olive::undo_stack.push(ca); + + gizmo_dragging_actions_.clear(); + } + break; + } + } +} + +void Node::gizmo_world_to_screen(const QMatrix4x4& matrix, const QMatrix4x4& projection) { + for (int i=0;iget_point_count();j++) { + + // Convert the world point from the gizmo into a screen point relative to the sequence's dimensions + + QVector3D screen_pos = g->world_pos.at(j).project(matrix, + projection, + QRect(0, + 0, + parent_clip->track()->sequence()->width, + parent_clip->track()->sequence()->height)); + + g->screen_pos[j] = QPoint(screen_pos.x(), parent_clip->track()->sequence()->height-screen_pos.y()); + + } + } +} + +bool Node::are_gizmos_enabled() { + return (gizmos.size() > 0); +} + +double Node::Now() +{ + return playhead_to_clip_seconds(parent_clip, parent_clip->track()->sequence()->playhead); +} + +long Node::NowInFrames() +{ + return playhead_to_clip_frame(parent_clip, parent_clip->track()->sequence()->playhead); +} + +void Node::redraw(double) { + /* + // run javascript + QPainter p(&img); + painter_wrapper.img = &img; + painter_wrapper.painter = &p; + + jsEngine.globalObject().setProperty("painter", wrapper_obj); + jsEngine.globalObject().setProperty("width", parent_clip->media_width()); + jsEngine.globalObject().setProperty("height", parent_clip->media_height()); + + for (int i=0;ifieldCount();j++) { + EffectField* field = row->field(j); + if (!field->id.isEmpty()) { + switch (field->type) { + case EffectField::EFFECT_FIELD_DOUBLE: + jsEngine.globalObject().setProperty(field->id, field->get_double_value(timecode)); + break; + case EffectField::EFFECT_FIELD_COLOR: + jsEngine.globalObject().setProperty(field->id, field->get_color_value(timecode).name()); + break; + case EffectField::EFFECT_FIELD_STRING: + jsEngine.globalObject().setProperty(field->id, field->get_string_value(timecode)); + break; + case EffectField::EFFECT_FIELD_BOOL: + jsEngine.globalObject().setProperty(field->id, field->get_bool_value(timecode)); + break; + case EffectField::EFFECT_FIELD_COMBO: + jsEngine.globalObject().setProperty(field->id, field->get_combo_index(timecode)); + break; + case EffectField::EFFECT_FIELD_FONT: + jsEngine.globalObject().setProperty(field->id, field->get_font_name(timecode)); + break; + } + } + } + } + + jsEngine.evaluate(script); + */ +} + +bool Node::valueHasChanged(double timecode) { + if (cachedValues.isEmpty()) { + + for (int i=0;iFieldCount();j++) { + cachedValues.append(crow->Field(j)->GetValueAt(timecode)); + } + } + return true; + + } else { + + bool changed = false; + int index = 0; + for (int i=0;iFieldCount();j++) { + EffectField* field = crow->Field(j); + if (cachedValues.at(index) != field->GetValueAt(timecode)) { + changed = true; + } + cachedValues[index] = field->GetValueAt(timecode); + index++; + } + } + return changed; + + } +} + +void Node::delete_texture() { + if (texture_ctx != nullptr) { + texture_ctx->functions()->glDeleteTextures(1, &texture); + texture = 0; + texture_ctx = nullptr; + } +} + +int GetNodeLibraryIndexFromId(const QString& id) { + for (int i=0;iid() == id) { + return i; + } + } + + return -1; +} + +/* +const EffectMeta* Node::GetMetaFromName(const QString& input) { + int split_index = input.indexOf('/'); + QString category; + if (split_index > -1) { + category = input.left(split_index); + } + QString name = input.mid(split_index + 1); + + for (int j=0;j. + +***/ + +#ifndef EFFECT_H +#define EFFECT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "timeline/tracktypes.h" +#include "rendering/qopenglshaderprogramptr.h" +#include "inputs.h" +#include "effects/effectgizmo.h" + +class EffectGizmo; +class KeyframeDataChange; + +class Clip; +using ClipPtr = std::shared_ptr; + +class Node; +using NodePtr = std::shared_ptr; + +enum NodeType { + kTransformEffect, + kTextInput, + kSolidInput, + kNoiseInput, + kVolumeEffect, + kPanEffect, + kToneInput, + kShakeEffect, + kTimecodeEffect, + kMaskEffect, + kFillLeftRightEffect, + kVstEffect, + kCornerPinEffect, + kRichTextInput, + kMediaInput, + kShaderEffect, + kImageOutput, + kCrossDissolveTransition, + kLinearFadeTransition, + kExponentialFadeTransition, + kLogarithmicFadeTransition, + kInvalidNode +}; + +double log_volume(double linear); + +enum EffectType { + EFFECT_TYPE_INVALID, + EFFECT_TYPE_EFFECT, + EFFECT_TYPE_TRANSITION +}; + +enum EffectKeyframeType { + EFFECT_KEYFRAME_LINEAR, + EFFECT_KEYFRAME_BEZIER, + EFFECT_KEYFRAME_HOLD +}; + +struct GLTextureCoords { + QMatrix4x4 matrix; + + QVector3D vertex_top_left; + QVector3D vertex_top_right; + QVector3D vertex_bottom_left; + QVector3D vertex_bottom_right; + + QVector2D texture_top_left; + QVector2D texture_top_right; + QVector2D texture_bottom_left; + QVector2D texture_bottom_right; + + float opacity; +}; + +class Node : public QObject { + Q_OBJECT +public: + Node(Clip *c); + ~Node(); + + Clip* parent_clip; + + virtual QString name() = 0; + virtual QString id() = 0; + virtual QString category(); + virtual QString description() = 0; + virtual EffectType type() = 0; + virtual olive::TrackType subtype() = 0; + virtual bool IsCreatable(); + virtual NodePtr Create(Clip *c) = 0; + + void AddRow(EffectRow* row); + int IndexOfRow(EffectRow* row); + EffectRow* row(int i); + int row_count(); + + EffectGizmo* add_gizmo(int type); + EffectGizmo* gizmo(int i); + int gizmo_count(); + + QVector GetAllEdges(); + + bool IsEnabled(); + bool IsExpanded(); + + virtual void refresh(); + + virtual NodePtr copy(Clip* c); + void copy_field_keyframes(NodePtr e); + + virtual void load(QXmlStreamReader& stream); + virtual void custom_load(QXmlStreamReader& stream); + virtual void save(QXmlStreamWriter& stream); + + void load_from_string(const QByteArray &s); + QByteArray save_to_string(); + + // glsl handling + bool is_open(); + void open(); + void close(); + bool is_shader_linked(); + QOpenGLShaderProgram* GetShaderPipeline(); + + enum VideoEffectFlags { + ShaderFlag = 0x1, + CoordsFlag = 0x2, + SuperimposeFlag = 0x4 + }; + int Flags(); + void SetFlags(int flags); + + int getIterations(); + void setIterations(int i); + + const QPointF& pos(); + + virtual void process_image(double timecode, uint8_t* input, uint8_t* output, int size); + virtual void process_shader(double timecode, GLTextureCoords&, int iteration); + virtual void process_coords(double timecode, GLTextureCoords& coords, int data); + virtual GLuint process_superimpose(QOpenGLContext *ctx, double timecode); + virtual void process_audio(double timecode_start, double timecode_end, float **samples, int nb_samples, int nb_channels, int type); + + virtual void gizmo_draw(double timecode, GLTextureCoords& coords); + void gizmo_move(EffectGizmo* sender, int x_movement, int y_movement, double timecode, bool done); + void gizmo_world_to_screen(const QMatrix4x4 &matrix, const QMatrix4x4 &projection); + bool are_gizmos_enabled(); + + /** + * @brief Get the current clip/media time + * + * A convenience function that can be plugged into GetValueAt() to get the value wherever the appropriate Sequence's + * playhead it. + * + * @return + * + * Current clip/media time in seconds. + */ + double Now(); + + /** + * @brief Retrieve the current clip as a frame number + * + * Same as Now() but retrieves the value as a frame number (in the appropriate Sequence's frame rate) instead of + * seconds. + * + * @return + * + * The current clip time in frames + */ + long NowInFrames(); + + template + T randomNumber() + { + static std::random_device device; + static std::mt19937 generator(device()); + static std::uniform_int_distribution<> distribution(std::numeric_limits::min(), std::numeric_limits::max()); + return distribution(generator); + } + + template + T randomFloat() + { + static std::random_device device; + static std::mt19937 generator(device()); + static std::uniform_int_distribution<> distribution(-1.0, 1.0); + return distribution(generator); + } + +public slots: + void FieldChanged(); + void SetEnabled(bool b); + void SetExpanded(bool e); + void SetPos(const QPointF& pos); +signals: + void EnabledChanged(bool); +private slots: + void delete_self(); + void move_up(); + void move_down(); + void save_to_file(); + void load_from_file(); +protected: + // glsl effect + QOpenGLShaderProgramPtr shader_program_; + QString shader_vert_path_; + QString shader_frag_path_; + QString shader_function_name_; + + // superimpose effect + QImage img; + GLuint texture; + QOpenGLContext* texture_ctx; + int tex_width_; + int tex_height_; + + // enable effect to update constantly + virtual bool AlwaysUpdate(); + +private: + bool isOpen; + QVector rows; + QVector gizmos; + bool bound; + int iterations; + + bool enabled_; + bool expanded_; + + int flags_; + + QVector gizmo_dragging_actions_; + + QPointF pos_; + + // superimpose functions + virtual void redraw(double timecode); + bool valueHasChanged(double timecode); + QVector cachedValues; + void delete_texture(); + void validate_meta_path(); +}; + +namespace olive { + extern QVector node_library; +} + +#endif // EFFECT_H diff --git a/nodes/nodedatatypes.cpp b/nodes/nodedatatypes.cpp new file mode 100644 index 000000000..53636cc7d --- /dev/null +++ b/nodes/nodedatatypes.cpp @@ -0,0 +1,74 @@ +#include "nodedatatypes.h" + +QString olive::nodes::DataTypeToString(DataType type) { + switch (type) { + case kFloat: + return "DOUBLE"; + case kVec2: + return "VEC2"; + case kVec3: + return "VEC3"; + case kVec4: + return "VEC4"; + case kArray: + return "ARRAY"; + case kColor: + return "COLOR"; + case kString: + return "STRING"; + case kBoolean: + return "BOOL"; + case kCombo: + return "COMBO"; + case kFont: + return "FONT"; + case kFile: + return "FILE"; + case kInteger: + return "INTEGER"; + case kTexture: + return "TEXTURE"; + case kMatrix: + return "MATRIX"; + case kUI: + return "UI"; + default: + return QString(); + } +} + +olive::nodes::DataType olive::nodes::StringToDataType(const QString &s) +{ + if (s == "DOUBLE") { + return kFloat; + } else if (s == "VEC2") { + return kVec2; + } else if (s == "VEC3") { + return kVec3; + } else if (s == "VEC4") { + return kVec4; + } else if (s == "ARRAY") { + return kArray; + } else if (s == "COLOR") { + return kColor; + } else if (s == "STRING") { + return kString; + } else if (s == "BOOL") { + return kBoolean; + } else if (s == "COMBO") { + return kCombo; + } else if (s == "FONT") { + return kFont; + } else if (s == "FILE") { + return kFile; + } else if (s == "INTEGER") { + return kInteger; + } else if (s == "TEXTURE") { + return kTexture; + } else if (s == "MATRIX") { + return kMatrix; + } else if (s == "UI") { + return kUI; + } + return kInvalid; +} diff --git a/nodes/nodedatatypes.h b/nodes/nodedatatypes.h new file mode 100644 index 000000000..3250457e4 --- /dev/null +++ b/nodes/nodedatatypes.h @@ -0,0 +1,102 @@ +#ifndef NODEDATATYPES_H +#define NODEDATATYPES_H + +#include + +namespace olive { +namespace nodes { + +/** + * @brief The EffectFieldType enum + * + * Predetermined types of fields. Used throughout Olive to identify what kind of data to expect from GetValueAt(). + * + * This enum is also currently used to match an external XML effect's fields with the correct derived class (e.g. + * EFFECT_FIELD_DOUBLE matches to DoubleInput). + */ +enum DataType { + /** Invalid data type. Used only for error handling. */ + kInvalid, + + /** Values are doubles. Also corresponds to DoubleInput. */ + kFloat, + + /** Value is an 2-component vector of floats. */ + kVec2, + + /** Value is an 3-component vector of floats. */ + kVec3, + + /** Value is an 4-component vector of floats. */ + kVec4, + + /** Value is an array of floats. This cannot be an input field, and can only be passed between nodes. */ + kArray, + + /** Values are colors. Equivalent to kVec4 but represents as a color. Corresponds to ColorInput. */ + kColor, + + /** Values are strings. Also corresponds to StringInput. */ + kString, + + /** Values are booleans. Also corresponds to BoolInput. */ + kBoolean, + + /** Values are arbitrary data. Also corresponds to ComboInput. */ + kCombo, + + /** Values are font family names (in string). Also corresponds to FontInput. */ + kFont, + + /** Values are filenames (in string). Also corresponds to FileInput. */ + kFile, + + /** Values are integers. */ + kInteger, + + /** Value is a texture. This cannot be an input field, and can only be passed between nodes. */ + kTexture, + + /** Value is a 4x4 matrix. This cannot be an input field, and can only be passed between nodes. */ + kMatrix, + + /** Values is a UI object with no data. Corresponds to nothing. */ + kUI, + + /** Total count of valid node data types. Never use this as an actual data type. */ + kDataTypeCount +}; + +/** + * @brief Convert a node data type to a unique string that can be saved to an XML file + * + * @param type + * + * The data type to convert to string + * + * @return + * + * The unique string identifier for this data type. + */ +QString DataTypeToString(DataType type); + +/** + * @brief Convert a string to a node data type. + * + * Generally this string should be a string that was received from DataTypeToString() to ensure compatibility and + * correctness. + * + * @param s + * + * The string to convert to a data type + * + * @return + * + * The data type that this string represents + */ +DataType StringToDataType(const QString& s); + +} +} + +#endif // NODEDATATYPES_H diff --git a/nodes/nodeedge.cpp b/nodes/nodeedge.cpp new file mode 100644 index 000000000..da21eaac8 --- /dev/null +++ b/nodes/nodeedge.cpp @@ -0,0 +1,21 @@ +#include "nodeedge.h" + +#include + +#include "effects/effectrow.h" + +NodeEdge::NodeEdge(EffectRow *output, EffectRow *input) : + output_(output), + input_(input) +{ +} + +EffectRow *NodeEdge::output() +{ + return output_; +} + +EffectRow *NodeEdge::input() +{ + return input_; +} diff --git a/nodes/nodeedge.h b/nodes/nodeedge.h new file mode 100644 index 000000000..a6b49581d --- /dev/null +++ b/nodes/nodeedge.h @@ -0,0 +1,39 @@ +#ifndef NODEEDGE_H +#define NODEEDGE_H + +#include + +class EffectRow; + +class NodeEdge; +using NodeEdgePtr = std::shared_ptr; + +class NodeEdge +{ +public: + /** + * @brief NodeEdge Constructor + * + * Creates a node edge and stores its two connections. + * + * This should not be used directly. Use the static function EffectRow::ConnectEdge instead. + * + * @param output + * + * Output/from EffectRow that this edge "starts" at + * + * @param input + * + * Input/to EffectRow that this edge "ends" at + */ + NodeEdge(EffectRow* output, EffectRow* input); + + EffectRow* output(); + EffectRow* input(); + +private: + EffectRow* output_; + EffectRow* input_; +}; + +#endif // NODEEDGE_H diff --git a/nodes/nodegraph.cpp b/nodes/nodegraph.cpp new file mode 100644 index 000000000..c2d912821 --- /dev/null +++ b/nodes/nodegraph.cpp @@ -0,0 +1,14 @@ +#include "nodegraph.h" + +#include + +NodeGraph::NodeGraph() : + output_node_(nullptr) +{ + +} + +Node *NodeGraph::OutputNode() +{ + return output_node_.get(); +} diff --git a/nodes/nodegraph.h b/nodes/nodegraph.h new file mode 100644 index 000000000..4ce08b030 --- /dev/null +++ b/nodes/nodegraph.h @@ -0,0 +1,53 @@ +#ifndef NODEGRAPH_H +#define NODEGRAPH_H + +#include "nodes/node.h" + +class NodeGraph +{ +public: + NodeGraph(); + + /** + * @brief Add a node to this graph + * + * The graph takes ownership of the node. + * + * @param node + */ + void AddNode(NodePtr node); + + /** + * @brief Process the graph + * + * Using the output node set by SetOutputNode(), this function will work backwards and perform every action in the + * node hierarchy necessary to eventually process the output node. After this function returns, the output node should + * contain valid values ready for accessing. + * + * Each node will cache its output for optimized playback and rendering. This will happen transparently and under most + * circumstances, this function will be able to return immediately. + */ + void Process(); + + /** + * @brief Set the output node for this node graph + * + * This node will be presented as the final node that all other nodes converge on. This node can be used to retrieve + * the result of the graph. + * + * @param node + * + * The node to set as the output node. The graph takes ownership of the node and the user cannot delete it. + */ + void SetOutputNode(NodePtr node); + + /** + * @brief Returns the currently set output node + */ + Node* OutputNode(); + +private: + NodePtr output_node_; +}; + +#endif // NODEGRAPH_H diff --git a/nodes/nodes.h b/nodes/nodes.h new file mode 100644 index 000000000..e3135d7e1 --- /dev/null +++ b/nodes/nodes.h @@ -0,0 +1,8 @@ +#ifndef NODES_H +#define NODES_H + +#include "nodes/nodemedia.h" +#include "nodes/nodeimageoutput.h" +#include "nodes/nodeshader.h" + +#endif // NODES_H diff --git a/nodes/nodes/nodeimageoutput.cpp b/nodes/nodes/nodeimageoutput.cpp new file mode 100644 index 000000000..994fa181a --- /dev/null +++ b/nodes/nodes/nodeimageoutput.cpp @@ -0,0 +1,43 @@ +#include "nodeimageoutput.h" + +NodeImageOutput::NodeImageOutput(Clip *c) : + Node(c) +{ + EffectRow* input_texture = new EffectRow(this, "texture", tr("Texture"), true, false); + input_texture->AddAcceptedNodeInput(olive::nodes::kTexture); +} + +QString NodeImageOutput::name() +{ + return tr("Image Output"); +} + +QString NodeImageOutput::id() +{ + return "org.olivevideoeditor.Olive.imageoutput"; +} + +QString NodeImageOutput::category() +{ + return tr("Outputs"); +} + +QString NodeImageOutput::description() +{ + return tr("Used for outputting images outside of the node graph."); +} + +EffectType NodeImageOutput::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType NodeImageOutput::subtype() +{ + return olive::kTypeVideo; +} + +NodePtr NodeImageOutput::Create(Clip *c) +{ + return std::make_shared(c); +} diff --git a/nodes/nodes/nodeimageoutput.h b/nodes/nodes/nodeimageoutput.h new file mode 100644 index 000000000..c4ebd2798 --- /dev/null +++ b/nodes/nodes/nodeimageoutput.h @@ -0,0 +1,20 @@ +#ifndef NODEIMAGEOUTPUT_H +#define NODEIMAGEOUTPUT_H + +#include "nodes/node.h" + +class NodeImageOutput : public Node +{ +public: + NodeImageOutput(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString category() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; +}; + +#endif // NODEIMAGEOUTPUT_H diff --git a/nodes/nodes/nodemedia.cpp b/nodes/nodes/nodemedia.cpp new file mode 100644 index 000000000..f5b3e3272 --- /dev/null +++ b/nodes/nodes/nodemedia.cpp @@ -0,0 +1,46 @@ +#include "nodemedia.h" + +NodeMedia::NodeMedia(Clip* c) : + Node(c) +{ + EffectRow* matrix_input = new EffectRow(this, "matrix", tr("Matrix"), true, false); + matrix_input->AddAcceptedNodeInput(olive::nodes::kMatrix); + + EffectRow* texture_output = new EffectRow(this, "texture", tr("Texture"), true, false); + texture_output->SetOutputDataType(olive::nodes::kTexture); +} + +QString NodeMedia::name() +{ + return tr("Media"); +} + +QString NodeMedia::id() +{ + return "org.olivevideoeditor.Olive.media"; +} + +QString NodeMedia::category() +{ + return tr("Inputs"); +} + +QString NodeMedia::description() +{ + return tr("Retrieve frames from a media source."); +} + +EffectType NodeMedia::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType NodeMedia::subtype() +{ + return olive::kTypeVideo; +} + +NodePtr NodeMedia::Create(Clip *c) +{ + return std::make_shared(c); +} diff --git a/nodes/nodes/nodemedia.h b/nodes/nodes/nodemedia.h new file mode 100644 index 000000000..4d97716ca --- /dev/null +++ b/nodes/nodes/nodemedia.h @@ -0,0 +1,20 @@ +#ifndef MEDIANODE_H +#define MEDIANODE_H + +#include "nodes/node.h" + +class NodeMedia : public Node +{ +public: + NodeMedia(Clip *c); + + virtual QString name() override; + virtual QString id() override; + virtual QString category() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual NodePtr Create(Clip *c) override; +}; + +#endif // MEDIANODE_H diff --git a/nodes/nodes/nodeshader.cpp b/nodes/nodes/nodeshader.cpp new file mode 100644 index 000000000..d5d924e7c --- /dev/null +++ b/nodes/nodes/nodeshader.cpp @@ -0,0 +1,222 @@ +#include "nodeshader.h" + +NodeShader::NodeShader(Clip* c, + const QString &name, + const QString &id, + const QString &category, + const QString &filename) : + Node(c), + name_(name), + id_(id), + category_(category), + filename_(filename) +{ + if (!filename_.isEmpty()) { + QFile effect_file(filename_); + if (effect_file.open(QFile::ReadOnly)) { + QXmlStreamReader reader(&effect_file); + + while (!reader.atEnd()) { + if (reader.name() == "field" && reader.isStartElement()) { + int type = olive::nodes::kInvalid; + QString id; + QString name; + + // get field type + const QXmlStreamAttributes& attributes = reader.attributes(); + for (int i=0;iSetDefault(attr.value().toDouble()); + } else if (attr.name() == "min") { + double_field->SetMinimum(attr.value().toDouble()); + } else if (attr.name() == "max") { + double_field->SetMaximum(attr.value().toDouble()); + } + } + + field = double_field; + } + break; + case olive::nodes::kColor: + { + QColor color; + + field = new ColorInput(this, id, name); + + for (int i=0;iSetValueAt(0, color); + } + break; + case olive::nodes::kString: + field = new StringInput(this, id, name); + for (int i=0;iSetValueAt(0, attr.value().toString()); + } + } + break; + case olive::nodes::kBoolean: + field = new BoolInput(this, id, name); + for (int i=0;iSetValueAt(0, attr.value() == "1"); + } + } + break; + case olive::nodes::kCombo: + { + ComboInput* combo_field = new ComboInput(this, id, name); + int combo_default_index = 0; + for (int i=0;iAddItem(reader.text().toString(), combo_item_count); + combo_item_count++; + } + } + combo_field->SetValueAt(0, combo_default_index); + field = combo_field; + } + break; + case olive::nodes::kFont: + field = new FontInput(this, id, name); + for (int i=0;iSetValueAt(0, attr.value().toString()); + } + } + break; + case olive::nodes::kFile: + field = new FileInput(this, id, name); + for (int i=0;iSetValueAt(0, attr.value().toString()); + } + } + break; + } + } + } else if (reader.name() == "shader" && reader.isStartElement()) { + SetFlags(Flags() | ShaderFlag); + const QXmlStreamAttributes& attributes = reader.attributes(); + for (int i=0;i(Field(0))->SetCheckable(c); +} + +void ButtonWidget::SetChecked(bool c) +{ + static_cast(Field(0))->SetChecked(c); +} diff --git a/nodes/widgets/buttonwidget.h b/nodes/widgets/buttonwidget.h new file mode 100644 index 000000000..cd35ba7a9 --- /dev/null +++ b/nodes/widgets/buttonwidget.h @@ -0,0 +1,35 @@ +#ifndef BUTTONWIDGET_H +#define BUTTONWIDGET_H + +#include "effects/effectrow.h" + +class ButtonWidget : public EffectRow +{ +public: + ButtonWidget(Node* parent, const QString& name, const QString& text); + + /** + * @brief Wrapper for ButtonField::SetCheckable. + */ + void SetCheckable(bool c); + +public slots: + /** + * @brief Wrapper for ButtonField::SetChecked() + */ + void SetChecked(bool c); + +signals: + /** + * @brief Wrapper for ButtonField::CheckedChanged() + */ + void CheckedChanged(bool); + + /** + * @brief Wrapper for ButtonField::Toggled() + */ + void Toggled(bool); + +}; + +#endif // BUTTONWIDGET_H diff --git a/nodes/widgets/labelwidget.cpp b/nodes/widgets/labelwidget.cpp new file mode 100644 index 000000000..6d22601c8 --- /dev/null +++ b/nodes/widgets/labelwidget.cpp @@ -0,0 +1,7 @@ +#include "labelwidget.h" + +LabelWidget::LabelWidget(Node *parent, const QString &name, const QString &text) : + EffectRow(parent, nullptr, name, false, false) +{ + AddField(new LabelField(this, text)); +} diff --git a/nodes/widgets/labelwidget.h b/nodes/widgets/labelwidget.h new file mode 100644 index 000000000..97e86d158 --- /dev/null +++ b/nodes/widgets/labelwidget.h @@ -0,0 +1,12 @@ +#ifndef LABELWIDGET_H +#define LABELWIDGET_H + +#include "effects/effectrow.h" + +class LabelWidget : public EffectRow +{ +public: + LabelWidget(Node* parent, const QString& name, const QString& text); +}; + +#endif // LABELWIDGET_H diff --git a/olive.pro b/olive.pro index 5882fef7b..34edcce5d 100644 --- a/olive.pro +++ b/olive.pro @@ -1,398 +1,468 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2018-05-11T10:31:59 -# -#------------------------------------------------- - -QT += core gui multimedia opengl svg - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -mac { - TARGET = Olive -} -!mac { - TARGET = olive-editor -} -TEMPLATE = app - -# The following define makes your compiler emit warnings if you use -# any feature of Qt which has been marked as deprecated (the exact warnings -# depend on your compiler). Please consult the documentation of the -# deprecated API in order to know how to port your code away from it. -DEFINES += QT_DEPRECATED_WARNINGS - -# You can also make your code fail to compile if you use deprecated APIs. -# In order to do so, uncomment the following line. -# You can also select to disable deprecated APIs only up to a certain version of Qt. -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -# Tries to get the current Git short hash -system("which git") { - GITPATH = $$PWD - - win32 { - GITPATH = $$system(cygpath $$PWD) - } - - GITHASHVAR = $$system(git --git-dir=\"$$GITPATH/.git\" --work-tree=\"$$GITPATH\" log -1 --format=%h) - - # Fallback for Ubuntu/Launchpad (extracts Git hash from debian/changelog rather than Git repo) - # (see https://answers.launchpad.net/launchpad/+question/678556) - isEmpty(GITHASHVAR) { - GITHASHVAR = $$system(sh $$PWD/debian/gitfromlog.sh $$PWD/debian/changelog) - } - - DEFINES += GITHASH=\\"\"$$GITHASHVAR\\"\" -} - -CONFIG += c++11 - -SOURCES += \ - main.cpp \ - ui/mainwindow.cpp \ - panels/project.cpp \ - panels/effectcontrols.cpp \ - panels/viewer.cpp \ - panels/timeline.cpp \ - ui/sourcetable.cpp \ - dialogs/aboutdialog.cpp \ - ui/timelinewidget.cpp \ - project/media.cpp \ - project/footage.cpp \ - timeline/sequence.cpp \ - timeline/clip.cpp \ - global/config.cpp \ - dialogs/newsequencedialog.cpp \ - ui/viewerwidget.cpp \ - ui/viewercontainer.cpp \ - dialogs/exportdialog.cpp \ - ui/collapsiblewidget.cpp \ - panels/panels.cpp \ - rendering/exportthread.cpp \ - ui/timelineheader.cpp \ - project/previewgenerator.cpp \ - ui/labelslider.cpp \ - dialogs/preferencesdialog.cpp \ - ui/audiomonitor.cpp \ - undo/undo.cpp \ - ui/scrollarea.cpp \ - ui/comboboxex.cpp \ - ui/colorbutton.cpp \ - dialogs/replaceclipmediadialog.cpp \ - ui/fontcombobox.cpp \ - ui/keyframeview.cpp \ - ui/texteditex.cpp \ - dialogs/demonotice.cpp \ - timeline/marker.cpp \ - dialogs/speeddialog.cpp \ - dialogs/mediapropertiesdialog.cpp \ - project/projectmodel.cpp \ - project/loadthread.cpp \ - dialogs/loaddialog.cpp \ - global/debug.cpp \ - global/path.cpp \ - effects/internal/linearfadetransition.cpp \ - effects/internal/transformeffect.cpp \ - effects/internal/solideffect.cpp \ - effects/internal/texteffect.cpp \ - effects/internal/timecodeeffect.cpp \ - effects/internal/audionoiseeffect.cpp \ - effects/internal/paneffect.cpp \ - effects/internal/toneeffect.cpp \ - effects/internal/volumeeffect.cpp \ - effects/internal/crossdissolvetransition.cpp \ - effects/internal/shakeeffect.cpp \ - effects/internal/exponentialfadetransition.cpp \ - effects/internal/logarithmicfadetransition.cpp \ - effects/internal/cornerpineffect.cpp \ - global/math.cpp \ - effects/effect.cpp \ - effects/effectrow.cpp \ - effects/effectgizmo.cpp \ - project/clipboard.cpp \ - ui/resizablescrollbar.cpp \ - ui/sourceiconview.cpp \ - project/sourcescommon.cpp \ - ui/keyframenavigator.cpp \ - panels/grapheditor.cpp \ - ui/graphview.cpp \ - ui/keyframedrawing.cpp \ - ui/clickablelabel.cpp \ - effects/keyframe.cpp \ - ui/rectangleselect.cpp \ - dialogs/actionsearch.cpp \ - ui/embeddedfilechooser.cpp \ - effects/internal/fillleftrighteffect.cpp \ - effects/internal/voideffect.cpp \ - dialogs/texteditdialog.cpp \ - dialogs/debugdialog.cpp \ - ui/viewerwindow.cpp \ - project/projectfilter.cpp \ - effects/internal/frei0reffect.cpp \ - effects/effectloaders.cpp \ - effects/internal/vsthost.cpp \ - ui/flowlayout.cpp \ - dialogs/proxydialog.cpp \ - project/proxygenerator.cpp \ - dialogs/advancedvideodialog.cpp \ - ui/cursors.cpp \ - ui/menuhelper.cpp \ - global/global.cpp \ - ui/focusfilter.cpp \ - undo/comboaction.cpp \ - ui/mediaiconservice.cpp \ - ui/panel.cpp \ - effects/internal/dropshadoweffect.cpp \ - rendering/renderfunctions.cpp \ - rendering/renderthread.cpp \ - rendering/cacher.cpp \ - rendering/clipqueue.cpp \ - rendering/audio.cpp \ - dialogs/clippropertiesdialog.cpp \ - rendering/framebufferobject.cpp \ - ui/updatenotification.cpp \ - ui/icons.cpp \ - effects/fields/doublefield.cpp \ - effects/fields/fontfield.cpp \ - effects/effectfield.cpp \ - effects/fields/colorfield.cpp \ - effects/fields/stringfield.cpp \ - effects/fields/boolfield.cpp \ - effects/fields/combofield.cpp \ - effects/fields/filefield.cpp \ - effects/fields/labelfield.cpp \ - effects/fields/buttonfield.cpp \ - ui/effectui.cpp \ - effects/transition.cpp \ - ui/styling.cpp \ - undo/undostack.cpp \ - effects/internal/richtexteffect.cpp \ - ui/blur.cpp \ - ui/menu.cpp \ - timeline/mediaimportdata.cpp \ - dialogs/autocutsilencedialog.cpp \ - ui/columnedgridlayout.cpp - -HEADERS += \ - ui/mainwindow.h \ - panels/project.h \ - panels/effectcontrols.h \ - panels/viewer.h \ - panels/timeline.h \ - ui/sourcetable.h \ - dialogs/aboutdialog.h \ - ui/timelinewidget.h \ - project/media.h \ - project/footage.h \ - timeline/sequence.h \ - timeline/clip.h \ - global/config.h \ - dialogs/newsequencedialog.h \ - ui/viewerwidget.h \ - ui/viewercontainer.h \ - dialogs/exportdialog.h \ - ui/collapsiblewidget.h \ - panels/panels.h \ - rendering/exportthread.h \ - ui/timelinetools.h \ - ui/timelineheader.h \ - project/previewgenerator.h \ - ui/labelslider.h \ - dialogs/preferencesdialog.h \ - ui/audiomonitor.h \ - undo/undo.h \ - ui/scrollarea.h \ - ui/comboboxex.h \ - ui/colorbutton.h \ - dialogs/replaceclipmediadialog.h \ - ui/fontcombobox.h \ - ui/keyframeview.h \ - ui/texteditex.h \ - dialogs/demonotice.h \ - timeline/marker.h \ - timeline/selection.h \ - dialogs/speeddialog.h \ - dialogs/mediapropertiesdialog.h \ - project/projectmodel.h \ - project/loadthread.h \ - dialogs/loaddialog.h \ - global/debug.h \ - global/path.h \ - effects/internal/transformeffect.h \ - effects/internal/solideffect.h \ - effects/internal/texteffect.h \ - effects/internal/timecodeeffect.h \ - effects/internal/audionoiseeffect.h \ - effects/internal/paneffect.h \ - effects/internal/toneeffect.h \ - effects/internal/volumeeffect.h \ - effects/internal/shakeeffect.h \ - effects/internal/linearfadetransition.h \ - effects/internal/crossdissolvetransition.h \ - effects/internal/exponentialfadetransition.h \ - effects/internal/logarithmicfadetransition.h \ - effects/internal/cornerpineffect.h \ - global/math.h \ - effects/effect.h \ - effects/effectrow.h \ - effects/internal/cubetransition.h \ - effects/effectgizmo.h \ - project/clipboard.h \ - ui/resizablescrollbar.h \ - ui/sourceiconview.h \ - project/sourcescommon.h \ - ui/keyframenavigator.h \ - panels/grapheditor.h \ - ui/graphview.h \ - ui/keyframedrawing.h \ - ui/clickablelabel.h \ - effects/keyframe.h \ - ui/rectangleselect.h \ - dialogs/actionsearch.h \ - ui/embeddedfilechooser.h \ - effects/internal/fillleftrighteffect.h \ - effects/internal/voideffect.h \ - dialogs/texteditdialog.h \ - dialogs/debugdialog.h \ - ui/viewerwindow.h \ - project/projectfilter.h \ - effects/internal/frei0reffect.h \ - effects/effectloaders.h \ - effects/internal/vsthost.h \ - ui/flowlayout.h \ - dialogs/proxydialog.h \ - project/proxygenerator.h \ - dialogs/advancedvideodialog.h \ - ui/cursors.h \ - ui/menuhelper.h \ - global/global.h \ - project/projectelements.h \ - ui/focusfilter.h \ - undo/comboaction.h \ - ui/mediaiconservice.h \ - ui/panel.h \ - effects/internal/dropshadoweffect.h \ - rendering/renderfunctions.h \ - rendering/renderthread.h \ - rendering/clipqueue.h \ - rendering/cacher.h \ - rendering/audio.h \ - dialogs/clippropertiesdialog.h \ - rendering/framebufferobject.h \ - ui/updatenotification.h \ - ui/icons.h \ - effects/fields/doublefield.h \ - effects/fields/fontfield.h \ - effects/effectfield.h \ - effects/effectfields.h \ - effects/fields/stringfield.h \ - effects/fields/filefield.h \ - effects/fields/labelfield.h \ - ui/effectui.h \ - effects/fields/boolfield.h \ - effects/fields/buttonfield.h \ - effects/fields/colorfield.h \ - effects/fields/combofield.h \ - effects/transition.h \ - ui/styling.h \ - undo/undostack.h \ - effects/internal/richtexteffect.h \ - ui/blur.h \ - ui/menu.h \ - timeline/mediaimportdata.h \ - dialogs/autocutsilencedialog.h \ - ui/columnedgridlayout.h - -FORMS += - -TRANSLATIONS += \ - ts/olive_de.ts \ - ts/olive_es.ts \ - ts/olive_fr.ts \ - ts/olive_it.ts \ - ts/olive_cs.ts \ - ts/olive_ar.ts \ - ts/olive_ru.ts \ - ts/olive_uk.ts \ - ts/olive_bs.ts \ - ts/olive_sr.ts \ - ts/olive_id.ts - -win32 { - CONFIG(debug, debug|release) { - CONFIG += console - } - - RC_FILE = packaging/windows/resources.rc - LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 -luser32 - contains(DEFINES, OLIVE_OCIO) { - LIBS += -lOpenColorIO - } -} - -mac { - LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -framework CoreFoundation - ICON = packaging/macos/olive.icns - INCLUDEPATH = /usr/local/include -} - -unix:!mac { - CONFIG += link_pkgconfig - PKGCONFIG += libavutil libavformat libavcodec libavfilter libswscale libswresample -} - -RESOURCES += \ - icons/icons.qrc \ - effects/internal/internalshaders.qrc \ - cursors/cursors.qrc - -unix:!mac:isEmpty(PREFIX) { - PREFIX = /usr/local -} - -unix:!mac:target.path = $$PREFIX/bin - -effects.files = $$PWD/effects/shaders/* -unix:!mac:effects.path = $$PREFIX/share/olive-editor/effects - -translations.files = $$PWD/ts/*.qm -unix:!mac:translations.path = $$PREFIX/share/olive-editor/ts - -unix:!mac { - metainfo.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.appdata.xml - metainfo.path = $$PREFIX/share/metainfo - desktop.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.desktop - desktop.path = $$PREFIX/share/applications - mime.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.xml - mime.path = $$PREFIX/share/mime/packages - icon16.files = $$PWD/packaging/linux/icons/16x16/org.olivevideoeditor.Olive.png - icon16.path = $$PREFIX/share/icons/hicolor/16x16/apps - icon32.files = $$PWD/packaging/linux/icons/32x32/org.olivevideoeditor.Olive.png - icon32.path = $$PREFIX/share/icons/hicolor/32x32/apps - icon48.files = $$PWD/packaging/linux/icons/48x48/org.olivevideoeditor.Olive.png - icon48.path = $$PREFIX/share/icons/hicolor/48x48/apps - icon64.files = $$PWD/packaging/linux/icons/64x64/org.olivevideoeditor.Olive.png - icon64.path = $$PREFIX/share/icons/hicolor/64x64/apps - icon128.files = $$PWD/packaging/linux/icons/128x128/org.olivevideoeditor.Olive.png - icon128.path = $$PREFIX/share/icons/hicolor/128x128/apps - icon256.files = $$PWD/packaging/linux/icons/256x256/org.olivevideoeditor.Olive.png - icon256.path = $$PREFIX/share/icons/hicolor/256x256/apps - icon512.files = $$PWD/packaging/linux/icons/512x512/org.olivevideoeditor.Olive.png - icon512.path = $$PREFIX/share/icons/hicolor/512x512/apps - mime16.files = $$PWD/packaging/linux/icons/16x16/application-vnd.olive-project.png - mime16.path = $$PREFIX/share/icons/hicolor/16x16/mimetypes - mime32.files = $$PWD/packaging/linux/icons/32x32/application-vnd.olive-project.png - mime32.path = $$PREFIX/share/icons/hicolor/32x32/mimetypes - mime48.files = $$PWD/packaging/linux/icons/48x48/application-vnd.olive-project.png - mime48.path = $$PREFIX/share/icons/hicolor/48x48/mimetypes - mime64.files = $$PWD/packaging/linux/icons/64x64/application-vnd.olive-project.png - mime64.path = $$PREFIX/share/icons/hicolor/64x64/mimetypes - mime128.files = $$PWD/packaging/linux/icons/128x128/application-vnd.olive-project.png - mime128.path = $$PREFIX/share/icons/hicolor/128x128/mimetypes - mime256.files = $$PWD/packaging/linux/icons/256x256/application-vnd.olive-project.png - mime256.path = $$PREFIX/share/icons/hicolor/256x256/mimetypes - mime512.files = $$PWD/packaging/linux/icons/512x512/application-vnd.olive-project.png - mime512.path = $$PREFIX/share/icons/hicolor/512x512/mimetypes - INSTALLS += target effects translations metainfo desktop mime icon16 icon32 icon48 icon64 icon128 icon256 icon512 mime16 mime32 mime48 mime64 mime128 mime256 mime512 -} +#------------------------------------------------- +# +# Project created by QtCreator 2018-05-11T10:31:59 +# +#------------------------------------------------- + +QT += core gui multimedia opengl svg + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +mac { + TARGET = Olive +} +!mac { + TARGET = olive-editor +} +TEMPLATE = app + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +# Tries to get the current Git short hash +system("which git") { + GITPATH = $$PWD + + win32 { + GITPATH = $$system(cygpath $$PWD) + } + + GITHASHVAR = $$system(git --git-dir=\"$$GITPATH/.git\" --work-tree=\"$$GITPATH\" log -1 --format=%h) + + # Fallback for Ubuntu/Launchpad (extracts Git hash from debian/changelog rather than Git repo) + # (see https://answers.launchpad.net/launchpad/+question/678556) + isEmpty(GITHASHVAR) { + GITHASHVAR = $$system(sh $$PWD/debian/gitfromlog.sh $$PWD/debian/changelog) + } + + DEFINES += GITHASH=\\"\"$$GITHASHVAR\\"\" +} + +CONFIG += c++11 + +gcc:QMAKE_CXXFLAGS += -Wno-reorder + +SOURCES += \ + main.cpp \ + ui/mainwindow.cpp \ + panels/project.cpp \ + panels/effectcontrols.cpp \ + panels/viewer.cpp \ + panels/timeline.cpp \ + ui/sourcetable.cpp \ + dialogs/aboutdialog.cpp \ + project/media.cpp \ + project/footage.cpp \ + timeline/sequence.cpp \ + timeline/clip.cpp \ + global/config.cpp \ + dialogs/newsequencedialog.cpp \ + ui/viewerwidget.cpp \ + ui/viewercontainer.cpp \ + dialogs/exportdialog.cpp \ + ui/collapsiblewidget.cpp \ + panels/panels.cpp \ + rendering/exportthread.cpp \ + ui/timelineheader.cpp \ + project/previewgenerator.cpp \ + ui/labelslider.cpp \ + dialogs/preferencesdialog.cpp \ + ui/audiomonitor.cpp \ + undo/undo.cpp \ + ui/comboboxex.cpp \ + ui/colorbutton.cpp \ + dialogs/replaceclipmediadialog.cpp \ + ui/keyframeview.cpp \ + ui/texteditex.cpp \ + dialogs/demonotice.cpp \ + timeline/marker.cpp \ + dialogs/speeddialog.cpp \ + dialogs/mediapropertiesdialog.cpp \ + project/projectmodel.cpp \ + project/loadthread.cpp \ + dialogs/loaddialog.cpp \ + global/debug.cpp \ + global/path.cpp \ + effects/internal/linearfadetransition.cpp \ + effects/internal/transformeffect.cpp \ + effects/internal/solideffect.cpp \ + effects/internal/texteffect.cpp \ + effects/internal/timecodeeffect.cpp \ + effects/internal/audionoiseeffect.cpp \ + effects/internal/paneffect.cpp \ + effects/internal/toneeffect.cpp \ + effects/internal/volumeeffect.cpp \ + effects/internal/crossdissolvetransition.cpp \ + effects/internal/shakeeffect.cpp \ + effects/internal/exponentialfadetransition.cpp \ + effects/internal/logarithmicfadetransition.cpp \ + effects/internal/cornerpineffect.cpp \ + global/math.cpp \ + effects/effectrow.cpp \ + effects/effectgizmo.cpp \ + ui/resizablescrollbar.cpp \ + ui/sourceiconview.cpp \ + project/sourcescommon.cpp \ + ui/keyframenavigator.cpp \ + panels/grapheditor.cpp \ + ui/graphview.cpp \ + ui/keyframedrawing.cpp \ + ui/clickablelabel.cpp \ + effects/keyframe.cpp \ + ui/rectangleselect.cpp \ + dialogs/actionsearch.cpp \ + ui/embeddedfilechooser.cpp \ + effects/internal/fillleftrighteffect.cpp \ + effects/internal/voideffect.cpp \ + dialogs/texteditdialog.cpp \ + dialogs/debugdialog.cpp \ + ui/viewerwindow.cpp \ + project/projectfilter.cpp \ + effects/effectloaders.cpp \ + effects/internal/vsthost.cpp \ + ui/flowlayout.cpp \ + dialogs/proxydialog.cpp \ + project/proxygenerator.cpp \ + dialogs/advancedvideodialog.cpp \ + ui/cursors.cpp \ + ui/menuhelper.cpp \ + global/global.cpp \ + ui/focusfilter.cpp \ + undo/comboaction.cpp \ + ui/mediaiconservice.cpp \ + ui/panel.cpp \ + effects/internal/dropshadoweffect.cpp \ + rendering/renderfunctions.cpp \ + rendering/renderthread.cpp \ + rendering/cacher.cpp \ + rendering/clipqueue.cpp \ + rendering/audio.cpp \ + dialogs/clippropertiesdialog.cpp \ + rendering/framebufferobject.cpp \ + ui/updatenotification.cpp \ + ui/icons.cpp \ + effects/fields/doublefield.cpp \ + effects/fields/fontfield.cpp \ + effects/effectfield.cpp \ + effects/fields/colorfield.cpp \ + effects/fields/stringfield.cpp \ + effects/fields/boolfield.cpp \ + effects/fields/combofield.cpp \ + effects/fields/filefield.cpp \ + effects/fields/labelfield.cpp \ + effects/fields/buttonfield.cpp \ + ui/effectui.cpp \ + effects/transition.cpp \ + ui/styling.cpp \ + undo/undostack.cpp \ + effects/internal/richtexteffect.cpp \ + ui/blur.cpp \ + ui/menu.cpp \ + timeline/mediaimportdata.cpp \ + dialogs/autocutsilencedialog.cpp \ + ui/columnedgridlayout.cpp \ + rendering/shadergenerators.cpp \ + global/timing.cpp \ + rendering/pixelformats.cpp \ + timeline/timelinefunctions.cpp \ + timeline/track.cpp \ + timeline/tracklist.cpp \ + project/savethread.cpp \ + project/projectfunctions.cpp \ + ui/timelinearea.cpp \ + timeline/timelineshared.cpp \ + ui/timelineview.cpp \ + ui/timelinelabel.cpp \ + timeline/selection.cpp \ + global/clipboard.cpp \ + timeline/timelinetools.cpp \ + timeline/ghost.cpp \ + ui/waveform.cpp \ + panels/nodeeditor.cpp \ + ui/nodeview.cpp \ + ui/nodeui.cpp \ + panels/effectspanel.cpp \ + nodes/nodedatatypes.cpp \ + nodes/inputs/boolinput.cpp \ + nodes/inputs/comboinput.cpp \ + nodes/inputs/colorinput.cpp \ + nodes/inputs/stringinput.cpp \ + nodes/inputs/fontinput.cpp \ + nodes/inputs/vecinput.cpp \ + nodes/widgets/labelwidget.cpp \ + nodes/inputs/fileinput.cpp \ + nodes/widgets/buttonwidget.cpp \ + nodes/nodegraph.cpp \ + nodes/nodes/nodeimageoutput.cpp \ + nodes/nodes/nodemedia.cpp \ + nodes/nodes/nodeshader.cpp \ + decoders/ffmpegdecoder.cpp \ + decoders/decoder.cpp \ + nodes/node.cpp \ + nodes/nodeedge.cpp \ + ui/nodeedgeui.cpp + +HEADERS += \ + ui/mainwindow.h \ + panels/project.h \ + panels/effectcontrols.h \ + panels/viewer.h \ + panels/timeline.h \ + ui/sourcetable.h \ + dialogs/aboutdialog.h \ + project/media.h \ + project/footage.h \ + timeline/sequence.h \ + timeline/clip.h \ + global/config.h \ + dialogs/newsequencedialog.h \ + ui/viewerwidget.h \ + ui/viewercontainer.h \ + dialogs/exportdialog.h \ + ui/collapsiblewidget.h \ + panels/panels.h \ + rendering/exportthread.h \ + ui/timelineheader.h \ + project/previewgenerator.h \ + ui/labelslider.h \ + dialogs/preferencesdialog.h \ + ui/audiomonitor.h \ + undo/undo.h \ + ui/comboboxex.h \ + ui/colorbutton.h \ + dialogs/replaceclipmediadialog.h \ + ui/keyframeview.h \ + ui/texteditex.h \ + dialogs/demonotice.h \ + timeline/marker.h \ + timeline/selection.h \ + dialogs/speeddialog.h \ + dialogs/mediapropertiesdialog.h \ + project/projectmodel.h \ + project/loadthread.h \ + dialogs/loaddialog.h \ + global/debug.h \ + global/path.h \ + effects/internal/transformeffect.h \ + effects/internal/solideffect.h \ + effects/internal/texteffect.h \ + effects/internal/timecodeeffect.h \ + effects/internal/audionoiseeffect.h \ + effects/internal/paneffect.h \ + effects/internal/toneeffect.h \ + effects/internal/volumeeffect.h \ + effects/internal/shakeeffect.h \ + effects/internal/linearfadetransition.h \ + effects/internal/crossdissolvetransition.h \ + effects/internal/exponentialfadetransition.h \ + effects/internal/logarithmicfadetransition.h \ + effects/internal/cornerpineffect.h \ + global/math.h \ + effects/effectrow.h \ + effects/internal/cubetransition.h \ + effects/effectgizmo.h \ + ui/resizablescrollbar.h \ + ui/sourceiconview.h \ + project/sourcescommon.h \ + ui/keyframenavigator.h \ + panels/grapheditor.h \ + ui/graphview.h \ + ui/keyframedrawing.h \ + ui/clickablelabel.h \ + effects/keyframe.h \ + ui/rectangleselect.h \ + dialogs/actionsearch.h \ + ui/embeddedfilechooser.h \ + effects/internal/fillleftrighteffect.h \ + effects/internal/voideffect.h \ + dialogs/texteditdialog.h \ + dialogs/debugdialog.h \ + ui/viewerwindow.h \ + project/projectfilter.h \ + effects/effectloaders.h \ + effects/internal/vsthost.h \ + ui/flowlayout.h \ + dialogs/proxydialog.h \ + project/proxygenerator.h \ + dialogs/advancedvideodialog.h \ + ui/cursors.h \ + ui/menuhelper.h \ + global/global.h \ + project/projectelements.h \ + ui/focusfilter.h \ + undo/comboaction.h \ + ui/mediaiconservice.h \ + ui/panel.h \ + effects/internal/dropshadoweffect.h \ + rendering/renderfunctions.h \ + rendering/renderthread.h \ + rendering/clipqueue.h \ + rendering/cacher.h \ + rendering/audio.h \ + dialogs/clippropertiesdialog.h \ + rendering/framebufferobject.h \ + ui/updatenotification.h \ + ui/icons.h \ + effects/fields/doublefield.h \ + effects/fields/fontfield.h \ + effects/effectfield.h \ + effects/effectfields.h \ + effects/fields/stringfield.h \ + effects/fields/filefield.h \ + effects/fields/labelfield.h \ + ui/effectui.h \ + effects/fields/boolfield.h \ + effects/fields/buttonfield.h \ + effects/fields/colorfield.h \ + effects/fields/combofield.h \ + effects/transition.h \ + ui/styling.h \ + undo/undostack.h \ + effects/internal/richtexteffect.h \ + ui/blur.h \ + ui/menu.h \ + rendering/qopenglshaderprogramptr.h \ + timeline/mediaimportdata.h \ + dialogs/autocutsilencedialog.h \ + ui/columnedgridlayout.h \ + rendering/shadergenerators.h \ + global/timing.h \ + rendering/pixelformats.h \ + timeline/ghost.h \ + timeline/timelinefunctions.h \ + timeline/track.h \ + timeline/tracklist.h \ + project/savethread.h \ + project/projectfunctions.h \ + ui/timelinearea.h \ + timeline/timelineshared.h \ + ui/timelineview.h \ + ui/timelinelabel.h \ + global/clipboard.h \ + timeline/timelinetools.h \ + ui/waveform.h \ + panels/nodeeditor.h \ + ui/nodeview.h \ + ui/nodeui.h \ + panels/effectspanel.h \ + nodes/nodedatatypes.h \ + nodes/inputs.h \ + nodes/inputs/boolinput.h \ + nodes/inputs/comboinput.h \ + nodes/inputs/colorinput.h \ + nodes/inputs/stringinput.h \ + nodes/inputs/fontinput.h \ + nodes/inputs/vecinput.h \ + nodes/widgets/labelwidget.h \ + nodes/inputs/fileinput.h \ + nodes/widgets/buttonwidget.h \ + nodes/nodegraph.h \ + nodes/nodes/nodemedia.h \ + nodes/nodes/nodeimageoutput.h \ + nodes/nodes/nodeshader.h \ + nodes/nodes.h \ + decoders/ffmpegdecoder.h \ + decoders/decoder.h \ + nodes/node.h \ + timeline/tracktypes.h \ + nodes/nodeedge.h \ + ui/nodeedgeui.h + +FORMS += + +TRANSLATIONS += \ + ts/olive_de.ts \ + ts/olive_es.ts \ + ts/olive_fr.ts \ + ts/olive_it.ts \ + ts/olive_cs.ts \ + ts/olive_ar.ts \ + ts/olive_ru.ts \ + ts/olive_uk.ts \ + ts/olive_bs.ts \ + ts/olive_sr.ts \ + ts/olive_id.ts + +win32 { + CONFIG(debug, debug|release) { + CONFIG += console + } + + RC_FILE = packaging/windows/resources.rc + LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lOpenColorIO -lopengl32 -luser32 +} + +mac { + LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lOpenColorIO -framework CoreFoundation + ICON = packaging/macos/olive.icns + INCLUDEPATH = /usr/local/include +} + +unix:!mac { + CONFIG += link_pkgconfig + PKGCONFIG += libavutil libavformat libavcodec libavfilter libswscale libswresample OpenColorIO +} + +RESOURCES += \ + icons/icons.qrc \ + effects/internal/internalshaders.qrc \ + cursors/cursors.qrc + +unix:!mac:isEmpty(PREFIX) { + PREFIX = /usr/local +} + +unix:!mac:target.path = $$PREFIX/bin + +effects.files = $$PWD/effects/shaders/* +unix:!mac:effects.path = $$PREFIX/share/olive-editor/effects + +translations.files = $$PWD/ts/*.qm +unix:!mac:translations.path = $$PREFIX/share/olive-editor/ts + +unix:!mac { + metainfo.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.appdata.xml + metainfo.path = $$PREFIX/share/metainfo + desktop.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.desktop + desktop.path = $$PREFIX/share/applications + mime.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.xml + mime.path = $$PREFIX/share/mime/packages + icon16.files = $$PWD/packaging/linux/icons/16x16/org.olivevideoeditor.Olive.png + icon16.path = $$PREFIX/share/icons/hicolor/16x16/apps + icon32.files = $$PWD/packaging/linux/icons/32x32/org.olivevideoeditor.Olive.png + icon32.path = $$PREFIX/share/icons/hicolor/32x32/apps + icon48.files = $$PWD/packaging/linux/icons/48x48/org.olivevideoeditor.Olive.png + icon48.path = $$PREFIX/share/icons/hicolor/48x48/apps + icon64.files = $$PWD/packaging/linux/icons/64x64/org.olivevideoeditor.Olive.png + icon64.path = $$PREFIX/share/icons/hicolor/64x64/apps + icon128.files = $$PWD/packaging/linux/icons/128x128/org.olivevideoeditor.Olive.png + icon128.path = $$PREFIX/share/icons/hicolor/128x128/apps + icon256.files = $$PWD/packaging/linux/icons/256x256/org.olivevideoeditor.Olive.png + icon256.path = $$PREFIX/share/icons/hicolor/256x256/apps + icon512.files = $$PWD/packaging/linux/icons/512x512/org.olivevideoeditor.Olive.png + icon512.path = $$PREFIX/share/icons/hicolor/512x512/apps + mime16.files = $$PWD/packaging/linux/icons/16x16/application-vnd.olive-project.png + mime16.path = $$PREFIX/share/icons/hicolor/16x16/mimetypes + mime32.files = $$PWD/packaging/linux/icons/32x32/application-vnd.olive-project.png + mime32.path = $$PREFIX/share/icons/hicolor/32x32/mimetypes + mime48.files = $$PWD/packaging/linux/icons/48x48/application-vnd.olive-project.png + mime48.path = $$PREFIX/share/icons/hicolor/48x48/mimetypes + mime64.files = $$PWD/packaging/linux/icons/64x64/application-vnd.olive-project.png + mime64.path = $$PREFIX/share/icons/hicolor/64x64/mimetypes + mime128.files = $$PWD/packaging/linux/icons/128x128/application-vnd.olive-project.png + mime128.path = $$PREFIX/share/icons/hicolor/128x128/mimetypes + mime256.files = $$PWD/packaging/linux/icons/256x256/application-vnd.olive-project.png + mime256.path = $$PREFIX/share/icons/hicolor/256x256/mimetypes + mime512.files = $$PWD/packaging/linux/icons/512x512/application-vnd.olive-project.png + mime512.path = $$PREFIX/share/icons/hicolor/512x512/mimetypes + INSTALLS += target effects translations metainfo desktop mime icon16 icon32 icon48 icon64 icon128 icon256 icon512 mime16 mime32 mime48 mime64 mime128 mime256 mime512 + INSTALLS += target effects translations metainfo desktop mime icon16 icon32 icon48 icon64 icon128 icon256 icon512 +} diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp index bb5e1ce9b..b06716e8b 100644 --- a/panels/effectcontrols.cpp +++ b/panels/effectcontrols.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -32,7 +32,8 @@ #include #include "panels/panels.h" -#include "effects/effect.h" +#include "nodes/node.h" +#include "effects/effectloaders.h" #include "effects/transition.h" #include "timeline/clip.h" #include "ui/collapsiblewidget.h" @@ -46,8 +47,9 @@ #include "ui/viewerwidget.h" #include "ui/menuhelper.h" #include "ui/icons.h" -#include "project/clipboard.h" +#include "global/clipboard.h" #include "global/config.h" +#include "global/global.h" #include "ui/timelineheader.h" #include "ui/keyframeview.h" #include "ui/resizablescrollbar.h" @@ -55,7 +57,7 @@ #include "ui/menu.h" EffectControls::EffectControls(QWidget *parent) : - Panel(parent), + EffectsPanel(parent), zoom(1) { setup_ui(); @@ -80,53 +82,12 @@ EffectControls::EffectControls(QWidget *parent) : connect(scrollArea->verticalScrollBar(), SIGNAL(valueChanged(int)), verticalScrollBar, SLOT(setValue(int))); } -EffectControls::~EffectControls() -{ - Clear(true); -} - -bool EffectControls::keyframe_focus() { - return headers->hasFocus() || keyframeView->hasFocus(); -} - void EffectControls::set_zoom(bool in) { zoom *= (in) ? 2 : 0.5; update_keyframes(); - scroll_to_frame(olive::ActiveSequence->playhead); -} -void EffectControls::menu_select(QAction* q) { - ComboAction* ca = new ComboAction(); - for (int i=0;itrack() < 0) == (effect_menu_subtype == EFFECT_TYPE_VIDEO)) { - const EffectMeta* meta = reinterpret_cast(q->data().value()); - if (effect_menu_type == EFFECT_TYPE_TRANSITION) { - if (c->opening_transition == nullptr) { - ca->append(new AddTransitionCommand(c, - nullptr, - nullptr, - meta, - olive::CurrentConfig.default_transition_length)); - } - if (c->closing_transition == nullptr) { - ca->append(new AddTransitionCommand(nullptr, - c, - nullptr, - meta, - olive::CurrentConfig.default_transition_length)); - } - } else { - ca->append(new AddEffectCommand(c, nullptr, meta)); - } - } - } - olive::UndoStack.push(ca); - if (effect_menu_type == EFFECT_TYPE_TRANSITION) { - update_ui(true); - } else { - Reload(); - panel_sequence_viewer->viewer_widget->frame_update(); + if (!selected_clips_.isEmpty()) { + scroll_to_frame(selected_clips_.first()->track()->sequence()->playhead); } } @@ -144,182 +105,10 @@ void EffectControls::delete_selected_keyframes() { keyframeView->delete_selected_keyframes(); } -void EffectControls::copy(bool del) { - bool cleared = false; - - ComboAction* ca = nullptr; - if (del) { - ca = new ComboAction(); - } - - for (int i=0;iIsSelected()) { - Effect* e = open_effects_.at(i)->GetEffect(); - - if (e->meta->type == EFFECT_TYPE_EFFECT) { - - if (!cleared) { - clear_clipboard(); - cleared = true; - clipboard_type = CLIPBOARD_TYPE_EFFECT; - } - - clipboard.append(e->copy(nullptr)); - - if (del) { - - DeleteEffect(ca, e); - - } - - } - } - } - - if (del) { - if (ca->hasActions()) { - olive::UndoStack.push(ca); - } else { - delete ca; - } - } -} - void EffectControls::scroll_to_frame(long frame) { scroll_to_frame_internal(horizontalScrollBar, frame - keyframeView->visible_in, zoom, keyframeView->width()); } -void EffectControls::cut() { - copy(true); -} - -void EffectControls::show_effect_menu(int type, int subtype) { - effect_menu_type = type; - effect_menu_subtype = subtype; - - effects_loaded.lock(); - - Menu effects_menu(this); - effects_menu.setToolTipsVisible(true); - - for (int i=0;isetText(em.name); - action->setData(reinterpret_cast(&em)); - if (!em.tooltip.isEmpty()) { - action->setToolTip(em.tooltip); - } - - QMenu* parent = &effects_menu; - if (!em.category.isEmpty()) { - bool found = false; - for (int j=0;jmenu() != nullptr) { - if (action->menu()->title() == em.category) { - parent = action->menu(); - found = true; - break; - } - } - } - if (!found) { - parent = new Menu(&effects_menu); - parent->setToolTipsVisible(true); - parent->setTitle(em.category); - - bool found = false; - for (int i=0;itext() > em.category) { - effects_menu.insertMenu(comp_action, parent); - found = true; - break; - } - } - if (!found) effects_menu.addMenu(parent); - } - } - - bool found = false; - for (int i=0;iactions().size();i++) { - QAction* comp_action = parent->actions().at(i); - if (comp_action->text() > action->text()) { - parent->insertAction(comp_action, action); - found = true; - break; - } - } - if (!found) parent->addAction(action); - } - } - - effects_loaded.unlock(); - - connect(&effects_menu, SIGNAL(triggered(QAction*)), this, SLOT(menu_select(QAction*))); - effects_menu.exec(QCursor::pos()); -} - -void EffectControls::Clear(bool clear_cache) { - // clear existing clips - deselect_all_effects(nullptr); - - for (int i=0;iSetEffects(open_effects_); - - vcontainer->setVisible(false); - acontainer->setVisible(false); - headers->setVisible(false); - keyframeView->setEnabled(false); - - if (clear_cache) { - selected_clips_.clear(); - } - - UpdateTitle(); -} - -bool EffectControls::IsEffectSelected(Effect *e) -{ - for (int i=0;iGetEffect() == e && open_effects_.at(i)->IsSelected()) { - return true; - } - } - return false; -} - -void EffectControls::deselect_all_effects(QWidget* sender) { - - for (int i=0;iheader_click(false, false); - } - } - - if (panel_sequence_viewer != nullptr) { - panel_sequence_viewer->viewer_widget->update(); - } -} - -void EffectControls::open_effect(QVBoxLayout* layout, Effect* e) { - EffectUI* container = new EffectUI(e); - - connect(container, SIGNAL(CutRequested()), this, SLOT(cut())); - connect(container, SIGNAL(CopyRequested()), this, SLOT(copy())); - connect(container, SIGNAL(deselect_others(QWidget*)), this, SLOT(deselect_all_effects(QWidget*))); - - open_effects_.append(container); - - layout->addWidget(container); -} - void EffectControls::UpdateTitle() { if (selected_clips_.isEmpty()) { setWindowTitle(panel_name + tr("(none)")); @@ -544,149 +333,51 @@ void EffectControls::effects_area_context_menu() { menu.exec(QCursor::pos()); } -void EffectControls::DeleteEffect(ComboAction* ca, Effect* effect_ref) { - if (effect_ref->meta->type == EFFECT_TYPE_EFFECT) { - - ca->append(new EffectDeleteCommand(effect_ref)); - - } else if (effect_ref->meta->type == EFFECT_TYPE_TRANSITION) { - - // Retrieve shared ptr for this transition - - Clip* attached_clip = effect_ref->parent_clip; - - TransitionPtr t = nullptr; - - if (attached_clip->opening_transition.get() == effect_ref) { - - t = attached_clip->opening_transition; - - } else if (attached_clip->closing_transition.get() == effect_ref) { - - t = attached_clip->closing_transition; - - } - - if (t == nullptr) { - - qWarning() << "Failed to delete transition, couldn't find clip link."; - - } else { - - ca->append(new DeleteTransitionCommand(t)); - - } - - } -} - -void EffectControls::DeleteSelectedEffects() { - ComboAction* ca = new ComboAction(); - - for (int i=0;iIsSelected()) { - DeleteEffect(ca, open_effects_.at(i)->GetEffect()); - } - } - - if (ca->hasActions()) { - olive::UndoStack.push(ca); - update_ui(true); - } else { - delete ca; - } -} - -void EffectControls::Reload() { - Clear(false); - Load(); -} - -void EffectControls::SetClips() +bool EffectControls::focused() { - Clear(true); - - if (olive::ActiveSequence == nullptr) { - selected_clips_.clear(); - } else { - // replace clip vector - selected_clips_ = olive::ActiveSequence->SelectedClips(false); - - Load(); + if (this->hasFocus() + || headers->hasFocus() + || keyframeView->hasFocus()) { + return true; } + + return EffectsPanel::focused(); } -void EffectControls::Load() { - bool graph_editor_row_is_still_active = false; +void EffectControls::video_effect_click() { + olive::Global->ShowEffectMenu(EFFECT_TYPE_EFFECT, olive::kTypeVideo, selected_clips_); +} - // load in new clips - for (int i=0;iShowEffectMenu(EFFECT_TYPE_EFFECT, olive::kTypeAudio, selected_clips_); +} - QVBoxLayout* layout; +void EffectControls::video_transition_click() { + olive::Global->ShowEffectMenu(EFFECT_TYPE_TRANSITION, olive::kTypeVideo, selected_clips_); +} - if (c->track() < 0) { - vcontainer->setVisible(true); - layout = video_effect_layout; - } else { - acontainer->setVisible(true); - layout = audio_effect_layout; - } +void EffectControls::audio_transition_click() { + olive::Global->ShowEffectMenu(EFFECT_TYPE_TRANSITION, olive::kTypeAudio, selected_clips_); +} - // Create a list of the effects we'll open - QVector effects_to_open; +void EffectControls::resizeEvent(QResizeEvent*) { + update_scrollbar(); +} - // Determine based on the current selections whether to load all effects or just the transitions - bool whole_clip_is_selected = c->IsSelected(); +void EffectControls::ClearEvent() +{ + keyframeView->SetEffects(open_effects_); - if (whole_clip_is_selected) { - for (int j=0;jeffects.size();j++) { - effects_to_open.append(c->effects.at(j).get()); - } - } - if (c->opening_transition != nullptr - && (whole_clip_is_selected || c->sequence->IsTransitionSelected(c->opening_transition.get()))) { - effects_to_open.append(c->opening_transition.get()); - } - if (c->closing_transition != nullptr - && (whole_clip_is_selected || c->sequence->IsTransitionSelected(c->closing_transition.get()))) { - effects_to_open.append(c->closing_transition.get()); - } + vcontainer->setVisible(false); + acontainer->setVisible(false); + headers->setVisible(false); + keyframeView->setEnabled(false); - for (int j=0;jGetEffect()->meta == effects_to_open.at(j)->meta - && !open_effects_.at(k)->IsAttachedToClip(c)) { - - open_effects_.at(k)->AddAdditionalEffect(effects_to_open.at(j)); - - already_opened = true; - - break; - } - } - - if (!already_opened) { - open_effect(layout, effects_to_open.at(j)); - } - - // Check if one of the open effects contains the row currently active in the graph editor. If not, we'll have - // to clear the graph editor later. - if (!graph_editor_row_is_still_active) { - for (int k=0;krow_count();k++) { - EffectRow* row = effects_to_open.at(j)->row(k); - if (row == panel_graph_editor->get_row()) { - graph_editor_row_is_still_active = true; - break; - } - } - } - } - } + UpdateTitle(); +} +void EffectControls::LoadEvent() +{ keyframeView->SetEffects(open_effects_); if (selected_clips_.size() > 0) { @@ -697,47 +388,29 @@ void EffectControls::Load() { QTimer::singleShot(50, this, SLOT(queue_post_update())); } - // If the graph editor's currently active row is not part of the current effects, clear it - if (!graph_editor_row_is_still_active) { - panel_graph_editor->set_row(nullptr); + for (int i=0;iGetEffect(); + + if (e->subtype() == olive::kTypeVideo) { + vcontainer->setVisible(true); + layout = video_effect_layout; + } else if (e->subtype() == olive::kTypeAudio) { + acontainer->setVisible(true); + layout = audio_effect_layout; + } + + if (layout != nullptr) { + layout->addWidget(container); + } } UpdateTitle(); update_keyframes(); } -void EffectControls::video_effect_click() { - show_effect_menu(EFFECT_TYPE_EFFECT, EFFECT_TYPE_VIDEO); -} - -void EffectControls::audio_effect_click() { - show_effect_menu(EFFECT_TYPE_EFFECT, EFFECT_TYPE_AUDIO); -} - -void EffectControls::video_transition_click() { - show_effect_menu(EFFECT_TYPE_TRANSITION, EFFECT_TYPE_VIDEO); -} - -void EffectControls::audio_transition_click() { - show_effect_menu(EFFECT_TYPE_TRANSITION, EFFECT_TYPE_AUDIO); -} - -void EffectControls::resizeEvent(QResizeEvent*) { - update_scrollbar(); -} - -bool EffectControls::is_focused() { - if (this->hasFocus()) return true; - - for (int i=0;iIsFocused()) { - return true; - } - } - - return false; -} - EffectsArea::EffectsArea(QWidget* parent) : QWidget(parent) {} diff --git a/panels/effectcontrols.h b/panels/effectcontrols.h index 6dbe1278d..94f41eb72 100644 --- a/panels/effectcontrols.h +++ b/panels/effectcontrols.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -32,11 +32,12 @@ #include #include "project/projectelements.h" +#include "timeline/track.h" #include "ui/timelineheader.h" #include "ui/keyframeview.h" #include "ui/resizablescrollbar.h" #include "ui/keyframeview.h" -#include "ui/panel.h" +#include "effectspanel.h" #include "ui/effectui.h" class EffectsArea : public QWidget { @@ -52,24 +53,14 @@ public slots: void receive_wheel_event(QWheelEvent* e); }; -class EffectControls : public Panel +class EffectControls : public EffectsPanel { Q_OBJECT public: explicit EffectControls(QWidget *parent = nullptr); - virtual ~EffectControls() override; - - void Reload(); - void SetClips(); - void Clear(bool clear_cache = true); - - bool IsEffectSelected(Effect* e); - - void DeleteSelectedEffects(); - bool is_focused(); + virtual bool focused() override; void set_zoom(bool in); - bool keyframe_focus(); void delete_selected_keyframes(); void scroll_to_frame(long frame); @@ -78,25 +69,19 @@ public: ResizableScrollBar* horizontalScrollBar; QScrollBar* verticalScrollBar; - QMutex effects_loaded; - virtual void Retranslate() override; virtual void LoadLayoutState(const QByteArray& data) override; virtual QByteArray SaveLayoutState() override; public slots: - void cut(); - void copy(bool del = false); void update_keyframes(); private slots: - void menu_select(QAction* q); - void video_effect_click(); void audio_effect_click(); void video_transition_click(); void audio_transition_click(); - void deselect_all_effects(QWidget*); + void update_scrollbar(); void queue_post_update(); @@ -104,23 +89,14 @@ private slots: void effects_area_context_menu(); protected: virtual void resizeEvent(QResizeEvent *event) override; + virtual void ClearEvent() override; + virtual void LoadEvent() override; private: - QVector selected_clips_; - QVector open_effects_; - - void Load(); - - void DeleteEffect(ComboAction* ca, Effect* effect_ref); - - void show_effect_menu(int type, int subtype); void load_keyframes(); - void open_effect(QVBoxLayout* hlayout, Effect *e); void UpdateTitle(); void setup_ui(); - int effect_menu_type; - int effect_menu_subtype; QString panel_name; QWidget* video_effect_area; diff --git a/panels/effectspanel.cpp b/panels/effectspanel.cpp new file mode 100644 index 000000000..fab4f34ef --- /dev/null +++ b/panels/effectspanel.cpp @@ -0,0 +1,275 @@ +#include "effectspanel.h" + +#include "timeline.h" +#include "global/clipboard.h" +#include "panels.h" + +EffectsPanel::EffectsPanel(QWidget *parent) : + Panel(parent) +{ + +} + +EffectsPanel::~EffectsPanel() +{ + Clear(true); +} + +void EffectsPanel::Clear(bool clear_cache) { + AboutToClearEvent(); + + // clear existing clips + deselect_all_effects(nullptr); + + for (int i=0;iIsFocused()) { + return true; + } + } + + return Panel::focused(); +} + +void EffectsPanel::ClearEvent() {} + +void EffectsPanel::LoadEvent() {} + +void EffectsPanel::Reload() { + Clear(false); + Load(); +} + +void EffectsPanel::SetClips() +{ + Clear(true); + + Sequence* top_sequence = Timeline::GetTopSequence().get(); + + if (top_sequence == nullptr) { + selected_clips_.clear(); + } else { + // replace clip vector + selected_clips_ = top_sequence->SelectedClips(false); + + Load(); + } +} + +void EffectsPanel::Load() { + bool graph_editor_row_is_still_active = false; + + // load in new clips + for (int i=0;i effects_to_open; + + // Determine based on the current selections whether to load all effects or just the transitions + bool whole_clip_is_selected = c->IsSelected(); + + if (whole_clip_is_selected) { + for (int j=0;jeffects.size();j++) { + effects_to_open.append(c->effects.at(j).get()); + } + } + if (c->opening_transition != nullptr + && (whole_clip_is_selected || c->track()->IsTransitionSelected(c->opening_transition.get()))) { + effects_to_open.append(c->opening_transition.get()); + } + if (c->closing_transition != nullptr + && (whole_clip_is_selected || c->track()->IsTransitionSelected(c->closing_transition.get()))) { + effects_to_open.append(c->closing_transition.get()); + } + + for (int j=0;jGetEffect()->id() == effects_to_open.at(j)->id() + && !open_effects_.at(k)->IsAttachedToClip(c)) { + + open_effects_.at(k)->AddAdditionalEffect(effects_to_open.at(j)); + + already_opened = true; + + break; + } + } + + if (!already_opened) { + open_effect(effects_to_open.at(j)); + } + + // Check if one of the open effects contains the row currently active in the graph editor. If not, we'll have + // to clear the graph editor later. + if (!graph_editor_row_is_still_active) { + for (int k=0;krow_count();k++) { + EffectRow* row = effects_to_open.at(j)->row(k); + if (row == panel_graph_editor->get_row()) { + graph_editor_row_is_still_active = true; + break; + } + } + } + } + } + + // If the graph editor's currently active row is not part of the current effects, clear it + if (!graph_editor_row_is_still_active) { + panel_graph_editor->set_row(nullptr); + } + + LoadEvent(); +} + +bool EffectsPanel::IsEffectSelected(Node *e) +{ + for (int i=0;iGetEffect() == e && open_effects_.at(i)->IsSelected()) { + return true; + } + } + return false; +} + +void EffectsPanel::deselect_all_effects(QWidget* sender) { + + for (int i=0;iDeselect(); + } + } + + if (panel_sequence_viewer != nullptr) { + panel_sequence_viewer->viewer_widget()->update(); + } +} + +void EffectsPanel::cut() { + copy(true); +} + +void EffectsPanel::copy(bool del) { + bool cleared = false; + + ComboAction* ca = nullptr; + if (del) { + ca = new ComboAction(); + } + + for (int i=0;iIsSelected()) { + Node* e = open_effects_.at(i)->GetEffect(); + + if (e->type() == EFFECT_TYPE_EFFECT) { + + if (!cleared) { + olive::clipboard.Clear(); + cleared = true; + olive::clipboard.SetType(Clipboard::CLIPBOARD_TYPE_EFFECT); + } + + olive::clipboard.Append(e->copy(nullptr)); + + if (del) { + + DeleteEffect(ca, e); + + } + + } + } + } + + if (del) { + if (ca->hasActions()) { + olive::undo_stack.push(ca); + } else { + delete ca; + } + } +} + +void EffectsPanel::AboutToClearEvent() +{ +} + +void EffectsPanel::DeleteEffect(ComboAction* ca, Node* effect_ref) { + if (effect_ref->type() == EFFECT_TYPE_EFFECT) { + + ca->append(new EffectDeleteCommand(effect_ref)); + + } else if (effect_ref->type() == EFFECT_TYPE_TRANSITION) { + + // Retrieve shared ptr for this transition + + Clip* attached_clip = effect_ref->parent_clip; + + TransitionPtr t = nullptr; + + if (attached_clip->opening_transition.get() == effect_ref) { + + t = attached_clip->opening_transition; + + } else if (attached_clip->closing_transition.get() == effect_ref) { + + t = attached_clip->closing_transition; + + } + + if (t == nullptr) { + + qWarning() << "Failed to delete transition, couldn't find clip link."; + + } else { + + ca->append(new DeleteTransitionCommand(t)); + + } + + } +} + +void EffectsPanel::DeleteSelectedEffects() { + ComboAction* ca = new ComboAction(); + + for (int i=0;iIsSelected()) { + DeleteEffect(ca, open_effects_.at(i)->GetEffect()); + } + } + + if (ca->hasActions()) { + olive::undo_stack.push(ca); + update_ui(true); + } else { + delete ca; + } +} + +void EffectsPanel::open_effect(Node* e) { + EffectUI* container = new EffectUI(e); + + connect(container, SIGNAL(CutRequested()), this, SLOT(cut())); + connect(container, SIGNAL(CopyRequested()), this, SLOT(copy())); + connect(container, SIGNAL(deselect_others(QWidget*)), this, SLOT(deselect_all_effects(QWidget*))); + + open_effects_.append(container); +} diff --git a/panels/effectspanel.h b/panels/effectspanel.h new file mode 100644 index 000000000..1100dd94f --- /dev/null +++ b/panels/effectspanel.h @@ -0,0 +1,42 @@ +#ifndef EFFECTSPANEL_H +#define EFFECTSPANEL_H + +#include "ui/panel.h" +#include "ui/effectui.h" +#include "timeline/clip.h" + +class EffectsPanel : public Panel +{ + Q_OBJECT +public: + explicit EffectsPanel(QWidget *parent = nullptr); + virtual ~EffectsPanel() override; + + void Reload(); + void SetClips(); + void Clear(bool clear_cache = true); + + virtual bool focused() override; + + bool IsEffectSelected(Node* e); + void DeleteSelectedEffects(); + +public slots: + void cut(); + void copy(bool del = false); +protected: + virtual void AboutToClearEvent(); + virtual void ClearEvent(); + virtual void LoadEvent(); + + QVector selected_clips_; + QVector open_effects_; +private: + void Load(); + void open_effect(Node *e); + void DeleteEffect(ComboAction* ca, Node* effect_ref); +private slots: + void deselect_all_effects(QWidget*); +}; + +#endif // EFFECTSPANEL_H diff --git a/panels/grapheditor.cpp b/panels/grapheditor.cpp index bf07f4a31..9622a5831 100644 --- a/panels/grapheditor.cpp +++ b/panels/grapheditor.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -27,10 +27,10 @@ #include "ui/keyframenavigator.h" #include "ui/timelineheader.h" -#include "ui/timelinetools.h" +#include "timeline/timelinetools.h" #include "ui/labelslider.h" #include "ui/graphview.h" -#include "effects/effect.h" +#include "nodes/node.h" #include "effects/effectfields.h" #include "effects/effectrow.h" #include "timeline/clip.h" @@ -150,7 +150,7 @@ void GraphEditor::update_panel() { for (int i=0;iFieldCount();i++) { EffectField* field = row->Field(i); if (field->type() == EffectField::EFFECT_FIELD_DOUBLE) { - field->UpdateWidgetValue(field_sliders_.at(slider_index), field->Now()); + field->UpdateWidgetValue(field_sliders_.at(slider_index), row->GetParentEffect()->Now()); slider_index++; } } @@ -161,6 +161,11 @@ void GraphEditor::update_panel() { } } +bool GraphEditor::focused() +{ + return hasFocus() || view->hasFocus() || header->hasFocus(); +} + void GraphEditor::set_row(EffectRow *r) { for (int i=0;isetText(row->GetParentEffect()->parent_clip->name() - + " :: " + row->GetParentEffect()->meta->name + + " :: " + row->GetParentEffect()->name() + " :: " + row->name()); header->set_visible_in(r->GetParentEffect()->parent_clip->timeline_in()); @@ -220,14 +225,6 @@ void GraphEditor::set_row(EffectRow *r) { update_panel(); } -bool GraphEditor::view_is_focused() { - return view->hasFocus() || header->hasFocus(); -} - -bool GraphEditor::view_is_under_mouse() { - return view->underMouse() || header->underMouse(); -} - void GraphEditor::delete_selected_keys() { view->delete_selected_keys(); } diff --git a/panels/grapheditor.h b/panels/grapheditor.h index d7fb2fdc7..e0d7355dc 100644 --- a/panels/grapheditor.h +++ b/panels/grapheditor.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -41,8 +41,7 @@ public: void set_row(EffectRow* r); void update_panel(); - bool view_is_focused(); - bool view_is_under_mouse(); + virtual bool focused() override; void delete_selected_keys(); void select_all(); diff --git a/panels/nodeeditor.cpp b/panels/nodeeditor.cpp new file mode 100644 index 000000000..2d632ff5d --- /dev/null +++ b/panels/nodeeditor.cpp @@ -0,0 +1,214 @@ +#include "nodeeditor.h" + +#include +#include +#include +#include +#include +#include + +#include "global/global.h" + +NodeEditor::NodeEditor(QWidget *parent) : + EffectsPanel(parent), + view_(&scene_) +{ + setWindowTitle(tr("Node Editor")); + resize(720, 480); + + QWidget* central_widget = new QWidget(); + setWidget(central_widget); + + QSplitter* splitter = new QSplitter(); + splitter->setOrientation(Qt::Horizontal); + splitter->setChildrenCollapsible(false); + + splitter->addWidget(&view_); + + QSizePolicy view_policy; + view_policy.setHorizontalStretch(1); + view_.setSizePolicy(view_policy); + view_.setInteractive(true); + view_.setDragMode(QGraphicsView::RubberBandDrag); + + prop_layout_ = new QVBoxLayout(&props_); + prop_layout_->setMargin(0); + prop_layout_->setSpacing(0); + prop_layout_->addStretch(); + splitter->addWidget(&props_); + + QVBoxLayout* layout = new QVBoxLayout(central_widget); + layout->setSpacing(0); + layout->setMargin(0); + + layout->addWidget(splitter); + + connect(&scene_, SIGNAL(changed(const QList&)), this, SLOT(ItemsChanged())); + connect(&scene_, SIGNAL(selectionChanged()), this, SLOT(UpdateNodeProperties())); + connect(&view_, SIGNAL(RequestContextMenu()), this, SLOT(ContextMenu())); +} + +NodeEditor::~NodeEditor() +{ + Clear(true); +} + +void NodeEditor::Retranslate() +{ +} + +void NodeEditor::LoadEvent() +{ + if (!open_effects_.isEmpty()) { + QVector added_edges; + + Clip* first_clip = open_effects_.first()->GetEffect()->parent_clip; + + for (int i=0;iGetEffect(); + + if (node->parent_clip == first_clip) { + NodeUI* node_ui = new NodeUI(); + + effect_ui->SetSelectable(false); + + node_ui->SetNode(node); + node_ui->AddToScene(&scene_); + node_ui->setPos(effect_ui->GetEffect()->pos()); + + nodes_.append(node_ui); + + effect_ui->setVisible(false); + prop_layout_->insertWidget(prop_layout_->count()-1, effect_ui); + } + } + } + + LoadEdges(); +} + +void NodeEditor::AboutToClearEvent() +{ + foreach (NodeUI* node, nodes_) { + delete node; + } + + nodes_.clear(); + + ClearEdges(); +} + +void NodeEditor::ClearEdges() +{ + foreach (NodeEdgeUI* edge, edges_) { + scene_.removeItem(edge); + delete edge; + } + + edges_.clear(); + + DisconnectAllRows(); +} + +void NodeEditor::LoadEdges() +{ + if (!open_effects_.isEmpty()) { + QVector added_edges; + + Clip* first_clip = open_effects_.first()->GetEffect()->parent_clip; + + for (int i=0;iGetEffect(); + + if (n->parent_clip == first_clip) { + + // Connect all rows to this + for (int j=0;jrow_count();j++) { + ConnectRow(n->row(j)); + } + + // Get node edges + QVector edges = n->GetAllEdges(); + for (int j=0;jadjust(); + } + + foreach (NodeUI* node, nodes_) { + node->GetNode()->SetPos(node->pos()); + } +} + +void NodeEditor::ReloadEdges() +{ + ClearEdges(); + LoadEdges(); +} + +void NodeEditor::ContextMenu() +{ + if (!open_effects_.isEmpty()) { + Clip* c = open_effects_.first()->GetEffect()->parent_clip; + olive::Global->ShowEffectMenu(EFFECT_TYPE_EFFECT, c->type(), {c}); + } +} + +void NodeEditor::UpdateNodeProperties() +{ + // Find matching widget + for (int j=0;jsetVisible(false); + } + + QList sel = scene_.selectedItems(); + for (int i=0;i(sel.at(i)); + + if (node_ui != nullptr) { + for (int j=0;jGetEffect() == node_ui->GetNode()) { + open_effects_.at(j)->setVisible(true); + break; + } + } + } + } +} diff --git a/panels/nodeeditor.h b/panels/nodeeditor.h new file mode 100644 index 000000000..f38ff4ee9 --- /dev/null +++ b/panels/nodeeditor.h @@ -0,0 +1,46 @@ +#ifndef NODEEDITOR_H +#define NODEEDITOR_H + +#include + +#include "effectspanel.h" +#include "ui/nodeview.h" +#include "ui/nodeui.h" +#include "ui/nodeedgeui.h" + +class NodeEditor : public EffectsPanel { + Q_OBJECT +public: + NodeEditor(QWidget* parent = nullptr); + virtual ~NodeEditor() override; + + virtual void Retranslate() override; + +protected: + virtual void LoadEvent() override; + virtual void AboutToClearEvent() override; + +private: + QGraphicsScene scene_; + NodeView view_; + QWidget props_; + QVector nodes_; + QVector edges_; + QVector connected_rows_; + QVBoxLayout* prop_layout_; + QVector nodes_open_in_props_; + + void ClearEdges(); + void LoadEdges(); + + void ConnectRow(EffectRow* row); + void DisconnectAllRows(); + +private slots: + void ItemsChanged(); + void ReloadEdges(); + void ContextMenu(); + void UpdateNodeProperties(); +}; + +#endif // NODEEDITOR_H diff --git a/panels/panels.cpp b/panels/panels.cpp index 3c4a2d057..2145e7478 100644 --- a/panels/panels.cpp +++ b/panels/panels.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -24,59 +24,49 @@ #include "timeline/clip.h" #include "effects/transition.h" #include "global/config.h" -#include "effects/effectloaders.h" #include "global/debug.h" +#include "global/math.h" #include #include -Project* panel_project = nullptr; +QVector panel_project; EffectControls* panel_effect_controls = nullptr; Viewer* panel_sequence_viewer = nullptr; Viewer* panel_footage_viewer = nullptr; -Timeline* panel_timeline = nullptr; +QVector panel_timeline; GraphEditor* panel_graph_editor = nullptr; +NodeEditor* panel_node_editor = nullptr; void update_ui(bool modified) { if (modified) { panel_effect_controls->SetClips(); + panel_node_editor->SetClips(); } panel_effect_controls->update_keyframes(); - panel_timeline->repaint_timeline(); + for (int i=0;irepaint_timeline(); + } panel_sequence_viewer->update_viewer(); panel_graph_editor->update_panel(); } QDockWidget *get_focused_panel(bool force_hover) { QDockWidget* w = nullptr; - if (olive::CurrentConfig.hover_focus || force_hover) { - if (panel_project->underMouse()) { - w = panel_project; - } else if (panel_effect_controls->underMouse()) { - w = panel_effect_controls; - } else if (panel_sequence_viewer->underMouse()) { - w = panel_sequence_viewer; - } else if (panel_footage_viewer->underMouse()) { - w = panel_footage_viewer; - } else if (panel_timeline->underMouse()) { - w = panel_timeline; - } else if (panel_graph_editor->view_is_under_mouse()) { - w = panel_graph_editor; + if (olive::config.hover_focus || force_hover) { + for (int i=0;iunderMouse()) { + w = olive::panels.at(i); + break; + } } } if (w == nullptr) { - if (panel_project->is_focused()) { - w = panel_project; - } else if (panel_effect_controls->keyframe_focus() || panel_effect_controls->is_focused()) { - w = panel_effect_controls; - } else if (panel_sequence_viewer->is_focused()) { - w = panel_sequence_viewer; - } else if (panel_footage_viewer->is_focused()) { - w = panel_footage_viewer; - } else if (panel_timeline->focused()) { - w = panel_timeline; - } else if (panel_graph_editor->view_is_focused()) { - w = panel_graph_editor; + for (int i=0;ifocused()) { + w = olive::panels.at(i); + break; + } } } return w; @@ -88,15 +78,18 @@ void alloc_panels(QWidget* parent) { panel_footage_viewer = new Viewer(parent); panel_footage_viewer->setObjectName("footage_viewer"); panel_footage_viewer->show_videoaudio_buttons(true); - panel_project = new Project(parent); - panel_project->setObjectName("proj_root"); + Project* first_project_panel = new Project(parent); + first_project_panel->setObjectName("proj_root"); + panel_project.append(first_project_panel); panel_effect_controls = new EffectControls(parent); - EffectInit::StartLoading(); panel_effect_controls->setObjectName("fx_controls"); - panel_timeline = new Timeline(parent); - panel_timeline->setObjectName("timeline"); + Timeline* first_timeline_panel = new Timeline(parent); + first_timeline_panel->setObjectName("timeline"); + panel_timeline.append(first_timeline_panel); panel_graph_editor = new GraphEditor(parent); panel_graph_editor->setObjectName("graph_editor"); + panel_node_editor = new NodeEditor(parent); + panel_node_editor->setObjectName("node_editor"); } void free_panels() { @@ -104,12 +97,19 @@ void free_panels() { panel_sequence_viewer = nullptr; delete panel_footage_viewer; panel_footage_viewer = nullptr; - delete panel_project; - panel_project = nullptr; + + for (int i=0;i. + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -26,13 +26,15 @@ #include "viewer.h" #include "grapheditor.h" #include "project.h" +#include "nodeeditor.h" -extern Project* panel_project; +extern QVector panel_project; extern EffectControls* panel_effect_controls; extern Viewer* panel_sequence_viewer; extern Viewer* panel_footage_viewer; -extern Timeline* panel_timeline; +extern QVector panel_timeline; extern GraphEditor* panel_graph_editor; +extern NodeEditor* panel_node_editor; void update_ui(bool modified); QDockWidget* get_focused_panel(bool force_hover = false); diff --git a/panels/project.cpp b/panels/project.cpp index e4781a160..289a1e7be 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -51,10 +51,10 @@ extern "C" { #include "rendering/cacher.h" #include "dialogs/replaceclipmediadialog.h" #include "panels/effectcontrols.h" -#include "dialogs/newsequencedialog.h" #include "dialogs/mediapropertiesdialog.h" +#include "dialogs/newsequencedialog.h" #include "dialogs/loaddialog.h" -#include "project/clipboard.h" +#include "global/clipboard.h" #include "ui/sourcetable.h" #include "ui/sourceiconview.h" #include "ui/icons.h" @@ -62,14 +62,10 @@ extern "C" { #include "ui/mediaiconservice.h" #include "project/sourcescommon.h" #include "project/projectfilter.h" +#include "project/projectfunctions.h" #include "global/debug.h" #include "ui/menu.h" -#define MAXIMUM_RECENT_PROJECTS 10 // FIXME: should be configurable - -QString autorecovery_filename; -QStringList recent_projects; - Project::Project(QWidget *parent) : Panel(parent), sorter(this), @@ -87,7 +83,7 @@ Project::Project(QWidget *parent) : // optional toolbar toolbar_widget = new QWidget(); - toolbar_widget->setVisible(olive::CurrentConfig.show_project_toolbar); + toolbar_widget->setVisible(olive::config.show_project_toolbar); toolbar_widget->setObjectName("project_toolbar"); QHBoxLayout* toolbar = new QHBoxLayout(toolbar_widget); @@ -225,97 +221,6 @@ void Project::Retranslate() { setWindowTitle(tr("Project")); } -QString Project::get_next_sequence_name(QString start) { - if (start.isEmpty()) start = tr("Sequence"); - - int n = 1; - bool found = true; - QString name; - while (found) { - found = false; - name = start + " "; - if (n < 10) { - name += "0"; - } - name += QString::number(n); - for (int i=0;iget_name(), name, Qt::CaseInsensitive) == 0) { - found = true; - n++; - break; - } - } - } - return name; -} - -SequencePtr create_sequence_from_media(QVector& media_list) { - SequencePtr s(new Sequence()); - - s->name = panel_project->get_next_sequence_name(); - - // Retrieve default Sequence settings from Config - s->width = olive::CurrentConfig.default_sequence_width; - s->height = olive::CurrentConfig.default_sequence_height; - s->frame_rate = olive::CurrentConfig.default_sequence_framerate; - s->audio_frequency = olive::CurrentConfig.default_sequence_audio_frequency; - s->audio_layout = olive::CurrentConfig.default_sequence_audio_channel_layout; - - bool got_video_values = false; - bool got_audio_values = false; - for (int i=0;iget_type()) { - case MEDIA_TYPE_FOOTAGE: - { - Footage* m = media->to_footage(); - if (m->ready) { - if (!got_video_values) { - for (int j=0;jvideo_tracks.size();j++) { - const FootageStream& ms = m->video_tracks.at(j); - s->width = ms.video_width; - s->height = ms.video_height; - if (!qFuzzyCompare(ms.video_frame_rate, 0.0)) { - s->frame_rate = ms.video_frame_rate * m->speed; - - if (ms.video_interlacing != VIDEO_PROGRESSIVE) s->frame_rate *= 2; - - // only break with a decent frame rate, otherwise there may be a better candidate - got_video_values = true; - break; - } - } - } - if (!got_audio_values && m->audio_tracks.size() > 0) { - const FootageStream& ms = m->audio_tracks.at(0); - s->audio_frequency = ms.audio_frequency; - got_audio_values = true; - } - } - } - break; - case MEDIA_TYPE_SEQUENCE: - { - // Clone all attributes of the original sequence (seq) into the new one (s) - Sequence* seq = media->to_sequence().get(); - - s->width = seq->width; - s->height = seq->height; - s->frame_rate = seq->frame_rate; - s->audio_frequency = seq->audio_frequency; - s->audio_layout = seq->audio_layout; - - got_video_values = true; - got_audio_values = true; - } - break; - } - if (got_video_values && got_audio_values) break; - } - - return s; -} - void Project::duplicate_selected() { QModelIndexList items = get_current_selected(); bool duped = false; @@ -323,12 +228,12 @@ void Project::duplicate_selected() { for (int j=0;jget_type() == MEDIA_TYPE_SEQUENCE) { - create_sequence_internal(ca, i->to_sequence()->copy(), false, item_to_media(items.at(j).parent())); + olive::project_model.CreateSequence(ca, i->to_sequence()->copy(), false, item_to_media(items.at(j).parent())); duped = true; } } if (duped) { - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); } else { delete ca; } @@ -339,27 +244,15 @@ void Project::replace_selected_file() { if (selected_items.size() == 1) { MediaPtr item = item_to_media_ptr(selected_items.at(0)); if (item->get_type() == MEDIA_TYPE_FOOTAGE) { - replace_media(item, nullptr); + sources_common.replace_media(item, nullptr); } } } -void Project::replace_media(MediaPtr item, QString filename) { - if (filename.isEmpty()) { - filename = QFileDialog::getOpenFileName( - this, - tr("Replace '%1'").arg(item->get_name()), - "", - tr("All Files") + " (*)"); - } - if (!filename.isEmpty()) { - ReplaceMediaCommand* rmc = new ReplaceMediaCommand(item, filename); - olive::UndoStack.push(rmc); - } -} - void Project::replace_clip_media() { - if (olive::ActiveSequence == nullptr) { + Sequence* top_sequence = Timeline::GetTopSequence().get(); + + if (top_sequence == nullptr) { QMessageBox::critical(this, tr("No active sequence"), tr("No sequence is active, please open the sequence you want to replace clips from."), @@ -368,7 +261,7 @@ void Project::replace_clip_media() { QModelIndexList selected_items = get_current_selected(); if (selected_items.size() == 1) { Media* item = item_to_media(selected_items.at(0)); - if (item->get_type() == MEDIA_TYPE_SEQUENCE && olive::ActiveSequence == item->to_sequence()) { + if (item->get_type() == MEDIA_TYPE_SEQUENCE && top_sequence == item->to_sequence().get()) { QMessageBox::critical(this, tr("Active sequence selected"), tr("You cannot insert a sequence into itself, so no clips of this media would be in this sequence."), @@ -408,7 +301,7 @@ void Project::open_properties() { item->get_name()); if (!new_name.isEmpty()) { MediaRename* mr = new MediaRename(item, new_name); - olive::UndoStack.push(mr); + olive::undo_stack.push(mr); } } } @@ -416,11 +309,11 @@ void Project::open_properties() { } void Project::new_folder() { - MediaPtr m = create_folder_internal(nullptr); - olive::UndoStack.push(new AddMediaCommand(m, get_selected_folder())); + MediaPtr m = olive::project::CreateFolder(nullptr); + olive::undo_stack.push(new AddMediaCommand(m, get_selected_folder())); QModelIndex index = olive::project_model.create_index(m->row(), 0, m.get()); - switch (olive::CurrentConfig.project_view_type) { + switch (olive::config.project_view_type) { case olive::PROJECT_VIEW_TREE: tree_view->edit(sorter.mapFromSource(index)); break; @@ -430,54 +323,10 @@ void Project::new_folder() { } } -void Project::new_sequence() { - NewSequenceDialog nsd(this); - nsd.set_sequence_name(get_next_sequence_name()); - nsd.exec(); -} - -MediaPtr Project::create_sequence_internal(ComboAction *ca, SequencePtr s, bool open, Media* parent) { - - MediaPtr item = std::make_shared(); - item->set_sequence(s); - - if (ca != nullptr) { - - ca->append(new AddMediaCommand(item, parent)); - - if (open) { - ca->append(new ChangeSequenceAction(s)); - } - - } else { - - olive::project_model.appendChild(parent, item); - - if (open) { - olive::Global->set_sequence(s); - } - - } - - return item; - -} - -QString Project::get_file_name_from_path(const QString& path) { - return path.mid(path.lastIndexOf('/')+1); -} - -bool Project::is_focused() { +bool Project::focused() { return tree_view->hasFocus() || icon_view->hasFocus(); } -MediaPtr Project::create_folder_internal(QString name) { - MediaPtr item = std::make_shared(); - item->set_folder(); - item->set_name(name); - return item; -} - Media* Project::item_to_media(const QModelIndex &index) { return static_cast(sorter.mapToSource(index).internalPointer()); } @@ -492,21 +341,6 @@ MediaPtr Project::item_to_media_ptr(const QModelIndex &index) { return raw_ptr->parentItem()->get_shared_ptr(raw_ptr); } -void Project::get_all_media_from_table(QList& items, QList& list, int search_type) { - for (int i=0;iget_type() == MEDIA_TYPE_FOLDER) { - QList children; - for (int j=0;jchildCount();j++) { - children.append(item->child(j)); - } - get_all_media_from_table(children, list, search_type); - } else if (search_type == item->get_type() || search_type == -1) { - list.append(item); - } - } -} - bool Project::IsToolbarVisible() { return toolbar_widget->isVisible(); @@ -522,20 +356,6 @@ bool Project::IsProjectWidget(QObject *child) return (child == tree_view || child == icon_view); } -bool delete_clips_in_clipboard_with_media(ComboAction* ca, Media* m) { - int delete_count = 0; - if (clipboard_type == CLIPBOARD_TYPE_CLIP) { - for (int i=0;i(clipboard.at(i)); - if (c->media() == m) { - ca->append(new RemoveClipsFromClipboard(i-delete_count)); - delete_count++; - } - } - } - return (delete_count > 0); -} - void Project::delete_selected_media() { ComboAction* ca = new ComboAction(); QModelIndexList selected_items = get_current_selected(); @@ -550,25 +370,22 @@ void Project::delete_selected_media() { // check if media is in use QVector parents; - QList sequence_items; - QList all_top_level_items; - for (int i=0;i 0) { + QVector all_sequences = olive::project_model.GetAllSequences(); + if (all_sequences.size() > 0) { - QList media_items; - get_all_media_from_table(items, media_items, MEDIA_TYPE_FOOTAGE); + QVector all_footage = olive::project_model.GetAllFootage(); - for (int i=0;ito_footage(); bool confirm_delete = false; - for (int j=0;jto_sequence().get(); - for (int k=0;kclips.size();k++) { - ClipPtr c = s->clips.at(k); + for (int j=0;jto_sequence().get(); + QVector sequence_clips = s->GetAllClips(); + + for (int k=0;kmedia() == item) { if (!confirm_delete) { // we found a reference, so we know we'll need to ask if the user wants to delete it @@ -608,25 +425,25 @@ void Project::delete_selected_media() { parent = parent->parentItem(); } - j = sequence_items.size(); - k = s->clips.size(); + j = all_sequences.size(); + k = sequence_clips.size(); } else if (confirm.clickedButton() == abort_button) { // break out of loop - i = media_items.size(); - j = sequence_items.size(); - k = s->clips.size(); + i = all_footage.size(); + j = all_sequences.size(); + k = sequence_clips.size(); remove = false; } } if (confirm_delete) { - ca->append(new DeleteClipAction(s, k)); + ca->append(new DeleteClipAction(c)); } } } } if (confirm_delete) { - delete_clips_in_clipboard_with_media(ca, item); + olive::clipboard.DeleteClipsWithMedia(ca, item); } } } @@ -636,7 +453,10 @@ void Project::delete_selected_media() { panel_graph_editor->set_row(nullptr); panel_effect_controls->Clear(true); - if (olive::ActiveSequence != nullptr) olive::ActiveSequence->selections.clear(); + Sequence* top_sequence = Timeline::GetTopSequence().get(); + if (top_sequence != nullptr) { + top_sequence->ClearSelections(); + } // remove media and parents for (int m=0;mto_sequence().get(); - if (s == olive::ActiveSequence.get()) { - ca->append(new ChangeSequenceAction(nullptr)); - } + Timeline::CloseSequence(s); if (s == panel_footage_viewer->seq.get()) { panel_footage_viewer->set_media(nullptr); } } else if (items.at(i)->get_type() == MEDIA_TYPE_FOOTAGE) { - if (panel_footage_viewer->seq != nullptr) { - for (int j=0;jseq->clips.size();j++) { - ClipPtr c = panel_footage_viewer->seq->clips.at(j); - if (c != nullptr && c->media() == items.at(i)) { - panel_footage_viewer->set_media(nullptr); - break; - } - } + if (panel_footage_viewer->media == items.at(i)) { + panel_footage_viewer->set_media(nullptr); } } } - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); // redraw clips if (redraw) { @@ -687,253 +499,6 @@ void Project::delete_selected_media() { } } -void Project::process_file_list(QStringList& files, bool recursive, MediaPtr replace, Media* parent) { - bool imported = false; - - // retrieve the array of image formats from the user's configuration - QStringList image_sequence_formats = olive::CurrentConfig.img_seq_formats.split("|"); - - // a cache of image sequence formatted URLS to assist the user in importing image sequences - QVector image_sequence_urls; - QVector image_sequence_importassequence; - - if (!recursive) last_imported_media.clear(); - - bool create_undo_action = (!recursive && replace == nullptr); - ComboAction* ca = nullptr; - if (create_undo_action) ca = new ComboAction(); - - // Loop through received files - for (int i=0;iappend(new AddMediaCommand(folder, parent)); - } else { - olive::project_model.appendChild(parent, folder); - } - - process_file_list(subdir_filenames, true, nullptr, folder.get()); - - imported = true; - - } else if (!files.at(i).isEmpty()) { - QString file = files.at(i); - - // Check if the user is importing an Olive project file - if (file.endsWith(".ove", Qt::CaseInsensitive)) { - - // This file is an Olive project file. Ask the user if they really want to import it. - if (QMessageBox::question(this, - tr("Import a Project"), - tr("\"%1\" is an Olive project file. It will merge with this project. " - "Do you wish to continue?").arg(file), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - - // load the project without clearing the current one - olive::Global->ImportProject(file); - - } - - } else { - - // This file is NOT an Olive project file - - // Used later if this file is part of an already processed image sequence - bool skip = false; - - /* Heuristic to determine whether file is part of an image sequence */ - - // Firstly, we run a heuristic on whether this file is an image by checking its file extension - - bool file_is_an_image = false; - - // Get the string position of the extension in the filename - int lastcharindex = file.lastIndexOf("."); - - if (lastcharindex != -1 && lastcharindex > file.lastIndexOf('/')) { - - QString ext = file.mid(lastcharindex+1); - - // If the file extension is part of a predetermined list (from Config::img_seq_formats), we'll treat it - // as an image - if (image_sequence_formats.contains(ext, Qt::CaseInsensitive)) { - file_is_an_image = true; - } - - } else { - - // If we're here, the file has no extension, but we'll still check if its an image sequence just in case - lastcharindex = file.length(); - file_is_an_image = true; - - } - - // Some image sequence's don't start at "0", if it is indeed an image sequence, we'll use this variable - // later to determine where it does start - int start_number = 0; - - // Check if we passed the earlier heuristic to check whether this is a file, and whether the last number in - // the filename (before the extension) is a number - if (file_is_an_image && file[lastcharindex-1].isDigit()) { - - // Check how many digits are at the end of this filename - int digit_count = 0; - int digit_test = lastcharindex-1; - while (file[digit_test].isDigit()) { - digit_count++; - digit_test--; - } - - // Retrieve the integer represented at the end of this filename - digit_test++; - int file_number = file.mid(digit_test, digit_count).toInt(); - - // Check whether a file exists with the same format but one number higher or one number lower - if (QFileInfo::exists(QString(file.left(digit_test) + QString("%1").arg(file_number-1, digit_count, 10, QChar('0')) + file.mid(lastcharindex))) - || QFileInfo::exists(QString(file.left(digit_test) + QString("%1").arg(file_number+1, digit_count, 10, QChar('0')) + file.mid(lastcharindex)))) { - - // - // If so, it certainly looks like it *could* be an image sequence, but we'll ask the user just in case - // - - // Firstly we should check if this file is part of a sequence the user has already confirmed as either a - // sequence or not a sequence (e.g. if the user happened to select a bunch of images that happen to increase - // consecutively). We format the filename with FFmpeg's '%Nd' (N = digits) formatting for reading image - // sequences - QString new_filename = file.left(digit_test) + "%" + QString::number(digit_count) + "d" + file.mid(lastcharindex); - - int does_url_cache_already_contain_this = image_sequence_urls.indexOf(new_filename); - - if (does_url_cache_already_contain_this > -1) { - - // We've already processed an image with the same formatting - - // Check if the last time we saw this formatting, the user chose to import as a sequence - if (image_sequence_importassequence.at(does_url_cache_already_contain_this)) { - - // If so, no need to import this file too, so we signal to the rest of the function to skip this file - skip = true; - - } - - // If not, we can fall-through to the next step which is importing normally - - } else { - - // If we're here, we've never seen a file with this formatting before, so we'll ask whether to import - // as a sequence or not - - // Add this file formatting file to the URL cache - image_sequence_urls.append(new_filename); - - // This does look like an image sequence, let's ask the user if it'll indeed be an image sequence - if (QMessageBox::question(this, - tr("Image sequence detected"), - tr("The file '%1' appears to be part of an image sequence. " - "Would you like to import it as such?").arg(file), - QMessageBox::Yes | QMessageBox::No, - QMessageBox::Yes) == QMessageBox::Yes) { - - // Proceed to the next step of this with the formatted filename - file = new_filename; - - // Cache the user's answer alongside the image_sequence_urls value - in this case, YES, this will be an - // image sequence - image_sequence_importassequence.append(true); - - - // FFmpeg needs to know what file number to start at in the sequence. In case the image sequence doesn't - // start at a zero, we'll loop decreasing the number until it doesn't exist anymore - QString test_filename_format = QString("%1%2%3").arg(file.left(digit_test), "%1", file.mid(lastcharindex)); - int test_file_number = file_number; - do { - test_file_number--; - } while (QFileInfo::exists(test_filename_format.arg(QString("%1").arg(test_file_number, digit_count, 10, QChar('0'))))); - - // set the image sequence's start number to the last that existed - start_number = test_file_number + 1; - - } else { - - // Cache the user's response to the image sequence question - i.e. none of the files imported with this - // formatting should be imported as an image sequence - image_sequence_importassequence.append(false); - - } - } - - } - - } - - // If we're not skipping this file, let's import it - if (!skip) { - MediaPtr item; - FootagePtr m; - - if (replace != nullptr) { - item = replace; - } else { - item = std::make_shared(); - } - - m = std::make_shared(); - - m->using_inout = false; - m->url = file; - m->name = get_file_name_from_path(files.at(i)); - m->start_number = start_number; - - item->set_footage(m); - - last_imported_media.append(item.get()); - - if (replace == nullptr) { - if (create_undo_action) { - ca->append(new AddMediaCommand(item, parent)); - } else { - olive::project_model.appendChild(parent, item); - } - } - - imported = true; - } - - } - - - } - } - if (create_undo_action) { - if (imported) { - olive::UndoStack.push(ca); - - for (int i=0;iselectionModel()->select(row_select, QItemSelectionModel::Select); - } else if (olive::CurrentConfig.project_view_type == olive::PROJECT_VIEW_ICON) { + } else if (olive::config.project_view_type == olive::PROJECT_VIEW_ICON) { // if we're in icon view, we just "browse" to the parent folder icon_view->setRootIndex(hierarchy); @@ -998,340 +563,43 @@ bool Project::reveal_media(Media *media, QModelIndex parent) { return false; } -void Project::import_dialog() { - QFileDialog fd(this, tr("Import media..."), "", tr("All Files") + " (*)"); - fd.setFileMode(QFileDialog::ExistingFiles); - - if (fd.exec()) { - QStringList files = fd.selectedFiles(); - process_file_list(files, false, nullptr, get_selected_folder()); - } -} - void Project::delete_clips_using_selected_media() { - if (olive::ActiveSequence == nullptr) { + Sequence* top_sequence = Timeline::GetTopSequence().get(); + + if (top_sequence == nullptr) { QMessageBox::critical(this, tr("No active sequence"), tr("No sequence is active, please open the sequence you want to delete clips from."), QMessageBox::Ok); } else { - ComboAction* ca = new ComboAction(); - bool deleted = false; + QModelIndexList items = get_current_selected(); - for (int i=0;iclips.size();i++) { - const ClipPtr& c = olive::ActiveSequence->clips.at(i); - if (c != nullptr) { - for (int j=0;jmedia() == m) { - ca->append(new DeleteClipAction(olive::ActiveSequence.get(), i)); - deleted = true; - } - } - } - } - for (int j=0;j media; -void Project::clear() { - // clear graph editor - panel_graph_editor->set_row(nullptr); + media.resize(items.size()); - // clear effects cache - panel_effect_controls->Clear(true); - - // delete sequences first because it's important to close all the clips before deleting the media - QVector sequences = list_all_project_sequences(); - for (int i=0;iset_sequence(nullptr); - } - - // delete everything else - olive::project_model.clear(); - - // update tree view (sometimes this doesn't seem to update reliably) - tree_view->update(); -} - -void save_marker(QXmlStreamWriter& stream, const Marker& m) { - stream.writeStartElement("marker"); - stream.writeAttribute("frame", QString::number(m.frame)); - stream.writeAttribute("name", m.name); - stream.writeEndElement(); -} - -void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, const QModelIndex& parent) { - for (int i=0;iget_type()) { - if (m->get_type() == MEDIA_TYPE_FOLDER) { - if (set_ids_only) { - m->temp_id = folder_id; // saves a temporary ID for matching in the project file - folder_id++; - } else { - // if we're saving folders, save the folder - stream.writeStartElement("folder"); - stream.writeAttribute("name", m->get_name()); - stream.writeAttribute("id", QString::number(m->temp_id)); - if (!item.parent().isValid()) { - stream.writeAttribute("parent", "0"); - } else { - stream.writeAttribute("parent", QString::number(olive::project_model.getItem(item.parent())->temp_id)); - } - stream.writeEndElement(); - } - // save_folder(stream, item, type, set_ids_only); - } else { - int folder = m->parentItem()->temp_id; - if (type == MEDIA_TYPE_FOOTAGE) { - Footage* f = m->to_footage(); - f->save_id = media_id; - stream.writeStartElement("footage"); - stream.writeAttribute("id", QString::number(media_id)); - stream.writeAttribute("folder", QString::number(folder)); - stream.writeAttribute("name", f->name); - stream.writeAttribute("url", proj_dir.relativeFilePath(f->url)); - stream.writeAttribute("duration", QString::number(f->length)); - stream.writeAttribute("using_inout", QString::number(f->using_inout)); - stream.writeAttribute("in", QString::number(f->in)); - stream.writeAttribute("out", QString::number(f->out)); - stream.writeAttribute("speed", QString::number(f->speed)); - stream.writeAttribute("alphapremul", QString::number(f->alpha_is_premultiplied)); - stream.writeAttribute("startnumber", QString::number(f->start_number)); - - stream.writeAttribute("proxy", QString::number(f->proxy)); - stream.writeAttribute("proxypath", f->proxy_path); - - // save video stream metadata - for (int j=0;jvideo_tracks.size();j++) { - const FootageStream& ms = f->video_tracks.at(j); - stream.writeStartElement("video"); - stream.writeAttribute("id", QString::number(ms.file_index)); - stream.writeAttribute("width", QString::number(ms.video_width)); - stream.writeAttribute("height", QString::number(ms.video_height)); - stream.writeAttribute("framerate", QString::number(ms.video_frame_rate, 'f', 10)); - stream.writeAttribute("infinite", QString::number(ms.infinite_length)); - stream.writeEndElement(); // video - } - - // save audio stream metadata - for (int j=0;jaudio_tracks.size();j++) { - const FootageStream& ms = f->audio_tracks.at(j); - stream.writeStartElement("audio"); - stream.writeAttribute("id", QString::number(ms.file_index)); - stream.writeAttribute("channels", QString::number(ms.audio_channels)); - stream.writeAttribute("layout", QString::number(ms.audio_layout)); - stream.writeAttribute("frequency", QString::number(ms.audio_frequency)); - stream.writeEndElement(); // audio - } - - // save footage markers - for (int j=0;jmarkers.size();j++) { - save_marker(stream, f->markers.at(j)); - } - - stream.writeEndElement(); // footage - media_id++; - } else if (type == MEDIA_TYPE_SEQUENCE) { - Sequence* s = m->to_sequence().get(); - if (set_ids_only) { - s->save_id = sequence_id; - sequence_id++; - } else { - stream.writeStartElement("sequence"); - stream.writeAttribute("id", QString::number(s->save_id)); - stream.writeAttribute("folder", QString::number(folder)); - stream.writeAttribute("name", s->name); - stream.writeAttribute("width", QString::number(s->width)); - stream.writeAttribute("height", QString::number(s->height)); - stream.writeAttribute("framerate", QString::number(s->frame_rate, 'f', 10)); - stream.writeAttribute("afreq", QString::number(s->audio_frequency)); - stream.writeAttribute("alayout", QString::number(s->audio_layout)); - if (s == olive::ActiveSequence.get()) { - stream.writeAttribute("open", "1"); - } - stream.writeAttribute("workarea", QString::number(s->using_workarea)); - stream.writeAttribute("workareaIn", QString::number(s->workarea_in)); - stream.writeAttribute("workareaOut", QString::number(s->workarea_out)); - - QVector transition_save_cache; - QVector transition_clip_save_cache; - - for (int j=0;jclips.size();j++) { - const ClipPtr& c = s->clips.at(j); - if (c != nullptr) { - stream.writeStartElement("clip"); // clip - stream.writeAttribute("id", QString::number(j)); - stream.writeAttribute("enabled", QString::number(c->enabled())); - stream.writeAttribute("name", c->name()); - stream.writeAttribute("clipin", QString::number(c->clip_in())); - stream.writeAttribute("in", QString::number(c->timeline_in())); - stream.writeAttribute("out", QString::number(c->timeline_out())); - stream.writeAttribute("track", QString::number(c->track())); - - stream.writeAttribute("r", QString::number(c->color().red())); - stream.writeAttribute("g", QString::number(c->color().green())); - stream.writeAttribute("b", QString::number(c->color().blue())); - - stream.writeAttribute("autoscale", QString::number(c->autoscaled())); - stream.writeAttribute("speed", QString::number(c->speed().value, 'f', 10)); - stream.writeAttribute("maintainpitch", QString::number(c->speed().maintain_audio_pitch)); - stream.writeAttribute("reverse", QString::number(c->reversed())); - - if (c->media() != nullptr) { - stream.writeAttribute("type", QString::number(c->media()->get_type())); - switch (c->media()->get_type()) { - case MEDIA_TYPE_FOOTAGE: - stream.writeAttribute("media", QString::number(c->media()->to_footage()->save_id)); - stream.writeAttribute("stream", QString::number(c->media_stream_index())); - break; - case MEDIA_TYPE_SEQUENCE: - stream.writeAttribute("sequence", QString::number(c->media()->to_sequence()->save_id)); - break; - } - } - - // save markers - // only necessary for null media clips, since media has its own markers - if (c->media() == nullptr) { - for (int k=0;kget_markers().size();k++) { - save_marker(stream, c->get_markers().at(k)); - } - } - - // save clip links - stream.writeStartElement("linked"); // linked - for (int k=0;klinked.size();k++) { - stream.writeStartElement("link"); // link - stream.writeAttribute("id", QString::number(c->linked.at(k))); - stream.writeEndElement(); // link - } - stream.writeEndElement(); // linked - - // save opening and closing transitions - for (int t=kTransitionOpening;t<=kTransitionClosing;t++) { - TransitionPtr transition = (t == kTransitionOpening) ? c->opening_transition : c->closing_transition; - - if (transition != nullptr) { - stream.writeStartElement((t == kTransitionOpening) ? "opening" : "closing"); - - // check if this is a shared transition and the transition has already been saved - int transition_cache_index = transition_save_cache.indexOf(transition); - - if (transition_cache_index > -1) { - // if so, just save a reference to the other clip - stream.writeAttribute("shared", - QString::number(transition_clip_save_cache.at(transition_cache_index))); - } else { - // otherwise save the whole transition - transition->save(stream); - transition_save_cache.append(transition); - transition_clip_save_cache.append(j); - } - - stream.writeEndElement(); // opening - } - } - - for (int k=0;keffects.size();k++) { - stream.writeStartElement("effect"); // effect - c->effects.at(k)->save(stream); - stream.writeEndElement(); // effect - } - - stream.writeEndElement(); // clip - } - } - for (int j=0;jmarkers.size();j++) { - save_marker(stream, s->markers.at(j)); - } - stream.writeEndElement(); - } - } - } + for (int i=0;iget_type() == MEDIA_TYPE_FOLDER) { - save_folder(stream, type, set_ids_only, item); - } - } -} + top_sequence->DeleteClipsUsingMedia(media); -void Project::save_project(bool autorecovery) { - folder_id = 1; - media_id = 1; - sequence_id = 1; - - QFile file(autorecovery ? autorecovery_filename : olive::ActiveProjectFilename); - if (!file.open(QIODevice::WriteOnly)) { - qCritical() << "Could not open file"; - return; - } - - QXmlStreamWriter stream(&file); - stream.setAutoFormatting(true); - stream.writeStartDocument(); // doc - - stream.writeStartElement("project"); // project - - stream.writeTextElement("version", QString::number(olive::kSaveVersion)); - - stream.writeTextElement("url", olive::ActiveProjectFilename); - proj_dir = QFileInfo(olive::ActiveProjectFilename).absoluteDir(); - - save_folder(stream, MEDIA_TYPE_FOLDER, true); - - stream.writeStartElement("folders"); // folders - save_folder(stream, MEDIA_TYPE_FOLDER, false); - stream.writeEndElement(); // folders - - stream.writeStartElement("media"); // media - save_folder(stream, MEDIA_TYPE_FOOTAGE, false); - stream.writeEndElement(); // media - - save_folder(stream, MEDIA_TYPE_SEQUENCE, true); - - stream.writeStartElement("sequences"); // sequences - save_folder(stream, MEDIA_TYPE_SEQUENCE, false); - stream.writeEndElement();// sequences - - stream.writeEndElement(); // project - - stream.writeEndDocument(); // doc - - file.close(); - - if (!autorecovery) { - add_recent_project(olive::ActiveProjectFilename); - olive::Global->set_modified(false); } } void Project::update_view_type() { - tree_view->setVisible(olive::CurrentConfig.project_view_type == olive::PROJECT_VIEW_TREE); - icon_view_container->setVisible(olive::CurrentConfig.project_view_type == olive::PROJECT_VIEW_ICON - || olive::CurrentConfig.project_view_type == olive::PROJECT_VIEW_LIST); + tree_view->setVisible(olive::config.project_view_type == olive::PROJECT_VIEW_TREE); + icon_view_container->setVisible(olive::config.project_view_type == olive::PROJECT_VIEW_ICON + || olive::config.project_view_type == olive::PROJECT_VIEW_LIST); - switch (olive::CurrentConfig.project_view_type) { + switch (olive::config.project_view_type) { case olive::PROJECT_VIEW_TREE: sources_common.view = tree_view; break; case olive::PROJECT_VIEW_ICON: case olive::PROJECT_VIEW_LIST: - icon_view->setViewMode(olive::CurrentConfig.project_view_type == olive::PROJECT_VIEW_ICON ? + icon_view->setViewMode(olive::config.project_view_type == olive::PROJECT_VIEW_ICON ? QListView::IconMode : QListView::ListMode); // update list/grid size since they use this value slightly differently @@ -1343,43 +611,21 @@ void Project::update_view_type() { } void Project::set_icon_view() { - olive::CurrentConfig.project_view_type = olive::PROJECT_VIEW_ICON; + olive::config.project_view_type = olive::PROJECT_VIEW_ICON; update_view_type(); } void Project::set_list_view() { - olive::CurrentConfig.project_view_type = olive::PROJECT_VIEW_LIST; + olive::config.project_view_type = olive::PROJECT_VIEW_LIST; update_view_type(); } void Project::set_tree_view() { - olive::CurrentConfig.project_view_type = olive::PROJECT_VIEW_TREE; + olive::config.project_view_type = olive::PROJECT_VIEW_TREE; update_view_type(); } -void Project::save_recent_projects() { - // save to file - QFile f(olive::Global->get_recent_project_list_file()); - if (f.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) { - QTextStream out(&f); - for (int i=0;i 0) { - out << "\n"; - } - out << recent_projects.at(i); - } - f.close(); - } else { - qWarning() << "Could not save recent projects"; - } -} - -void Project::clear_recent_projects() { - recent_projects.clear(); - save_recent_projects(); -} - void Project::set_icon_view_size(int s) { if (icon_view->viewMode() == QListView::IconMode) { icon_view->setGridSize(QSize(s, s)); @@ -1404,46 +650,8 @@ void Project::make_new_menu() { new_menu.exec(QCursor::pos()); } -void Project::add_recent_project(QString url) { - bool found = false; - for (int i=0;i MAXIMUM_RECENT_PROJECTS) { - recent_projects.removeLast(); - } - } - save_recent_projects(); -} - -void Project::list_all_sequences_worker(QVector* list, Media* parent) { - for (int i=0;iget_type()) { - case MEDIA_TYPE_SEQUENCE: - list->append(item); - break; - case MEDIA_TYPE_FOLDER: - list_all_sequences_worker(list, item); - break; - } - } -} - -QVector Project::list_all_project_sequences() { - QVector list; - list_all_sequences_worker(&list, nullptr); - return list; -} - QModelIndexList Project::get_current_selected() { - if (olive::CurrentConfig.project_view_type == olive::PROJECT_VIEW_TREE) { + if (olive::config.project_view_type == olive::PROJECT_VIEW_TREE) { return tree_view->selectionModel()->selectedRows(); } return icon_view->selectionModel()->selectedIndexes(); diff --git a/panels/project.h b/panels/project.h index e3dfbf3bf..2cc8c826c 100644 --- a/panels/project.h +++ b/panels/project.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -37,20 +37,8 @@ #include "ui/sourceiconview.h" #include "timeline/mediaimportdata.h" #include "undo/undo.h" - #include "ui/sourcetable.h" -#define LOAD_TYPE_VERSION 69 -#define LOAD_TYPE_URL 70 - -extern QString autorecovery_filename; -extern QStringList recent_projects; - -SequencePtr create_sequence_from_media(QVector &media_list); - -QString get_channel_layout_name(int channels, uint64_t layout); -QString get_interlacing_name(int interlacing); - class Project : public Panel { Q_OBJECT public: @@ -59,40 +47,22 @@ public: void ConnectFilterToModel(); void DisconnectFilterToModel(); - bool is_focused(); - void clear(); - MediaPtr create_sequence_internal(ComboAction *ca, SequencePtr s, bool open, Media* parent); - QString get_next_sequence_name(QString start = nullptr); - void process_file_list(QStringList& files, bool recursive = false, MediaPtr replace = nullptr, Media *parent = nullptr); - void replace_media(MediaPtr item, QString filename); + virtual bool focused() override; + Media* get_selected_folder(); bool reveal_media(Media *media, QModelIndex parent = QModelIndex()); - void add_recent_project(QString url); - - void save_project(bool autorecovery); - - MediaPtr create_folder_internal(QString name); Media* item_to_media(const QModelIndex& index); MediaPtr item_to_media_ptr(const QModelIndex &index); - void save_recent_projects(); - - QVector list_all_project_sequences(); - - QVector last_imported_media; - QModelIndexList get_current_selected(); - void get_all_media_from_table(QList &items, QList &list, int type = -1); - bool IsToolbarVisible(); bool IsProjectWidget(QObject *child); virtual void Retranslate() override; protected: public slots: - void import_dialog(); void delete_selected_media(); void duplicate_selected(); void delete_clips_using_selected_media(); @@ -100,17 +70,8 @@ public slots: void replace_clip_media(); void open_properties(); void new_folder(); - void new_sequence(); - void SetToolbarVisible(bool visible); private: - void save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, const QModelIndex &parent = QModelIndex()); - int folder_id; - int media_id; - int sequence_id; - void list_all_sequences_worker(QVector *list, Media* parent); - QString get_file_name_from_path(const QString &path); - QDir proj_dir; QWidget* icon_view_container; QSlider* icon_size_slider; QPushButton* directory_up; @@ -127,7 +88,6 @@ private slots: void set_icon_view(); void set_list_view(); void set_tree_view(); - void clear_recent_projects(); void set_icon_view_size(int); void set_up_dir_enabled(); void go_up_dir(); diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 2b2f5e002..4833a288a 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -33,18 +33,20 @@ #include #include #include +#include #include "global/global.h" #include "panels/panels.h" #include "project/projectelements.h" -#include "ui/timelinewidget.h" +#include "ui/timelineview.h" #include "ui/icons.h" #include "ui/viewerwidget.h" #include "rendering/audio.h" #include "rendering/cacher.h" #include "rendering/renderfunctions.h" #include "global/config.h" -#include "project/clipboard.h" +#include "global/clipboard.h" +#include "global/math.h" #include "ui/timelineheader.h" #include "ui/resizablescrollbar.h" #include "ui/audiomonitor.h" @@ -53,49 +55,42 @@ #include "ui/mainwindow.h" #include "undo/undostack.h" #include "global/debug.h" +#include "global/timing.h" #include "ui/menu.h" -int olive::timeline::kTrackDefaultHeight = 40; -int olive::timeline::kTrackMinHeight = 30; -int olive::timeline::kTrackHeightIncrement = 10; - Timeline::Timeline(QWidget *parent) : Panel(parent), cursor_frame(0), - cursor_track(0), + cursor_track(nullptr), zoom(1.0), zoom_just_changed(false), showing_all(false), - snapping(true), - snapped(false), - snap_point(0), selecting(false), rect_select_init(false), rect_select_proc(false), moving_init(false), moving_proc(false), move_insert(false), - trim_target(-1), - trim_type(TRIM_NONE), + trim_target(nullptr), + trim_type(olive::timeline::TRIM_NONE), splitting(false), importing(false), importing_files(false), creating(false), transition_tool_init(false), transition_tool_proc(false), - transition_tool_open_clip(-1), - transition_tool_close_clip(-1), + transition_tool_open_clip(nullptr), + transition_tool_close_clip(nullptr), hand_moving(false), block_repaints(false), - scroll(0) + scroll(0), + sequence_(nullptr) { setup_ui(); headers->viewer = panel_sequence_viewer; - video_area->bottom_align = true; - video_area->scrollBar = videoScrollbar; - audio_area->scrollBar = audioScrollbar; + video_area->SetAlignment(olive::timeline::kAlignmentBottom); tool_buttons.append(toolArrowButton); tool_buttons.append(toolEditButton); @@ -106,19 +101,134 @@ Timeline::Timeline(QWidget *parent) : tool_buttons.append(toolTransitionButton); tool_buttons.append(toolHandButton); + tool_button_group = new QButtonGroup(this); + tool_button_group->addButton(toolArrowButton); + tool_button_group->addButton(toolEditButton); + tool_button_group->addButton(toolRippleButton); + tool_button_group->addButton(toolRazorButton); + tool_button_group->addButton(toolSlipButton); + tool_button_group->addButton(toolSlideButton); + tool_button_group->addButton(toolTransitionButton); + tool_button_group->addButton(toolHandButton); + toolArrowButton->click(); connect(horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(setScroll(int))); - connect(videoScrollbar, SIGNAL(valueChanged(int)), video_area, SLOT(setScroll(int))); - connect(audioScrollbar, SIGNAL(valueChanged(int)), audio_area, SLOT(setScroll(int))); connect(horizontalScrollBar, SIGNAL(resize_move(double)), this, SLOT(resize_move(double))); + connect(this, SIGNAL(SequenceChanged(SequencePtr)), panel_sequence_viewer, SLOT(set_sequence(SequencePtr))); + connect(this, SIGNAL(visibilityChanged(bool)), this, SLOT(visibility_changed_slot(bool))); update_sequence(); Retranslate(); } -Timeline::~Timeline() {} +Timeline *Timeline::GetTopTimeline() +{ + for (int i=0;iisVisible()) { + return panel_timeline.at(i); + } + } + + return nullptr; +} + +SequencePtr Timeline::GetTopSequence() +{ + Timeline* top_timeline = GetTopTimeline(); + + if (top_timeline != nullptr) { + return top_timeline->sequence_; + } + + return nullptr; +} + +void Timeline::OpenSequence(SequencePtr s) +{ + Q_ASSERT(s != nullptr); + + for (int i=0;isequence_ == s) { + t->raise(); + return; + } else if (t->sequence_ == nullptr) { + t->SetSequence(s); + t->raise(); + return; + } + } + + Timeline* t = new Timeline(olive::MainWindow); + olive::MainWindow->tabifyDockWidget(panel_timeline.last(), t); + t->SetSequence(s); + t->show(); + t->raise(); + panel_timeline.append(t); +} + +void Timeline::CloseSequence(Sequence *s) +{ + Q_ASSERT(s != nullptr); + + // Don't respond to a null sequence + if (s == nullptr) { + return; + } + + // If there's only one Timeline object left, just set it to nullptr without destroying it + if (panel_timeline.size() == 1) { + panel_timeline.first()->SetSequence(nullptr); + return; + } + + // If there are multiple, kill the Timeline object that has the specified sequence + for (int i=0;isequence_.get() == s) { + delete t; + panel_timeline.removeAt(i); + i--; + } + } +} + +void Timeline::CloseAll() +{ + while (panel_timeline.size() > 1) { + delete panel_timeline.last(); + panel_timeline.removeLast(); + } + panel_timeline.first()->SetSequence(nullptr); +} + +bool Timeline::IsImporting() +{ + for (int i=0;iimporting) { + return true; + } + } + return false; +} + +void Timeline::SetSequence(SequencePtr sequence) +{ + if (sequence_ == sequence) { + return; + } + + sequence_ = sequence; + update_sequence(); + video_area->SetTrackList(sequence_.get(), olive::kTypeVideo); + audio_area->SetTrackList(sequence_.get(), olive::kTypeAudio); + repaint_timeline(); + + emit SequenceChanged(sequence_); +} void Timeline::Retranslate() { toolArrowButton->setToolTip(tr("Pointer Tool") + " (V)"); @@ -138,326 +248,58 @@ void Timeline::Retranslate() { UpdateTitle(); } -void Timeline::split_clip_at_positions(ComboAction* ca, int clip_index, QVector positions) { - - QVector pre_splits; - - // Add the clip and each of its links to the pre_splits array - Clip* clip = olive::ActiveSequence->clips.at(clip_index).get(); - pre_splits.append(clip_index); - for (int i=0;ilinked.size();i++) { - pre_splits.append(clip->linked.at(i)); - } - - std::sort(positions.begin(), positions.end()); - - // Remove any duplicate positions - for (int i=1;i > post_splits(positions.size()); - - for (int i=positions.size()-1;i>=0;i--) { - - post_splits[i].resize(pre_splits.size()); - - for (int j=0;jset_timeline_out(positions.at(i+1)); - } - } - } - - for (int i=0;iappend(new AddClipCommand(olive::ActiveSequence.get(), post_splits[i])); - } - -} - -void Timeline::previous_cut() { - if (olive::ActiveSequence != nullptr - && olive::ActiveSequence->playhead > 0) { - long p_cut = 0; - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr) { - if (c->timeline_out() > p_cut && c->timeline_out() < olive::ActiveSequence->playhead) { - p_cut = c->timeline_out(); - } else if (c->timeline_in() > p_cut && c->timeline_in() < olive::ActiveSequence->playhead) { - p_cut = c->timeline_in(); - } - } - } - panel_sequence_viewer->seek(p_cut); - } -} - -void Timeline::next_cut() { - if (olive::ActiveSequence != nullptr) { - bool seek_enabled = false; - long n_cut = LONG_MAX; - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr) { - if (c->timeline_in() < n_cut && c->timeline_in() > olive::ActiveSequence->playhead) { - n_cut = c->timeline_in(); - seek_enabled = true; - } else if (c->timeline_out() < n_cut && c->timeline_out() > olive::ActiveSequence->playhead) { - n_cut = c->timeline_out(); - seek_enabled = true; - } - } - } - if (seek_enabled) panel_sequence_viewer->seek(n_cut); - } -} - -void ripple_clips(ComboAction* ca, Sequence* s, long point, long length, const QVector& ignore) { - ca->append(new RippleAction(s, point, length, ignore)); -} - void Timeline::toggle_show_all() { - if (olive::ActiveSequence != nullptr) { + if (sequence_ != nullptr) { showing_all = !showing_all; if (showing_all) { old_zoom = zoom; - set_zoom_value(double(timeline_area->width() - 200) / double(olive::ActiveSequence->getEndFrame())); + set_zoom_value(double(timeline_area->width() - 200) / double(sequence_->GetEndFrame())); } else { set_zoom_value(old_zoom); } } } -void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector& media_list) { - video_ghosts = false; - audio_ghosts = false; - - for (int i=0;iget_type()) { - case MEDIA_TYPE_FOOTAGE: - m = medium->to_footage(); - can_import = m->ready; - if (m->using_inout) { - double source_fr = 30; - if (m->video_tracks.size() > 0 && !qIsNull(m->video_tracks.at(0).video_frame_rate)) { - source_fr = m->video_tracks.at(0).video_frame_rate * m->speed; - } - default_clip_in = rescale_frame_number(m->in, source_fr, seq->frame_rate); - default_clip_out = rescale_frame_number(m->out, source_fr, seq->frame_rate); - } - break; - case MEDIA_TYPE_SEQUENCE: - s = medium->to_sequence().get(); - sequence_length = s->getEndFrame(); - if (seq != nullptr) sequence_length = rescale_frame_number(sequence_length, s->frame_rate, seq->frame_rate); - can_import = (s != seq && sequence_length != 0); - if (s->using_workarea) { - default_clip_in = rescale_frame_number(s->workarea_in, s->frame_rate, seq->frame_rate); - default_clip_out = rescale_frame_number(s->workarea_out, s->frame_rate, seq->frame_rate); - } - break; - default: - can_import = false; - } - - if (can_import) { - Ghost g; - g.clip = -1; - g.trim_type = TRIM_NONE; - g.old_clip_in = g.clip_in = default_clip_in; - g.media = medium; - g.in = entry_point; - g.transition = nullptr; - - switch (medium->get_type()) { - case MEDIA_TYPE_FOOTAGE: - // is video source a still image? - if (m->video_tracks.size() > 0 && m->video_tracks.at(0).infinite_length && m->audio_tracks.size() == 0) { - g.out = g.in + 100; - } else { - long length = m->get_length_in_frames(seq->frame_rate); - g.out = entry_point + length - default_clip_in; - if (m->using_inout) { - g.out -= (length - default_clip_out); - } - } - - if (import_data.type() == olive::timeline::kImportAudioOnly - || import_data.type() == olive::timeline::kImportBoth) { - for (int j=0;jaudio_tracks.size();j++) { - if (m->audio_tracks.at(j).enabled) { - g.track = j; - g.media_stream = m->audio_tracks.at(j).file_index; - ghosts.append(g); - audio_ghosts = true; - } - } - } - - if (import_data.type() == olive::timeline::kImportVideoOnly - || import_data.type() == olive::timeline::kImportBoth) { - for (int j=0;jvideo_tracks.size();j++) { - if (m->video_tracks.at(j).enabled) { - g.track = -1-j; - g.media_stream = m->video_tracks.at(j).file_index; - ghosts.append(g); - video_ghosts = true; - } - } - } - break; - case MEDIA_TYPE_SEQUENCE: - g.out = entry_point + sequence_length - default_clip_in; - - if (s->using_workarea) { - g.out -= (sequence_length - default_clip_out); - } - - if (import_data.type() == olive::timeline::kImportVideoOnly - || import_data.type() == olive::timeline::kImportBoth) { - g.track = -1; - ghosts.append(g); - } - - if (import_data.type() == olive::timeline::kImportAudioOnly - || import_data.type() == olive::timeline::kImportBoth) { - g.track = 0; - ghosts.append(g); - } - - video_ghosts = true; - audio_ghosts = true; - break; - } - entry_point = g.out; - } +void Timeline::toggle_links() +{ + if (sequence_ != nullptr) { + sequence_->ToggleLinksOnSelected(); } - for (int i=0;i added_clips; - for (int i=0;i(s); - c->set_media(g.media, g.media_stream); - c->set_timeline_in(g.in); - c->set_timeline_out(g.out); - c->set_clip_in(g.clip_in); - c->set_track(g.track); - if (c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - Footage* m = c->media()->to_footage(); - if (m->video_tracks.size() == 0) { - // audio only (greenish) - c->set_color(128, 192, 128); - } else if (m->audio_tracks.size() == 0) { - // video only (orangeish) - c->set_color(192, 160, 128); - } else { - // video and audio (blueish) - c->set_color(128, 128, 192); - } - c->set_name(m->name); - } else if (c->media()->get_type() == MEDIA_TYPE_SEQUENCE) { - // sequence (red?ish?) - c->set_color(192, 128, 128); - - c->set_name(c->media()->to_sequence()->name); - } - c->refresh(); - added_clips.append(c); - } - ca->append(new AddClipCommand(s, added_clips)); - - // link clips from the same media - for (int i=0;imedia() == cc->media()) { - c->linked.append(j); - } - } - - if (olive::CurrentConfig.add_default_effects_to_clips) { - if (c->track() < 0) { - // add default video effects - c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_TRANSFORM, EFFECT_TYPE_EFFECT))); - } else { - // add default audio effects - c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_VOLUME, EFFECT_TYPE_EFFECT))); - c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_PAN, EFFECT_TYPE_EFFECT))); - } - } - } - if (olive::CurrentConfig.enable_seek_to_import) { - panel_sequence_viewer->seek(earliest_point); - } - ghosts.clear(); - importing = false; - snapped = false; } void Timeline::add_transition() { ComboAction* ca = new ComboAction(); bool adding = false; - for (int i=0;iclips.size();i++) { - Clip* c = olive::ActiveSequence->clips.at(i).get(); - if (c != nullptr && c->IsSelected()) { - int transition_to_add = (c->track() < 0) ? TRANSITION_INTERNAL_CROSSDISSOLVE : TRANSITION_INTERNAL_LINEARFADE; - if (c->opening_transition == nullptr) { - ca->append(new AddTransitionCommand(c, - nullptr, - nullptr, - Effect::GetInternalMeta(transition_to_add, EFFECT_TYPE_TRANSITION), - olive::CurrentConfig.default_transition_length)); - adding = true; - } - if (c->closing_transition == nullptr) { - ca->append(new AddTransitionCommand(nullptr, - c, - nullptr, - Effect::GetInternalMeta(transition_to_add, EFFECT_TYPE_TRANSITION), - olive::CurrentConfig.default_transition_length)); - adding = true; - } + QVector selected_clips = sequence_->SelectedClips(); + + for (int i=0;itype() == olive::kTypeVideo) ? kCrossDissolveTransition + : kLinearFadeTransition; + + if (c->opening_transition == nullptr) { + ca->append(new AddTransitionCommand(c, + nullptr, + nullptr, + transition_to_add, + olive::config.default_transition_length)); + adding = true; + } + + if (c->closing_transition == nullptr) { + ca->append(new AddTransitionCommand(nullptr, + c, + nullptr, + transition_to_add, + olive::config.default_transition_length)); + adding = true; } } if (adding) { - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); } else { delete ca; } @@ -466,9 +308,9 @@ void Timeline::add_transition() { } void Timeline::nest() { - if (olive::ActiveSequence != nullptr) { + if (sequence_ != nullptr) { // get selected clips - QVector selected_clips = olive::ActiveSequence->SelectedClipIndexes(); + QVector selected_clips = sequence_->SelectedClips(); // nest them if (!selected_clips.isEmpty()) { @@ -476,7 +318,7 @@ void Timeline::nest() { // get earliest point in selected clips long earliest_point = LONG_MAX; for (int i=0;iclips.at(selected_clips.at(i))->timeline_in(), earliest_point); + earliest_point = qMin(selected_clips.first()->timeline_in(), earliest_point); } ComboAction* ca = new ComboAction(); @@ -484,68 +326,76 @@ void Timeline::nest() { // create "nest" sequence with the same attributes as the current sequence SequencePtr s = std::make_shared(); - s->name = panel_project->get_next_sequence_name(tr("Nested Sequence")); - s->width = olive::ActiveSequence->width; - s->height = olive::ActiveSequence->height; - s->frame_rate = olive::ActiveSequence->frame_rate; - s->audio_frequency = olive::ActiveSequence->audio_frequency; - s->audio_layout = olive::ActiveSequence->audio_layout; + s->name = olive::project_model.GetNextSequenceName(tr("Nested Sequence")); + s->width = sequence_->width; + s->height = sequence_->height; + s->frame_rate = sequence_->frame_rate; + s->audio_frequency = sequence_->audio_frequency; + s->audio_layout = sequence_->audio_layout; + + QVector new_clips; // copy all selected clips to the nest for (int i=0;iappend(new DeleteClipAction(olive::ActiveSequence.get(), selected_clips.at(i))); + ca->append(new DeleteClipAction(c)); // copy to new - ClipPtr copy(olive::ActiveSequence->clips.at(selected_clips.at(i))->copy(s.get())); + Track* track = s->GetTrackList(c->type())->TrackAt(c->track()->Index()); + ClipPtr copy = selected_clips.at(i)->copy(track); copy->set_timeline_in(copy->timeline_in() - earliest_point); copy->set_timeline_out(copy->timeline_out() - earliest_point); - s->clips.append(copy); + track->AddClip(copy); + + new_clips.append(copy); } // relink clips in new nested sequences - relink_clips_using_ids(selected_clips, s->clips); + olive::timeline::RelinkClips(selected_clips, new_clips); // add sequence to project - MediaPtr m = panel_project->create_sequence_internal(ca, s, false, nullptr); + MediaPtr m = olive::project_model.CreateSequence(ca, s, false, nullptr); // add nested sequence to active sequence QVector media_list; media_list.append(m.get()); - create_ghosts_from_media(olive::ActiveSequence.get(), earliest_point, media_list); + olive::timeline::CreateGhostsFromMedia(sequence_.get(), earliest_point, media_list); // ensure ghosts won't overlap anything - for (int j=0;jclips.size();j++) { - Clip* c = olive::ActiveSequence->clips.at(j).get(); - if (c != nullptr && !selected_clips.contains(j)) { + QVector all_sequence_clips = sequence_->GetAllClips(); + for (int j=0;jtrack() == g.track && !((c->timeline_in() < g.in - && c->timeline_out() < g.in) - || (c->timeline_in() > g.out - && c->timeline_out() > g.out))) { - // There's a clip occupied by the space taken up by this ghost. Move up/down a track, and seek again - if (g.track < 0) { - g.track--; - } else { - g.track++; - } + && c->timeline_out() < g.in) + || (c->timeline_in() > g.out + && c->timeline_out() > g.out))) { + + // There's a clip occupied by the space taken up by this ghost. Move up a track, and seek again. + g.track = g.track->Next(); + + // Restart entire loop again j = -1; break; + } } } } - add_clips_from_ghosts(ca, olive::ActiveSequence.get()); + sequence_->AddClipsFromGhosts(ca, ghosts); panel_graph_editor->set_row(nullptr); panel_effect_controls->Clear(true); - olive::ActiveSequence->selections.clear(); + sequence_->ClearSelections(); - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); update_ui(true); } @@ -553,10 +403,10 @@ void Timeline::nest() { } void Timeline::update_sequence() { - bool null_sequence = (olive::ActiveSequence == nullptr); + bool null_sequence = (sequence_ == nullptr); for (int i=0;isetEnabled(!null_sequence); + tool_buttons.at(i)->setEnabled(!null_sequence); } snappingButton->setEnabled(!null_sequence); zoomInButton->setEnabled(!null_sequence); @@ -568,32 +418,28 @@ void Timeline::update_sequence() { UpdateTitle(); } -int Timeline::get_snap_range() { - return getFrameFromScreenPoint(zoom, 10); -} - bool Timeline::focused() { - return (olive::ActiveSequence != nullptr && (headers->hasFocus() || video_area->hasFocus() || audio_area->hasFocus())); + return (sequence_ != nullptr && (headers->hasFocus() || video_area->hasFocus() || audio_area->hasFocus())); } void Timeline::repaint_timeline() { if (!block_repaints) { bool draw = true; - if (olive::ActiveSequence != nullptr + if (sequence_ != nullptr && !horizontalScrollBar->isSliderDown() && !horizontalScrollBar->is_resizing() && panel_sequence_viewer->playing && !zoom_just_changed) { // auto scroll - if (olive::CurrentConfig.autoscroll == olive::AUTOSCROLL_PAGE_SCROLL) { - int playhead_x = getTimelineScreenPointFromFrame(olive::ActiveSequence->playhead); - if (playhead_x < 0 || playhead_x > (editAreas->width() - videoScrollbar->width())) { - horizontalScrollBar->setValue(getScreenPointFromFrame(zoom, olive::ActiveSequence->playhead)); + if (olive::config.autoscroll == olive::AUTOSCROLL_PAGE_SCROLL) { + int playhead_x = getTimelineScreenPointFromFrame(sequence_->playhead); + if (playhead_x < 0 || playhead_x > (editAreas->width())) { + horizontalScrollBar->setValue(getScreenPointFromFrame(zoom, sequence_->playhead)); draw = false; } - } else if (olive::CurrentConfig.autoscroll == olive::AUTOSCROLL_SMOOTH_SCROLL) { - if (center_scroll_to_playhead(horizontalScrollBar, zoom, olive::ActiveSequence->playhead)) { + } else if (olive::config.autoscroll == olive::AUTOSCROLL_SMOOTH_SCROLL) { + if (center_scroll_to_playhead(horizontalScrollBar, zoom, sequence_->playhead)) { draw = false; } } @@ -604,7 +450,7 @@ void Timeline::repaint_timeline() { video_area->update(); audio_area->update(); - if (olive::ActiveSequence != nullptr + if (sequence_ != nullptr && !zoom_just_changed) { set_sb_max(); } @@ -615,18 +461,8 @@ void Timeline::repaint_timeline() { } void Timeline::select_all() { - if (olive::ActiveSequence != nullptr) { - olive::ActiveSequence->selections.clear(); - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr) { - Selection s; - s.in = c->timeline_in(); - s.out = c->timeline_out(); - s.track = c->track(); - olive::ActiveSequence->selections.append(s); - } - } + if (sequence_ != nullptr) { + sequence_->SelectAll(); repaint_timeline(); } } @@ -635,66 +471,9 @@ void Timeline::scroll_to_frame(long frame) { scroll_to_frame_internal(horizontalScrollBar, frame, zoom, timeline_area->width()); } -void Timeline::select_from_playhead() { - olive::ActiveSequence->selections.clear(); - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr - && c->timeline_in() <= olive::ActiveSequence->playhead - && c->timeline_out() > olive::ActiveSequence->playhead) { - Selection s; - s.in = c->timeline_in(); - s.out = c->timeline_out(); - s.track = c->track(); - olive::ActiveSequence->selections.append(s); - } - } -} - -bool Timeline::can_ripple_empty_space(long frame, int track) { - bool can_ripple_delete = true; - bool at_end_of_sequence = true; - rc_ripple_min = 0; - rc_ripple_max = LONG_MAX; - - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr) { - if (c->timeline_in() > frame || c->timeline_out() > frame) { - at_end_of_sequence = false; - } - if (c->track() == track) { - if (c->timeline_in() <= frame && c->timeline_out() >= frame) { - can_ripple_delete = false; - break; - } else if (c->timeline_out() < frame) { - rc_ripple_min = qMax(rc_ripple_min, c->timeline_out()); - } else if (c->timeline_in() > frame) { - rc_ripple_max = qMin(rc_ripple_max, c->timeline_in()); - } - } - } - } - - return (can_ripple_delete && !at_end_of_sequence); -} - -void Timeline::ripple_delete_empty_space() { - QVector sels; - - Selection s; - s.in = rc_ripple_min; - s.out = rc_ripple_max; - s.track = cursor_track; - - sels.append(s); - - delete_selection(sels, true); -} - void Timeline::resizeEvent(QResizeEvent *) { // adjust maximum scrollbar - if (olive::ActiveSequence != nullptr) set_sb_max(); + if (sequence_ != nullptr) set_sb_max(); // resize tool button widget to its contents @@ -723,35 +502,11 @@ void Timeline::resizeEvent(QResizeEvent *) { tool_button_widget->setFixedWidth((tool_button_children.at(0)->sizeHint().width())*cols + horizontal_spacing*(cols-1) + 1); } -void Timeline::delete_in_out_internal(bool ripple) { - if (olive::ActiveSequence != nullptr && olive::ActiveSequence->using_workarea) { - QVector areas; - int video_tracks = 0, audio_tracks = 0; - olive::ActiveSequence->getTrackLimits(&video_tracks, &audio_tracks); - for (int i=video_tracks;i<=audio_tracks;i++) { - Selection s; - s.in = olive::ActiveSequence->workarea_in; - s.out = olive::ActiveSequence->workarea_out; - s.track = i; - areas.append(s); - } - ComboAction* ca = new ComboAction(); - delete_areas_and_relink(ca, areas, true); - if (ripple) ripple_clips(ca, - olive::ActiveSequence.get(), - olive::ActiveSequence->workarea_in, - olive::ActiveSequence->workarea_in - olive::ActiveSequence->workarea_out); - ca->append(new SetTimelineInOutCommand(olive::ActiveSequence.get(), false, 0, 0)); - olive::UndoStack.push(ca); - update_ui(true); - } -} - void Timeline::toggle_enable_on_selected_clips() { - if (olive::ActiveSequence != nullptr) { + if (sequence_ != nullptr) { // get currently selected clips - QVector selected_clips = olive::ActiveSequence->SelectedClips(); + QVector selected_clips = sequence_->SelectedClips(); if (!selected_clips.isEmpty()) { // if clips are selected, create an undoable action @@ -764,82 +519,12 @@ void Timeline::toggle_enable_on_selected_clips() { } // push the action - olive::UndoStack.push(set_action); + olive::undo_stack.push(set_action); update_ui(false); } } } -void Timeline::delete_selection(QVector& selections, bool ripple_delete) { - if (selections.size() > 0) { - panel_graph_editor->set_row(nullptr); - panel_effect_controls->Clear(true); - - ComboAction* ca = new ComboAction(); - - // delete the areas currently selected by `selections` - // if we're ripple deleting, we don't want to delete the selections since we still need them for the ripple - delete_areas_and_relink(ca, selections, !ripple_delete); - - if (ripple_delete) { - long ripple_point = selections.at(0).in; - long ripple_length = selections.at(0).out - selections.at(0).in; - - // retrieve ripple_point and ripple_length from current selection - for (int i=1;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr && c->timeline_in() < ripple_point && c->timeline_out() > ripple_point) { - // conflict detected, but this clip may be getting deleted so let's check - bool deleted = false; - for (int j=0;jtrack() - && !(c->timeline_in() < s.in && c->timeline_out() < s.in) - && !(c->timeline_in() > s.out && c->timeline_out() > s.out)) { - deleted = true; - break; - } - } - if (!deleted) { - for (int j=0;jclips.size();j++) { - ClipPtr cc = olive::ActiveSequence->clips.at(j); - if (cc != nullptr - && cc->track() == c->track() - && cc->timeline_in() > c->timeline_out() - && cc->timeline_in() < c->timeline_out() + ripple_length) { - ripple_length = cc->timeline_in() - c->timeline_out(); - } - } - } - } - } - - if (can_ripple) { - ripple_clips(ca, olive::ActiveSequence.get(), ripple_point, -ripple_length); - panel_sequence_viewer->seek(ripple_point-1); - } - - // if we're rippling, we can clear the selections here - if we're not rippling, delete_areas_and_relink() will - // clear the selections for us - selections.clear(); - } - - olive::UndoStack.push(ca); - - update_ui(true); - } -} - void Timeline::set_zoom_value(double v) { // set zoom value zoom = v; @@ -851,12 +536,12 @@ void Timeline::set_zoom_value(double v) { zoom_just_changed = true; // set scrollbar to center the playhead - if (olive::ActiveSequence != nullptr) { + if (sequence_ != nullptr) { // update scrollbar maximum value for new zoom set_sb_max(); if (!horizontalScrollBar->is_resizing()) { - center_scroll_to_playhead(horizontalScrollBar, zoom, olive::ActiveSequence->playhead); + center_scroll_to_playhead(horizontalScrollBar, zoom, sequence_->playhead); } } @@ -869,12 +554,6 @@ void Timeline::multiply_zoom(double m) { set_zoom_value(zoom * m); } -void Timeline::decheck_tool_buttons(QObject* sender) { - for (int i=0;isetChecked(tool_buttons.at(i) == sender); - } -} - void Timeline::zoom_in() { multiply_zoom(2.0); } @@ -883,38 +562,9 @@ void Timeline::zoom_out() { multiply_zoom(0.5); } -int Timeline::GetTrackHeight(int track) { - for (int i=0;igetTrackLimits(&min_track, &max_track); - - // for each active track, set the track to increase/decrease based on `diff` - for (int i=min_track;i<=max_track;i++) { - SetTrackHeight(i, qMax(GetTrackHeight(i) + diff, olive::timeline::kTrackMinHeight)); + if (sequence_ != nullptr) { + sequence_->ChangeTrackHeightsRelatively(diff); } // update the timeline @@ -930,95 +580,12 @@ void Timeline::DecreaseTrackHeight() { } void Timeline::snapping_clicked(bool checked) { - snapping = checked; -} - -ClipPtr Timeline::split_clip(ComboAction* ca, bool transitions, int p, long frame) { - return split_clip(ca, transitions, p, frame, frame); -} - -ClipPtr Timeline::split_clip(ComboAction* ca, bool transitions, int p, long frame, long post_in) { - Clip* pre = olive::ActiveSequence->clips.at(p).get(); - if (pre != nullptr) { - - if (pre->timeline_in() < frame && pre->timeline_out() > frame) { - // duplicate clip without duplicating its transitions, we'll restore them later - - ClipPtr post = pre->copy(olive::ActiveSequence.get()); - - long new_clip_length = frame - pre->timeline_in(); - - post->set_timeline_in(post_in); - post->set_clip_in(pre->clip_in() + (post->timeline_in() - pre->timeline_in())); - - pre->move(ca, pre->timeline_in(), frame, pre->clip_in(), pre->track(), false); - - if (transitions) { - - // check if this clip has a closing transition - if (pre->closing_transition != nullptr) { - - // if so, move closing transition to the post clip - post->closing_transition = pre->closing_transition; - - // and set the original clip's closing transition to nothing - ca->append(new SetPointer(reinterpret_cast(&pre->closing_transition), nullptr)); - - // and set the transition's reference to the post clip - if (post->closing_transition->parent_clip == pre) { - ca->append(new SetPointer(reinterpret_cast(&post->closing_transition->parent_clip), post.get())); - } - if (post->closing_transition->secondary_clip == pre) { - ca->append(new SetPointer(reinterpret_cast(&post->closing_transition->secondary_clip), post.get())); - } - - // and make sure it's at the correct size to the closing clip - if (post->closing_transition != nullptr && post->closing_transition->get_true_length() > post->length()) { - ca->append(new ModifyTransitionCommand(post->closing_transition, post->length())); - post->closing_transition->set_length(post->length()); - } - - } - - // we're keeping the opening clip, so ensure that's a correct size too - if (pre->opening_transition != nullptr && pre->opening_transition->get_true_length() > new_clip_length) { - ca->append(new ModifyTransitionCommand(pre->opening_transition, new_clip_length)); - } - } - - return post; - - } else if (frame == pre->timeline_in() - && pre->opening_transition != nullptr - && pre->opening_transition->secondary_clip != nullptr) { - // special case for shared transitions to split it into two - - // set transition to single-clip mode - ca->append(new SetPointer(reinterpret_cast(&pre->opening_transition->secondary_clip), nullptr)); - - // clone transition for other clip - ca->append(new AddTransitionCommand(nullptr, - pre->opening_transition->secondary_clip, - pre->opening_transition, - nullptr, - 0) - ); - - } - - } - return nullptr; + olive::timeline::snapping = checked; } +/* bool Timeline::split_clip_and_relink(ComboAction *ca, int clip, long frame, bool relink) { - // see if we split this clip before - if (split_cache.contains(clip)) { - return false; - } - - split_cache.append(clip); - - Clip* c = olive::ActiveSequence->clips.at(clip).get(); + Clip* c = sequence_->clips.at(clip).get(); if (c != nullptr) { QVector pre_clips; QVector post_clips; @@ -1039,768 +606,154 @@ bool Timeline::split_clip_and_relink(ComboAction *ca, int clip, long frame, bool // find linked clips of old clip for (int i=0;ilinked.size();i++) { int l = c->linked.at(i); - if (!split_cache.contains(l)) { - Clip* link = olive::ActiveSequence->clips.at(l).get(); - if ((original_clip_is_selected && link->IsSelected()) || !original_clip_is_selected) { - split_cache.append(l); - ClipPtr s = split_clip(ca, true, l, frame); - if (s != nullptr) { - pre_clips.append(l); - post_clips.append(s); - } + Clip* link = sequence_->clips.at(l).get(); + if ((original_clip_is_selected && link->IsSelected()) || !original_clip_is_selected) { + ClipPtr s = split_clip(ca, true, l, frame); + if (s != nullptr) { + pre_clips.append(l); + post_clips.append(s); } } } relink_clips_using_ids(pre_clips, post_clips); } - ca->append(new AddClipCommand(olive::ActiveSequence.get(), post_clips)); + ca->append(new AddClipCommand(sequence_.get(), post_clips)); return true; } } return false; } +*/ -void Timeline::clean_up_selections(QVector& areas) { - for (int i=0;i ss.out) { - // do nothing - } else if (s.in >= ss.in && s.out <= ss.out) { - remove = true; - } else if (s.in <= ss.out && s.out > ss.out) { - ss.out = s.out; - remove = true; - } else if (s.out >= ss.in && s.in < ss.in) { - ss.in = s.in; - remove = true; - } - if (remove) { - areas.removeAt(i); - i--; - break; - } - } - } - } - } -} -bool selection_contains_transition(const Selection& s, Clip* c, int type) { - if (type == kTransitionOpening) { - return c->opening_transition != nullptr - && s.out == c->timeline_in() + c->opening_transition->get_true_length() - && ((c->opening_transition->secondary_clip == nullptr && s.in == c->timeline_in()) - || (c->opening_transition->secondary_clip != nullptr && s.in == c->timeline_in() - c->opening_transition->get_true_length())); - } else { - return c->closing_transition != nullptr - && s.in == c->timeline_out() - c->closing_transition->get_true_length() - && ((c->closing_transition->secondary_clip == nullptr && s.out == c->timeline_out()) - || (c->closing_transition->secondary_clip != nullptr && s.out == c->timeline_out() + c->closing_transition->get_true_length())); - } -} - -void Timeline::delete_areas_and_relink(ComboAction* ca, QVector& areas, bool deselect_areas) { - clean_up_selections(areas); - - panel_graph_editor->set_row(nullptr); - panel_effect_controls->Clear(true); - - QVector pre_clips; - QVector post_clips; - - for (int i=0;iclips.size();j++) { - Clip* c = olive::ActiveSequence->clips.at(j).get(); - if (c != nullptr && c->track() == s.track && !c->undeletable) { - if (selection_contains_transition(s, c, kTransitionOpening)) { - // delete opening transition - ca->append(new DeleteTransitionCommand(c->opening_transition)); - } else if (selection_contains_transition(s, c, kTransitionClosing)) { - // delete closing transition - ca->append(new DeleteTransitionCommand(c->closing_transition)); - } else if (c->timeline_in() >= s.in && c->timeline_out() <= s.out) { - // clips falls entirely within deletion area - ca->append(new DeleteClipAction(olive::ActiveSequence.get(), j)); - } else if (c->timeline_in() < s.in && c->timeline_out() > s.out) { - // middle of clip is within deletion area - - // duplicate clip - ClipPtr post = split_clip(ca, true, j, s.in, s.out); - - pre_clips.append(j); - post_clips.append(post); - } else if (c->timeline_in() < s.in && c->timeline_out() > s.in) { - // only out point is in deletion area - c->move(ca, c->timeline_in(), s.in, c->clip_in(), c->track()); - - if (c->closing_transition != nullptr) { - if (s.in < c->timeline_out() - c->closing_transition->get_true_length()) { - ca->append(new DeleteTransitionCommand(c->closing_transition)); - } else { - ca->append(new ModifyTransitionCommand(c->closing_transition, c->closing_transition->get_true_length() - (c->timeline_out() - s.in))); - } - } - } else if (c->timeline_in() < s.out && c->timeline_out() > s.out) { - // only in point is in deletion area - c->move(ca, s.out, c->timeline_out(), c->clip_in() + (s.out - c->timeline_in()), c->track()); - - if (c->opening_transition != nullptr) { - if (s.out > c->timeline_in() + c->opening_transition->get_true_length()) { - ca->append(new DeleteTransitionCommand(c->opening_transition)); - } else { - ca->append(new ModifyTransitionCommand(c->opening_transition, c->opening_transition->get_true_length() - (s.out - c->timeline_in()))); - } - } - } - } - } - } - - // deselect selected clip areas - if (deselect_areas) { - QVector area_copy = areas; - for (int i=0;iappend(new AddClipCommand(olive::ActiveSequence.get(), post_clips)); -} void Timeline::copy(bool del) { - bool cleared = false; - bool copied = false; - - long min_in = 0; - - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr) { - for (int j=0;jselections.size();j++) { - const Selection& s = olive::ActiveSequence->selections.at(j); - if (s.track == c->track() && !((c->timeline_in() <= s.in && c->timeline_out() <= s.in) || (c->timeline_in() >= s.out && c->timeline_out() >= s.out))) { - if (!cleared) { - clear_clipboard(); - cleared = true; - clipboard_type = CLIPBOARD_TYPE_CLIP; - } - - ClipPtr copied_clip = c->copy(nullptr); - - // copy linked IDs (we correct these later in paste()) - copied_clip->linked = c->linked; - - if (copied_clip->timeline_in() < s.in) { - copied_clip->set_clip_in(copied_clip->clip_in() + (s.in - copied_clip->timeline_in())); - copied_clip->set_timeline_in(s.in); - } - - if (copied_clip->timeline_out() > s.out) { - copied_clip->set_timeline_out(s.out); - } - - if (copied) { - min_in = qMin(min_in, s.in); - } else { - min_in = s.in; - copied = true; - } - - copied_clip->load_id = i; - - clipboard.append(copied_clip); - } - } - } - } - - for (int i=0;i(clipboard.at(i)); - c->set_timeline_in(c->timeline_in() - min_in); - c->set_timeline_out(c->timeline_out() - min_in); - } - - if (del && copied) { - delete_selection(olive::ActiveSequence->selections, false); - } -} - -void Timeline::relink_clips_using_ids(QVector& old_clips, QVector& new_clips) { - // relink pasted clips - for (int i=0;iclips.at(old_clips.at(i)); - for (int j=0;jlinked.size();j++) { - for (int k=0;klinked.at(j) == old_clips.at(k)) { - if (new_clips.at(i) != nullptr) { - new_clips.at(i)->linked.append(k); - } - } - } - } - } -} - -void Timeline::paste(bool insert) { - if (clipboard.size() > 0) { - if (clipboard_type == CLIPBOARD_TYPE_CLIP) { - ComboAction* ca = new ComboAction(); - - // create copies and delete areas that we'll be pasting to - QVector delete_areas; - QVector pasted_clips; - long paste_start = LONG_MAX; - long paste_end = LONG_MIN; - for (int i=0;i(clipboard.at(i)); - - // create copy of clip and offset by playhead - ClipPtr cc = c->copy(olive::ActiveSequence.get()); - - // convert frame rates - cc->set_timeline_in(rescale_frame_number(cc->timeline_in(), c->cached_frame_rate(), olive::ActiveSequence->frame_rate)); - cc->set_timeline_out(rescale_frame_number(cc->timeline_out(), c->cached_frame_rate(), olive::ActiveSequence->frame_rate)); - cc->set_clip_in(rescale_frame_number(cc->clip_in(), c->cached_frame_rate(), olive::ActiveSequence->frame_rate)); - - cc->set_timeline_in(cc->timeline_in() + olive::ActiveSequence->playhead); - cc->set_timeline_out(cc->timeline_out() + olive::ActiveSequence->playhead); - cc->set_track(c->track()); - - paste_start = qMin(paste_start, cc->timeline_in()); - paste_end = qMax(paste_end, cc->timeline_out()); - - pasted_clips.append(cc); - - if (!insert) { - Selection s; - s.in = cc->timeline_in(); - s.out = cc->timeline_out(); - s.track = c->track(); - delete_areas.append(s); - } - } - if (insert) { - split_cache.clear(); - split_all_clips_at_point(ca, olive::ActiveSequence->playhead); - ripple_clips(ca, olive::ActiveSequence.get(), paste_start, paste_end - paste_start); - } else { - delete_areas_and_relink(ca, delete_areas, false); - } - - // correct linked clips - for (int i=0;i(clipboard.at(i)); - - for (int j=0;jlinked.size();j++) { - for (int k=0;k(clipboard.at(k)); - if (comp->load_id == oc->linked.at(j)) { - pasted_clips.at(i)->linked.append(k); - } - } - } - } - - ca->append(new AddClipCommand(olive::ActiveSequence.get(), pasted_clips)); - - olive::UndoStack.push(ca); - - update_ui(true); - - if (olive::CurrentConfig.paste_seeks) { - panel_sequence_viewer->seek(paste_end); - } - } else if (clipboard_type == CLIPBOARD_TYPE_EFFECT) { - ComboAction* ca = new ComboAction(); - - bool replace = false; - bool skip = false; - bool ask_conflict = true; - - QVector selected_clips = olive::ActiveSequence->SelectedClips(); - - for (int i=0;i(clipboard.at(j)); - if ((c->track() < 0) == (e->meta->subtype == EFFECT_TYPE_VIDEO)) { - int found = -1; - if (ask_conflict) { - replace = false; - skip = false; - } - for (int k=0;keffects.size();k++) { - if (c->effects.at(k)->meta == e->meta) { - found = k; - break; - } - } - if (found >= 0 && ask_conflict) { - QMessageBox box(this); - box.setWindowTitle(tr("Effect already exists")); - box.setText(tr("Clip '%1' already contains a '%2' effect. " - "Would you like to replace it with the pasted one or add it as a separate effect?") - .arg(c->name(), e->meta->name)); - box.setIcon(QMessageBox::Icon::Question); - - box.addButton(tr("Add"), QMessageBox::YesRole); - QPushButton* replace_button = box.addButton(tr("Replace"), QMessageBox::NoRole); - QPushButton* skip_button = box.addButton(tr("Skip"), QMessageBox::RejectRole); - - QCheckBox* future_box = new QCheckBox(tr("Do this for all conflicts found"), &box); - box.setCheckBox(future_box); - - box.exec(); - - if (box.clickedButton() == replace_button) { - replace = true; - } else if (box.clickedButton() == skip_button) { - skip = true; - } - ask_conflict = !future_box->isChecked(); - } - - if (found >= 0 && skip) { - // do nothing - } else if (found >= 0 && replace) { - ca->append(new EffectDeleteCommand(c->effects.at(found).get())); - - ca->append(new AddEffectCommand(c, e->copy(c), nullptr, found)); - } else { - ca->append(new AddEffectCommand(c, e->copy(c), nullptr)); - } - } - } - } - if (ca->hasActions()) { - ca->appendPost(new ReloadEffectsCommand()); - olive::UndoStack.push(ca); - } else { - delete ca; - } - update_ui(true); - } - } -} - -void Timeline::edit_to_point_internal(bool in, bool ripple) { - if (olive::ActiveSequence != nullptr) { - if (olive::ActiveSequence->clips.size() > 0) { - // get track count - int track_min = INT_MAX; - int track_max = INT_MIN; - long sequence_end = 0; - - bool playhead_falls_on_in = false; - bool playhead_falls_on_out = false; - long next_cut = LONG_MAX; - long prev_cut = 0; - - // find closest in point to playhead - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr) { - track_min = qMin(track_min, c->track()); - track_max = qMax(track_max, c->track()); - - sequence_end = qMax(c->timeline_out(), sequence_end); - - if (c->timeline_in() == olive::ActiveSequence->playhead) - playhead_falls_on_in = true; - if (c->timeline_out() == olive::ActiveSequence->playhead) - playhead_falls_on_out = true; - if (c->timeline_in() > olive::ActiveSequence->playhead) - next_cut = qMin(c->timeline_in(), next_cut); - if (c->timeline_out() > olive::ActiveSequence->playhead) - next_cut = qMin(c->timeline_out(), next_cut); - if (c->timeline_in() < olive::ActiveSequence->playhead) - prev_cut = qMax(c->timeline_in(), prev_cut); - if (c->timeline_out() < olive::ActiveSequence->playhead) - prev_cut = qMax(c->timeline_out(), prev_cut); - } - } - - next_cut = qMin(sequence_end, next_cut); - - QVector areas; - ComboAction* ca = new ComboAction(); - bool push_undo = true; - long seek = olive::ActiveSequence->playhead; - - if ((in && (playhead_falls_on_out || (playhead_falls_on_in && olive::ActiveSequence->playhead == 0))) - || (!in && (playhead_falls_on_in || (playhead_falls_on_out && olive::ActiveSequence->playhead == sequence_end)))) { // one frame mode - if (ripple) { - // set up deletion areas based on track count - long in_point = olive::ActiveSequence->playhead; - if (!in) { - in_point--; - seek--; - } - - if (in_point >= 0) { - Selection s; - s.in = in_point; - s.out = in_point + 1; - for (int i=track_min;i<=track_max;i++) { - s.track = i; - areas.append(s); - } - - // trim and move clips around the in point - delete_areas_and_relink(ca, areas, true); - - if (ripple) ripple_clips(ca, olive::ActiveSequence.get(), in_point, -1); - } else { - push_undo = false; - } - } else { - push_undo = false; - } - } else { - // set up deletion areas based on track count - Selection s; - if (in) seek = prev_cut; - s.in = in ? prev_cut : olive::ActiveSequence->playhead; - s.out = in ? olive::ActiveSequence->playhead : next_cut; - - if (s.in == s.out) { - push_undo = false; - } else { - for (int i=track_min;i<=track_max;i++) { - s.track = i; - areas.append(s); - } - - // trim and move clips around the in point - delete_areas_and_relink(ca, areas, true); - if (ripple) ripple_clips(ca, olive::ActiveSequence.get(), s.in, s.in - s.out); - } - } - - if (push_undo) { - olive::UndoStack.push(ca); - - update_ui(true); - - if (seek != olive::ActiveSequence->playhead && ripple) { - panel_sequence_viewer->seek(seek); - } - } else { - delete ca; - } - } else { - panel_sequence_viewer->seek(0); - } - } -} - -bool Timeline::split_selection(ComboAction* ca) { - bool split = false; - - // temporary relinking vectors - QVector pre_splits; - QVector post_splits; - QVector secondary_post_splits; - - // find clips within selection and split - for (int j=0;jclips.size();j++) { - ClipPtr clip = olive::ActiveSequence->clips.at(j); - if (clip != nullptr) { - for (int i=0;iselections.size();i++) { - const Selection& s = olive::ActiveSequence->selections.at(i); - if (s.track == clip->track()) { - ClipPtr post_b = split_clip(ca, true, j, s.out); - ClipPtr post_a = split_clip(ca, true, j, s.in); - - pre_splits.append(j); - post_splits.append(post_a); - secondary_post_splits.append(post_b); - - if (post_a != nullptr) { - post_a->set_timeline_out(qMin(post_a->timeline_out(), s.out)); - } - - split = true; - } - } - } - } - - if (split) { - // relink after splitting - relink_clips_using_ids(pre_splits, post_splits); - relink_clips_using_ids(pre_splits, secondary_post_splits); - - ca->append(new AddClipCommand(olive::ActiveSequence.get(), post_splits)); - ca->append(new AddClipCommand(olive::ActiveSequence.get(), secondary_post_splits)); - - return true; - } - return false; -} - -bool Timeline::split_all_clips_at_point(ComboAction* ca, long point) { - bool split = false; - for (int j=0;jclips.size();j++) { - ClipPtr c = olive::ActiveSequence->clips.at(j); - if (c != nullptr) { - // always relinks - if (split_clip_and_relink(ca, j, point, true)) { - split = true; - } - } - } - return split; -} - -void Timeline::split_at_playhead() { - ComboAction* ca = new ComboAction(); - bool split_selected = false; - split_cache.clear(); - - if (olive::ActiveSequence->selections.size() > 0) { - // see if whole clips are selected - QVector pre_clips; - QVector post_clips; - for (int j=0;jclips.size();j++) { - Clip* clip = olive::ActiveSequence->clips.at(j).get(); - if (clip != nullptr && clip->IsSelected()) { - ClipPtr s = split_clip(ca, true, j, olive::ActiveSequence->playhead); - if (s != nullptr) { - pre_clips.append(j); - post_clips.append(s); - split_selected = true; - } - } - } - - if (split_selected) { - // relink clips if we split - relink_clips_using_ids(pre_clips, post_clips); - ca->append(new AddClipCommand(olive::ActiveSequence.get(), post_clips)); - } else { - // split a selection if not - split_selected = split_selection(ca); - } - } - - // if nothing was selected or no selections fell within playhead, simply split at playhead - if (!split_selected) { - split_selected = split_all_clips_at_point(ca, olive::ActiveSequence->playhead); - } - - if (split_selected) { - olive::UndoStack.push(ca); - update_ui(true); - } else { - delete ca; + if (sequence_ != nullptr) { + sequence_->AddSelectionsToClipboard(del); } } void Timeline::ripple_delete() { - if (olive::ActiveSequence != nullptr) { - if (olive::ActiveSequence->selections.size() > 0) { - panel_timeline->delete_selection(olive::ActiveSequence->selections, true); - } else if (olive::CurrentConfig.hover_focus && get_focused_panel() == panel_timeline) { - if (panel_timeline->can_ripple_empty_space(panel_timeline->cursor_frame, panel_timeline->cursor_track)) { - panel_timeline->ripple_delete_empty_space(); - } + + if (sequence_ != nullptr) { + + QVector selections = sequence_->Selections(); + + if (!selections.isEmpty()) { + + ComboAction* ca = new ComboAction(); + sequence_->DeleteAreas(ca, selections, true, true); + olive::undo_stack.push(ca); + repaint_timeline(); + + } else if (olive::config.hover_focus && get_focused_panel() == this) { + + ripple_delete_empty_space(); + } } + + } -void Timeline::deselect_area(long in, long out, int track) { - int len = olive::ActiveSequence->selections.size(); - for (int i=0;iselections[i]; - if (s.track == track) { - if (s.in >= in && s.out <= out) { - // whole selection is in deselect area - olive::ActiveSequence->selections.removeAt(i); - i--; - len--; - } else if (s.in < in && s.out > out) { - // middle of selection is in deselect area - Selection new_sel; - new_sel.in = out; - new_sel.out = s.out; - new_sel.track = s.track; - olive::ActiveSequence->selections.append(new_sel); +void Timeline::ripple_delete_empty_space() +{ + if (sequence_ != nullptr) { + ComboAction* ca = new ComboAction(); + sequence_->RippleDeleteEmptySpace(ca, cursor_track, cursor_frame); + olive::undo_stack.push(ca); - s.out = in; - } else if (s.in < in && s.out > in) { - // only out point is in deselect area - s.out = in; - } else if (s.in < out && s.out > out) { - // only in point is in deselect area - s.in = out; - } - } + repaint_timeline(); } } -bool Timeline::snap_to_point(long point, long* l) { - int limit = get_snap_range(); - if (*l > point-limit-1 && *l < point+limit+1) { - snap_point = point; - *l = point; - snapped = true; - return true; - } - return false; -} - -bool Timeline::snap_to_timeline(long* l, bool use_playhead, bool use_markers, bool use_workarea) { - snapped = false; - if (snapping) { - if (use_playhead && !panel_sequence_viewer->playing) { - // snap to playhead - if (snap_to_point(olive::ActiveSequence->playhead, l)) return true; - } - - // snap to marker - if (use_markers) { - for (int i=0;imarkers.size();i++) { - if (snap_to_point(olive::ActiveSequence->markers.at(i).frame, l)) return true; - } - } - - // snap to in/out - if (use_workarea && olive::ActiveSequence->using_workarea) { - if (snap_to_point(olive::ActiveSequence->workarea_in, l)) return true; - if (snap_to_point(olive::ActiveSequence->workarea_out, l)) return true; - } - - // snap to clip/transition - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr) { - if (snap_to_point(c->timeline_in(), l)) { - return true; - } else if (snap_to_point(c->timeline_out(), l)) { - return true; - } else if (c->opening_transition != nullptr - && snap_to_point(c->timeline_in() + c->opening_transition->get_true_length(), l)) { - return true; - } else if (c->closing_transition != nullptr - && snap_to_point(c->timeline_out() - c->closing_transition->get_true_length(), l)) { - return true; - } else { - // try to snap to clip markers - for (int j=0;jget_markers().size();j++) { - if (snap_to_point(c->get_markers().at(j).frame + c->timeline_in() - c->clip_in(), l)) { - return true; - } - } - } - } - } - } - return false; -} - void Timeline::set_marker() { // determine if any clips are selected, and if so add markers to clips rather than the sequence - QVector clips_selected; - bool clip_mode = false; - for (int i=0;iclips.size();i++) { - Clip* c = olive::ActiveSequence->clips.at(i).get(); - if (c != nullptr - && c->IsSelected()) { + QVector selected_clips = sequence_->SelectedClips(); - // only add markers if the playhead is inside the clip - if (olive::ActiveSequence->playhead >= c->timeline_in() - && olive::ActiveSequence->playhead <= c->timeline_out()) { - clips_selected.append(i); + if (selected_clips.isEmpty()) { + + Marker::SetOnSequence(sequence_.get()); + + } else { + + // Remove any clips that don't contain the playhead + for (int i=0;itimeline_out() < sequence_->playhead + || c->timeline_in() > sequence_->playhead) { + selected_clips.removeAt(i); + i--; } - - // we are definitely adding markers to clips though - clip_mode = true; - } + + // Check if we removed them all + if (selected_clips.isEmpty()) { + return; + } + + // If not, let's create markers on them + Marker::SetOnClips(selected_clips); + } - - // if we've selected clips but none of the clips are within the playhead, - // nothing to do here - if (clip_mode && clips_selected.size() == 0) { - return; - } - - // pass off to internal set marker function - set_marker_internal(olive::ActiveSequence.get(), clips_selected); - } void Timeline::delete_inout() { - panel_timeline->delete_in_out_internal(false); + if (sequence_ != nullptr) { + sequence_->DeleteInToOut(false); + } } void Timeline::ripple_delete_inout() { - panel_timeline->delete_in_out_internal(true); + if (sequence_ != nullptr) { + sequence_->DeleteInToOut(true); + } } void Timeline::ripple_to_in_point() { - panel_timeline->edit_to_point_internal(true, true); + if (sequence_ != nullptr) { + sequence_->EditToPoint(true, true); + } } void Timeline::ripple_to_out_point() { - panel_timeline->edit_to_point_internal(false, true); + if (sequence_ != nullptr) { + sequence_->EditToPoint(false, true); + } } void Timeline::edit_to_in_point() { - panel_timeline->edit_to_point_internal(true, false); + if (sequence_ != nullptr) { + sequence_->EditToPoint(true, false); + } } void Timeline::edit_to_out_point() { - panel_timeline->edit_to_point_internal(false, false); -} - -void Timeline::toggle_links() { - LinkCommand* command = new LinkCommand(); - command->s = olive::ActiveSequence.get(); - for (int i=0;iclips.size();i++) { - Clip* c = olive::ActiveSequence->clips.at(i).get(); - if (c != nullptr && c->IsSelected()) { - if (!command->clips.contains(i)) command->clips.append(i); - - if (c->linked.size() > 0) { - command->link = false; // prioritize unlinking - - for (int j=0;jlinked.size();j++) { // add links to the command - if (!command->clips.contains(c->linked.at(j))) command->clips.append(c->linked.at(j)); - } - } - } - } - if (command->clips.size() > 0) { - olive::UndoStack.push(command); - repaint_timeline(); - } else { - delete command; + if (sequence_ != nullptr) { + sequence_->EditToPoint(false, false); } } void Timeline::deselect() { - olive::ActiveSequence->selections.clear(); - repaint_timeline(); + if (sequence_ != nullptr) { + sequence_->ClearSelections(); + repaint_timeline(); + } +} + +void Timeline::split_at_playhead() +{ + if (sequence_ != nullptr) { + sequence_->Split(); + repaint_timeline(); + } } long getFrameFromScreenPoint(double zoom, int x) { - long f = qRound(double(x) / zoom); + long f = qFloor(double(x) / zoom); if (f < 0) { return 0; } @@ -1815,6 +768,41 @@ long Timeline::getTimelineFrameFromScreenPoint(int x) { return getFrameFromScreenPoint(zoom, x + scroll); } +QVector Timeline::GetTracksInRectangle(int global_top, int global_bottom) +{ + QVector tracks; + + TimelineArea* area; + + foreach (area, areas) { + QPoint relative_tl = area->mapFromGlobal(QPoint(0, global_top)); + QPoint relative_br = area->mapFromGlobal(QPoint(0, global_bottom)); + + int rect_top = qMin(relative_tl.y(), relative_br.y()); + int rect_bottom = qMax(relative_tl.y(), relative_br.y()); + + // determine which clips are in this rectangular selection + TrackList* track_list = area->track_list(); + for (int j=0;jTrackCount();j++) { + Track* track = track_list->TrackAt(j); + + int track_top = area->view()->getScreenPointFromTrack(track); + int track_bottom = track_top + track->height(); + + // See if this track touches this rectangle at all + if (!(track_bottom < rect_top + || track_top > rect_bottom)) { + + // It does, so we add it to the list + tracks.append(track); + + } + } + } + + return tracks; +} + int Timeline::getTimelineScreenPointFromFrame(long frame) { return getScreenPointFromFrame(zoom, frame) - scroll; } @@ -1824,29 +812,29 @@ void Timeline::add_btn_click() { QAction* titleMenuItem = new QAction(&add_menu); titleMenuItem->setText(tr("Title...")); - titleMenuItem->setData(ADD_OBJ_TITLE); + titleMenuItem->setData(olive::timeline::ADD_OBJ_TITLE); add_menu.addAction(titleMenuItem); QAction* solidMenuItem = new QAction(&add_menu); solidMenuItem->setText(tr("Solid Color...")); - solidMenuItem->setData(ADD_OBJ_SOLID); + solidMenuItem->setData(olive::timeline::ADD_OBJ_SOLID); add_menu.addAction(solidMenuItem); QAction* barsMenuItem = new QAction(&add_menu); barsMenuItem->setText(tr("Bars...")); - barsMenuItem->setData(ADD_OBJ_BARS); + barsMenuItem->setData(olive::timeline::ADD_OBJ_BARS); add_menu.addAction(barsMenuItem); add_menu.addSeparator(); QAction* toneMenuItem = new QAction(&add_menu); toneMenuItem->setText(tr("Tone...")); - toneMenuItem->setData(ADD_OBJ_TONE); + toneMenuItem->setData(olive::timeline::ADD_OBJ_TONE); add_menu.addAction(toneMenuItem); QAction* noiseMenuItem = new QAction(&add_menu); noiseMenuItem->setText(tr("Noise...")); - noiseMenuItem->setData(ADD_OBJ_NOISE); + noiseMenuItem->setData(olive::timeline::ADD_OBJ_NOISE); add_menu.addAction(noiseMenuItem); connect(&add_menu, SIGNAL(triggered(QAction*)), this, SLOT(add_menu_item(QAction*))); @@ -1856,7 +844,7 @@ void Timeline::add_btn_click() { void Timeline::add_menu_item(QAction* action) { creating = true; - creating_object = action->data().toInt(); + creating_object = static_cast(action->data().toInt()); } void Timeline::setScroll(int s) { @@ -1873,7 +861,7 @@ void Timeline::record_btn_click() { QMessageBox::Ok); } else { creating = true; - creating_object = ADD_OBJ_AUDIO; + creating_object = olive::timeline::ADD_OBJ_AUDIO; olive::MainWindow->statusBar()->showMessage( tr("Click on the timeline where you want to start recording (drag to limit the recording to a certain timeframe)"), 10000); @@ -1885,23 +873,25 @@ void Timeline::transition_tool_click() { Menu transition_menu(this); - for (int i=0;isetObjectName("v"); - a->setData(reinterpret_cast(&em)); + transition_menu.addAction(tr("Video Transitions"))->setEnabled(false); + + for (int i=0;itype() == EFFECT_TYPE_TRANSITION && node->subtype() == olive::kTypeVideo) { + QAction* a = transition_menu.addAction(node->name()); + a->setData(i); } } transition_menu.addSeparator(); - for (int i=0;isetObjectName("a"); - a->setData(reinterpret_cast(&em)); + transition_menu.addAction(tr("Audio Transitions"))->setEnabled(false); + + for (int i=0;itype() == EFFECT_TYPE_TRANSITION && node->subtype() == olive::kTypeAudio) { + QAction* a = transition_menu.addAction(node->name()); + a->setData(i); } } @@ -1913,17 +903,9 @@ void Timeline::transition_tool_click() { } void Timeline::transition_menu_select(QAction* a) { - transition_tool_meta = reinterpret_cast(a->data().value()); - - if (a->objectName() == "v") { - transition_tool_side = -1; - } else { - transition_tool_side = 1; - } - - decheck_tool_buttons(sender()); + transition_tool_meta = static_cast(a->data().toInt()); timeline_area->setCursor(Qt::CrossCursor); - tool = TIMELINE_TOOL_TRANSITION; + olive::timeline::current_tool = olive::timeline::TIMELINE_TOOL_TRANSITION; toolTransitionButton->setChecked(true); } @@ -1932,15 +914,15 @@ void Timeline::resize_move(double z) { } void Timeline::set_sb_max() { - headers->set_scrollbar_max(horizontalScrollBar, olive::ActiveSequence->getEndFrame(), editAreas->width() - getScreenPointFromFrame(zoom, 200)); + headers->set_scrollbar_max(horizontalScrollBar, sequence_->GetEndFrame(), editAreas->width() - getScreenPointFromFrame(zoom, 200)); } void Timeline::UpdateTitle() { QString title = tr("Timeline: "); - if (olive::ActiveSequence == nullptr) { + if (sequence_ == nullptr) { setWindowTitle(title + tr("(none)")); } else { - setWindowTitle(title + olive::ActiveSequence->name); + setWindowTitle(title + sequence_->name); update_ui(false); } } @@ -1965,42 +947,42 @@ void Timeline::setup_ui() { toolArrowButton = new QPushButton(); toolArrowButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/arrow.svg"))); toolArrowButton->setCheckable(true); - toolArrowButton->setProperty("tool", TIMELINE_TOOL_POINTER); + toolArrowButton->setProperty("tool", olive::timeline::TIMELINE_TOOL_POINTER); connect(toolArrowButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolArrowButton); toolEditButton = new QPushButton(); toolEditButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/beam.svg"))); toolEditButton->setCheckable(true); - toolEditButton->setProperty("tool", TIMELINE_TOOL_EDIT); + toolEditButton->setProperty("tool", olive::timeline::TIMELINE_TOOL_EDIT); connect(toolEditButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolEditButton); toolRippleButton = new QPushButton(); toolRippleButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/ripple.svg"))); toolRippleButton->setCheckable(true); - toolRippleButton->setProperty("tool", TIMELINE_TOOL_RIPPLE); + toolRippleButton->setProperty("tool", olive::timeline::TIMELINE_TOOL_RIPPLE); connect(toolRippleButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolRippleButton); toolRazorButton = new QPushButton(); toolRazorButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/razor.svg"))); toolRazorButton->setCheckable(true); - toolRazorButton->setProperty("tool", TIMELINE_TOOL_RAZOR); + toolRazorButton->setProperty("tool", olive::timeline::TIMELINE_TOOL_RAZOR); connect(toolRazorButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolRazorButton); toolSlipButton = new QPushButton(); toolSlipButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/slip.svg"))); toolSlipButton->setCheckable(true); - toolSlipButton->setProperty("tool", TIMELINE_TOOL_SLIP); + toolSlipButton->setProperty("tool", olive::timeline::TIMELINE_TOOL_SLIP); connect(toolSlipButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolSlipButton); toolSlideButton = new QPushButton(); toolSlideButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/slide.svg"))); toolSlideButton->setCheckable(true); - toolSlideButton->setProperty("tool", TIMELINE_TOOL_SLIDE); + toolSlideButton->setProperty("tool", olive::timeline::TIMELINE_TOOL_SLIDE); connect(toolSlideButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolSlideButton); @@ -2008,7 +990,7 @@ void Timeline::setup_ui() { toolHandButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/hand.svg"))); toolHandButton->setCheckable(true); - toolHandButton->setProperty("tool", TIMELINE_TOOL_HAND); + toolHandButton->setProperty("tool", olive::timeline::TIMELINE_TOOL_HAND); connect(toolHandButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolHandButton); toolTransitionButton = new QPushButton(); @@ -2057,8 +1039,11 @@ void Timeline::setup_ui() { timeline_area_layout->setSpacing(0); timeline_area_layout->setContentsMargins(0, 0, 0, 0); + QHBoxLayout* timeline_header_layout = new QHBoxLayout(); + timeline_header_layout->addSpacing(olive::timeline::kTimelineLabelFixedWidth); headers = new TimelineHeader(); - timeline_area_layout->addWidget(headers); + timeline_header_layout->addWidget(headers); + timeline_area_layout->addLayout(timeline_header_layout); editAreas = new QWidget(); QHBoxLayout* editAreaLayout = new QHBoxLayout(editAreas); @@ -2069,41 +1054,13 @@ void Timeline::setup_ui() { splitter->setChildrenCollapsible(false); splitter->setOrientation(Qt::Vertical); - QWidget* videoContainer = new QWidget(); + video_area = new TimelineArea(this); + areas.append(video_area); + splitter->addWidget(video_area); - QHBoxLayout* videoContainerLayout = new QHBoxLayout(videoContainer); - videoContainerLayout->setSpacing(0); - videoContainerLayout->setContentsMargins(0, 0, 0, 0); - - video_area = new TimelineWidget(); - video_area->setFocusPolicy(Qt::ClickFocus); - videoContainerLayout->addWidget(video_area); - - videoScrollbar = new QScrollBar(); - videoScrollbar->setMaximum(0); - videoScrollbar->setSingleStep(20); - videoScrollbar->setOrientation(Qt::Vertical); - videoContainerLayout->addWidget(videoScrollbar); - - splitter->addWidget(videoContainer); - - QWidget* audioContainer = new QWidget(); - QHBoxLayout* audioContainerLayout = new QHBoxLayout(audioContainer); - audioContainerLayout->setSpacing(0); - audioContainerLayout->setContentsMargins(0, 0, 0, 0); - - audio_area = new TimelineWidget(); - audio_area->setFocusPolicy(Qt::ClickFocus); - - audioContainerLayout->addWidget(audio_area); - - audioScrollbar = new QScrollBar(); - audioScrollbar->setMaximum(0); - audioScrollbar->setOrientation(Qt::Vertical); - - audioContainerLayout->addWidget(audioScrollbar); - - splitter->addWidget(audioContainer); + audio_area = new TimelineArea(this); + areas.append(audio_area); + splitter->addWidget(audio_area); editAreaLayout->addWidget(splitter); @@ -2128,17 +1085,16 @@ void Timeline::setup_ui() { void Timeline::set_tool() { QPushButton* button = static_cast(sender()); - decheck_tool_buttons(button); - tool = button->property("tool").toInt(); + olive::timeline::current_tool = static_cast(button->property("tool").toInt()); creating = false; - switch (tool) { - case TIMELINE_TOOL_EDIT: + switch (olive::timeline::current_tool) { + case olive::timeline::TIMELINE_TOOL_EDIT: timeline_area->setCursor(Qt::IBeamCursor); break; - case TIMELINE_TOOL_RAZOR: + case olive::timeline::TIMELINE_TOOL_RAZOR: timeline_area->setCursor(olive::cursor::Razor); break; - case TIMELINE_TOOL_HAND: + case olive::timeline::TIMELINE_TOOL_HAND: timeline_area->setCursor(Qt::OpenHandCursor); break; default: @@ -2146,9 +1102,17 @@ void Timeline::set_tool() { } } +void Timeline::visibility_changed_slot(bool visibility) +{ + if (visibility) { + emit SequenceChanged(sequence_); + } +} + void olive::timeline::MultiplyTrackSizesByDPI() { kTrackDefaultHeight *= QApplication::desktop()->devicePixelRatio(); kTrackMinHeight *= QApplication::desktop()->devicePixelRatio(); kTrackHeightIncrement *= QApplication::desktop()->devicePixelRatio(); + kTimelineLabelFixedWidth *= QApplication::desktop()->devicePixelRatio(); } diff --git a/panels/timeline.h b/panels/timeline.h index 93260f377..1830c6bd4 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -25,145 +25,62 @@ #include #include -#include "ui/timelinewidget.h" -#include "ui/timelinetools.h" +#include "ui/timelinearea.h" +#include "timeline/timelinetools.h" +#include "timeline/timelinefunctions.h" #include "timeline/selection.h" #include "timeline/clip.h" #include "timeline/mediaimportdata.h" +#include "timeline/ghost.h" #include "undo/undo.h" #include "ui/timelineheader.h" #include "ui/resizablescrollbar.h" #include "ui/audiomonitor.h" #include "ui/panel.h" -enum CreateObjects { - ADD_OBJ_TITLE, - ADD_OBJ_SOLID, - ADD_OBJ_BARS, - ADD_OBJ_TONE, - ADD_OBJ_NOISE, - ADD_OBJ_AUDIO -}; - -enum TrimType { - TRIM_NONE, - TRIM_IN, - TRIM_OUT -}; - -namespace olive { - namespace timeline { - const int kGhostThickness = 2; - const int kClipTextPadding = 3; - - /** - * @brief Set default track sizes - * - * Olive has a few default constants used for adjusting track heights in the Timeline. For HiDPI, it makes - * sense to multiply these by the current DPI scale. It uses a variable from QApplication to do this multiplication, - * which means the QApplication instance needs to be instantiated before these are calculated. Therefore, call this - * function ONCE after QApplication is created to multiply the track heights correctly. - */ - void MultiplyTrackSizesByDPI(); - - extern int kTrackDefaultHeight; - extern int kTrackMinHeight; - extern int kTrackHeightIncrement; - } -} - -int getScreenPointFromFrame(double zoom, long frame); -long getFrameFromScreenPoint(double zoom, int x); -bool selection_contains_transition(const Selection& s, Clip *c, int type); -void ripple_clips(ComboAction *ca, Sequence *s, long point, long length, const QVector& ignore = QVector()); - -struct Ghost { - int clip; - long in; - long out; - int track; - long clip_in; - - long old_in; - long old_out; - int old_track; - long old_clip_in; - - // importing variables - Media* media; - int media_stream; - - // other variables - long ghost_length; - long media_length; - TrimType trim_type; - - // transition trimming - TransitionPtr transition; -}; - class Timeline : public Panel { Q_OBJECT public: explicit Timeline(QWidget *parent = nullptr); - virtual ~Timeline() override; - bool focused(); + static Timeline* GetTopTimeline(); + static SequencePtr GetTopSequence(); + static void OpenSequence(SequencePtr s); + static void CloseSequence(Sequence* s); + static void CloseAll(); + static bool IsImporting(); + + void SetSequence(SequencePtr sequence); + + virtual bool focused() override; void multiply_zoom(double m); void copy(bool del); - ClipPtr split_clip(ComboAction* ca, bool transitions, int p, long frame); - ClipPtr split_clip(ComboAction* ca, bool transitions, int p, long frame, long post_in); - bool split_selection(ComboAction* ca); - bool split_all_clips_at_point(ComboAction *ca, long point); - bool split_clip_and_relink(ComboAction* ca, int clip, long frame, bool relink); - void split_clip_at_positions(ComboAction* ca, int clip_index, QVector positions); - void clean_up_selections(QVector& areas); - void deselect_area(long in, long out, int track); - void delete_areas_and_relink(ComboAction *ca, QVector& areas, bool deselect_areas); - void relink_clips_using_ids(QVector& old_clips, QVector& new_clips); void update_sequence(); - void edit_to_point_internal(bool in, bool ripple); - void delete_in_out_internal(bool ripple); - - void create_ghosts_from_media(Sequence *seq, long entry_point, QVector &media_list); - void add_clips_from_ghosts(ComboAction *ca, Sequence *s); - int getTimelineScreenPointFromFrame(long frame); long getTimelineFrameFromScreenPoint(int x); int getDisplayScreenPointFromFrame(long frame); long getDisplayFrameFromScreenPoint(int x); - int get_snap_range(); - bool snap_to_point(long point, long* l); - bool snap_to_timeline(long* l, bool use_playhead, bool use_markers, bool use_workarea); + QVector GetTracksInRectangle(int global_top, int global_bottom); + void set_marker(); // shared information - int tool; long cursor_frame; - int cursor_track; + Track* cursor_track; double zoom; bool zoom_just_changed; long drag_frame_start; - int drag_track_start; + Track* drag_track_start; void update_effect_controls(); bool showing_all; double old_zoom; - int GetTrackHeight(int track); - void SetTrackHeight(int track, int height); - - // snapping - bool snapping; - bool snapped; - long snap_point; - // selecting functions bool selecting; - int selection_offset; - void delete_selection(QVector &selections, bool ripple); + QVector selection_cache; void select_all(); bool rect_select_init; bool rect_select_proc; @@ -173,19 +90,16 @@ public: bool moving_init; bool moving_proc; QVector ghosts; - bool video_ghosts; - bool audio_ghosts; bool move_insert; // trimming - int trim_target; - TrimType trim_type; + Clip* trim_target; + olive::timeline::TrimType trim_type; int transition_select; // splitting bool splitting; - QVector split_tracks; - QVector split_cache; + QVector split_tracks; // importing bool importing; @@ -193,15 +107,14 @@ public: // creating variables bool creating; - int creating_object; + olive::timeline::CreateObjects creating_object; // transition variables bool transition_tool_init; bool transition_tool_proc; - int transition_tool_open_clip; - int transition_tool_close_clip; - const EffectMeta* transition_tool_meta; - int transition_tool_side; + Clip* transition_tool_open_clip; + Clip* transition_tool_close_clip; + NodeType transition_tool_meta; // hand tool variables bool hand_moving; @@ -214,6 +127,8 @@ public: AudioMonitor* audio_monitor; ResizableScrollBar* horizontalScrollBar; + QVector tool_buttons; + QButtonGroup* tool_button_group; QPushButton* toolArrowButton; QPushButton* toolEditButton; QPushButton* toolRippleButton; @@ -225,7 +140,6 @@ public: QPushButton* snappingButton; void scroll_to_frame(long frame); - void select_from_playhead(); bool can_ripple_empty_space(long frame, int track); @@ -233,11 +147,10 @@ public: protected: virtual void resizeEvent(QResizeEvent *event) override; public slots: - void paste(bool insert = false); void repaint_timeline(); void toggle_show_all(); - void deselect(); void toggle_links(); + void deselect(); void split_at_playhead(); void ripple_delete(); void ripple_delete_empty_space(); @@ -254,9 +167,6 @@ public slots: void IncreaseTrackHeight(); void DecreaseTrackHeight(); - void previous_cut(); - void next_cut(); - void add_transition(); void nest(); @@ -264,6 +174,9 @@ public slots: void zoom_in(); void zoom_out(); +signals: + void SequenceChanged(SequencePtr s); + private slots: void snapping_clicked(bool checked); void add_btn_click(); @@ -274,12 +187,13 @@ private slots: void transition_menu_select(QAction*); void resize_move(double d); void set_tool(); + void visibility_changed_slot(bool visibility); private: + SequencePtr sequence_; + void ChangeTrackHeightUniformly(int diff); void set_zoom_value(double v); - QVector tool_buttons; - void decheck_tool_buttons(QObject* sender); void set_tool(int tool); int scroll; void set_sb_max(); @@ -291,14 +205,11 @@ private: long rc_ripple_min; long rc_ripple_max; - QVector track_heights; - QWidget* timeline_area; - TimelineWidget* video_area; - TimelineWidget* audio_area; + TimelineArea* video_area; + TimelineArea* audio_area; + QVector areas; QWidget* editAreas; - QScrollBar* videoScrollbar; - QScrollBar* audioScrollbar; QPushButton* zoomInButton; QPushButton* zoomOutButton; QPushButton* recordButton; diff --git a/panels/viewer.cpp b/panels/viewer.cpp index a89c6a3b7..6a3d66872 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -34,6 +34,7 @@ extern "C" { #include #include #include +#include #include "rendering/audio.h" #include "timeline.h" @@ -43,6 +44,7 @@ extern "C" { #include "timeline/clip.h" #include "panels/panels.h" #include "global/config.h" +#include "global/timing.h" #include "project/footage.h" #include "project/media.h" #include "undo/undo.h" @@ -68,7 +70,8 @@ Viewer::Viewer(QWidget *parent) : created_sequence(false), minimum_zoom(1.0), cue_recording_internal(false), - playback_speed(0) + playback_speed(0), + mode_(kTimelineMode) { setup_ui(); @@ -76,8 +79,8 @@ Viewer::Viewer(QWidget *parent) : headers->snapping = false; headers->show_text(false); viewer_container->viewer = this; - viewer_widget = viewer_container->child; - viewer_widget->viewer = this; + viewer_widget_ = viewer_container->child; + viewer_widget_->viewer = this; set_media(nullptr); current_timecode_slider->setEnabled(false); @@ -92,23 +95,31 @@ Viewer::Viewer(QWidget *parent) : connect(&playback_updater, SIGNAL(timeout()), this, SLOT(timer_update())); connect(&recording_flasher, SIGNAL(timeout()), this, SLOT(recording_flasher_update())); connect(horizontal_bar, SIGNAL(valueChanged(int)), headers, SLOT(set_scroll(int))); - connect(horizontal_bar, SIGNAL(valueChanged(int)), viewer_widget, SLOT(set_waveform_scroll(int))); + connect(horizontal_bar, SIGNAL(valueChanged(int)), viewer_widget_, SLOT(set_waveform_scroll(int))); connect(horizontal_bar, SIGNAL(resize_move(double)), this, SLOT(resize_move(double))); update_playhead_timecode(0); update_end_timecode(); } -Viewer::~Viewer() {} +void Viewer::SetMode(Viewer::Mode mode) +{ + mode_ = mode; +} + +Viewer::Mode Viewer::mode() +{ + return mode_; +} void Viewer::Retranslate() { /// Viewer panels are retranslated through the MainWindow to differentiate Media and Sequence Viewers // update_window_title(); } -bool Viewer::is_focused() { +bool Viewer::focused() { return headers->hasFocus() - || viewer_widget->hasFocus() + || viewer_widget_->hasFocus() || go_to_start_button->hasFocus() || prev_frame_button->hasFocus() || play_button->hasFocus() @@ -116,24 +127,19 @@ bool Viewer::is_focused() { || go_to_end_frame->hasFocus(); } -bool Viewer::is_main_sequence() { - return main_sequence; -} - -void Viewer::set_main_sequence() { - set_sequence(true, olive::ActiveSequence); -} - void Viewer::reset_all_audio() { // reset all clip audio if (seq != nullptr) { long last_frame = 0; - for (int i=0;iclips.size();i++) { - ClipPtr c = seq->clips.at(i); - if (c != nullptr) { - c->reset_audio(); - last_frame = qMax(last_frame, c->timeline_out()); - } + + QVector all_clips = seq->GetAllClips(); + for (int i=0;ireset_audio(); + last_frame = qMax(last_frame, c->timeline_out()); + } audio_ibuffer_frame = seq->playhead; @@ -145,148 +151,26 @@ void Viewer::reset_all_audio() { clear_audio_ibuffer(); } -long timecode_to_frame(const QString& s, int view, double frame_rate) { - QList list = s.split(QRegExp("[:;]")); - - if (view == olive::kTimecodeFrames || (list.size() == 1 && view != olive::kTimecodeMilliseconds)) { - return s.toLong(); - } - - int frRound = qRound(frame_rate); - int hours, minutes, seconds, frames; - - if (view == olive::kTimecodeMilliseconds) { - long milliseconds = s.toLong(); - - hours = milliseconds/3600000; - milliseconds -= (hours*3600000); - minutes = milliseconds/60000; - milliseconds -= (minutes*60000); - seconds = milliseconds/1000; - milliseconds -= (seconds*1000); - frames = qRound64((milliseconds*0.001)*frame_rate); - - seconds = qRound64(seconds * frame_rate); - minutes = qRound64(minutes * frame_rate * 60); - hours = qRound64(hours * frame_rate * 3600); - } else { - hours = ((list.size() > 0) ? list.at(0).toInt() : 0) * frRound * 3600; - minutes = ((list.size() > 1) ? list.at(1).toInt() : 0) * frRound * 60; - seconds = ((list.size() > 2) ? list.at(2).toInt() : 0) * frRound; - frames = (list.size() > 3) ? list.at(3).toInt() : 0; - } - - int f = (frames + seconds + minutes + hours); - - if ((view == olive::kTimecodeDrop || view == olive::kTimecodeMilliseconds) && frame_rate_is_droppable(frame_rate)) { - // return drop - int d; - int m; - - int dropFrames = qRound(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate - int framesPer10Minutes = qRound(frame_rate * 60 * 10); //Number of frames per ten minutes - int framesPerMinute = (qRound(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames - - d = f / framesPer10Minutes; - f -= dropFrames*9*d; - - m = f % framesPer10Minutes; - - if (m > dropFrames) { - f -= (dropFrames * ((m - dropFrames) / framesPerMinute)); - } - } - - // return non-drop - return f; -} - -QString frame_to_timecode(long f, int view, double frame_rate) { - if (view == olive::kTimecodeFrames) { - return QString::number(f); - } - - // return timecode - int hours = 0; - int mins = 0; - int secs = 0; - int frames = 0; - QString token = ":"; - - if ((view == olive::kTimecodeDrop || view == olive::kTimecodeMilliseconds) && frame_rate_is_droppable(frame_rate)) { - //CONVERT A FRAME NUMBER TO DROP FRAME TIMECODE - //Code by David Heidelberger, adapted from Andrew Duncan, further adapted for Olive by Olive Team - //Given an int called framenumber and a double called framerate - //Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off. - - int d; - int m; - - int dropFrames = qRound(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate - int framesPerHour = qRound(frame_rate*60*60); //Number of frqRound64ames in an hour - int framesPer24Hours = framesPerHour*24; //Number of frames in a day - timecode rolls over after 24 hours - int framesPer10Minutes = qRound(frame_rate * 60 * 10); //Number of frames per ten minutes - int framesPerMinute = (qRound(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames - - //If framenumber is greater than 24 hrs, next operation will rollover clock - f = f % framesPer24Hours; // % is the modulus operator, which returns a remainder. a % b = the remainder of a/b - - d = f / framesPer10Minutes; // \ means integer division, which is a/b without a remainder. Some languages you could use floor(a/b) - m = f % framesPer10Minutes; - - //In the original post, the next line read m>1, which only worked for 29.97. Jean-Baptiste Mardelle correctly pointed out that m should be compared to dropFrames. - if (m > dropFrames) { - f = f + (dropFrames*9*d) + dropFrames * ((m - dropFrames) / framesPerMinute); - } else { - f = f + dropFrames*9*d; - } - - int frRound = qRound(frame_rate); - frames = f % frRound; - secs = (f / frRound) % 60; - mins = ((f / frRound) / 60) % 60; - hours = (((f / frRound) / 60) / 60); - - token = ";"; - } else { - // non-drop timecode - - int int_fps = qRound(frame_rate); - hours = f / (3600 * int_fps); - mins = f / (60*int_fps) % 60; - secs = f/int_fps % 60; - frames = f%int_fps; - } - if (view == olive::kTimecodeMilliseconds) { - return QString::number((hours*3600000)+(mins*60000)+(secs*1000)+qCeil(frames*1000/frame_rate)); - } - return QString(QString::number(hours).rightJustified(2, '0') + - ":" + QString::number(mins).rightJustified(2, '0') + - ":" + QString::number(secs).rightJustified(2, '0') + - token + QString::number(frames).rightJustified(2, '0') - ); -} - -bool frame_rate_is_droppable(double rate) { - return (qFuzzyCompare(rate, 23.976) || qFuzzyCompare(rate, 29.97) || qFuzzyCompare(rate, 59.94)); -} - void Viewer::seek(long p) { pause(); - if (main_sequence) { + + if (mode_ == kTimelineMode) { seq->playhead = p; } else { - seq->playhead = qMin(seq->getEndFrame(), qMax(0L, p)); + seq->playhead = qMin(seq->GetEndFrame(), qMax(0L, p)); } + bool update_fx = false; - if (main_sequence) { - panel_timeline->scroll_to_frame(p); + + if (mode_ == kTimelineMode) { + panel_timeline.first()->scroll_to_frame(p); panel_effect_controls->scroll_to_frame(p); - if (olive::CurrentConfig.seek_also_selects) { - panel_timeline->select_from_playhead(); + if (olive::config.seek_also_selects) { + seq->SelectAtPlayhead(); update_fx = true; } } + reset_all_audio(); audio_scrub = true; last_playhead = seq->playhead; @@ -298,7 +182,7 @@ void Viewer::go_to_start() { } void Viewer::go_to_end() { - if (seq != nullptr) seek(seq->getEndFrame()); + if (seq != nullptr) seek(seq->GetEndFrame()); } void Viewer::close_media() { @@ -333,7 +217,7 @@ void Viewer::go_to_out() { } } -void Viewer::cue_recording(long start, long end, int track) { +void Viewer::cue_recording(long start, long end, Track* track) { recording_start = start; recording_end = end; recording_track = track; @@ -384,7 +268,7 @@ void Viewer::play(bool in_to_out) { if (panel_sequence_viewer->playing) panel_sequence_viewer->pause(); if (panel_footage_viewer->playing) panel_footage_viewer->pause(); - long sequence_end_frame = seq->getEndFrame(); + long sequence_end_frame = seq->GetEndFrame(); if (sequence_end_frame == 0) { return; } @@ -397,11 +281,11 @@ void Viewer::play(bool in_to_out) { uncue_recording(); } - bool seek_to_in = (seq->using_workarea && (olive::CurrentConfig.loop || playing_in_to_out)); + bool seek_to_in = (seq->using_workarea && (olive::config.loop || playing_in_to_out)); if (!is_recording_cued() && playback_speed >= 0 && (playing_in_to_out - || (olive::CurrentConfig.auto_seek_to_beginning && seq->playhead >= sequence_end_frame) + || (olive::config.auto_seek_to_beginning && seq->playhead >= sequence_end_frame) || (seek_to_in && seq->playhead >= seq->workarea_out))) { seek(seek_to_in ? seq->workarea_in : 0); } @@ -448,29 +332,46 @@ void Viewer::pause() { // import audio QStringList file_list; file_list.append(get_recorded_audio_filename()); - panel_project->process_file_list(file_list); + olive::project_model.process_file_list(file_list); // add it to the sequence - ClipPtr c = std::make_shared(seq.get()); - Media* m = panel_project->last_imported_media.at(0); + QVector last_imported_media = olive::project_model.GetLastImportedMedia(); + Media* m = last_imported_media.first(); Footage* f = m->to_footage(); // wait for footage to be completely ready before taking metadata from it f->ready_lock.lock(); - - c->set_media(m, 0); // latest media - c->set_timeline_in(recording_start); - c->set_timeline_out(recording_start + f->get_length_in_frames(seq->frame_rate)); - c->set_clip_in(0); - c->set_track(recording_track); - c->set_color(128, 192, 128); - c->set_name(m->get_name()); - f->ready_lock.unlock(); - QVector add_clips; - add_clips.append(c); - olive::UndoStack.push(new AddClipCommand(seq.get(), add_clips)); // add clip + // Check if we were able to import the audio we just recorded + if (f->invalid) { + + QMessageBox::critical(this, + tr("Failed to import recorded file"), + tr("An error occurred trying to import the recorded audio"), + QMessageBox::Ok); + + } else { + + // Make a clip out of it and add it to the Sequence + + ClipPtr c = std::make_shared(recording_track); + + c->set_media(m, 0); // latest media + c->set_timeline_in(recording_start); + c->set_timeline_out(recording_start + f->get_length_in_frames(seq->frame_rate)); + c->set_clip_in(0); + c->set_color(128, 192, 128); + c->set_name(m->get_name()); + + QVector add_clips; + add_clips.append(c); + olive::undo_stack.push(new AddClipCommand(add_clips)); // add clip + + } + + + } } } @@ -480,17 +381,17 @@ void Viewer::update_playhead_timecode(long p) { } void Viewer::update_end_timecode() { - end_timecode->setText((seq == nullptr) ? frame_to_timecode(0, olive::CurrentConfig.timecode_view, 30) : frame_to_timecode(seq->getEndFrame(), olive::CurrentConfig.timecode_view, seq->frame_rate)); + end_timecode->setText((seq == nullptr) ? frame_to_timecode(0, olive::config.timecode_view, 30) : frame_to_timecode(seq->GetEndFrame(), olive::config.timecode_view, seq->frame_rate)); } void Viewer::update_header_zoom() { if (seq != nullptr) { - long sequenceEndFrame = seq->getEndFrame(); + long sequenceEndFrame = seq->GetEndFrame(); if (cached_end_frame != sequenceEndFrame) { - minimum_zoom = (sequenceEndFrame > 0) ? ((double) headers->width() / (double) sequenceEndFrame) : 1; + minimum_zoom = (sequenceEndFrame > 0) ? (double(headers->width()) / double(sequenceEndFrame)) : 1; headers->update_zoom(qMax(headers->get_zoom(), minimum_zoom)); set_sb_max(); - viewer_widget->waveform_zoom = headers->get_zoom(); + viewer_widget_->waveform_zoom = headers->get_zoom(); } else { headers->update(); } @@ -498,11 +399,10 @@ void Viewer::update_header_zoom() { } void Viewer::update_parents(bool reload_fx) { - if (main_sequence) { + if (mode_ == kTimelineMode) { update_ui(reload_fx); } else { update_viewer(); - panel_timeline->repaint_timeline(); } } @@ -510,27 +410,77 @@ int Viewer::get_playback_speed() { return playback_speed; } +ViewerWidget *Viewer::viewer_widget() +{ + return viewer_widget_; +} + void Viewer::set_marker() { - set_marker_internal(seq.get()); + Marker::SetOnSequence(seq.get()); } void Viewer::resizeEvent(QResizeEvent *e) { QDockWidget::resizeEvent(e); if (seq != nullptr) { set_sb_max(); - viewer_widget->update(); + viewer_widget_->update(); } } void Viewer::update_viewer() { update_header_zoom(); - viewer_widget->frame_update(); + viewer_widget_->frame_update(); if (seq != nullptr) { update_playhead_timecode(seq->playhead); } update_end_timecode(); } +void Viewer::prev_cut() +{ + if (seq != nullptr + && seq->playhead > 0) { + + QVector sequence_clips = seq->GetAllClips(); + + long p_cut = 0; + for (int i=0;itimeline_out() > p_cut && c->timeline_out() < seq->playhead) { + p_cut = c->timeline_out(); + } else if (c->timeline_in() > p_cut && c->timeline_in() < seq->playhead) { + p_cut = c->timeline_in(); + } + } + panel_sequence_viewer->seek(p_cut); + } +} + +void Viewer::next_cut() +{ + if (seq != nullptr) { + + QVector sequence_clips = seq->GetAllClips(); + + bool seek_enabled = false; + long n_cut = LONG_MAX; + for (int i=0;itimeline_in() < n_cut && c->timeline_in() > seq->playhead) { + n_cut = c->timeline_in(); + seek_enabled = true; + } else if (c->timeline_out() < n_cut && c->timeline_out() > seq->playhead) { + n_cut = c->timeline_out(); + seek_enabled = true; + } + } + + if (seek_enabled) { + panel_sequence_viewer->seek(n_cut); + } + } +} + void Viewer::initiate_drag(olive::timeline::MediaImportType drag_type) { // FIXME: This should contain actual metadata rather than fake metadata @@ -545,7 +495,7 @@ void Viewer::initiate_drag(olive::timeline::MediaImportType drag_type) void Viewer::clear_in() { if (seq != nullptr && seq->using_workarea) { - olive::UndoStack.push(new SetTimelineInOutCommand(seq.get(), true, 0, seq->workarea_out)); + olive::undo_stack.push(new SetTimelineInOutCommand(seq.get(), true, 0, seq->workarea_out)); update_parents(); } } @@ -553,7 +503,7 @@ void Viewer::clear_in() { void Viewer::clear_out() { if (seq != nullptr && seq->using_workarea) { - olive::UndoStack.push(new SetTimelineInOutCommand(seq.get(), true, seq->workarea_in, seq->getEndFrame())); + olive::undo_stack.push(new SetTimelineInOutCommand(seq.get(), true, seq->workarea_in, seq->GetEndFrame())); update_parents(); } } @@ -561,7 +511,7 @@ void Viewer::clear_out() { void Viewer::clear_inout_point() { if (seq != nullptr && seq->using_workarea) { - olive::UndoStack.push(new SetTimelineInOutCommand(seq.get(), false, 0, 0)); + olive::undo_stack.push(new SetTimelineInOutCommand(seq.get(), false, 0, 0)); update_parents(); } } @@ -607,9 +557,9 @@ void Viewer::update_window_title() { void Viewer::set_zoom_value(double d) { headers->update_zoom(d); - if (viewer_widget->waveform) { - viewer_widget->waveform_zoom = d; - viewer_widget->update(); + if (viewer_widget_->waveform) { + viewer_widget_->waveform_zoom = d; + viewer_widget_->update(); } if (seq != nullptr) { set_sb_max(); @@ -619,7 +569,7 @@ void Viewer::set_zoom_value(double d) { } void Viewer::set_sb_max() { - headers->set_scrollbar_max(horizontal_bar, seq->getEndFrame(), headers->width()); + headers->set_scrollbar_max(horizontal_bar, seq->GetEndFrame(), headers->width()); } void Viewer::set_playback_speed(int s) { @@ -631,15 +581,15 @@ void Viewer::set_playback_speed(int s) { } long Viewer::get_seq_in() { - return ((olive::CurrentConfig.loop || playing_in_to_out) && seq->using_workarea) + return ((olive::config.loop || playing_in_to_out) && seq->using_workarea) ? seq->workarea_in : 0; } long Viewer::get_seq_out() { - return ((olive::CurrentConfig.loop || playing_in_to_out) && seq->using_workarea && previous_playhead < seq->workarea_out) + return ((olive::config.loop || playing_in_to_out) && seq->using_workarea && previous_playhead < seq->workarea_out) ? seq->workarea_out - : seq->getEndFrame(); + : seq->GetEndFrame(); } void Viewer::setup_ui() { @@ -769,7 +719,6 @@ void Viewer::setup_ui() { } void Viewer::set_media(Media* m) { - main_sequence = false; media = m; SequencePtr new_sequence = nullptr; @@ -793,7 +742,7 @@ void Viewer::set_media(Media* m) { new_sequence->workarea_out = footage->out; } - new_sequence->frame_rate = olive::CurrentConfig.default_sequence_framerate; + new_sequence->frame_rate = olive::config.default_sequence_framerate; if (footage->video_tracks.size() > 0) { const FootageStream& video_stream = footage->video_tracks.at(0); @@ -803,7 +752,7 @@ void Viewer::set_media(Media* m) { new_sequence->frame_rate = video_stream.video_frame_rate * footage->speed; } - ClipPtr c = std::make_shared(new_sequence.get()); + ClipPtr c = std::make_shared(new_sequence->GetTrackList(olive::kTypeVideo)->First()); c->set_media(media, video_stream.file_index); c->set_timeline_in(0); c->set_timeline_out(footage->get_length_in_frames(new_sequence->frame_rate)); @@ -811,36 +760,37 @@ void Viewer::set_media(Media* m) { // FIXME: Move this magic number to Config c->set_timeline_out(150); } - c->set_track(-1); + Track* track = new_sequence->GetTrackList(olive::kTypeVideo)->First(); + c->set_track(track); c->set_clip_in(0); c->refresh(); - new_sequence->clips.append(c); + track->AddClip(c); } else { - new_sequence->width = olive::CurrentConfig.default_sequence_width; - new_sequence->height = olive::CurrentConfig.default_sequence_height; + new_sequence->width = olive::config.default_sequence_width; + new_sequence->height = olive::config.default_sequence_height; } if (footage->audio_tracks.size() > 0) { const FootageStream& audio_stream = footage->audio_tracks.at(0); new_sequence->audio_frequency = audio_stream.audio_frequency; - ClipPtr c = std::make_shared(new_sequence.get()); + Track* track = new_sequence->GetTrackList(olive::kTypeAudio)->First(); + ClipPtr c = std::make_shared(track); c->set_media(media, audio_stream.file_index); c->set_timeline_in(0); c->set_timeline_out(footage->get_length_in_frames(new_sequence->frame_rate)); - c->set_track(0); c->set_clip_in(0); c->refresh(); - new_sequence->clips.append(c); + track->AddClip(c); if (footage->video_tracks.size() == 0) { - viewer_widget->waveform = true; - viewer_widget->waveform_clip = c; - viewer_widget->waveform_ms = &audio_stream; - viewer_widget->frame_update(); + viewer_widget_->waveform = true; + viewer_widget_->waveform_clip = c; + viewer_widget_->waveform_ms = &audio_stream; + viewer_widget_->frame_update(); } } else { - new_sequence->audio_frequency = olive::CurrentConfig.default_sequence_audio_frequency; + new_sequence->audio_frequency = olive::config.default_sequence_audio_frequency; } new_sequence->audio_layout = AV_CH_LAYOUT_STEREO; @@ -852,7 +802,7 @@ void Viewer::set_media(Media* m) { } } - set_sequence(false, new_sequence); + set_sequence(new_sequence); } void Viewer::update_playhead() { @@ -864,11 +814,11 @@ void Viewer::timer_update() { seq->playhead = qMax(0, qRound(playhead_start + ((QDateTime::currentMSecsSinceEpoch()-start_msecs) * 0.001 * seq->frame_rate * playback_speed))); - if (olive::CurrentConfig.seek_also_selects) { - panel_timeline->select_from_playhead(); + if (olive::config.seek_also_selects) { + seq->SelectAtPlayhead(); } - update_parents(olive::CurrentConfig.seek_also_selects); + update_parents(olive::config.seek_also_selects); if (playing) { if (playback_speed < 0 && seq->playhead == 0) { @@ -878,12 +828,12 @@ void Viewer::timer_update() { pause(); } } else if (playback_speed > 0) { - long end_frame = seq->getEndFrame(); - if ((olive::CurrentConfig.auto_seek_to_beginning || previous_playhead < end_frame) && seq->playhead >= end_frame) { + long end_frame = seq->GetEndFrame(); + if ((olive::config.auto_seek_to_beginning || previous_playhead < end_frame) && seq->playhead >= end_frame) { pause(); } if (seq->using_workarea && seq->playhead >= seq->workarea_out) { - if (olive::CurrentConfig.loop) { + if (olive::config.loop) { // loop play(); } else if (playing_in_to_out) { @@ -917,7 +867,7 @@ void Viewer::drag_audio_only() } void Viewer::clean_created_seq() { - viewer_widget->waveform = false; + viewer_widget_->waveform = false; if (created_sequence) { // TODO delete undo commands referencing this sequence to avoid crashes @@ -936,32 +886,28 @@ void Viewer::clean_created_seq() { } } -void Viewer::set_sequence(bool main, SequencePtr s) { +void Viewer::set_sequence(SequencePtr s) { pause(); reset_all_audio(); - viewer_widget->wait_until_render_is_paused(); + viewer_widget_->wait_until_render_is_paused(); // If we had a current sequence open, close it if (seq != nullptr) { - close_active_clips(seq.get()); + seq->Close(); } clean_created_seq(); - main_sequence = main; - - - - seq = (main) ? olive::ActiveSequence : s; + seq = s; bool null_sequence = (seq == nullptr); headers->setEnabled(!null_sequence); current_timecode_slider->setEnabled(!null_sequence); - viewer_widget->setEnabled(!null_sequence); - viewer_widget->setVisible(!null_sequence); + viewer_widget_->setEnabled(!null_sequence); + viewer_widget_->setVisible(!null_sequence); go_to_start_button->setEnabled(!null_sequence); prev_frame_button->setEnabled(!null_sequence); play_button->setEnabled(!null_sequence); @@ -992,7 +938,7 @@ void Viewer::set_sequence(bool main, SequencePtr s) { update_header_zoom(); - viewer_widget->frame_update(); + viewer_widget_->frame_update(); update(); } diff --git a/panels/viewer.h b/panels/viewer.h index e4c233d4e..9ce04ee39 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -32,26 +32,27 @@ #include "ui/panel.h" #include "ui/viewerwidget.h" -#include "ui/timelinewidget.h" +#include "ui/timelineview.h" #include "ui/timelineheader.h" #include "ui/labelslider.h" #include "ui/resizablescrollbar.h" -bool frame_rate_is_droppable(double rate); -long timecode_to_frame(const QString& s, int view, double frame_rate); -QString frame_to_timecode(long f, int view, double frame_rate); - class Viewer : public Panel { Q_OBJECT public: explicit Viewer(QWidget *parent = nullptr); - ~Viewer(); - bool is_focused(); - bool is_main_sequence(); - void set_main_sequence(); + enum Mode { + kFootageMode, + kTimelineMode + }; + + void SetMode(Mode mode); + Mode mode(); + + virtual bool focused() override; void set_media(Media *m); void compose(); void set_playpause_icon(bool play); @@ -77,19 +78,19 @@ public: QTimer playback_updater; - void cue_recording(long start, long end, int track); + void cue_recording(long start, long end, Track *track); void uncue_recording(); bool is_recording_cued(); long recording_start; long recording_end; - int recording_track; + Track* recording_track; void reset_all_audio(); void update_parents(bool reload_fx = false); int get_playback_speed(); - ViewerWidget* viewer_widget; + ViewerWidget* viewer_widget(); Media* media; SequencePtr seq; @@ -108,6 +109,8 @@ protected: virtual void resizeEvent(QResizeEvent *event) override; public slots: + void set_sequence(SequencePtr s); + void play_wake(); void go_to_start(); void go_to_in(); @@ -120,7 +123,8 @@ public slots: void go_to_end(); void close_media(); void update_viewer(); - + void prev_cut(); + void next_cut(); private slots: @@ -133,11 +137,8 @@ private slots: void drag_audio_only(); private: - void update_window_title(); void clean_created_seq(); - void set_sequence(bool main, SequencePtr s); - bool main_sequence; bool created_sequence; long cached_end_frame; QString panel_name; @@ -153,6 +154,8 @@ private: void setup_ui(); + ViewerWidget* viewer_widget_; + ResizableScrollBar* horizontal_bar; ViewerContainer* viewer_container; LabelSlider* current_timecode_slider; @@ -172,6 +175,8 @@ private: long previous_playhead; int playback_speed; + + Mode mode_; }; #endif // VIEWER_H diff --git a/project/clipboard.cpp b/project/clipboard.cpp deleted file mode 100644 index 47a13385b..000000000 --- a/project/clipboard.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "clipboard.h" - -#include "timeline/clip.h" -#include "effects/effect.h" -#include "effects/transition.h" - -int clipboard_type = CLIPBOARD_TYPE_CLIP; -QVector clipboard; -QVector clipboard_transitions; - -void clear_clipboard() { - clipboard.clear(); - clipboard_transitions.clear(); -} diff --git a/project/clipboard.h b/project/clipboard.h deleted file mode 100644 index 4ca8c580d..000000000 --- a/project/clipboard.h +++ /dev/null @@ -1,38 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef CLIPBOARD_H -#define CLIPBOARD_H - -#include - -#include - -#define CLIPBOARD_TYPE_CLIP 0 -#define CLIPBOARD_TYPE_EFFECT 1 - -using VoidPtr = std::shared_ptr; - -extern int clipboard_type; -extern QVector clipboard_transitions; -extern QVector clipboard; -void clear_clipboard(); - -#endif // CLIPBOARD_H diff --git a/project/footage.cpp b/project/footage.cpp index 7ac98617a..a08d052fc 100644 --- a/project/footage.cpp +++ b/project/footage.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -23,21 +23,26 @@ #include #include #include +#include +#include +namespace OCIO = OCIO_NAMESPACE::v1; #include "project/previewgenerator.h" #include "timeline/clip.h" +#include "global/config.h" +#include "global/global.h" -Footage::Footage() { - ready = (false); - preview_gen = (nullptr); - invalid = (false); - in = (0); - out = (0); - speed = (1.0); - alpha_is_premultiplied = (false); - proxy = (false); - start_number = 0; - +Footage::Footage() : + ready(false), + preview_gen(nullptr), + invalid(false), + in(0), + out(0), + speed(1.0), + alpha_is_associated(true), + proxy(false), + start_number(0) +{ ready_lock.lock(); } @@ -45,6 +50,79 @@ Footage::~Footage() { reset(); } +void Footage::Save(QXmlStreamWriter &stream) +{ + QDir proj_dir = QFileInfo(olive::ActiveProjectFilename).absoluteDir(); + + stream.writeStartElement("footage"); + stream.writeAttribute("id", QString::number(save_id)); + stream.writeAttribute("name", name); + stream.writeAttribute("url", proj_dir.relativeFilePath(url)); + stream.writeAttribute("duration", QString::number(length)); + stream.writeAttribute("using_inout", QString::number(using_inout)); + stream.writeAttribute("in", QString::number(in)); + stream.writeAttribute("out", QString::number(out)); + stream.writeAttribute("speed", QString::number(speed)); + stream.writeAttribute("alphapremul", QString::number(alpha_is_associated)); + stream.writeAttribute("startnumber", QString::number(start_number)); + stream.writeAttribute("colorspace", Colorspace()); + + stream.writeAttribute("proxy", QString::number(proxy)); + stream.writeAttribute("proxypath", proxy_path); + + // save video stream metadata + for (int j=0;jparseColorSpaceFromString(url.toUtf8()); + + if (!guess_colorspace.isEmpty()) { + return guess_colorspace; + } + + return olive::config.ocio_default_input_colorspace; +} + +void Footage::SetColorspace(const QString &cs) +{ + colorspace_ = cs; +} + void Footage::reset() { if (preview_gen != nullptr) { preview_gen->cancel(); @@ -77,3 +155,25 @@ FootageStream* Footage::get_stream_from_file_index(bool video, int index) { } return nullptr; } + +QString Footage::get_interlacing_name(int interlacing) { + switch (interlacing) { + case VIDEO_PROGRESSIVE: return QCoreApplication::translate("InterlacingName", "None (Progressive)"); + case VIDEO_TOP_FIELD_FIRST: return QCoreApplication::translate("InterlacingName", "Upper Field First"); + case VIDEO_BOTTOM_FIELD_FIRST: return QCoreApplication::translate("InterlacingName", "Lower Field First"); + default: return QCoreApplication::translate("InterlacingName", "Invalid"); + } +} + +QString Footage::get_channel_layout_name(int channels, uint64_t layout) { + switch (channels) { + case 0: return QCoreApplication::translate("ChannelLayoutName", "Invalid"); + case 1: return QCoreApplication::translate("ChannelLayoutName", "Mono"); + case 2: return QCoreApplication::translate("ChannelLayoutName", "Stereo"); + default: { + char buf[50]; + av_get_channel_layout_string(buf, sizeof(buf), channels, layout); + return QString(buf); + } + } +} diff --git a/project/footage.h b/project/footage.h index a6f45d8ac..8b250b9e4 100644 --- a/project/footage.h +++ b/project/footage.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -62,13 +62,16 @@ struct FootageStream { // preview thumbnail/waveform bool preview_done; QImage video_preview; - QVector audio_preview; + QVector audio_preview; }; -struct Footage { +class Footage { +public: Footage(); ~Footage(); + void Save(QXmlStreamWriter& stream); + // footage metadata QString url; QString name; @@ -79,9 +82,13 @@ struct Footage { bool ready; bool invalid; double speed; - bool alpha_is_premultiplied; + bool alpha_is_associated; int start_number; + // color management + QString Colorspace(); + void SetColorspace(const QString& cs); + // proxy config bool proxy; QString proxy_path; @@ -102,6 +109,11 @@ struct Footage { long get_length_in_frames(double frame_rate); FootageStream *get_stream_from_file_index(bool video, int index); void reset(); + + static QString get_channel_layout_name(int channels, uint64_t layout); + static QString get_interlacing_name(int interlacing); +private: + QString colorspace_; }; using FootagePtr = std::shared_ptr; diff --git a/project/loadthread.cpp b/project/loadthread.cpp index 9e616dfe4..dee719ecc 100644 --- a/project/loadthread.cpp +++ b/project/loadthread.cpp @@ -1,42 +1,43 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #include "loadthread.h" #include "global/global.h" - #include "ui/mainwindow.h" - #include "panels/panels.h" - #include "project/projectelements.h" - #include "global/config.h" #include "rendering/renderfunctions.h" #include "project/previewgenerator.h" +#include "project/projectfunctions.h" #include "effects/internal/voideffect.h" #include "global/debug.h" +#include "effects/effectloaders.h" #include #include +const int LOAD_TYPE_VERSION = 100; +const int LOAD_TYPE_URL = 101; + LoadThread::LoadThread(const QString& filename, bool autorecovery) : filename_(filename), autorecovery_(autorecovery), @@ -59,7 +60,7 @@ void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) { QString tag = stream.name().toString(); // variables to store effect metadata in - int effect_id = -1; + QString effect_id; QString effect_name; bool effect_enabled = true; long effect_length = -1; @@ -67,10 +68,10 @@ void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) { // loop through attributes for effect metadata for (int j=0;jsequence->clips.size();i++) { - Clip* test_clip = c->sequence->clips.at(i).get(); + + QVector sequence_clips = c->track()->sequence()->GetAllClips(); + for (int i=0;iload_id == clip_id) { sharing_clip = test_clip; break; @@ -111,32 +114,37 @@ void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) { // Effect loading occurs in another thread, and while it's usually very quick, just for safety we wait here // for all the effects to finish loading - panel_effect_controls->effects_loaded.lock(); + olive::effects_loaded.lock(); - const EffectMeta* meta = nullptr; + NodeType type = kInvalidNode; // find effect with this name - if (!effect_name.isEmpty()) { - meta = get_meta_from_name(effect_name); + if (!effect_id.isEmpty()) { + for (int i=0;iid() == effect_id) { + type = static_cast(i); + break; + } + } } - panel_effect_controls->effects_loaded.unlock(); + olive::effects_loaded.unlock(); - int type; + TransitionType ttype; if (tag == "opening") { - type = kTransitionOpening; + ttype = kTransitionOpening; } else if (tag == "closing") { - type = kTransitionClosing; + ttype = kTransitionClosing; } else { - type = kTransitionNone; + ttype = kTransitionNone; } // effect construction if (cancelled_) return; - if (type == kTransitionNone) { - if (meta == nullptr) { + if (ttype == kTransitionNone) { + if (type == kInvalidNode) { // create void effect - EffectPtr ve(new VoidEffect(c, effect_name)); + NodePtr ve = std::make_shared(c, effect_name, effect_id); ve->SetEnabled(effect_enabled); ve->load(stream); @@ -144,7 +152,7 @@ void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) { c->effects.append(ve); } else { - EffectPtr e(Effect::Create(c, meta)); + NodePtr e = olive::node_library[type]->Create(c); e->SetEnabled(effect_enabled); e->load(stream); @@ -153,14 +161,16 @@ void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) { c->effects.append(e); } } else { - TransitionPtr t = Transition::Create(c, nullptr, meta); - if (effect_length > -1) t->set_length(effect_length); + TransitionPtr t = std::static_pointer_cast(olive::node_library[type]->Create(c)); + if (effect_length > -1) { + t->set_length(effect_length); + } t->SetEnabled(effect_enabled); t->load(stream); t->moveToThread(QApplication::instance()->thread()); - if (type == kTransitionOpening) { + if (ttype == kTransitionOpening) { c->opening_transition = t; } else { c->closing_transition = t; @@ -262,7 +272,7 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { switch (type) { case MEDIA_TYPE_FOLDER: { - MediaPtr folder = panel_project->create_folder_internal(nullptr); + MediaPtr folder = olive::project::CreateFolder(nullptr); folder->temp_id2 = 0; for (int j=0;jspeed = attr.value().toDouble(); } else if (attr.name() == "alphapremul") { - f->alpha_is_premultiplied = (attr.value() == "1"); + f->alpha_is_associated = (attr.value() == "1"); } else if (attr.name() == "proxy") { f->proxy = (attr.value() == "1"); } else if (attr.name() == "proxypath") { f->proxy_path = attr.value().toString(); } else if (attr.name() == "startnumber") { f->start_number = attr.value().toInt(); + } else if (attr.name() == "colorspace") { + f->SetColorspace(attr.value().toString()); } } @@ -438,7 +450,9 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { int media_id = -1; int stream_id = -1; - ClipPtr c = std::make_shared(s.get()); + Track* t; + ClipPtr c = std::make_shared(t); + //ClipPtr c = std::make_shared(s.get()); QColor clip_color; ClipSpeed speed_info = c->speed(); @@ -457,8 +471,6 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { c->set_timeline_in(attr.value().toLong()); } else if (attr.name() == "out") { c->set_timeline_out(attr.value().toLong()); - } else if (attr.name() == "track") { - c->set_track(attr.value().toInt()); } else if (attr.name() == "r") { clip_color.setRed(attr.value().toInt()); } else if (attr.name() == "g") { @@ -516,7 +528,8 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { for (int k=0;klinked.append(link_attr.value().toInt()); + // FIXME reimplement this + //c->linked.append(link_attr.value().toInt()); break; } } @@ -544,11 +557,12 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { } if (cancelled_) return false; - s->clips.append(c); + //s->clips.append(c); } } if (cancelled_) return false; + /* // correct links, clip IDs, transitions for (int i=0;iclips.size();i++) { // correct links @@ -583,6 +597,7 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { MediaPtr m = panel_project->create_sequence_internal(nullptr, s, false, parent); loaded_sequences.append(m.get()); + */ } break; } @@ -778,11 +793,11 @@ void LoadThread::success_func() { olive::Global->update_project_filename(orig_filename); } else { - panel_project->add_recent_project(filename_); + olive::Global->add_recent_project(filename_); } olive::Global->set_modified(autorecovery_); if (open_seq != nullptr) { - olive::Global->set_sequence(open_seq); + Timeline::OpenSequence(open_seq); } } diff --git a/project/loadthread.h b/project/loadthread.h index 2a8d1a07b..7f57f60cc 100644 --- a/project/loadthread.h +++ b/project/loadthread.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/project/media.cpp b/project/media.cpp index cde487055..a242142c5 100644 --- a/project/media.cpp +++ b/project/media.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -33,40 +33,36 @@ extern "C" { #include "undo/undo.h" #include "undo/undostack.h" #include "global/config.h" -#include "panels/viewer.h" #include "panels/project.h" #include "ui/icons.h" #include "projectmodel.h" #include "global/debug.h" - -QString get_interlacing_name(int interlacing) { - switch (interlacing) { - case VIDEO_PROGRESSIVE: return QCoreApplication::translate("InterlacingName", "None (Progressive)"); - case VIDEO_TOP_FIELD_FIRST: return QCoreApplication::translate("InterlacingName", "Top Field First"); - case VIDEO_BOTTOM_FIELD_FIRST: return QCoreApplication::translate("InterlacingName", "Bottom Field First"); - default: return QCoreApplication::translate("InterlacingName", "Invalid"); - } -} - -QString get_channel_layout_name(int channels, uint64_t layout) { - switch (channels) { - case 0: return QCoreApplication::translate("ChannelLayoutName", "Invalid"); - case 1: return QCoreApplication::translate("ChannelLayoutName", "Mono"); - case 2: return QCoreApplication::translate("ChannelLayoutName", "Stereo"); - default: { - char buf[50]; - av_get_channel_layout_string(buf, sizeof(buf), channels, layout); - return QString(buf); - } - } -} +#include "global/timing.h" Media::Media() : root(false), - type(-1) + type(-1), + disable_thumbnail_(false) { } +void Media::Save(QXmlStreamWriter &stream) +{ + switch (type) { + case MEDIA_TYPE_FOLDER: + stream.writeStartElement("folder"); + stream.writeAttribute("name", get_name()); + stream.writeEndElement(); + break; + case MEDIA_TYPE_FOOTAGE: + to_footage()->Save(stream); + break; + case MEDIA_TYPE_SEQUENCE: + to_sequence()->Save(stream); + break; + } +} + Footage* Media::to_footage() { return static_cast(object.get()); } @@ -154,7 +150,7 @@ void Media::update_tooltip(const QString& error) { if (i > 0) { tooltip += ", "; } - tooltip += get_interlacing_name(f->video_tracks.at(i).video_interlacing); + tooltip += Footage::get_interlacing_name(f->video_tracks.at(i).video_interlacing); } } @@ -175,7 +171,7 @@ void Media::update_tooltip(const QString& error) { if (i > 0) { tooltip += ", "; } - tooltip += get_channel_layout_name(f->audio_tracks.at(i).audio_channels, f->audio_tracks.at(i).audio_layout); + tooltip += Footage::get_channel_layout_name(f->audio_tracks.at(i).audio_channels, f->audio_tracks.at(i).audio_layout); } // tooltip += "\n"; } @@ -198,7 +194,7 @@ void Media::update_tooltip(const QString& error) { QString::number(s->height), QString::number(s->frame_rate), QString::number(s->audio_frequency), - get_channel_layout_name(av_get_channel_layout_nb_channels(s->audio_layout), s->audio_layout) + Footage::get_channel_layout_name(av_get_channel_layout_nb_channels(s->audio_layout), s->audio_layout) ); } break; @@ -230,6 +226,11 @@ void Media::set_name(const QString &n) { } } +void Media::disable_thumbnail(bool disable) +{ + disable_thumbnail_ = disable; +} + double Media::get_frame_rate(int stream) { switch (get_type()) { case MEDIA_TYPE_FOOTAGE: @@ -265,7 +266,7 @@ bool Media::setData(int col, const QVariant &value) { if (col == 0) { QString n = value.toString(); if (!n.isEmpty() && get_name() != n) { - olive::UndoStack.push(new MediaRename(this, value.toString())); + olive::undo_stack.push(new MediaRename(this, value.toString())); return true; } } @@ -287,7 +288,7 @@ int Media::columnCount() const { QString Media::GetStringDuration() { if (get_type() == MEDIA_TYPE_SEQUENCE) { Sequence* s = to_sequence().get(); - return frame_to_timecode(s->getEndFrame(), olive::CurrentConfig.timecode_view, s->frame_rate); + return frame_to_timecode(s->GetEndFrame(), olive::config.timecode_view, s->frame_rate); } if (get_type() == MEDIA_TYPE_FOOTAGE) { Footage* f = to_footage(); @@ -297,7 +298,7 @@ QString Media::GetStringDuration() { r = f->video_tracks.at(0).video_frame_rate * f->speed; long len = f->get_length_in_frames(r); - if (len > 0) return frame_to_timecode(len, olive::CurrentConfig.timecode_view, r); + if (len > 0) return frame_to_timecode(len, olive::config.timecode_view, r); } return QString(); } @@ -308,7 +309,8 @@ QVariant Media::data(int column, int role) { if (column == 0) { if (get_type() == MEDIA_TYPE_FOOTAGE) { Footage* f = to_footage(); - if (f->video_tracks.size() > 0 + if (!disable_thumbnail_ + && f->video_tracks.size() > 0 && f->video_tracks.at(0).preview_done) { return QIcon(QPixmap::fromImage(f->video_tracks.at(0).video_preview)); } diff --git a/project/media.h b/project/media.h index 2daff6aaf..e9555b310 100644 --- a/project/media.h +++ b/project/media.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -47,6 +47,8 @@ class Media public: Media(); + void Save(QXmlStreamWriter& stream); + Footage *to_footage(); SequencePtr to_sequence(); void set_icon(const QString& str); @@ -60,6 +62,7 @@ public: int get_type(); const QString& get_name(); void set_name(const QString& n); + void disable_thumbnail(bool disable); double get_frame_rate(int stream = -1); int get_sampling_rate(int stream = -1); @@ -95,6 +98,7 @@ private: QString folder_name; QString tooltip; QIcon icon; + bool disable_thumbnail_; }; #endif // MEDIA_H diff --git a/project/previewgenerator.cpp b/project/previewgenerator.cpp index f14fe8ddf..36895599b 100644 --- a/project/previewgenerator.cpp +++ b/project/previewgenerator.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -168,10 +168,7 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) { f.open(QFile::ReadOnly); QByteArray data = f.readAll(); ms.audio_preview.resize(data.size()); - for (int j=0;jupdate_tooltip(); } - if (olive::ActiveSequence != nullptr) { - olive::ActiveSequence->RefreshClips(media_); + QVector all_sequences = olive::project_model.GetAllSequences(); + for (int i=0;ito_sequence()->RefreshClipsUsingMedia(media_); } } } @@ -244,7 +242,7 @@ void PreviewGenerator::generate_waveform() { int64_t* media_lengths = new int64_t[fmt_ctx_->nb_streams]{0}; // stores samples while scanning before they get sent to preview file - qint16*** waveform_cache_data = new qint16** [fmt_ctx_->nb_streams]; + qint8*** waveform_cache_data = new qint8** [fmt_ctx_->nb_streams]; int waveform_cache_count = 0; // defaults to false, sets to true if we find a valid stream to make a preview of @@ -258,8 +256,8 @@ void PreviewGenerator::generate_waveform() { // we only generate previews for video and audio // and only if the thumbnail and waveform sizes are > 0 - if ((fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && olive::CurrentConfig.thumbnail_resolution > 0) - || (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && olive::CurrentConfig.waveform_resolution > 0)) { + if ((fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && olive::config.thumbnail_resolution > 0) + || (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && olive::config.waveform_resolution > 0)) { AVCodec* codec = avcodec_find_decoder(fmt_ctx_->streams[i]->codecpar->codec_id); if (codec != nullptr) { @@ -274,11 +272,11 @@ void PreviewGenerator::generate_waveform() { if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // allocate sample cache for this stream - waveform_cache_data[i] = new qint16* [fmt_ctx_->streams[i]->codecpar->channels]; + waveform_cache_data[i] = new qint8* [fmt_ctx_->streams[i]->codecpar->channels]; // each channel gets a min and a max value so we allocate two ints for each one for (int j=0;jstreams[i]->codecpar->channels;j++) { - waveform_cache_data[i][j] = new qint16[2]; + waveform_cache_data[i][j] = new qint8[2]; } // if codec context has no defined channel layout, guess it from the channel count @@ -332,7 +330,7 @@ void PreviewGenerator::generate_waveform() { if (s != nullptr) { if (fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (!s->preview_done) { - int dstH = olive::CurrentConfig.thumbnail_resolution; + int dstH = olive::config.thumbnail_resolution; int dstW = qRound(dstH * (float(temp_frame->width)/float(temp_frame->height))); sws_ctx = sws_getContext( @@ -380,7 +378,7 @@ void PreviewGenerator::generate_waveform() { AVFrame* swr_frame = av_frame_alloc(); swr_frame->channel_layout = temp_frame->channel_layout; swr_frame->sample_rate = temp_frame->sample_rate; - swr_frame->format = AV_SAMPLE_FMT_S16P; + swr_frame->format = AV_SAMPLE_FMT_U8P; swr_ctx = swr_alloc_set_opts( nullptr, @@ -401,7 +399,7 @@ void PreviewGenerator::generate_waveform() { // `config.waveform_resolution` determines how many samples per second are stored in waveform. // `sample_rate` is samples per second, so `interval` is how many samples are averaged in // each "point" of the waveform - int interval = qFloor((temp_frame->sample_rate/olive::CurrentConfig.waveform_resolution)/4)*4; + int interval = qFloor((temp_frame->sample_rate/olive::config.waveform_resolution)/4)*4; // get the amount of bytes in an audio sample int sample_size = av_get_bytes_per_sample(static_cast(swr_frame->format)); @@ -418,11 +416,11 @@ void PreviewGenerator::generate_waveform() { // if so, we dump our cached values into the preview and reset them // for the next interval for (int j=0;jchannels;j++) { - qint16& min = waveform_cache_data[packet->stream_index][j][0]; - qint16& max = waveform_cache_data[packet->stream_index][j][1]; + qint8& min = waveform_cache_data[packet->stream_index][j][0]; + qint8& max = waveform_cache_data[packet->stream_index][j][1]; - s->audio_preview.append(min >> 8); - s->audio_preview.append(max >> 8); + s->audio_preview.append(min); + s->audio_preview.append(max); } waveform_cache_count = 0; @@ -430,8 +428,8 @@ void PreviewGenerator::generate_waveform() { // standard processing for each channel of information for (int j=0;jchannels;j++) { - qint16& min = waveform_cache_data[packet->stream_index][j][0]; - qint16& max = waveform_cache_data[packet->stream_index][j][1]; + qint8& min = waveform_cache_data[packet->stream_index][j][0]; + qint8& max = waveform_cache_data[packet->stream_index][j][1]; // if we're starting over, reset cache to zero if (waveform_cache_count == 0) { @@ -439,8 +437,10 @@ void PreviewGenerator::generate_waveform() { max = 0; } - // store most minimum and most maximum samples of this interval - qint16 sample = qint16((swr_frame->data[j][i+1] << 8) | swr_frame->data[j][i]); + // Convert unsigned 8-bit PCM sample to signed + qint8 sample = qint8(int(swr_frame->data[j][i]-128)); + + // Store most minimum and most maximum samples of this interval min = qMin(min, sample); max = qMax(max, sample); } @@ -589,7 +589,7 @@ void PreviewGenerator::run() { FootageStream& ms = footage_->audio_tracks[i]; QFile f(get_waveform_path(hash, ms)); f.open(QFile::WriteOnly); - f.write(ms.audio_preview.constData(), ms.audio_preview.size()); + f.write(reinterpret_cast(ms.audio_preview.constData()), ms.audio_preview.size()); f.close(); //dout << "saved" << ms->file_index << "waveform to" << get_waveform_path(hash, ms); } diff --git a/project/previewgenerator.h b/project/previewgenerator.h index 8b273be64..9f7ddea25 100644 --- a/project/previewgenerator.h +++ b/project/previewgenerator.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/project/projectelements.h b/project/projectelements.h index 6868b19c3..c83feaaa5 100644 --- a/project/projectelements.h +++ b/project/projectelements.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/project/projectfilter.cpp b/project/projectfilter.cpp index 5bc330364..e7691007a 100644 --- a/project/projectfilter.cpp +++ b/project/projectfilter.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/project/projectfilter.h b/project/projectfilter.h index 7550d5ebc..40eee7b0a 100644 --- a/project/projectfilter.h +++ b/project/projectfilter.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/project/projectfunctions.cpp b/project/projectfunctions.cpp new file mode 100644 index 000000000..9c36836c2 --- /dev/null +++ b/project/projectfunctions.cpp @@ -0,0 +1,83 @@ +#include "projectfunctions.h" + +#include "projectmodel.h" +#include "global/config.h" + +MediaPtr olive::project::CreateFolder(QString name) { + + MediaPtr item = std::make_shared(); + + item->set_folder(); + item->set_name(name); + + return item; + +} + +SequencePtr olive::project::CreateSequenceFromMedia(QVector &media_list) +{ + SequencePtr s = std::make_shared(); + + s->name = olive::project_model.GetNextSequenceName(); + + // Retrieve default Sequence settings from Config + s->width = olive::config.default_sequence_width; + s->height = olive::config.default_sequence_height; + s->frame_rate = olive::config.default_sequence_framerate; + s->audio_frequency = olive::config.default_sequence_audio_frequency; + s->audio_layout = olive::config.default_sequence_audio_channel_layout; + + bool got_video_values = false; + bool got_audio_values = false; + for (int i=0;iget_type()) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* m = media->to_footage(); + if (m->ready) { + if (!got_video_values) { + for (int j=0;jvideo_tracks.size();j++) { + const FootageStream& ms = m->video_tracks.at(j); + s->width = ms.video_width; + s->height = ms.video_height; + if (!qFuzzyCompare(ms.video_frame_rate, 0.0)) { + s->frame_rate = ms.video_frame_rate * m->speed; + + if (ms.video_interlacing != VIDEO_PROGRESSIVE) s->frame_rate *= 2; + + // only break with a decent frame rate, otherwise there may be a better candidate + got_video_values = true; + break; + } + } + } + if (!got_audio_values && m->audio_tracks.size() > 0) { + const FootageStream& ms = m->audio_tracks.at(0); + s->audio_frequency = ms.audio_frequency; + got_audio_values = true; + } + } + } + break; + case MEDIA_TYPE_SEQUENCE: + { + // Clone all attributes of the original sequence (seq) into the new one (s) + Sequence* seq = media->to_sequence().get(); + + s->width = seq->width; + s->height = seq->height; + s->frame_rate = seq->frame_rate; + s->audio_frequency = seq->audio_frequency; + s->audio_layout = seq->audio_layout; + + got_video_values = true; + got_audio_values = true; + } + break; + } + if (got_video_values && got_audio_values) break; + } + + return s; +} diff --git a/project/projectfunctions.h b/project/projectfunctions.h new file mode 100644 index 000000000..1c62d8f24 --- /dev/null +++ b/project/projectfunctions.h @@ -0,0 +1,18 @@ +#ifndef PROJECTFUNCTIONS_H +#define PROJECTFUNCTIONS_H + +#include "project/media.h" +#include "timeline/sequence.h" +#include "timeline/mediaimportdata.h" + +namespace olive { +namespace project { + +MediaPtr CreateFolder(QString name); + +SequencePtr CreateSequenceFromMedia(QVector &media_list); + +} +} + +#endif // PROJECTFUNCTIONS_H diff --git a/project/projectmodel.cpp b/project/projectmodel.cpp index 56caa3672..234abf6be 100644 --- a/project/projectmodel.cpp +++ b/project/projectmodel.cpp @@ -1,30 +1,37 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #include "projectmodel.h" +#include + #include "panels/panels.h" #include "panels/viewer.h" #include "ui/viewerwidget.h" +#include "ui/mainwindow.h" #include "project/media.h" #include "global/debug.h" +#include "global/config.h" +#include "global/global.h" +#include "projectfunctions.h" +#include "previewgenerator.h" ProjectModel olive::project_model; @@ -36,6 +43,57 @@ ProjectModel::~ProjectModel() { destroy_root(); } +int ProjectModel::PrepareToSave() +{ + int element_count = 0; + + PrepareToSaveInternal(element_count, get_root()); + + return element_count; +} + +void ProjectModel::PrepareToSaveInternal(int& element_count, Media *root) +{ + for (int i=0;ichildCount();i++) { + Media* child = root->child(i); + + switch (child->get_type()) { + case MEDIA_TYPE_FOLDER: + child->temp_id = element_count; + break; + case MEDIA_TYPE_FOOTAGE: + child->to_footage()->save_id = element_count; + break; + case MEDIA_TYPE_SEQUENCE: + child->to_sequence()->save_id = element_count; + break; + } + + element_count++; + + if (child->childCount() > 0) { + PrepareToSaveInternal(element_count, child); + } + } +} + +void ProjectModel::Save(QXmlStreamWriter &stream, Media* root) +{ + if (root == nullptr) { + root = get_root(); + } + + for (int i=0;ichildCount();i++) { + Media* child = root->child(i); + + child->Save(stream); + + if (child->childCount() > 0) { + Save(stream, child); + } + } +} + void ProjectModel::make_root() { root_item_ = std::make_shared(); root_item_->temp_id = 0; @@ -50,7 +108,7 @@ void ProjectModel::destroy_root() { panel_footage_viewer->set_media(nullptr); } - root_item_ = std::make_shared(); + root_item_ = nullptr; } void ProjectModel::clear() { @@ -196,6 +254,92 @@ void ProjectModel::set_icon(Media* m, const QIcon &ico) { emit dataChanged(index, index); } +QVector ProjectModel::GetAllSequences() +{ + return GetAllMediaOfType(MEDIA_TYPE_SEQUENCE); +} + +QVector ProjectModel::GetAllFootage() +{ + return GetAllMediaOfType(MEDIA_TYPE_FOOTAGE); +} + +QVector ProjectModel::GetAllFolders() +{ + return GetAllMediaOfType(MEDIA_TYPE_FOLDER); +} + +QString ProjectModel::GetNextSequenceName(QString prepend) +{ + if (prepend.isEmpty()) { + prepend = tr("Sequence %1"); + } + + int sequence_number = 1; + QString test; + QVector all_sequences = GetAllSequences(); + bool found; + + do { + found = false; + test = prepend.arg(sequence_number); + for (int i=0;iget_name() == test) { + found = true; + sequence_number++; + break; + } + } + } while (found); + + return test; +} + +MediaPtr ProjectModel::CreateSequence(ComboAction *ca, SequencePtr s, bool open, Media *parent) +{ + MediaPtr item = std::make_shared(); + item->set_sequence(s); + + if (ca != nullptr) { + + ca->append(new AddMediaCommand(item, parent)); + + } else { + + appendChild(parent, item); + + } + + if (open) { + Timeline::OpenSequence(s); + } + + return item; +} + +QVector ProjectModel::GetAllMediaOfType(int search_type) +{ + QVector media_list; + RecurseTree(root_item_.get(), media_list, search_type); + return media_list; +} + +void ProjectModel::RecurseTree(Media* parent, QVector& list, int search_type) +{ + for (int i=0;ichildCount();i++) { + + Media* child = parent->child(i); + + if (child->get_type() == search_type) { + list.append(child); + } + + if (child->childCount() > 0) { + RecurseTree(child, list, search_type); + } + } +} + void ProjectModel::appendChild(Media* parent, MediaPtr child) { QModelIndex row_start; @@ -258,3 +402,260 @@ int ProjectModel::childCount(Media *parent) { } return parent->childCount(); } + +void ProjectModel::process_file_list(QStringList& files, bool recursive, MediaPtr replace, Media* parent) { + bool imported = false; + + // retrieve the array of image formats from the user's configuration + QStringList image_sequence_formats = olive::config.img_seq_formats.split("|"); + + // a cache of image sequence formatted URLS to assist the user in importing image sequences + QVector image_sequence_urls; + QVector image_sequence_importassequence; + + if (!recursive) last_imported_media.clear(); + + bool create_undo_action = (!recursive && replace == nullptr); + ComboAction* ca = nullptr; + if (create_undo_action) ca = new ComboAction(); + + // Loop through received files + for (int i=0;iappend(new AddMediaCommand(folder, parent)); + } else { + appendChild(parent, folder); + } + + process_file_list(subdir_filenames, true, nullptr, folder.get()); + + imported = true; + + } else if (!files.at(i).isEmpty()) { + QString file = files.at(i); + + // Check if the user is importing an Olive project file + if (file.endsWith(".ove", Qt::CaseInsensitive)) { + + // This file is an Olive project file. Ask the user if they really want to import it. + if (QMessageBox::question(olive::MainWindow, + tr("Import a Project"), + tr("\"%1\" is an Olive project file. It will merge with this project. " + "Do you wish to continue?").arg(file), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + + // load the project without clearing the current one + olive::Global->ImportProject(file); + + } + + } else { + + // This file is NOT an Olive project file + + // Used later if this file is part of an already processed image sequence + bool skip = false; + + /* Heuristic to determine whether file is part of an image sequence */ + + // Firstly, we run a heuristic on whether this file is an image by checking its file extension + + bool file_is_an_image = false; + + // Get the string position of the extension in the filename + int lastcharindex = file.lastIndexOf("."); + + if (lastcharindex != -1 && lastcharindex > file.lastIndexOf('/')) { + + QString ext = file.mid(lastcharindex+1); + + // If the file extension is part of a predetermined list (from Config::img_seq_formats), we'll treat it + // as an image + if (image_sequence_formats.contains(ext, Qt::CaseInsensitive)) { + file_is_an_image = true; + } + + } else { + + // If we're here, the file has no extension, but we'll still check if its an image sequence just in case + lastcharindex = file.length(); + file_is_an_image = true; + + } + + // Some image sequence's don't start at "0", if it is indeed an image sequence, we'll use this variable + // later to determine where it does start + int start_number = 0; + + // Check if we passed the earlier heuristic to check whether this is a file, and whether the last number in + // the filename (before the extension) is a number + if (file_is_an_image && file[lastcharindex-1].isDigit()) { + + // Check how many digits are at the end of this filename + int digit_count = 0; + int digit_test = lastcharindex-1; + while (file[digit_test].isDigit()) { + digit_count++; + digit_test--; + } + + // Retrieve the integer represented at the end of this filename + digit_test++; + int file_number = file.mid(digit_test, digit_count).toInt(); + + // Check whether a file exists with the same format but one number higher or one number lower + if (QFileInfo::exists(QString(file.left(digit_test) + QString("%1").arg(file_number-1, digit_count, 10, QChar('0')) + file.mid(lastcharindex))) + || QFileInfo::exists(QString(file.left(digit_test) + QString("%1").arg(file_number+1, digit_count, 10, QChar('0')) + file.mid(lastcharindex)))) { + + // + // If so, it certainly looks like it *could* be an image sequence, but we'll ask the user just in case + // + + // Firstly we should check if this file is part of a sequence the user has already confirmed as either a + // sequence or not a sequence (e.g. if the user happened to select a bunch of images that happen to increase + // consecutively). We format the filename with FFmpeg's '%Nd' (N = digits) formatting for reading image + // sequences + QString new_filename = file.left(digit_test) + "%" + QString::number(digit_count) + "d" + file.mid(lastcharindex); + + int does_url_cache_already_contain_this = image_sequence_urls.indexOf(new_filename); + + if (does_url_cache_already_contain_this > -1) { + + // We've already processed an image with the same formatting + + // Check if the last time we saw this formatting, the user chose to import as a sequence + if (image_sequence_importassequence.at(does_url_cache_already_contain_this)) { + + // If so, no need to import this file too, so we signal to the rest of the function to skip this file + skip = true; + + } + + // If not, we can fall-through to the next step which is importing normally + + } else { + + // If we're here, we've never seen a file with this formatting before, so we'll ask whether to import + // as a sequence or not + + // Add this file formatting file to the URL cache + image_sequence_urls.append(new_filename); + + // This does look like an image sequence, let's ask the user if it'll indeed be an image sequence + if (QMessageBox::question(olive::MainWindow, + tr("Image sequence detected"), + tr("The file '%1' appears to be part of an image sequence. " + "Would you like to import it as such?").arg(file), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::Yes) == QMessageBox::Yes) { + + // Proceed to the next step of this with the formatted filename + file = new_filename; + + // Cache the user's answer alongside the image_sequence_urls value - in this case, YES, this will be an + // image sequence + image_sequence_importassequence.append(true); + + + // FFmpeg needs to know what file number to start at in the sequence. In case the image sequence doesn't + // start at a zero, we'll loop decreasing the number until it doesn't exist anymore + QString test_filename_format = QString("%1%2%3").arg(file.left(digit_test), "%1", file.mid(lastcharindex)); + int test_file_number = file_number; + do { + test_file_number--; + } while (QFileInfo::exists(test_filename_format.arg(QString("%1").arg(test_file_number, digit_count, 10, QChar('0'))))); + + // set the image sequence's start number to the last that existed + start_number = test_file_number + 1; + + } else { + + // Cache the user's response to the image sequence question - i.e. none of the files imported with this + // formatting should be imported as an image sequence + image_sequence_importassequence.append(false); + + } + } + + } + + } + + // If we're not skipping this file, let's import it + if (!skip) { + MediaPtr item; + FootagePtr m; + + if (replace != nullptr) { + item = replace; + } else { + item = std::make_shared(); + } + + m = std::make_shared(); + + // Edge case for PNGs that standardized unassociated alpha + if (file.endsWith("png", Qt::CaseInsensitive)) { + m->alpha_is_associated = false; + } + + m->using_inout = false; + m->url = file; + m->name = QFileInfo(files.at(i)).fileName(); + m->start_number = start_number; + + item->set_footage(m); + + last_imported_media.append(item.get()); + + if (replace == nullptr) { + if (create_undo_action) { + ca->append(new AddMediaCommand(item, parent)); + } else { + appendChild(parent, item); + } + } + + imported = true; + } + + } + + + } + } + if (create_undo_action) { + if (imported) { + olive::undo_stack.push(ca); + + for (int i=0;i ProjectModel::GetLastImportedMedia() +{ + return last_imported_media; +} diff --git a/project/projectmodel.h b/project/projectmodel.h index 17edade63..d9c0f4311 100644 --- a/project/projectmodel.h +++ b/project/projectmodel.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -24,6 +24,7 @@ #include #include "project/media.h" +#include "undo/comboaction.h" class ProjectModel : public QAbstractItemModel { @@ -32,6 +33,42 @@ public: ProjectModel(QObject* parent = nullptr); ~ProjectModel() override; + /** + * @brief Makes preparations for saving the project file. + * + * Some items require IDs to link between them (e.g. the Footage or Nested Sequences used by Clips, linked clips, + * etc). This function sets up those IDs before saving. + * + * NOTE: This function should **always** be called before Save(). + * + * @return + * + * A count of the elements in the project to save into the project file. The loading system can later use this value + * to determine the load progress. + */ + int PrepareToSave(); + + /** + * @brief Initiate a save of all the project data + * + * Recursively goes through the entire project tree saving everything to a specified QXmlStreamWriter object. + * + * It's not recommended to use this function directly as it expects an existing QXmlStreamWriter and doesn't write a + * header and footer for the resulting XML document. Instead use olive::Save(). + * + * NOTE: **Always** call PrepareToSave() just before calling this function to set up valid IDs for saving. + * + * @param stream + * + * A QXmlStreamWriter object. + * + * @param root + * + * Used for recursion, set to any child and called again whenever a child is found with children. If this is nullptr, + * this function will loop over the root item. + */ + void Save(QXmlStreamWriter& stream, Media *root = nullptr); + void make_root(); void destroy_root(); void clear(); @@ -58,8 +95,34 @@ public: int childCount(Media* parent = nullptr); void set_icon(Media* m, const QIcon &ico); + void process_file_list(QStringList& files, bool recursive = false, MediaPtr replace = nullptr, Media *parent = nullptr); + + /** + * @brief Get a list of the last imported media + * + * @return + * + * Returns a list of all the Media processed by the last call to process_file_list(). + */ + QVector GetLastImportedMedia(); + + QVector GetAllSequences(); + QVector GetAllFootage(); + QVector GetAllFolders(); + + QString GetNextSequenceName(QString prepend = QString()); + + MediaPtr CreateSequence(ComboAction *ca, SequencePtr s, bool open, Media* parent); + private: MediaPtr root_item_; + + QVector last_imported_media; + + QVector GetAllMediaOfType(int search_type); + void RecurseTree(Media* parent, QVector &list, int search_type); + + void PrepareToSaveInternal(int& element_count, Media* root); }; namespace olive { diff --git a/project/proxygenerator.cpp b/project/proxygenerator.cpp index 6a6cd11ce..f90f5699d 100644 --- a/project/proxygenerator.cpp +++ b/project/proxygenerator.cpp @@ -1,42 +1,43 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #include "proxygenerator.h" -#include "global/path.h" -#include "project/previewgenerator.h" -#include "ui/mainwindow.h" - -#include -#include -#include -#include - -#include - extern "C" { #include #include #include } +#include +#include +#include +#include +#include + +#include "global/path.h" +#include "project/previewgenerator.h" +#include "ui/mediaiconservice.h" +#include "ui/mainwindow.h" + +// TODO provide more codecs than just this one enum AVCodecID temp_enc_codec = AV_CODEC_ID_PRORES; ProxyGenerator::ProxyGenerator() : cancelled(false) {} @@ -123,7 +124,7 @@ void ProxyGenerator::transcode(const ProxyInfo& info) { enc_ctx->width = qFloor(dec_ctx->width*info.size_multiplier); enc_ctx->height = qFloor(dec_ctx->height*info.size_multiplier); enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio; - enc_ctx->pix_fmt = enc_codec->pix_fmts[0]; + enc_ctx->pix_fmt = avcodec_find_best_pix_fmt_of_list(enc_codec->pix_fmts, dec_ctx->pix_fmt, 1, nullptr); enc_ctx->framerate = dec_ctx->framerate; enc_ctx->time_base = in_stream->time_base; out_stream->time_base = in_stream->time_base; @@ -356,9 +357,15 @@ void ProxyGenerator::run() { // set skip to false skip = false; + // set media icon to animated loading icon + olive::media_icon_service->SetMediaIcon(info.media, ICON_TYPE_LOADING); + // transcode proxy transcode(info); + // set media icon back to video + olive::media_icon_service->SetMediaIcon(info.media, ICON_TYPE_VIDEO); + // we're finished with this proxy, remove it proxy_queue.removeFirst(); diff --git a/project/proxygenerator.h b/project/proxygenerator.h index 964e03ee2..cc3da033e 100644 --- a/project/proxygenerator.h +++ b/project/proxygenerator.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/project/savethread.cpp b/project/savethread.cpp new file mode 100644 index 000000000..380f03be1 --- /dev/null +++ b/project/savethread.cpp @@ -0,0 +1,99 @@ +#include "savethread.h" + +#include +#include +#include +#include + +#include "global/global.h" +#include "global/config.h" +#include "projectmodel.h" + +/* +void RecursiveSave() { + +} + +void save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, const QModelIndex& parent) { + for (int i=0;iget_type()) { + if (m->get_type() == MEDIA_TYPE_FOLDER) { + if (set_ids_only) { + m->temp_id = folder_id; // saves a temporary ID for matching in the project file + folder_id++; + } else { + // if we're saving folders, save the folder + stream.writeStartElement("folder"); + stream.writeAttribute("name", m->get_name()); + stream.writeAttribute("id", QString::number(m->temp_id)); + if (!item.parent().isValid()) { + stream.writeAttribute("parent", "0"); + } else { + stream.writeAttribute("parent", QString::number(olive::project_model.getItem(item.parent())->temp_id)); + } + stream.writeEndElement(); + } + // save_folder(stream, item, type, set_ids_only); + } else { + int folder = m->parentItem()->temp_id; + if (type == MEDIA_TYPE_FOOTAGE) { + + } else if (type == MEDIA_TYPE_SEQUENCE) { + Sequence* s = m->to_sequence().get(); + if (set_ids_only) { + s->save_id = sequence_id; + sequence_id++; + } else { + s->Save(stream); + } + } + } + } + + if (m->get_type() == MEDIA_TYPE_FOLDER) { + save_folder(stream, type, set_ids_only, item); + } + } +} +*/ + +void olive::Save(bool autorecovery) +{ + QFile file(autorecovery ? olive::Global->get_autorecovery_filename() : olive::ActiveProjectFilename); + if (!file.open(QIODevice::WriteOnly)) { + qCritical() << "Could not open file"; + return; + } + + QXmlStreamWriter stream(&file); + stream.setAutoFormatting(true); + stream.writeStartDocument(); // doc + + stream.writeStartElement("project"); // project + + stream.writeTextElement("version", QString::number(olive::kSaveVersion)); + + stream.writeTextElement("url", olive::ActiveProjectFilename); + + // Prepare project for saving and retrieve element count + int element_count = olive::project_model.PrepareToSave(); + + // Write element count to file - used by the loading thread to determine its loading progress + stream.writeTextElement("elements", QString::number(element_count)); + + olive::project_model.Save(stream); + + stream.writeEndElement(); // project + + stream.writeEndDocument(); // doc + + file.close(); + + if (!autorecovery) { + olive::Global->add_recent_project(olive::ActiveProjectFilename); + olive::Global->set_modified(false); + } +} diff --git a/project/savethread.h b/project/savethread.h new file mode 100644 index 000000000..0687defd5 --- /dev/null +++ b/project/savethread.h @@ -0,0 +1,8 @@ +#ifndef SAVETHREAD_H +#define SAVETHREAD_H + +namespace olive { +void Save(bool autorecovery); +} + +#endif // SAVETHREAD_H diff --git a/project/sourcescommon.cpp b/project/sourcescommon.cpp index e0e80746c..cff3d855f 100644 --- a/project/sourcescommon.cpp +++ b/project/sourcescommon.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "ui/menuhelper.h" @@ -43,6 +44,7 @@ #include "dialogs/proxydialog.h" #include "ui/viewerwidget.h" #include "project/proxygenerator.h" +#include "project/projectfunctions.h" #include "ui/mainwindow.h" #include "ui/menu.h" #include "undo/undostack.h" @@ -64,14 +66,13 @@ void SourcesCommon::create_seq_from_selected() { } ComboAction* ca = new ComboAction(); - SequencePtr s = create_sequence_from_media(media_list); + SequencePtr s = olive::project::CreateSequenceFromMedia(media_list); - // add clips to it - panel_timeline->create_ghosts_from_media(s.get(), 0, media_list); - panel_timeline->add_clips_from_ghosts(ca, s.get()); + // add clips to it + s->AddClipsFromGhosts(ca, olive::timeline::CreateGhostsFromMedia(s.get(), 0, media_list)); - project_parent->create_sequence_internal(ca, s, true, nullptr); - olive::UndoStack.push(ca); + olive::project_model.CreateSequence(ca, s, true, nullptr); + olive::undo_stack.push(ca); } } @@ -81,7 +82,7 @@ void SourcesCommon::show_context_menu(QWidget* parent, const QModelIndexList& it selected_items = items; QAction* import_action = menu.addAction(tr("Import...")); - QObject::connect(import_action, SIGNAL(triggered(bool)), project_parent, SLOT(import_dialog())); + QObject::connect(import_action, SIGNAL(triggered(bool)), olive::Global.get(), SLOT(open_import_dialog())); Menu* new_menu = new Menu(tr("New")); menu.addMenu(new_menu); @@ -239,6 +240,21 @@ void SourcesCommon::show_context_menu(QWidget* parent, const QModelIndexList& it menu.exec(QCursor::pos()); } +void SourcesCommon::replace_media(MediaPtr item, QString filename) +{ + if (filename.isEmpty()) { + filename = QFileDialog::getOpenFileName( + olive::MainWindow, + tr("Replace '%1'").arg(item->get_name()), + "", + tr("All Files") + " (*)"); + } + if (!filename.isEmpty()) { + ReplaceMediaCommand* rmc = new ReplaceMediaCommand(item, filename); + olive::undo_stack.push(rmc); + } +} + void SourcesCommon::mousePressEvent(QMouseEvent *) { stop_rename_timer(); } @@ -255,11 +271,11 @@ void SourcesCommon::item_click(Media *m, const QModelIndex& index) { void SourcesCommon::mouseDoubleClickEvent(const QModelIndexList& selected_items) { stop_rename_timer(); if (selected_items.size() == 0) { - project_parent->import_dialog(); + olive::Global->open_import_dialog(); } else if (selected_items.size() == 1) { Media* media = project_parent->item_to_media(selected_items.at(0)); if (media->get_type() == MEDIA_TYPE_SEQUENCE) { - olive::UndoStack.push(new ChangeSequenceAction(media->to_sequence())); + Timeline::OpenSequence(media->to_sequence()); } else { OpenSelectedMediaInMediaViewer(project_parent->item_to_media(selected_items.at(0))); } @@ -285,14 +301,14 @@ void SourcesCommon::dropEvent(QWidget* parent, && drop_item.isValid() && m->get_type() == MEDIA_TYPE_FOOTAGE && !QFileInfo(paths.at(0)).isDir() - && olive::CurrentConfig.drop_on_media_to_replace + && olive::config.drop_on_media_to_replace && QMessageBox::question( parent, tr("Replace Media"), tr("You dropped a file onto '%1'. Would you like to replace it with the dropped file?").arg(m->get_name()), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { replace = true; - project_parent->replace_media(m, paths.at(0)); + replace_media(m, paths.at(0)); } if (!replace) { QModelIndex parent; @@ -303,7 +319,7 @@ void SourcesCommon::dropEvent(QWidget* parent, parent = drop_item.parent(); } } - project_parent->process_file_list(paths, false, nullptr, panel_project->item_to_media(parent)); + olive::project_model.process_file_list(paths, false, nullptr, project_parent->item_to_media(parent)); } } event->acceptProposedAction(); @@ -342,7 +358,7 @@ void SourcesCommon::dropEvent(QWidget* parent, MediaMove* mm = new MediaMove(); mm->to = m.get(); mm->items = move_items; - olive::UndoStack.push(mm); + olive::undo_stack.push(mm); } } } @@ -386,7 +402,7 @@ void SourcesCommon::rename_interval() { void SourcesCommon::item_renamed(Media* item) { if (editing_item == item) { MediaRename* mr = new MediaRename(item, "idk"); - olive::UndoStack.push(mr); + olive::undo_stack.push(mr); editing_item = nullptr; } } @@ -430,9 +446,10 @@ void SourcesCommon::clear_proxies_from_selected() { f->proxy_path.clear(); } - if (olive::ActiveSequence != nullptr) { + QVector all_sequences = olive::project_model.GetAllSequences(); + for (int i=0;ito_sequence()->Close(); } // delete proxies requested to be deleted @@ -440,9 +457,9 @@ void SourcesCommon::clear_proxies_from_selected() { QFile::remove(delete_list.at(i)); } - if (olive::ActiveSequence != nullptr) { + if (panel_sequence_viewer->seq != nullptr) { // update viewer (will re-open active clips with original media) - panel_sequence_viewer->viewer_widget->frame_update(); + panel_sequence_viewer->viewer_widget()->frame_update(); } olive::Global->set_modified(true); diff --git a/project/sourcescommon.h b/project/sourcescommon.h index b710f552a..b74b1165b 100644 --- a/project/sourcescommon.h +++ b/project/sourcescommon.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -27,10 +27,10 @@ #include "project/footage.h" #include "project/projectfilter.h" +#include "media.h" class Project; class QMouseEvent; -class Media; class QAbstractItemView; class QDropEvent; @@ -41,6 +41,8 @@ public: QAbstractItemView* view; void show_context_menu(QWidget* parent, const QModelIndexList &items); + void replace_media(MediaPtr item, QString filename); + void mousePressEvent(QMouseEvent* e); void mouseDoubleClickEvent(const QModelIndexList& selected_items); void dropEvent(QWidget *parent, QDropEvent* e, const QModelIndex& drop_item, const QModelIndexList &items); diff --git a/rendering/audio.cpp b/rendering/audio.cpp index fb7083bf6..7664334ac 100644 --- a/rendering/audio.cpp +++ b/rendering/audio.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -52,10 +52,9 @@ QAudioInput* audio_input = nullptr; QFile output_recording; bool recording = false; -bool audio_rendering = false; int audio_rendering_rate = 0; -qint8 audio_ibuffer[audio_ibuffer_size]; +float audio_ibuffer[audio_ibuffer_size]; qint64 audio_ibuffer_read = 0; long audio_ibuffer_frame = 0; double audio_ibuffer_timecode = 0; @@ -70,7 +69,7 @@ QAudioDeviceInfo get_audio_device(QAudio::Mode mode) { QList devs = QAudioDeviceInfo::availableDevices(mode); // try to retrieve preferred device from config - QString preferred_device = (mode == QAudio::AudioOutput) ? olive::CurrentConfig.preferred_audio_output : olive::CurrentConfig.preferred_audio_input; + QString preferred_device = (mode == QAudio::AudioOutput) ? olive::config.preferred_audio_output : olive::config.preferred_audio_input; if (!preferred_device.isEmpty()) { for (int i=0;ilock.lock(); audio_write_lock.lock(); - memset(audio_ibuffer, 0, audio_ibuffer_size); + memset(audio_ibuffer, 0, audio_ibuffer_size * sizeof(float)); audio_ibuffer_read = 0; audio_write_lock.unlock(); if (audio_thread != nullptr) audio_thread->lock.unlock(); } int current_audio_freq() { - return audio_rendering ? audio_rendering_rate : audio_output->format().sampleRate(); + return olive::Global->is_exporting() + ? audio_rendering_rate : audio_output->format().sampleRate(); } qint64 get_buffer_offset_from_frame(double framerate, long frame) { if (frame >= audio_ibuffer_frame) { - int multiplier = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)*av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); + int multiplier = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); return qFloor((double(frame - audio_ibuffer_frame)/framerate)*current_audio_freq())*multiplier; } else { qWarning() << "Invalid values passed to get_buffer_offset_from_frame" << frame << "<" << audio_ibuffer_frame; @@ -191,15 +191,13 @@ void AudioSenderThread::run() { if (close) { break; } else if (panel_sequence_viewer->playing || panel_footage_viewer->playing || audio_scrub) { - int written_bytes = 0; - int adjusted_read_index = audio_ibuffer_read%audio_ibuffer_size; - int max_write = audio_ibuffer_size - adjusted_read_index; + int adjusted_read_index = (audio_ibuffer_read%audio_ibuffer_size); + int max_write = (audio_ibuffer_size - adjusted_read_index) * sizeof(float); int actual_write = send_audio_to_output(adjusted_read_index, max_write); - written_bytes += actual_write; if (actual_write == max_write) { // got all the bytes, write again - written_bytes += send_audio_to_output(0, audio_ibuffer_size); + send_audio_to_output(0, audio_ibuffer_size); } audio_scrub = false; @@ -210,42 +208,38 @@ void AudioSenderThread::run() { int AudioSenderThread::send_audio_to_output(qint64 offset, int max) { // send audio to device - qint64 actual_write = audio_io_device->write(reinterpret_cast(audio_ibuffer)+offset, max); + audio_write_lock.lock(); - qint64 audio_ibuffer_limit = audio_ibuffer_read + actual_write; + qint64 actual_write = audio_io_device->write(reinterpret_cast(&audio_ibuffer[offset]), max); if (actual_write > 0) { // average values and send to audio monitor int channels = audio_output->format().channelCount(); - qint64 lim = offset + actual_write; - QVector averages; + qint64 lim = offset + (actual_write/sizeof(float)); + QVector averages; averages.resize(channels); - averages.fill(0); + averages.fill(0.0); - int counter = 0; - qint16 sample; - for (qint64 i=offset;iaudio_monitor->set_value(averages); + panel_timeline.first()->audio_monitor->set_value(averages); } - memset(audio_ibuffer+offset, 0, actual_write); + memset(&audio_ibuffer[offset], 0, actual_write); - audio_ibuffer_read = audio_ibuffer_limit; + audio_ibuffer_read += (actual_write / sizeof(float)); + + audio_write_lock.unlock(); return actual_write; } double log_volume(double linear) { // expects a value between 0 and 1 (or more if amplifying) - return (qExp(linear)-1)/(M_E-1); + return (qExp(linear)-1.0f)/(M_E-1.0f); } void int32_to_char_array(qint32 i, char* array) { @@ -325,8 +319,7 @@ void write_wave_trailer(QFile& f) { } bool start_recording() { - if (olive::ActiveSequence == nullptr) { - qCritical() << "No active sequence to record into"; + if (!olive::Global->CheckForActiveSequence(true)) { return false; } @@ -356,8 +349,8 @@ bool start_recording() { } QAudioFormat audio_format = audio_output->format(); - if (olive::CurrentConfig.recording_mode != audio_format.channelCount()) { - audio_format.setChannelCount(olive::CurrentConfig.recording_mode); + if (olive::config.recording_mode != audio_format.channelCount()) { + audio_format.setChannelCount(olive::config.recording_mode); } QAudioDeviceInfo info = get_audio_device(QAudio::AudioInput); diff --git a/rendering/audio.h b/rendering/audio.h index 4721766cf..19b768b26 100644 --- a/rendering/audio.h +++ b/rendering/audio.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -55,13 +55,12 @@ extern AudioSenderThread* audio_thread; extern QMutex audio_write_lock; #define audio_ibuffer_size 192000 -extern qint8 audio_ibuffer[audio_ibuffer_size]; +extern float audio_ibuffer[audio_ibuffer_size]; extern qint64 audio_ibuffer_read; extern long audio_ibuffer_frame; extern double audio_ibuffer_timecode; extern bool audio_scrub; extern bool recording; -extern bool audio_rendering; extern int audio_rendering_rate; void clear_audio_ibuffer(); diff --git a/rendering/cacher.cpp b/rendering/cacher.cpp index 9b23bbf6c..173edb467 100644 --- a/rendering/cacher.cpp +++ b/rendering/cacher.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -28,63 +28,67 @@ #include -#include #include #include #include #include +#include "panels/panels.h" #include "project/projectelements.h" #include "rendering/audio.h" #include "rendering/renderfunctions.h" -#include "panels/panels.h" +#include "global/timing.h" #include "global/config.h" +#include "global/global.h" #include "global/debug.h" #include "ui/mainwindow.h" // Enable verbose audio messages - good for debugging reversed audio //#define AUDIOWARNINGS -const AVPixelFormat kDestPixFmt = AV_PIX_FMT_RGBA; -const AVSampleFormat kDestSampleFmt = AV_SAMPLE_FMT_S16; +const AVSampleFormat kDestSampleFmt = AV_SAMPLE_FMT_FLTP; -double bytes_to_seconds(int nb_bytes, int nb_channels, int sample_rate) { - return (double(nb_bytes >> 1) / nb_channels / sample_rate); +double samples_to_seconds(int nb_samples, int nb_channels, int sample_rate) { + return (double(nb_samples) / double(nb_channels) / double(sample_rate)); } -void apply_audio_effects(Clip* clip, double timecode_start, AVFrame* frame, int nb_bytes, QVector nests) { +int samples_to_bytes(int nb_samples, int nb_channels) { + return nb_samples * nb_channels * sizeof(float); +} + +void apply_audio_effects(Clip* clip, double timecode_start, AVFrame* frame, int nb_samples, int nb_channels, QVector nests) { // perform all audio effects double timecode_end; - timecode_end = timecode_start + bytes_to_seconds(nb_bytes, frame->channels, frame->sample_rate); + timecode_end = timecode_start + samples_to_seconds(nb_samples, frame->channels, frame->sample_rate); for (int j=0;jeffects.size();j++) { - Effect* e = clip->effects.at(j).get(); + Node* e = clip->effects.at(j).get(); if (e->IsEnabled()) { - e->process_audio(timecode_start, timecode_end, frame->data[0], nb_bytes, 2); + e->process_audio(timecode_start, timecode_end, reinterpret_cast(frame->data), nb_samples, nb_channels, kTransitionNone); } } if (clip->opening_transition != nullptr) { if (clip->media() != nullptr && clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - double transition_start = (clip->clip_in(true) / clip->sequence->frame_rate); - double transition_end = (clip->clip_in(true) + clip->opening_transition->get_length()) / clip->sequence->frame_rate; + double transition_start = (clip->clip_in(true) / clip->track()->sequence()->frame_rate); + double transition_end = (clip->clip_in(true) + clip->opening_transition->get_length()) / clip->track()->sequence()->frame_rate; if (timecode_end < transition_end) { double adjustment = transition_end - transition_start; double adjusted_range_start = (timecode_start - transition_start) / adjustment; double adjusted_range_end = (timecode_end - transition_start) / adjustment; - clip->opening_transition->process_audio(adjusted_range_start, adjusted_range_end, frame->data[0], nb_bytes, kTransitionOpening); + clip->opening_transition->process_audio(adjusted_range_start, adjusted_range_end, reinterpret_cast(frame->data), nb_samples, nb_channels, kTransitionOpening); } } } if (clip->closing_transition != nullptr) { if (clip->media() != nullptr && clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) { long length_with_transitions = clip->timeline_out(true) - clip->timeline_in(true); - double transition_start = (clip->clip_in(true) + length_with_transitions - clip->closing_transition->get_length()) / clip->sequence->frame_rate; - double transition_end = (clip->clip_in(true) + length_with_transitions) / clip->sequence->frame_rate; + double transition_start = (clip->clip_in(true) + length_with_transitions - clip->closing_transition->get_length()) / clip->track()->sequence()->frame_rate; + double transition_end = (clip->clip_in(true) + length_with_transitions) / clip->track()->sequence()->frame_rate; if (timecode_start > transition_start) { double adjustment = transition_end - transition_start; double adjusted_range_start = (timecode_start - transition_start) / adjustment; double adjusted_range_end = (timecode_end - transition_start) / adjustment; - clip->closing_transition->process_audio(adjusted_range_start, adjusted_range_end, frame->data[0], nb_bytes, kTransitionClosing); + clip->closing_transition->process_audio(adjusted_range_start, adjusted_range_end, reinterpret_cast(frame->data), nb_samples, nb_channels, kTransitionClosing); } } } @@ -93,9 +97,10 @@ void apply_audio_effects(Clip* clip, double timecode_start, AVFrame* frame, int Clip* next_nest = nests.last(); nests.removeLast(); apply_audio_effects(next_nest, - timecode_start + (double(clip->timeline_in(true)-clip->clip_in(true))/clip->sequence->frame_rate), + timecode_start + (double(clip->timeline_in(true)-clip->clip_in(true))/clip->track()->sequence()->frame_rate), frame, - nb_bytes, + nb_samples, + nb_channels, nests); } } @@ -122,16 +127,16 @@ void Cacher::CacheAudioWorker() { bool reverse_audio = IsReversed(); long frame_skip = 0; - double last_fr = clip->sequence->frame_rate; + double last_fr = clip->track()->sequence()->frame_rate; if (!nests_.isEmpty()) { for (int i=nests_.size()-1;i>=0;i--) { - timeline_in = rescale_frame_number(timeline_in, last_fr, nests_.at(i)->sequence->frame_rate) + nests_.at(i)->timeline_in(true) - nests_.at(i)->clip_in(true); - timeline_out = rescale_frame_number(timeline_out, last_fr, nests_.at(i)->sequence->frame_rate) + nests_.at(i)->timeline_in(true) - nests_.at(i)->clip_in(true); - target_frame = rescale_frame_number(target_frame, last_fr, nests_.at(i)->sequence->frame_rate) + nests_.at(i)->timeline_in(true) - nests_.at(i)->clip_in(true); + timeline_in = rescale_frame_number(timeline_in, last_fr, nests_.at(i)->track()->sequence()->frame_rate) + nests_.at(i)->timeline_in(true) - nests_.at(i)->clip_in(true); + timeline_out = rescale_frame_number(timeline_out, last_fr, nests_.at(i)->track()->sequence()->frame_rate) + nests_.at(i)->timeline_in(true) - nests_.at(i)->clip_in(true); + target_frame = rescale_frame_number(target_frame, last_fr, nests_.at(i)->track()->sequence()->frame_rate) + nests_.at(i)->timeline_in(true) - nests_.at(i)->clip_in(true); timeline_out = qMin(timeline_out, nests_.at(i)->timeline_out(true)); - frame_skip = rescale_frame_number(frame_skip, last_fr, nests_.at(i)->sequence->frame_rate); + frame_skip = rescale_frame_number(frame_skip, last_fr, nests_.at(i)->track()->sequence()->frame_rate); long validator = nests_.at(i)->timeline_in(true) - timeline_in; if (validator > 0) { @@ -139,12 +144,13 @@ void Cacher::CacheAudioWorker() { //timeline_in = nests_.at(i)->timeline_in(true); } - last_fr = nests_.at(i)->sequence->frame_rate; + last_fr = nests_.at(i)->track()->sequence()->frame_rate; } } if (temp_reverse) { - long seq_end = olive::ActiveSequence->getEndFrame(); + // FIXME breakable? + long seq_end = Timeline::GetTopSequence()->GetEndFrame(); timeline_in = seq_end - timeline_in; timeline_out = seq_end - timeline_out; target_frame = seq_end - target_frame; @@ -156,16 +162,16 @@ void Cacher::CacheAudioWorker() { while (true) { AVFrame* frame; - int nb_bytes = INT_MAX; + int nb_samples = INT_MAX; if (clip->media() == nullptr) { frame = frame_; - nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; - while ((frame_sample_index_ == -1 || frame_sample_index_ >= nb_bytes) && nb_bytes > 0) { + nb_samples = frame->nb_samples; + while ((frame_sample_index_ == -1 || frame_sample_index_ >= nb_samples) && nb_samples > 0) { // create "new frame" - memset(frame_->data[0], 0, nb_bytes); - apply_audio_effects(clip, bytes_to_seconds(frame->pts, frame->channels, frame->sample_rate), frame, nb_bytes, nests_); - frame_->pts += nb_bytes; + memset(frame_->data[0], 0, nb_samples); + apply_audio_effects(clip, samples_to_seconds(frame->pts, frame->channels, frame->sample_rate), frame, nb_samples, frame->channels, nests_); + frame_->pts += nb_samples; frame_sample_index_ = 0; if (audio_buffer_write == 0) { audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame)); @@ -183,7 +189,7 @@ void Cacher::CacheAudioWorker() { // retrieve frame bool new_frame = false; - while ((frame_sample_index_ == -1 || frame_sample_index_ >= nb_bytes) && nb_bytes > 0) { + while ((frame_sample_index_ == -1 || frame_sample_index_ >= nb_samples) && nb_samples > 0) { // no more audio left in frame, get a new one if (!reached_end) { @@ -337,10 +343,10 @@ void Cacher::CacheAudioWorker() { if (frame_sample_index_ < 0) { frame_sample_index_ = 0; } else { - frame_sample_index_ -= nb_bytes; + frame_sample_index_ -= nb_samples; } - nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; + nb_samples = frame->nb_samples; if (audio_just_reset) { // get precise sample offset for the elected clip_in from this audio frame @@ -355,7 +361,7 @@ void Cacher::CacheAudioWorker() { dout << "fsts:" << frame_sts << "tsts:" << target_sts << "nbs:" << nb_samples << "nbb:" << nb_bytes << "rev_targetToSec:" << (reverse_target * timebase); dout << "fsi-calc:" << frame_sample_index; #endif - if (reverse_audio) frame_sample_index_ = nb_bytes - frame_sample_index_; + if (reverse_audio) frame_sample_index_ = nb_samples - frame_sample_index_; audio_just_reset = false; } @@ -392,9 +398,19 @@ void Cacher::CacheAudioWorker() { #endif // apply any audio effects to the data - if (nb_bytes == INT_MAX) nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; + if (nb_samples == INT_MAX) { + nb_samples = frame->nb_samples; + } if (new_frame) { - apply_audio_effects(clip, bytes_to_seconds(audio_buffer_write, 2, current_audio_freq()) + audio_ibuffer_timecode + ((double)clip->clip_in(true)/clip->sequence->frame_rate) - ((double)timeline_in/last_fr), frame, nb_bytes, nests_); + apply_audio_effects(clip, + samples_to_seconds(audio_buffer_write, 2, current_audio_freq()) + + audio_ibuffer_timecode + + (double(clip->clip_in(true))/clip->track()->sequence()->frame_rate) + - (double(timeline_in)/last_fr), + frame, + nb_samples, + frame->channels, + nests_); } } @@ -402,30 +418,25 @@ void Cacher::CacheAudioWorker() { if (frame->nb_samples == 0) { break; } else { - qint64 buffer_timeline_out = get_buffer_offset_from_frame(clip->sequence->frame_rate, timeline_out); + qint64 buffer_timeline_out = get_buffer_offset_from_frame(clip->track()->sequence()->frame_rate, timeline_out); audio_write_lock.lock(); - int sample_skip = 4*qMax(0, qAbs(playback_speed_)-1); - int sample_byte_size = av_get_bytes_per_sample(static_cast(frame->format)); + int sample_skip = qMax(0, qAbs(playback_speed_)-1); - while (frame_sample_index_ < nb_bytes + while (frame_sample_index_ < nb_samples && audio_buffer_write < audio_ibuffer_read+(audio_ibuffer_size>>1) && audio_buffer_write < buffer_timeline_out) { for (int i=0;ichannels;i++) { - int upper_byte_index = (audio_buffer_write+1)%audio_ibuffer_size; - int lower_byte_index = (audio_buffer_write)%audio_ibuffer_size; - qint16 old_sample = static_cast((audio_ibuffer[upper_byte_index] & 0xFF) << 8 | (audio_ibuffer[lower_byte_index] & 0xFF)); - qint16 new_sample = static_cast((frame->data[0][frame_sample_index_+1] & 0xFF) << 8 | (frame->data[0][frame_sample_index_] & 0xFF)); - qint16 mixed_sample = mix_audio_sample(old_sample, new_sample); + int buffer_index = audio_buffer_write%audio_ibuffer_size; - audio_ibuffer[upper_byte_index] = quint8((mixed_sample >> 8) & 0xFF); - audio_ibuffer[lower_byte_index] = quint8(mixed_sample & 0xFF); + audio_ibuffer[buffer_index] += reinterpret_cast(frame->data[i])[frame_sample_index_]; - audio_buffer_write+=sample_byte_size; - frame_sample_index_+=sample_byte_size; + audio_buffer_write++; } + frame_sample_index_++; + frame_sample_index_ += sample_skip; if (audio_reset_) break; @@ -443,7 +454,7 @@ void Cacher::CacheAudioWorker() { if (audio_thread != nullptr) audio_thread->notifyReceiver(); } - if (frame_sample_index_ >= nb_bytes) { + if (frame_sample_index_ >= nb_samples) { frame_sample_index_ = -1; } else { // assume we have no more data to send @@ -597,15 +608,15 @@ void Cacher::CacheVideoWorker() { // For reversed playback, we flip the queue stats as "upcoming" frames are going to be played before the "previous" // frames now if (reversed) { - previous_queue_type = olive::CurrentConfig.upcoming_queue_type; - previous_queue_size = olive::CurrentConfig.upcoming_queue_size; - upcoming_queue_type = olive::CurrentConfig.previous_queue_type; - upcoming_queue_size = olive::CurrentConfig.previous_queue_size; + previous_queue_type = olive::config.upcoming_queue_type; + previous_queue_size = olive::config.upcoming_queue_size; + upcoming_queue_type = olive::config.previous_queue_type; + upcoming_queue_size = olive::config.previous_queue_size; } else { - previous_queue_type = olive::CurrentConfig.previous_queue_type; - previous_queue_size = olive::CurrentConfig.previous_queue_size; - upcoming_queue_type = olive::CurrentConfig.upcoming_queue_type; - upcoming_queue_size = olive::CurrentConfig.upcoming_queue_size; + previous_queue_type = olive::config.previous_queue_type; + previous_queue_size = olive::config.previous_queue_size; + upcoming_queue_type = olive::config.upcoming_queue_type; + upcoming_queue_size = olive::config.upcoming_queue_size; } // Determine "previous" queue statistics @@ -794,7 +805,7 @@ void Cacher::CacheVideoWorker() { void Cacher::Reset() { // if we seek to a whole other place in the timeline, we'll need to reset the cache with new values if (clip->media() == nullptr) { - if (clip->track() >= 0) { + if (clip->type() == olive::kTypeAudio) { // a null-media audio clip is usually an auto-generated sound clip such as Tone or Noise reached_end = false; audio_target_frame = playhead_; @@ -859,10 +870,8 @@ Cacher::Cacher(Clip* c) : {} void Cacher::OpenWorker() { - qint64 time_start = QDateTime::currentMSecsSinceEpoch(); - // set some defaults for the audio cacher - if (clip->track() >= 0) { + if (clip->type() == olive::kTypeAudio) { audio_reset_ = false; frame_sample_index_ = -1; audio_buffer_write = 0; @@ -870,10 +879,10 @@ void Cacher::OpenWorker() { reached_end = false; if (clip->media() == nullptr) { - if (clip->track() >= 0) { + if (clip->type() == olive::kTypeAudio) { frame_ = av_frame_alloc(); frame_->format = kDestSampleFmt; - frame_->channel_layout = clip->sequence->audio_layout; + frame_->channel_layout = clip->track()->sequence()->audio_layout; frame_->channels = av_get_channel_layout_nb_channels(frame_->channel_layout); frame_->sample_rate = current_audio_freq(); frame_->nb_samples = 2048; @@ -891,7 +900,8 @@ void Cacher::OpenWorker() { QByteArray ba; // do we have a proxy? - if (m->proxy + if ((!olive::Global->is_exporting() || !olive::config.dont_use_proxies_on_export) + && m->proxy && !m->proxy_path.isEmpty() && QFileInfo::exists(m->proxy_path)) { ba = m->proxy_path.toUtf8(); @@ -989,7 +999,26 @@ void Cacher::OpenWorker() { last_filter = yadif_filter; } - const char* chosen_format = av_get_pix_fmt_name(kDestPixFmt); + AVPixelFormat possible_pix_fmts[] = { + AV_PIX_FMT_RGBA, + AV_PIX_FMT_RGBA64, + AV_PIX_FMT_NONE + }; + + AVPixelFormat pix_fmt = avcodec_find_best_pix_fmt_of_list(possible_pix_fmts, + static_cast(stream->codecpar->format), + 1, + nullptr); + + if (pix_fmt == AV_PIX_FMT_RGBA) { + qDebug() << "This is an 8-bit image."; + media_pixel_format_ = olive::PIX_FMT_RGBA8; + } else { + qDebug() << "This is an HDR image."; + media_pixel_format_ = olive::PIX_FMT_RGBA16; + } + + const char* chosen_format = av_get_pix_fmt_name(pix_fmt); snprintf(filter_args, sizeof(filter_args), "pix_fmts=%s", chosen_format); AVFilterContext* format_conv; @@ -1011,8 +1040,8 @@ void Cacher::OpenWorker() { reverse_frame->format = kDestSampleFmt; reverse_frame->nb_samples = current_audio_freq()*10; - reverse_frame->channel_layout = clip->sequence->audio_layout; - reverse_frame->channels = av_get_channel_layout_nb_channels(clip->sequence->audio_layout); + reverse_frame->channel_layout = clip->track()->sequence()->audio_layout; + reverse_frame->channels = av_get_channel_layout_nb_channels(clip->track()->sequence()->audio_layout); av_frame_get_buffer(reverse_frame, 0); queue_.append(reverse_frame); @@ -1092,13 +1121,13 @@ void Cacher::OpenWorker() { frame_ = av_frame_alloc(); } - qInfo() << "Clip opened on track" << clip->track() << "(took" << (QDateTime::currentMSecsSinceEpoch() - time_start) << "ms)"; + qInfo() << "Clip opened on track" << clip->track(); is_valid_state_ = true; } void Cacher::CacheWorker() { - if (clip->track() < 0) { + if (clip->type() == olive::kTypeVideo) { // clip is a video track, start caching video CacheVideoWorker(); } else { @@ -1189,7 +1218,7 @@ void Cacher::Open() caching_ = true; queued_ = false; - start((clip->track() < 0) ? QThread::HighPriority : QThread::TimeCriticalPriority); + start((clip->type() == olive::kTypeVideo) ? QThread::HighPriority : QThread::TimeCriticalPriority); } void Cacher::Cache(long playhead, bool scrubbing, QVector& nests, int playback_speed) @@ -1338,6 +1367,11 @@ ClipQueue *Cacher::queue() return &queue_; } +const olive::PixelFormat &Cacher::media_pixel_format() +{ + return media_pixel_format_; +} + int Cacher::RetrieveFrameFromDecoder(AVFrame* f) { int result = 0; int receive_ret; diff --git a/rendering/cacher.h b/rendering/cacher.h index 2b5f827da..0b215a673 100644 --- a/rendering/cacher.h +++ b/rendering/cacher.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -43,6 +43,7 @@ extern "C" { #include #include "rendering/clipqueue.h" +#include "rendering/pixelformats.h" class Clip; @@ -254,6 +255,15 @@ public: */ ClipQueue* queue(); + /** + * @brief Retrieve OpenGL information about this media's bit depth + * + * @return + * + * A olive::rendering::PixelFormat value corresponding to a member of olive::rendering::bit_depths. + */ + const olive::PixelFormat& media_pixel_format(); + private: /** * @brief Reference to the parent clip. Set in the constructor and never changed during this object's lifetime. @@ -582,6 +592,11 @@ private: * @brief Internal function using the Cacher's known information to determine whether this media is playing in reverse */ bool IsReversed(); + + /** + * @brief Internal struct holding bit depth information for the current media + */ + olive::PixelFormat media_pixel_format_; }; #endif // CACHER_H diff --git a/rendering/clipqueue.cpp b/rendering/clipqueue.cpp index e1e6e2fe2..ea9278a5b 100644 --- a/rendering/clipqueue.cpp +++ b/rendering/clipqueue.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/rendering/clipqueue.h b/rendering/clipqueue.h index 700ed285e..7534fb09f 100644 --- a/rendering/clipqueue.h +++ b/rendering/clipqueue.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/rendering/exportthread.cpp b/rendering/exportthread.cpp index 11be69541..65e113818 100644 --- a/rendering/exportthread.cpp +++ b/rendering/exportthread.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -29,13 +29,11 @@ extern "C" { #include #include -#include #include #include #include #include "global/global.h" -#include "timeline/sequence.h" #include "panels/panels.h" #include "ui/viewerwidget.h" #include "rendering/renderthread.h" @@ -162,7 +160,8 @@ bool ExportThread::SetupVideo() { break; } break; - + default: + break; } // Set export to be multithreaded @@ -193,16 +192,16 @@ bool ExportThread::SetupVideo() { video_frame = av_frame_alloc(); av_frame_make_writable(video_frame); video_frame->format = AV_PIX_FMT_RGBA; - video_frame->width = olive::ActiveSequence->width; - video_frame->height = olive::ActiveSequence->height; + video_frame->width = params_.sequence->width; + video_frame->height = params_.sequence->height; av_frame_get_buffer(video_frame, 0); av_init_packet(&video_pkt); // Set up conversion context sws_ctx = sws_getContext( - olive::ActiveSequence->width, - olive::ActiveSequence->height, + params_.sequence->width, + params_.sequence->height, AV_PIX_FMT_RGBA, params_.video_width, params_.video_height, @@ -289,7 +288,7 @@ bool ExportThread::SetupAudio() { acodec_ctx->channel_layout, acodec_ctx->sample_fmt, acodec_ctx->sample_rate, - olive::ActiveSequence->audio_layout, + params_.sequence->audio_layout, AV_SAMPLE_FMT_S16, acodec_ctx->sample_rate, 0, @@ -408,14 +407,14 @@ void ExportThread::Export() long remaining_frames, frame_count = 1; // Use Sequence Viewer's render thread - TODO separate this into a new render thread for background rendering - RenderThread* renderer = panel_sequence_viewer->viewer_widget->get_renderer(); + RenderThread* renderer = panel_sequence_viewer->viewer_widget()->get_renderer(); // Override connection from RenderThread - disconnect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget, SLOT(queue_repaint())); + disconnect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget(), SLOT(queue_repaint())); connect(renderer, SIGNAL(ready()), this, SLOT(wake())); // Loop from now (set to the beginning frame earlier) to the end of the frame - while (olive::ActiveSequence->playhead <= params_.end_frame && !interrupt_) { + while (params_.sequence->playhead <= params_.end_frame && !interrupt_) { // Start timing how long this frame will take frame_start_time = QDateTime::currentMSecsSinceEpoch(); @@ -424,14 +423,14 @@ void ExportThread::Export() if (params_.audio_enabled) { waiting_for_audio_ = true; SetAudioWakeObject(this); - olive::rendering::compose_audio(nullptr, olive::ActiveSequence.get(), 1, true); + olive::rendering::compose_audio(nullptr, params_.sequence, 1, true); } // If we're exporting video, trigger a render on the RenderThread if (params_.video_enabled) { do { // TODO optimize by rendering the next frame while encoding the last - renderer->start_render(nullptr, olive::ActiveSequence.get(), 1, nullptr, video_frame->data[0], video_frame->linesize[0]/4); + renderer->start_render(nullptr, params_.sequence, 1, nullptr, video_frame->data[0], video_frame->linesize[0]/4); // Wait for RenderThread to return waitCond.wait(&mutex); @@ -450,7 +449,7 @@ void ExportThread::Export() } // Get the current sequence playhead in seconds (used for timestamp calculations later on) - double timecode_secs = double(olive::ActiveSequence->playhead - params_.start_frame) / olive::ActiveSequence->frame_rate; + double timecode_secs = double(params_.sequence->playhead - params_.start_frame) / params_.sequence->frame_rate; // If we're exporting video, construct an AVFrame in the destination codec's pixel format to convert the raw RGBA // OpenGL buffer to @@ -536,15 +535,15 @@ void ExportThread::Export() // Generating encoding statistics (e.g. the time it took to encode this frame/estimated remaining time) frame_time = (QDateTime::currentMSecsSinceEpoch()-frame_start_time); total_time += frame_time; - remaining_frames = (params_.end_frame - olive::ActiveSequence->playhead); + remaining_frames = (params_.end_frame - params_.sequence->playhead); avg_time = (total_time/frame_count); eta = (remaining_frames*avg_time); // Emit a signal for the percent of the sequence that's been encoded so far - emit ProgressChanged(qRound((double(olive::ActiveSequence->playhead - params_.start_frame) / double(params_.end_frame - params_.start_frame)) * 100.0), eta); + emit ProgressChanged(qRound((double(params_.sequence->playhead - params_.start_frame) / double(params_.end_frame - params_.start_frame)) * 100.0), eta); // Increment sequence playhead - olive::ActiveSequence->playhead++; + params_.sequence->playhead++; // Increment frame count (used for generating encoding statistics above) frame_count++; @@ -552,7 +551,7 @@ void ExportThread::Export() // Restore original connection from RenderThread disconnect(renderer, SIGNAL(ready()), this, SLOT(wake())); - connect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget, SLOT(queue_repaint())); + connect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget(), SLOT(queue_repaint())); if (interrupt_) { return; @@ -561,8 +560,7 @@ void ExportThread::Export() if (params_.video_enabled) vpkt_alloc = true; if (params_.audio_enabled) apkt_alloc = true; - olive::Global->set_rendering_state(false); - close_active_clips(olive::ActiveSequence.get()); + olive::Global->set_export_state(false); // If audio is enabled, flush the rest of the audio out of swresample if (params_.audio_enabled) { diff --git a/rendering/exportthread.h b/rendering/exportthread.h index 328221727..6180ec4e6 100644 --- a/rendering/exportthread.h +++ b/rendering/exportthread.h @@ -1,31 +1,37 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef EXPORTTHREAD_H #define EXPORTTHREAD_H +extern "C" { +#include +} + #include #include #include #include +#include "timeline/sequence.h" + struct AVFormatContext; struct AVCodecContext; struct AVFrame; @@ -35,19 +41,19 @@ struct AVCodec; struct SwsContext; struct SwrContext; -extern "C" { -#include -} - -#define COMPRESSION_TYPE_CBR 0 -#define COMPRESSION_TYPE_CFR 1 -#define COMPRESSION_TYPE_TARGETSIZE 2 -#define COMPRESSION_TYPE_TARGETBR 3 +enum CompressionType { + COMPRESSION_TYPE_CBR, + COMPRESSION_TYPE_CFR, + COMPRESSION_TYPE_TARGETSIZE, + COMPRESSION_TYPE_TARGETBR +}; // structs that store parameters passed from the export dialogs to this thread struct ExportParams { + // export parameters + Sequence* sequence; QString filename; bool video_enabled; int video_codec; diff --git a/rendering/framebuffercollection.cpp b/rendering/framebuffercollection.cpp new file mode 100644 index 000000000..f8f3ed2b0 --- /dev/null +++ b/rendering/framebuffercollection.cpp @@ -0,0 +1,68 @@ +#include "framebuffercollection.h" + +FramebufferCollection::FramebufferCollection() +{ + +} + +void FramebufferCollection::Create(QOpenGLContext* ctx, + int width, + int height, + int count) +{ + Q_ASSERT(count > 1); + + fbo_.resize(count); + for (int i=0;i + +#include "framebufferobject.h" + +class FramebufferCollection +{ +public: + FramebufferCollection(); + + void Create(QOpenGLContext *ctx, int width, int height, int count); + void Destroy(); + + GLuint CurrentTexture(); + const FramebufferObject& CurrentFramebuffer(); + const FramebufferObject& NextFramebuffer(); + bool TextureBelongsToCollection(GLuint tex); + + bool IsCreated(); +private: + QVector fbo_; + int fbo_index_; +}; + +#endif // FRAMEBUFFERCOLLECTION_H diff --git a/rendering/framebufferobject.cpp b/rendering/framebufferobject.cpp index b9585400b..153c6f7eb 100644 --- a/rendering/framebufferobject.cpp +++ b/rendering/framebufferobject.cpp @@ -1,6 +1,32 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "framebufferobject.h" #include +#include +#include + +#include "global/config.h" +#include "global/global.h" +#include "pixelformats.h" FramebufferObject::FramebufferObject() : buffer_(0), @@ -41,8 +67,20 @@ void FramebufferObject::Create(QOpenGLContext *ctx, int width, int height) f->glBindTexture(GL_TEXTURE_2D, texture_); // allocate storage for texture - f->glTexImage2D( - GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr + const olive::PixelFormatInfo& bit_depth = olive::pixel_formats.at(olive::Global->is_exporting() ? + olive::config.export_bit_depth : + olive::config.playback_bit_depth); + + ctx->functions()->glTexImage2D( + GL_TEXTURE_2D, + 0, + bit_depth.internal_format, + width, + height, + 0, + bit_depth.pixel_format, + bit_depth.pixel_type, + nullptr ); // set texture filtering to bilinear @@ -50,10 +88,14 @@ void FramebufferObject::Create(QOpenGLContext *ctx, int width, int height) f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // attach texture to framebuffer - f->glFramebufferTexture2D( - GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_, 0 + ctx->extraFunctions()->glFramebufferTexture2D( + GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_, 0 ); + // clear new texture + ctx->functions()->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); + // release texture f->glBindTexture(GL_TEXTURE_2D, 0); @@ -72,12 +114,44 @@ void FramebufferObject::Destroy() ctx_ = nullptr; } -const GLuint &FramebufferObject::buffer() +void FramebufferObject::BindBuffer() const +{ + if (ctx_ == nullptr) { + return; + } + ctx_->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer_); +} + +void FramebufferObject::ReleaseBuffer() const +{ + if (ctx_ == nullptr) { + return; + } + ctx_->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +void FramebufferObject::BindTexture() const +{ + if (ctx_ == nullptr) { + return; + } + ctx_->functions()->glBindTexture(GL_TEXTURE_2D, texture_); +} + +void FramebufferObject::ReleaseTexture() const +{ + if (ctx_ == nullptr) { + return; + } + ctx_->functions()->glBindTexture(GL_TEXTURE_2D, 0); +} + +const GLuint &FramebufferObject::buffer() const { return buffer_; } -const GLuint &FramebufferObject::texture() +const GLuint &FramebufferObject::texture() const { return texture_; } diff --git a/rendering/framebufferobject.h b/rendering/framebufferobject.h index fc5644392..0636dc64a 100644 --- a/rendering/framebufferobject.h +++ b/rendering/framebufferobject.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef FRAMEBUFFEROBJECT_H #define FRAMEBUFFEROBJECT_H @@ -13,8 +33,14 @@ public: void Create(QOpenGLContext* ctx, int width, int height); void Destroy(); - const GLuint& buffer(); - const GLuint& texture(); + const GLuint& buffer() const; + const GLuint& texture() const; + + void BindBuffer() const; + void ReleaseBuffer() const; + + void BindTexture() const; + void ReleaseTexture() const; private: QOpenGLContext* ctx_; GLuint buffer_; diff --git a/rendering/pixelformats.cpp b/rendering/pixelformats.cpp new file mode 100644 index 000000000..6f970e5d6 --- /dev/null +++ b/rendering/pixelformats.cpp @@ -0,0 +1,60 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "pixelformats.h" + +#include + +namespace olive { + +QVector pixel_formats; + +void InitializePixelFormats() { + + pixel_formats.resize(PIX_FMT_COUNT); + + pixel_formats[PIX_FMT_RGBA8].name = QCoreApplication::translate("bitdepths", "8-bit"); + pixel_formats[PIX_FMT_RGBA8].internal_format = GL_RGBA8; + pixel_formats[PIX_FMT_RGBA8].pixel_format = GL_RGBA; + pixel_formats[PIX_FMT_RGBA8].pixel_type = GL_UNSIGNED_BYTE; + pixel_formats[PIX_FMT_RGBA8].bytes_per_pixel = 4; + + pixel_formats[PIX_FMT_RGBA16].name = QCoreApplication::translate("bitdepths", "16-bit Integer"); + pixel_formats[PIX_FMT_RGBA16].internal_format = GL_RGBA16; + pixel_formats[PIX_FMT_RGBA16].pixel_format = GL_RGBA; + pixel_formats[PIX_FMT_RGBA16].pixel_type = GL_UNSIGNED_SHORT; + pixel_formats[PIX_FMT_RGBA16].bytes_per_pixel = 8; + + pixel_formats[PIX_FMT_RGBA16F].name = QCoreApplication::translate("bitdepths", "Half-Float (16-bit)"); + pixel_formats[PIX_FMT_RGBA16F].internal_format = GL_RGBA16F; + pixel_formats[PIX_FMT_RGBA16F].pixel_format = GL_RGBA; + pixel_formats[PIX_FMT_RGBA16F].pixel_type = GL_HALF_FLOAT; + pixel_formats[PIX_FMT_RGBA16F].bytes_per_pixel = 8; + + pixel_formats[PIX_FMT_RGBA32F].name = QCoreApplication::translate("bitdepths", "Full-Float (32-bit)"); + pixel_formats[PIX_FMT_RGBA32F].internal_format = GL_RGBA32F; + pixel_formats[PIX_FMT_RGBA32F].pixel_format = GL_RGBA; + pixel_formats[PIX_FMT_RGBA32F].pixel_type = GL_FLOAT; + pixel_formats[PIX_FMT_RGBA32F].bytes_per_pixel = 16; + +} + +} + diff --git a/rendering/pixelformats.h b/rendering/pixelformats.h new file mode 100644 index 000000000..37fe34850 --- /dev/null +++ b/rendering/pixelformats.h @@ -0,0 +1,65 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef BITDEPTHS_H +#define BITDEPTHS_H + +#include +#include +#include + +namespace olive { + +/** + * @brief The PixelFormat enum + * + * Olive's internal supported pixel formats. With the exception of OLIVE_PIX_FMT_COUNT, these must all + * be defined in InitializePixelFormats(). + */ +enum PixelFormat { + PIX_FMT_RGBA8, + PIX_FMT_RGBA16, + PIX_FMT_RGBA16F, + PIX_FMT_RGBA32F, + PIX_FMT_COUNT +}; + +/** + * @brief The PixelFormatInfo struct + * + * A struct of information pertaining to each enum PixelFormat. Primarily this is a means of retrieving OpenGL texture + * information for different pixel formats/bit depths. Using the values in pixel_formats is always recommended over + * manually using OpenGL constants (e.g. GL_RGBA or GL_RGBA32F) directly. + */ +struct PixelFormatInfo { + QString name; + GLint internal_format; + GLenum pixel_format; + GLenum pixel_type; + int bytes_per_pixel; +}; + +extern QVector pixel_formats; + +void InitializePixelFormats(); + +} + +#endif // BITDEPTHS_H diff --git a/rendering/qopenglshaderprogramptr.h b/rendering/qopenglshaderprogramptr.h new file mode 100644 index 000000000..e75bab495 --- /dev/null +++ b/rendering/qopenglshaderprogramptr.h @@ -0,0 +1,29 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef QOPENGLSHADERPROGRAMPTR_H +#define QOPENGLSHADERPROGRAMPTR_H + +#include +#include + +using QOpenGLShaderProgramPtr = std::shared_ptr; + +#endif // QOPENGLSHADERPROGRAMPTR_H diff --git a/rendering/renderfunctions.cpp b/rendering/renderfunctions.cpp index 3032ccd10..f2f1a2f7c 100644 --- a/rendering/renderfunctions.cpp +++ b/rendering/renderfunctions.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -24,32 +24,57 @@ extern "C" { #include } -#include #include #include +#include +#include +#include #include -#ifdef OLIVE_OCIO -#include -namespace OCIO = OCIO_NAMESPACE; -#endif - #include "timeline/clip.h" #include "timeline/sequence.h" #include "project/media.h" -#include "effects/effect.h" +#include "nodes/node.h" #include "project/footage.h" #include "effects/transition.h" - #include "ui/collapsiblewidget.h" - #include "rendering/audio.h" - #include "global/math.h" +#include "global/timing.h" #include "global/config.h" - #include "panels/timeline.h" -#include "panels/viewer.h" +#include "qopenglshaderprogramptr.h" +#include "shadergenerators.h" + +GLfloat olive::rendering::blit_vertices[] = { + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + + -1.0f, -1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.0f +}; + +GLfloat olive::rendering::blit_texcoords[] = { + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0 +}; + +GLfloat olive::rendering::flipped_blit_texcoords[] = { + 0.0, 1.0, + 1.0, 1.0, + 1.0, 0.0, + + 0.0, 1.0, + 0.0, 0.0, + 1.0, 0.0 +}; void PrepareToDraw(QOpenGLFunctions* f) { f->glGenerateMipmap(GL_TEXTURE_2D); @@ -59,63 +84,98 @@ void PrepareToDraw(QOpenGLFunctions* f) { f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); } -void full_blit() { - PrepareToDraw(QOpenGLContext::currentContext()->functions()); +void olive::rendering::Blit(QOpenGLShaderProgram* pipeline, bool flipped, QMatrix4x4 matrix) { - glPushMatrix(); - glLoadIdentity(); - glOrtho(0, 1, 0, 1, -1, 1); + QOpenGLFunctions* func = QOpenGLContext::currentContext()->functions(); + PrepareToDraw(func); - glBegin(GL_QUADS); - glTexCoord2f(0, 0); // top left - glVertex2f(0, 0); // top left - glTexCoord2f(1, 0); // top right - glVertex2f(1, 0); // top right - glTexCoord2f(1, 1); // bottom right - glVertex2f(1, 1); // bottom right - glTexCoord2f(0, 1); // bottom left - glVertex2f(0, 1); // bottom left - glEnd(); + QOpenGLVertexArrayObject m_vao; + m_vao.create(); + m_vao.bind(); + + QOpenGLBuffer m_vbo; + m_vbo.create(); + m_vbo.bind(); + m_vbo.allocate(blit_vertices, 18 * sizeof(GLfloat)); + m_vbo.release(); + + QOpenGLBuffer m_vbo2; + m_vbo2.create(); + m_vbo2.bind(); + m_vbo2.allocate(flipped ? flipped_blit_texcoords : blit_texcoords, 12 * sizeof(GLfloat)); + m_vbo2.release(); + + pipeline->bind(); + + pipeline->setUniformValue("mvp_matrix", matrix); + pipeline->setUniformValue("texture", 0); + + GLuint vertex_location = pipeline->attributeLocation("a_position"); + m_vbo.bind(); + func->glEnableVertexAttribArray(vertex_location); + func->glVertexAttribPointer(vertex_location, 3, GL_FLOAT, GL_FALSE, 0, 0); + m_vbo.release(); + + GLuint tex_location = pipeline->attributeLocation("a_texcoord"); + m_vbo2.bind(); + func->glEnableVertexAttribArray(tex_location); + func->glVertexAttribPointer(tex_location, 2, GL_FLOAT, GL_FALSE, 0, 0); + m_vbo2.release(); + + func->glDrawArrays(GL_TRIANGLES, 0, 6); + + pipeline->release(); - glPopMatrix(); } -void draw_clip(QOpenGLContext* ctx, GLuint fbo, GLuint texture, bool clear) { +void draw_clip(QOpenGLContext* ctx, + QOpenGLShaderProgram* pipeline, + GLuint fbo, + GLuint texture, + bool clear) { ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); if (clear) { - glClear(GL_COLOR_BUFFER_BIT); + ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); } - glBindTexture(GL_TEXTURE_2D, texture); + ctx->functions()->glBindTexture(GL_TEXTURE_2D, texture); - full_blit(); + olive::rendering::Blit(pipeline); - glBindTexture(GL_TEXTURE_2D, 0); + ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } -GLuint draw_clip(QOpenGLFramebufferObject* fbo, GLuint texture, bool clear) { - fbo->bind(); +GLuint draw_clip(QOpenGLContext* ctx, + QOpenGLShaderProgram* pipeline, + const FramebufferObject& fbo, + GLuint texture, + bool clear) { + + fbo.BindBuffer(); if (clear) { - glClear(GL_COLOR_BUFFER_BIT); + ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); } - glBindTexture(GL_TEXTURE_2D, texture); + ctx->functions()->glBindTexture(GL_TEXTURE_2D, texture); - full_blit(); + olive::rendering::Blit(pipeline); - glBindTexture(GL_TEXTURE_2D, 0); + ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); - fbo->release(); + fbo.ReleaseBuffer(); + + return fbo.texture(); - return fbo->texture(); } -void process_effect(Clip* c, - Effect* e, +void process_effect(QOpenGLContext* ctx, + QOpenGLShaderProgram* pipeline, + Clip* c, + Node* e, double timecode, GLTextureCoords& coords, GLuint& composite_texture, @@ -123,21 +183,25 @@ void process_effect(Clip* c, bool& texture_failed, int data) { if (e->IsEnabled()) { - if (e->Flags() & Effect::CoordsFlag) { + if (e->Flags() & Node::CoordsFlag) { e->process_coords(timecode, coords, data); } - bool can_process_shaders = ((e->Flags() & Effect::ShaderFlag) && olive::CurrentRuntimeConfig.shaders_are_enabled); - if (can_process_shaders || (e->Flags() & Effect::SuperimposeFlag)) { - e->startEffect(); - if (can_process_shaders && e->is_glsl_linked()) { + bool can_process_shaders = ((e->Flags() & Node::ShaderFlag) && olive::runtime_config.shaders_are_enabled); + if (can_process_shaders || (e->Flags() & Node::SuperimposeFlag)) { + + if (!e->is_open()) { + e->open(); + } + + if (can_process_shaders && e->is_shader_linked()) { for (int i=0;igetIterations();i++) { e->process_shader(timecode, coords, i); - composite_texture = draw_clip(c->fbo[fbo_switcher], composite_texture, true); + composite_texture = draw_clip(ctx, e->GetShaderPipeline(), c->fbo.at(fbo_switcher), composite_texture, true); fbo_switcher = !fbo_switcher; } } - if (e->Flags() & Effect::SuperimposeFlag) { - GLuint superimpose_texture = e->process_superimpose(timecode); + if (e->Flags() & Node::SuperimposeFlag) { + GLuint superimpose_texture = e->process_superimpose(ctx, timecode); if (superimpose_texture == 0) { qWarning() << "Superimpose texture was nullptr, retrying..."; @@ -150,38 +214,37 @@ void process_effect(Clip* c, } else { // if the source texture is not already a framebuffer texture, // we'll need to make it one before drawing a superimpose effect on it - if (composite_texture != c->fbo[0]->texture() && composite_texture != c->fbo[1]->texture()) { - draw_clip(c->fbo[!fbo_switcher], composite_texture, true); + if (composite_texture != c->fbo.at(0).texture() && composite_texture != c->fbo.at(1).texture()) { + draw_clip(ctx, pipeline, c->fbo.at(!fbo_switcher), composite_texture, true); } - composite_texture = draw_clip(c->fbo[!fbo_switcher], superimpose_texture, false); + composite_texture = draw_clip(ctx, pipeline, c->fbo.at(!fbo_switcher), superimpose_texture, false); } } - e->endEffect(); } } } GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { -// qint64 time = QDateTime::currentMSecsSinceEpoch(); - - GLuint final_fbo = params.main_buffer; + GLuint final_fbo = params.type == olive::kTypeVideo ? params.main_buffer->buffer() : 0; Sequence* s = params.seq; long playhead = s->playhead; if (!params.nests.isEmpty()) { + for (int i=0;imedia()->to_sequence().get(); playhead += params.nests.at(i)->clip_in(true) - params.nests.at(i)->timeline_in(true); - playhead = rescale_frame_number(playhead, params.nests.at(i)->sequence->frame_rate, s->frame_rate); + playhead = rescale_frame_number(playhead, params.nests.at(i)->track()->sequence()->frame_rate, s->frame_rate); } - if (params.video && params.nests.last()->fbo != nullptr) { - params.nests.last()->fbo[0]->bind(); - glClear(GL_COLOR_BUFFER_BIT); - final_fbo = params.nests.last()->fbo[0]->handle(); + if (params.type == olive::kTypeVideo && !params.nests.last()->fbo.isEmpty()) { + params.nests.last()->fbo.at(0).BindBuffer(); + params.ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); + final_fbo = params.nests.last()->fbo.at(0).buffer(); } + } int audio_track_count = 0; @@ -189,14 +252,15 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { QVector current_clips; // loop through clips, find currently active, and sort by track - for (int i=0;iclips.size();i++) { + QVector sequence_clips = s->GetAllClips(); + for (int i=0;iclips.at(i).get(); + Clip* c = sequence_clips.at(i); if (c != nullptr) { // if clip is video and we're processing video - if ((c->track() < 0) == params.video) { + if (c->type() == params.type) { bool clip_is_active = false; @@ -205,7 +269,7 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { Footage* m = c->media()->to_footage(); // does the clip have a valid media source? - if (!m->invalid && !(c->track() >= 0 && !is_audio_device_set())) { + if (!m->invalid && !(c->type() == olive::kTypeAudio && !is_audio_device_set())) { // is the media process and ready? if (m->ready) { @@ -222,7 +286,7 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { clip_is_active = true; // increment audio track count - if (c->track() >= 0) audio_track_count++; + if (c->type() == olive::kTypeAudio) audio_track_count++; } else if (c->IsOpen()) { @@ -256,7 +320,7 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { // track sorting is only necessary for video clips // audio clips are mixed equally, so we skip sorting for those - if (params.video) { + if (params.type == olive::kTypeVideo) { // insertion sort by track for (int j=0;jfunctions()->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); int half_width = s->width/2; int half_height = s->height/2; - glOrtho(-half_width, half_width, -half_height, half_height, -1, 10); + projection.ortho(-half_width, half_width, -half_height, half_height, -1, 1); } // loop through current clips @@ -307,10 +370,7 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { if (got_mutex && c->IsOpen()) { // if clip is a video clip - if (c->track() < 0) { - - // reset OpenGL to full color - glColor4f(1.0, 1.0, 1.0, 1.0); + if (c->type() == olive::kTypeVideo) { // textureID variable contains texture to be drawn on screen at the end GLuint textureID = 0; @@ -319,47 +379,55 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { int video_width = c->media_width(); int video_height = c->media_height(); + // prepare framebuffers for backend drawing operations + if (c->fbo.isEmpty()) { + // create 3 fbos for nested sequences, 2 for most clips + int fbo_count = (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) ? 3 : 2; + + c->fbo.resize(fbo_count); + + for (int j=0;jfbo[j].Create(params.ctx, video_width, video_height); + } + } + + bool convert_frame_to_internal = false; + // if media is footage if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { // retrieve video frame from cache and store it in c->texture - c->Cache(qMax(playhead, c->timeline_in()), false, params.nests, params.playback_speed); + c->Cache(qMax(playhead, c->timeline_in(true)), false, params.nests, params.playback_speed); if (!c->Retrieve()) { params.texture_failed = true; } else { // retrieve ID from c->texture - textureID = c->texture->textureId(); + textureID = c->texture; } if (textureID == 0) { + qWarning() << "Failed to create texture"; - } - } - // prepare framebuffers for backend drawing operations - if (c->fbo == nullptr) { - // create 3 fbos for nested sequences, 2 for most clips - int fbo_count = (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) ? 3 : 2; + } else { - c->fbo = new QOpenGLFramebufferObject* [size_t(fbo_count)]; + convert_frame_to_internal = true; - for (int j=0;jfbo[j] = new QOpenGLFramebufferObject(video_width, video_height); } } // if clip should actually be shown on screen in this frame if (playhead >= c->timeline_in(true) && playhead < c->timeline_out(true)) { - glPushMatrix(); // simple bool for switching between the two framebuffers bool fbo_switcher = false; - glViewport(0, 0, video_width, video_height); + params.ctx->functions()->glViewport(0, 0, video_width, video_height); if (c->media() != nullptr) { if (c->media()->get_type() == MEDIA_TYPE_SEQUENCE) { + // for a nested sequence, run this function again on that sequence and retrieve the texture // add nested sequence to nest list @@ -372,44 +440,70 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { params.nests.removeLast(); // compose_sequence() would have written to this clip's fbo[0], so we switch to fbo[1] - fbo_switcher = true; + fbo_switcher = !fbo_switcher; + } else if (c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - if (!c->media()->to_footage()->alpha_is_premultiplied) { - // alpha is not premultiplied, we'll need to multiply it for the rest of the pipeline - params.premultiply_program->bind(); - - textureID = draw_clip(c->fbo[0], textureID, true); - - params.premultiply_program->release(); - - fbo_switcher = true; - } - -#ifdef OLIVE_OCIO - // convert to linear colorspace - bool linear_convert = true; - if (linear_convert) + // Convert frame from source to linear colorspace + if (olive::config.enable_color_management) { - } -#endif + // Convert texture to sequence's internal format + if (textureID != c->fbo.at(0).texture() && textureID != c->fbo.at(1).texture()) { + textureID = draw_clip(params.ctx, params.pipeline, c->fbo.at(fbo_switcher), textureID, true); + fbo_switcher = !fbo_switcher; + } + // Check if this clip has an OCIO shader set up or not + if (c->ocio_shader == nullptr) { + + + // Set default input colorspace + QString input_cs = OCIO::ROLE_SCENE_LINEAR; + + if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { + input_cs = c->media()->to_footage()->Colorspace(); + } + + // Try to get a shader based on the input color space to scene linear + try { + OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + OCIO::ConstProcessorRcPtr processor = config->getProcessor(input_cs.toUtf8(), + OCIO::ROLE_SCENE_LINEAR); + + c->ocio_shader = olive::shader::SetupOCIO(params.ctx, + c->ocio_lut_texture, + processor, + c->media()->to_footage()->alpha_is_associated); + } catch (OCIO::Exception& e) { + qWarning() << e.what(); + } + } + + // Ensure we got a shader, and if so, blit with it + if (c->ocio_shader != nullptr) { + textureID = olive::rendering::OCIOBlit(c->ocio_shader.get(), + c->ocio_lut_texture, + c->fbo.at(fbo_switcher), + textureID); + + fbo_switcher = !fbo_switcher; + } + + } } } // set up default coordinates for drawing the clip GLTextureCoords coords; - coords.grid_size = 1; - coords.vertexTopLeftX = coords.vertexBottomLeftX = -video_width/2; - coords.vertexTopLeftY = coords.vertexTopRightY = -video_height/2; - coords.vertexTopRightX = coords.vertexBottomRightX = video_width/2; - coords.vertexBottomLeftY = coords.vertexBottomRightY = video_height/2; - coords.vertexBottomLeftZ = coords.vertexBottomRightZ = coords.vertexTopLeftZ = coords.vertexTopRightZ = 1; - coords.textureTopLeftY = coords.textureTopRightY = coords.textureTopLeftX = coords.textureBottomLeftX = 0.0; - coords.textureBottomLeftY = coords.textureBottomRightY = coords.textureTopRightX = coords.textureBottomRightX = 1.0; - coords.textureTopLeftQ = coords.textureTopRightQ = coords.textureTopLeftQ = coords.textureBottomLeftQ = 1; - coords.blendmode = -1; + coords.vertex_top_left = QVector3D(-video_width/2, -video_height/2, 0.0f); + coords.vertex_top_right = QVector3D(video_width/2, -video_height/2, 0.0f); + coords.vertex_bottom_left = QVector3D(-video_width/2, video_height/2, 0.0f); + coords.vertex_bottom_right = QVector3D(video_width/2, video_height/2, 0.0f); + coords.texture_top_left = QVector2D(0.0f, 0.0f); + coords.texture_top_right = QVector2D(1.0f, 0.0f); + coords.texture_bottom_left = QVector2D(0.0f, 1.0f); + coords.texture_bottom_right = QVector2D(1.0f, 1.0f); coords.opacity = 1.0; // == EFFECT CODE START == @@ -420,8 +514,8 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { // run through all of the clip's effects for (int j=0;jeffects.size();j++) { - Effect* e = c->effects.at(j).get(); - process_effect(c, e, timecode, coords, textureID, fbo_switcher, params.texture_failed, kTransitionNone); + Node* e = c->effects.at(j).get(); + process_effect(params.ctx, params.pipeline, c, e, timecode, coords, textureID, fbo_switcher, params.texture_failed, kTransitionNone); } @@ -429,7 +523,7 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { if (c->opening_transition != nullptr) { int transition_progress = playhead - c->timeline_in(true); if (transition_progress < c->opening_transition->get_length()) { - process_effect(c, c->opening_transition.get(), double(transition_progress)/double(c->opening_transition->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionOpening); + process_effect(params.ctx, params.pipeline, c, c->opening_transition.get(), double(transition_progress)/double(c->opening_transition->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionOpening); } } @@ -437,32 +531,37 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { if (c->closing_transition != nullptr) { int transition_progress = playhead - (c->timeline_out(true) - c->closing_transition->get_length()); if (transition_progress >= 0 && transition_progress < c->closing_transition->get_length()) { - process_effect(c, c->closing_transition.get(), double(transition_progress)/double(c->closing_transition->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionClosing); + process_effect(params.ctx, params.pipeline, c, c->closing_transition.get(), double(transition_progress)/double(c->closing_transition->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionClosing); } } // == EFFECT CODE END == - // Check whether the parent clip is auto-scaledc + // Check whether the parent clip is auto-scaled if (c->autoscaled() && (video_width != s->width && video_height != s->height)) { float width_multiplier = float(s->width) / float(video_width); float height_multiplier = float(s->height) / float(video_height); float scale_multiplier = qMin(width_multiplier, height_multiplier); - glScalef(scale_multiplier, scale_multiplier, 1); + + coords.matrix.scale(scale_multiplier, scale_multiplier); } // Configure effect gizmos if they exist if (params.gizmos != nullptr) { - params.gizmos->gizmo_draw(timecode, coords); // set correct gizmo coords - params.gizmos->gizmo_world_to_screen(); // convert gizmo coords to screen coords + // set correct gizmo coords at this matrix + params.gizmos->gizmo_draw(timecode, coords); + + // convert gizmo coords to screen coords + params.gizmos->gizmo_world_to_screen(coords.matrix, projection); } if (textureID > 0) { + // set viewport to sequence size params.ctx->functions()->glViewport(0, 0, s->width, s->height); @@ -474,46 +573,99 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { // use clip textures for nested sequences, otherwise use main frame buffers GLuint back_buffer_1; + GLuint back_buffer_2; GLuint backend_tex_1; GLuint backend_tex_2; + GLuint comp_texture; if (params.nests.size() > 0) { - back_buffer_1 = params.nests.last()->fbo[1]->handle(); - backend_tex_1 = params.nests.last()->fbo[1]->texture(); - backend_tex_2 = params.nests.last()->fbo[2]->texture(); + back_buffer_1 = params.nests.last()->fbo[1].buffer(); + back_buffer_2 = params.nests.last()->fbo[2].buffer(); + backend_tex_1 = params.nests.last()->fbo[1].texture(); + backend_tex_2 = params.nests.last()->fbo[2].texture(); + comp_texture = params.nests.last()->fbo[0].texture(); } else { - back_buffer_1 = params.backend_buffer1; - backend_tex_1 = params.backend_attachment1; - backend_tex_2 = params.backend_attachment2; + back_buffer_1 = params.backend_buffer1->buffer(); + back_buffer_2 = params.backend_buffer2->buffer(); + backend_tex_1 = params.backend_buffer1->texture(); + backend_tex_2 = params.backend_buffer2->texture(); + comp_texture = params.main_buffer->texture(); } // render a backbuffer params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, back_buffer_1); - glClearColor(0.0, 0.0, 0.0, 0.0); - glClear(GL_COLOR_BUFFER_BIT); + params.ctx->functions()->glClearColor(0.0, 0.0, 0.0, 0.0); + params.ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); // bind final clip texture - glBindTexture(GL_TEXTURE_2D, textureID); + params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, textureID); // set texture filter to bilinear PrepareToDraw(params.ctx->functions()); // draw clip on screen according to gl coordinates - glBegin(GL_QUADS); + params.pipeline->bind(); - glTexCoord2f(coords.textureTopLeftX, coords.textureTopLeftY); // top left - glVertex2f(coords.vertexTopLeftX, coords.vertexTopLeftY); // top left - glTexCoord2f(coords.textureTopRightX, coords.textureTopRightY); // top right - glVertex2f(coords.vertexTopRightX, coords.vertexTopRightY); // top right - glTexCoord2f(coords.textureBottomRightX, coords.textureBottomRightY); // bottom right - glVertex2f(coords.vertexBottomRightX, coords.vertexBottomRightY); // bottom right - glTexCoord2f(coords.textureBottomLeftX, coords.textureBottomLeftY); // bottom left - glVertex2f(coords.vertexBottomLeftX, coords.vertexBottomLeftY); // bottom left + params.pipeline->setUniformValue("mvp_matrix", projection * coords.matrix); + params.pipeline->setUniformValue("texture", 0); + params.pipeline->setUniformValue("opacity", coords.opacity); - glEnd(); + GLfloat vertices[] = { + coords.vertex_top_left.x(), coords.vertex_top_left.y(), 0.0f, + coords.vertex_top_right.x(), coords.vertex_top_right.y(), 0.0f, + coords.vertex_bottom_right.x(), coords.vertex_bottom_right.y(), 0.0f, + + coords.vertex_top_left.x(), coords.vertex_top_left.y(), 0.0f, + coords.vertex_bottom_left.x(), coords.vertex_bottom_left.y(), 0.0f, + coords.vertex_bottom_right.x(), coords.vertex_bottom_right.y(), 0.0f, + }; + + GLfloat texcoords[] = { + coords.texture_top_left.x(), coords.texture_top_left.y(), + coords.texture_top_right.x(), coords.texture_top_right.y(), + coords.texture_bottom_right.x(), coords.texture_bottom_right.y(), + + coords.texture_top_left.x(), coords.texture_top_left.y(), + coords.texture_bottom_left.x(), coords.texture_bottom_left.y(), + coords.texture_bottom_right.x(), coords.texture_bottom_right.y(), + }; + + QOpenGLVertexArrayObject vao; + vao.create(); + vao.bind(); + + QOpenGLBuffer vertex_buffer; + vertex_buffer.create(); + vertex_buffer.bind(); + vertex_buffer.allocate(vertices, 18 * sizeof(GLfloat)); + vertex_buffer.release(); + + QOpenGLBuffer texcoord_buffer; + texcoord_buffer.create(); + texcoord_buffer.bind(); + texcoord_buffer.allocate(texcoords, 12 * sizeof(GLfloat)); + texcoord_buffer.release(); + + GLuint vertex_location = params.pipeline->attributeLocation("a_position"); + vertex_buffer.bind(); + params.ctx->functions()->glEnableVertexAttribArray(vertex_location); + params.ctx->functions()->glVertexAttribPointer(vertex_location, 3, GL_FLOAT, GL_FALSE, 0, 0); + vertex_buffer.release(); + + GLuint tex_location = params.pipeline->attributeLocation("a_texcoord"); + texcoord_buffer.bind(); + params.ctx->functions()->glEnableVertexAttribArray(tex_location); + params.ctx->functions()->glVertexAttribPointer(tex_location, 2, GL_FLOAT, GL_FALSE, 0, 0); + texcoord_buffer.release(); + + params.ctx->functions()->glDrawArrays(GL_TRIANGLES, 0, 6); + + params.pipeline->setUniformValue("opacity", 1.0f); + + params.pipeline->release(); // release final clip texture - glBindTexture(GL_TEXTURE_2D, 0); + params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); @@ -531,14 +683,12 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { - // copy front buffer to back buffer (only if we're using blending modes - which we usually will be) - if (!olive::CurrentRuntimeConfig.disable_blending) { - if (params.nests.size() > 0) { - draw_clip(params.ctx, params.nests.last()->fbo[2]->handle(), params.nests.last()->fbo[0]->texture(), true); - } else { - draw_clip(params.ctx, params.backend_buffer2, params.main_attachment, true); - } + // copy front buffer to back buffer (only if we're using a blending mode) + /* + if (coords.blendmode >= 0) { + draw_clip(params.ctx, params.pipeline, back_buffer_2, comp_texture, true); } + */ @@ -550,18 +700,19 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { // bind front buffer as draw buffer params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, final_fbo); -// if (olive::CurrentRuntimeConfig.disable_blending) { - // some GPUs don't like the blending shader, so we provide a pure GL fallback here + // Check if we're using a blend mode (< 0 means no blend mode) + //if (coords.blendmode < 0) { params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_1); - glColor4f(coords.opacity, coords.opacity, coords.opacity, coords.opacity); - - full_blit(); + olive::rendering::Blit(params.pipeline); params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); + + //} else { + /* - } else { + // load background texture into texture unit 0 params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0 params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_2); @@ -577,9 +728,9 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { params.blend_mode_program->setUniformValue("background", 0); params.blend_mode_program->setUniformValue("foreground", 1); - glClear(GL_COLOR_BUFFER_BIT); + params.ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); - full_blit(); + olive::rendering::Blit(params.pipeline); // release blend mode shader params.blend_mode_program->release(); @@ -590,8 +741,10 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { // unbind texture from texture unit 0 params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0 params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); - } - */ + + */ + + //} // unbind framebuffer params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); @@ -601,10 +754,8 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { // == END FINAL DRAW ON SEQUENCE BUFFER == } - - glPopMatrix(); } - } else { + } else if (c->type() == olive::kTypeAudio) { if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) { params.nests.append(c); compose_sequence(params); @@ -646,15 +797,9 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { WakeAudioWakeObject(); } - if (params.video) { - glPopMatrix(); - } - -// qDebug() << "compose sequence took" << QDateTime::currentMSecsSinceEpoch() - time; - - if (!params.nests.isEmpty() && params.nests.last()->fbo != nullptr) { + if (!params.nests.isEmpty() && !params.nests.last()->fbo.isEmpty()) { // returns nested clip's texture - return params.nests.last()->fbo[0]->texture(); + return params.nests.last()->fbo[0].texture(); } return 0; @@ -665,57 +810,41 @@ void olive::rendering::compose_audio(Viewer* viewer, Sequence* seq, int playback params.viewer = viewer; params.ctx = nullptr; params.seq = seq; - params.video = false; + params.type = olive::kTypeAudio; params.gizmos = nullptr; params.wait_for_mutexes = wait_for_mutexes; params.playback_speed = playback_speed; - params.blend_mode_program = nullptr; compose_sequence(params); } -long rescale_frame_number(long framenumber, double source_frame_rate, double target_frame_rate) { - return qRound((double(framenumber)/source_frame_rate)*target_frame_rate); -} - -double get_timecode(Clip* c, long playhead) { - return double(playhead_to_clip_frame(c, playhead))/c->sequence->frame_rate; -} - -long playhead_to_clip_frame(Clip* c, long playhead) { - return (qMax(0L, playhead - c->timeline_in(true)) + c->clip_in(true)); -} - -double playhead_to_clip_seconds(Clip* c, long playhead) { - // returns time in seconds - long clip_frame = playhead_to_clip_frame(c, playhead); - - if (c->reversed()) { - clip_frame = c->media_length() - clip_frame - 1; +GLuint olive::rendering::OCIOBlit(QOpenGLShaderProgram *pipeline, + GLuint lut, + const FramebufferObject& fbo, + GLuint texture) +{ + if (pipeline == nullptr) { + return 0; } - double secs = (double(clip_frame)/c->sequence->frame_rate)*c->speed().value; - if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - secs *= c->media()->to_footage()->speed; - } + QOpenGLContext* ctx = QOpenGLContext::currentContext(); + QOpenGLExtraFunctions* xf = ctx->extraFunctions(); - return secs; -} + xf->glActiveTexture(GL_TEXTURE2); + xf->glBindTexture(GL_TEXTURE_3D, lut); + xf->glActiveTexture(GL_TEXTURE0); -int64_t seconds_to_timestamp(Clip *c, double seconds) { - return qRound64(seconds * av_q2d(av_inv_q(c->time_base()))); -} + pipeline->bind(); -int64_t playhead_to_timestamp(Clip* c, long playhead) { - return seconds_to_timestamp(c, playhead_to_clip_seconds(c, playhead)); -} + pipeline->setUniformValue("tex2", 2); -void close_active_clips(Sequence* s) { - if (s != nullptr) { - for (int i=0;iclips.size();i++) { - Clip* c = s->clips.at(i).get(); - if (c != nullptr) { - c->Close(true); - } - } - } + //textureID = draw_clip(params.ctx, pipeline, c->fbo.at(fbo_switcher), textureID, true); + GLuint textureID = draw_clip(ctx, pipeline, fbo, texture, true); + + pipeline->release(); + + xf->glActiveTexture(GL_TEXTURE2); + xf->glBindTexture(GL_TEXTURE_3D, 0); + xf->glActiveTexture(GL_TEXTURE0); + + return textureID; } diff --git a/rendering/renderfunctions.h b/rendering/renderfunctions.h index c370b0493..658ecc422 100644 --- a/rendering/renderfunctions.h +++ b/rendering/renderfunctions.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -24,16 +24,18 @@ #include #include #include +#include +namespace OCIO = OCIO_NAMESPACE::v1; #include "timeline/sequence.h" -#include "effects/effect.h" +#include "nodes/node.h" #include "panels/viewer.h" /** - * @brief The ComposeSequenceParams struct - * - * Struct sent to the compose_sequence() function. - */ + * @brief The ComposeSequenceParams struct + * + * Struct sent to the compose_sequence() function. + */ struct ComposeSequenceParams { /** @@ -52,6 +54,13 @@ struct ComposeSequenceParams { */ QOpenGLContext* ctx; + /** + * @brief The OpenGL pipeline used for rendering + * + * \see olive::rendering::GetPipeline(). + */ + QOpenGLShaderProgram* pipeline; + /** * @brief The sequence to compose * @@ -71,16 +80,16 @@ struct ComposeSequenceParams { /** * @brief Set compose mode to video or audio * - * **TRUE** if this function should render video, **FALSE** if this function should render audio. + * Accepts olive::kTypeVideo to render video, olive::kTypeAudio if this function should render audio. */ - bool video; + olive::TrackType type; /** * @brief Set to the Effect whose gizmos were chosen to be drawn on screen * * The currently active Effect that compose_sequence() will update the gizmos of. */ - Effect* gizmos; + Node* gizmos; /** * @brief A variable that compose_sequence() will set to **TRUE** if any of the clips couldn't be shown. @@ -129,19 +138,6 @@ struct ComposeSequenceParams { */ int playback_speed; - /** - * @brief Blending mode shader - * - * Used only for video rendering. Never accessed with audio rendering. - * - * A program containing the current active - * blending mode shader that can be bound during rendering. Must be compiled and linked beforehand. See - * RenderThread::blend_mode_program for how this is properly set up. - * - * \see ComposeSequenceParams::video - */ - QOpenGLShaderProgram* blend_mode_program; - /** * @brief Premultiply alpha shader * @@ -161,16 +157,7 @@ struct ComposeSequenceParams { * * When compose_sequence() is rendering the final image, this framebuffer will be bound. */ - GLuint main_buffer; - - /** - * @brief The attachment to the framebuffer in main_buffer - * - * Used only for video rendering. Never accessed with audio rendering. - * - * The OpenGL texture attached to the framebuffer referenced by main_buffer. - */ - GLuint main_attachment; + const FramebufferObject* main_buffer; /** * @brief Backend OpenGL framebuffer 1 used for further processing before rendering to main_buffer @@ -178,15 +165,7 @@ struct ComposeSequenceParams { * In some situations, compose_sequence() will do some processing through shaders that requires "ping-ponging" * between framebuffers. backend_buffer1 and backend_buffer2 are used for this purpose. */ - GLuint backend_buffer1; - - /** - * @brief Backend OpenGL framebuffer 1's texture attachment - * - * The texture that ComposeSequenceParams::backend_buffer1 renders to. Bound and drawn to - * ComposeSequenceParams::backend_buffer2 to "ping-pong" between them and various shaders. - */ - GLuint backend_attachment1; + const FramebufferObject* backend_buffer1; /** * @brief Backend OpenGL framebuffer 2 used for further processing before rendering to main_buffer @@ -194,51 +173,33 @@ struct ComposeSequenceParams { * In some situations, compose_sequence() will do some processing through shaders that requires "ping-ponging" * between framebuffers. backend_buffer1 and backend_buffer2 are used for this purpose. */ - GLuint backend_buffer2; - - /** - * @brief Backend OpenGL framebuffer 2's texture attachment - * - * The texture that ComposeSequenceParams::backend_buffer2 renders to. Bound and drawn to - * ComposeSequenceParams::backend_buffer1 to "ping-pong" between them and various shaders. - */ - GLuint backend_attachment2; - - /** - * @brief OpenGL shader containing OpenColorIO shader information - */ - QOpenGLShaderProgram* ocio_shader; - - /** - * @brief OpenGL texture containing LUT obtained form OpenColorIO - */ - GLuint ocio_lut_texture; + const FramebufferObject* backend_buffer2; }; namespace olive { namespace rendering { /** - * @brief Compose a frame of a given sequence - * - * For any given Sequence, this function will render the current frame indicated by Sequence::playhead. Will - * automatically open and close clips (memory allocation and file handles) as necessary, communicate with the - * Clip::cacher objects to retrieve upcoming frames and store them in memory, run Effect processing functions, and - * finally composite all the currently active clips together into a final texture. - * - * Will sometimes render a frame incomplete or inaccurately, e.g. if a video file hadn't finished opening by the time - * of the render or a clip's cacher didn't have the requested frame available at the time of the render. If so, - * the `texture_failed` variable of `params` will be set to **TRUE**. Check this after calling compose_sequence() and - * if it is **TRUE**, compose_sequence() should be called again later to attempt another render (unless the Sequence - * is being played, in which case just play the next frame rather than redrawing an old frame). - * - * @param params - * - * A struct of parameters to use while rendering. - * - * @return A reference to the OpenGL texture resulting from the render. Will usually be equal to - * ComposeSequenceParams::main_attachment unless it's rendering a nested sequence, in which case it'll be a reference - * to one of the textures referenced by Clip::fbo. Can be used directly to draw the rendered frame. - */ + * @brief Compose a frame of a given sequence + * + * For any given Sequence, this function will render the current frame indicated by Sequence::playhead. Will + * automatically open and close clips (memory allocation and file handles) as necessary, communicate with the + * Clip::cacher objects to retrieve upcoming frames and store them in memory, run Effect processing functions, and + * finally composite all the currently active clips together into a final texture. + * + * Will sometimes render a frame incomplete or inaccurately, e.g. if a video file hadn't finished opening by the time + * of the render or a clip's cacher didn't have the requested frame available at the time of the render. If so, + * the `texture_failed` variable of `params` will be set to **TRUE**. Check this after calling compose_sequence() and + * if it is **TRUE**, compose_sequence() should be called again later to attempt another render (unless the Sequence + * is being played, in which case just play the next frame rather than redrawing an old frame). + * + * @param params + * + * A struct of parameters to use while rendering. + * + * @return A reference to the OpenGL texture resulting from the render. Will usually be equal to + * ComposeSequenceParams::main_attachment unless it's rendering a nested sequence, in which case it'll be a reference + * to one of the textures referenced by Clip::fbo. Can be used directly to draw the rendered frame. + */ GLuint compose_sequence(ComposeSequenceParams ¶ms); /** @@ -268,141 +229,19 @@ void compose_audio(Viewer* viewer, Sequence *seq, int playback_speed, bool wait_ } } -/** - * @brief Rescale a frame number between two frame rates - * - * Converts a frame number from one frame rate to its equivalent in another frame rate - * - * @param framenumber - * - * The frame number to convert - * - * @param source_frame_rate - * - * Frame rate that the frame number is currently in - * - * @param target_frame_rate - * - * Frame rate to convert to - * - * @return - * - * Rescaled frame number - */ -long rescale_frame_number(long framenumber, double source_frame_rate, double target_frame_rate); +void UpdateOCIOGLState(const ComposeSequenceParams ¶ms); -/** - * @brief Get timecode - * - * Get the current clip/media time from the Timeline playhead in seconds. For instance if the playhead was at the start - * of a clip (whose in point wasn't trimmed), this would be 0.0 as it's the start of the clip/media; - * - * @param c - * - * Clip to get the timecode of - * - * @param playhead - * - * Sequence playhead to convert to a clip/media timecode - * - * @return - * - * Timecode in seconds - */ -double get_timecode(Clip *c, long playhead); - -/** - * @brief Convert playhead frame number to a clip frame number - * - * Converts a Timeline playhead to a the current clip's frame. Equivalent to - * `PLAYHEAD - CLIP_TIMELINE_IN + CLIP_MEDIA_IN`. All keyframes are in clip frames. - * - * @param c - * - * The clip to get the current frame number of - * - * @param playhead - * - * The current Timeline frame number - * - * @return - * - * The curren frame number of the clip at `playhead` - */ -long playhead_to_clip_frame(Clip* c, long playhead); - -/** - * @brief Converts the playhead to clip seconds - * - * Get the current timecode at the playhead in terms of clip seconds. - * - * FIXME: Possible duplicate of get_timecode()? Will need to research this more. - * - * @param c - * - * Clip to return clip seconds of. - * - * @param playhead - * - * Current Timeline playhead to convert to clip seconds - * - * @return - * - * Clip time in seconds - */ -double playhead_to_clip_seconds(Clip *c, long playhead); - -/** - * @brief Convert seconds to FFmpeg timestamp - * - * Used for interaction with FFmpeg, converts seconds in a floating-point value to a timestamp in AVStream->time_base - * units. - * - * @param c - * - * Clip to get timestamp of - * - * @param seconds - * - * Clip time in seconds - * - * @return - * - * An FFmpeg-compatible timestamp in AVStream->time_base units. - */ -int64_t seconds_to_timestamp(Clip* c, double seconds); - -/** - * @brief Convert Timeline playhead to FFmpeg timestamp - * - * Used for interaction with FFmpeg, converts the Timeline playhead to a timestamp in AVStream->time_base - * units. - * - * @param c - * - * Clip to get timestamp of - * - * @param playhead - * - * Timeline playhead to convert to a timestamp - * - * @return - * - * An FFmpeg-compatible timestamp in AVStream->time_base units. - */ -int64_t playhead_to_timestamp(Clip *c, long playhead); - -/** - * @brief Close all open clips in a Sequence - * - * Closes any currently open clips on a Sequence and waits for them to close before returning. This may be slow as a - * result on large Sequence objects. If a Clip is a nested Sequence, this function calls itself recursively on that - * Sequence too. - * - * @param s - * - * The Sequence to close all clips on. - */ -void close_active_clips(Sequence* s); +namespace olive { + namespace rendering { + extern GLfloat blit_vertices[]; + extern GLfloat blit_texcoords[]; + extern GLfloat flipped_blit_texcoords[]; + void Blit(QOpenGLShaderProgram* pipeline, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4()); + GLuint OCIOBlit(QOpenGLShaderProgram *pipeline, + GLuint lut, + const FramebufferObject& fbo, + GLuint texture); + } +} #endif // RENDERFUNCTIONS_H diff --git a/rendering/renderthread.cpp b/rendering/renderthread.cpp index c8d822f56..ea6f9dacd 100644 --- a/rendering/renderthread.cpp +++ b/rendering/renderthread.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -22,30 +22,35 @@ #include #include -#include #include +#include +#include #include -#ifdef OLIVE_OCIO -#include -namespace OCIO = OCIO_NAMESPACE; -#endif -#include "rendering/renderfunctions.h" +#include +namespace OCIO = OCIO_NAMESPACE::v1; + #include "timeline/sequence.h" +#include "effects/effectloaders.h" +#include "global/config.h" +#include "rendering/renderfunctions.h" +#include "rendering/shadergenerators.h" RenderThread::RenderThread() : gizmos(nullptr), share_ctx(nullptr), ctx(nullptr), - blend_mode_program(nullptr), - premultiply_program(nullptr), seq(nullptr), tex_width(-1), tex_height(-1), queued(false), texture_failed(false), + ocio_lut_texture(0), + ocio_shader(nullptr), running(true), - front_buffer_switcher(false) + ocio_config_date(0), + front_buffer_switcher(false), + pipeline_program(nullptr) { surface.create(); } @@ -80,6 +85,9 @@ void RenderThread::run() { } // create any buffers that don't yet exist + if (!composite_buffer.IsCreated()) { + composite_buffer.Create(ctx, seq->width, seq->height); + } if (!front_buffer_1.IsCreated()) { front_buffer_1.Create(ctx, seq->width, seq->height); } @@ -93,19 +101,18 @@ void RenderThread::run() { back_buffer_2.Create(ctx, seq->width, seq->height); } - if (blend_mode_program == nullptr) { - // create shader program to make blending modes work + // If there's no pipeline shader, create it now + if (pipeline_program == nullptr) { delete_shaders(); - blend_mode_program = new QOpenGLShaderProgram(); - blend_mode_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/internalshaders/common.vert"); - blend_mode_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/internalshaders/blending.frag"); - blend_mode_program->link(); + pipeline_program = olive::shader::GetPipeline(); + } - premultiply_program = new QOpenGLShaderProgram(); - premultiply_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/internalshaders/common.vert"); - premultiply_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/internalshaders/premultiply.frag"); - premultiply_program->link(); + // If there's no OpenColorIO shader or the configuration has changed, (re-)create it now + if (olive::config.enable_color_management && ocio_shader == nullptr) { + destroy_ocio(); + + set_up_ocio(); } // draw frame @@ -137,6 +144,53 @@ const GLuint &RenderThread::get_texture() void RenderThread::set_up_ocio() { + + OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + + // Get current OCIO display from Config (or defaults if there is no setting) + QString display = olive::config.ocio_display; + if (display.isEmpty()) { + display = config->getDefaultDisplay(); + } + + QString view = olive::config.ocio_view; + if (view.isEmpty()) { + view = config->getDefaultView(display.toUtf8()); + } + + // Get current display stats + OCIO::DisplayTransformRcPtr transform = OCIO::DisplayTransform::Create(); + transform->setInputColorSpaceName(OCIO::ROLE_SCENE_LINEAR); + transform->setDisplay(display.toUtf8()); + transform->setView(view.toUtf8()); + + if (!olive::config.ocio_look.isEmpty()) { + transform->setLooksOverride(olive::config.ocio_look.toUtf8()); + transform->setLooksOverrideEnabled(true); + } + + try { + + // Using the current configuration, try to get an OCIO processor with a corresponding input and output colorspace + OCIO::ConstProcessorRcPtr processor = config->getProcessor(transform); + + // Create a OCIO shader with this processor + ocio_shader = olive::shader::SetupOCIO(ctx, ocio_lut_texture, processor, true); + + } catch(OCIO::Exception & e) { + qCritical() << e.what(); + return; + } +} + +void RenderThread::destroy_ocio() +{ + // Destroy LUT texture + if (ocio_lut_texture > 0) { + ctx->functions()->glDeleteTextures(1, &ocio_lut_texture); + } + ocio_lut_texture = 0; + ocio_shader = nullptr; } void RenderThread::paint() { @@ -145,43 +199,66 @@ void RenderThread::paint() { params.viewer = nullptr; params.ctx = ctx; params.seq = seq; - params.video = true; + params.type = olive::kTypeVideo; params.texture_failed = false; params.wait_for_mutexes = true; params.playback_speed = playback_speed_; - params.blend_mode_program = blend_mode_program; - params.premultiply_program = premultiply_program; - params.backend_buffer1 = back_buffer_1.buffer(); - params.backend_buffer2 = back_buffer_2.buffer(); - params.backend_attachment1 = back_buffer_1.texture(); - params.backend_attachment2 = back_buffer_2.texture(); - params.main_buffer = front_buffer_switcher ? front_buffer_1.buffer() : front_buffer_2.buffer(); - params.main_attachment = front_buffer_switcher ? front_buffer_1.texture() : front_buffer_2.texture(); + params.pipeline = pipeline_program.get(); + params.backend_buffer1 = &back_buffer_1; + params.backend_buffer2 = &back_buffer_2; + params.main_buffer = &composite_buffer; // get currently selected gizmos gizmos = seq->GetSelectedGizmo(); params.gizmos = gizmos; + QOpenGLFunctions* f = ctx->functions(); + + f->glEnable(GL_BLEND); + + // bind composite framebuffer for drawing + f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, composite_buffer.buffer()); + + // Clear framebuffer to nothing + f->glClearColor(0.0, 0.0, 0.0, 0.0); + f->glClear(GL_COLOR_BUFFER_BIT); + + // Compose the current frame + olive::rendering::compose_sequence(params); + + // Copy composite buffer to front buffer + // First lock the appropriate mutex for exclusivity QMutex& active_mutex = front_buffer_switcher ? front_mutex1 : front_mutex2; active_mutex.lock(); - // bind framebuffer for drawing - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, params.main_buffer); + FramebufferObject& buffer = front_buffer_switcher ? front_buffer_1 : front_buffer_2; - glLoadIdentity(); + // Blit the composite buffer to one of the front buffers - glClearColor(0.0, 0.0, 0.0, 0.0); - glClear(GL_COLOR_BUFFER_BIT); + // If we're color managing, conver the linear composited frame to display color space + if (olive::config.enable_color_management && ocio_shader != nullptr) { - glMatrixMode(GL_MODELVIEW); + olive::rendering::OCIOBlit(ocio_shader.get(), + ocio_lut_texture, + buffer, + composite_buffer.texture()); - glEnable(GL_TEXTURE_2D); - glEnable(GL_BLEND); + } else { - olive::rendering::compose_sequence(params); + // If we're not color managing, just blit normally + buffer.BindBuffer(); + f->glClear(GL_COLOR_BUFFER_BIT); + composite_buffer.BindTexture(); + olive::rendering::Blit(pipeline_program.get()); + composite_buffer.ReleaseTexture(); + buffer.ReleaseBuffer(); + + } // flush changes - ctx->functions()->glFinish(); + f->glFinish(); + + f->glDisable(GL_BLEND); texture_failed = params.texture_failed; @@ -192,11 +269,11 @@ void RenderThread::paint() { // texture failed, try again queued = true; } else { - ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, params.main_buffer); - QImage img(tex_width, tex_height, QImage::Format_RGBA8888); - glReadPixels(0, 0, tex_width, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, img.bits()); + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, composite_buffer.buffer()); + QImage img(tex_width, tex_height, QImage::Format_RGBA8888_Premultiplied); + f->glReadPixels(0, 0, tex_width, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, img.bits()); img.save(save_fn); - ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); save_fn = ""; } } @@ -204,28 +281,25 @@ void RenderThread::paint() { if (pixel_buffer != nullptr) { // set main framebuffer to the current read buffer - ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, params.main_buffer); + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, composite_buffer.buffer()); // store pixels in buffer - glReadPixels(0, - 0, - pixel_buffer_linesize == 0 ? tex_width : pixel_buffer_linesize, - tex_height, - GL_RGBA, - GL_UNSIGNED_BYTE, - pixel_buffer); + f->glReadPixels(0, + 0, + pixel_buffer_linesize == 0 ? tex_width : pixel_buffer_linesize, + tex_height, + GL_RGBA, + GL_UNSIGNED_BYTE, + pixel_buffer); // release current read buffer - ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); pixel_buffer = nullptr; } - glDisable(GL_BLEND); - glDisable(GL_TEXTURE_2D); - // release - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } void RenderThread::start_render(QOpenGLContext *share, @@ -290,6 +364,7 @@ void RenderThread::wait_until_paused() } void RenderThread::delete_buffers() { + composite_buffer.Destroy(); front_buffer_1.Destroy(); front_buffer_2.Destroy(); back_buffer_1.Destroy(); @@ -297,17 +372,14 @@ void RenderThread::delete_buffers() { } void RenderThread::delete_shaders() { - delete blend_mode_program; - blend_mode_program = nullptr; - - delete premultiply_program; - premultiply_program = nullptr; + pipeline_program = nullptr; } void RenderThread::delete_ctx() { if (ctx != nullptr) { delete_shaders(); delete_buffers(); + destroy_ocio(); } delete ctx; diff --git a/rendering/renderthread.h b/rendering/renderthread.h index b0ce750f6..cea0a7099 100644 --- a/rendering/renderthread.h +++ b/rendering/renderthread.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -30,14 +30,9 @@ #include #include "timeline/sequence.h" -#include "effects/effect.h" +#include "nodes/node.h" #include "rendering/framebufferobject.h" - -// copied from source code to OCIODisplay -const int LUT3D_EDGE_SIZE = 32; - -// copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE -const int NUM_3D_ENTRIES = 98304; +#include "qopenglshaderprogramptr.h" class RenderThread : public QThread { Q_OBJECT @@ -49,7 +44,7 @@ public: QMutex* get_texture_mutex(); const GLuint& get_texture(); - Effect* gizmos; + Node* gizmos; void paint(); void start_render(QOpenGLContext* share, Sequence *s, @@ -65,15 +60,20 @@ public: public slots: // cleanup functions void delete_ctx(); + void delete_buffers(); + void delete_shaders(); + void destroy_ocio(); signals: void ready(); private: - // cleanup functions - void delete_buffers(); - void delete_shaders(); + // OpenColorIO functions void set_up_ocio(); - void destroy_ocio(); + + // OpenColorIO variables + GLuint ocio_lut_texture; + QOpenGLShaderProgramPtr ocio_shader; + qint64 ocio_config_date; FramebufferObject front_buffer_1; QMutex front_mutex1; @@ -81,6 +81,8 @@ private: FramebufferObject front_buffer_2; QMutex front_mutex2; + FramebufferObject composite_buffer; + bool front_buffer_switcher; QWaitCondition wait_cond_; @@ -92,17 +94,13 @@ private: QOffscreenSurface surface; QOpenGLContext* share_ctx; QOpenGLContext* ctx; - QOpenGLShaderProgram* blend_mode_program; - QOpenGLShaderProgram* premultiply_program; + QOpenGLShaderProgramPtr pipeline_program; FramebufferObject back_buffer_1; FramebufferObject back_buffer_2; - float ocio_lut_data[NUM_3D_ENTRIES]; - GLuint ocio_lut_texture; - QOpenGLShaderProgram* ocio_shader; - Sequence* seq; + int playback_speed_; int divider; int tex_width; diff --git a/rendering/shadergenerators.cpp b/rendering/shadergenerators.cpp new file mode 100644 index 000000000..a9b90c6bc --- /dev/null +++ b/rendering/shadergenerators.cpp @@ -0,0 +1,230 @@ +#include "shadergenerators.h" + +#include + +QOpenGLShaderProgramPtr olive::shader::GetPipeline(const QString& function_name, const QString& shader_code) +{ + QOpenGLShaderProgramPtr program = std::make_shared(); + + // Generate vertex shader + QString vert_shader = "#version 110\n" + "\n" + "#ifdef GL_ES\n" + "precision mediump int;\n" + "precision mediump float;\n" + "#endif\n" + "\n" + "uniform mat4 mvp_matrix;\n" + "\n" + "attribute vec4 a_position;\n" + "attribute vec2 a_texcoord;\n" + "\n" + "varying vec2 v_texcoord;\n" + "\n" + "void main() {\n" + " gl_Position = mvp_matrix * a_position;\n" + " v_texcoord = a_texcoord;\n" + "}\n"; + + // Generate fragment shader + QString frag_shader = "#version 110\n" + "\n" + "#ifdef GL_ES\n" + "precision mediump int;\n" + "precision mediump float;\n" + "#endif\n" + "\n" + "uniform sampler2D texture;\n" + "uniform float opacity;\n" + "uniform bool color_only;\n" + "uniform vec4 color_only_color;\n" + "varying vec2 v_texcoord;\n" + "\n"; + + // Finish the function with the main function + + // Check if additional code was passed to this function, add it here + if (shader_code.isEmpty()) { + + // If not, just add a pure main() function + + frag_shader.append("\n" + "void main() {\n" + " if (color_only) {\n" + " gl_FragColor = color_only_color;" + " } else {\n" + " vec4 color = texture2D(texture, v_texcoord)*opacity;\n" + " gl_FragColor = color;\n" + " }\n" + "}\n"); + + } else { + + // If additional code was passed, add it and reference it in main(). + // + // The function in the additional code is expected to be `vec4 function_name(vec4 color)`. The texture coordinate can be + // acquired through `v_texcoord`. + + frag_shader.append(shader_code); + + frag_shader.append(QString("\n" + "void main() {\n" + " vec4 color = %1(texture2D(texture, v_texcoord))*opacity;\n" + " gl_FragColor = color;\n" + "}\n").arg(function_name)); + + } + + + + + // Add shaders to program + program->addShaderFromSourceCode(QOpenGLShader::Vertex, vert_shader); + program->addShaderFromSourceCode(QOpenGLShader::Fragment, frag_shader); + program->link(); + + // Set opacity default to 100% + program->bind(); + program->setUniformValue("opacity", 1.0f); + program->release(); + + return program; +} + +QString olive::shader::GetAlphaDisassociateFunction(const QString &function_name) +{ + return QString("vec4 %1(vec4 col) {\n" + " if (col.a > 0.0) {\n" + " return vec4(col.rgb / col.a, col.a);" + " }\n" + " return col;\n" + "}\n").arg(function_name); +} + +QString olive::shader::GetAlphaReassociateFunction(const QString &function_name) +{ + return QString("vec4 %1(vec4 col) {\n" + " if (col.a > 0.0) {\n" + " return vec4(col.rgb * col.a, col.a);" + " }\n" + " return col;\n" + "}\n").arg(function_name); +} + +QString olive::shader::GetAlphaAssociateFunction(const QString &function_name) +{ + return QString("vec4 %1(vec4 col) {\n" + " return vec4(col.rgb * col.a, col.a);\n" + "}\n").arg(function_name); +} + +// copied from source code to OCIODisplay +const int OCIO_LUT3D_EDGE_SIZE = 32; + +// copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE +const int OCIO_NUM_3D_ENTRIES = 98304; + +QOpenGLShaderProgramPtr olive::shader::SetupOCIO(QOpenGLContext* ctx, + GLuint& lut_texture, + OCIO::ConstProcessorRcPtr processor, + bool alpha_is_associated) +{ + + QOpenGLExtraFunctions* xf = ctx->extraFunctions(); + + // Create LUT texture + xf->glGenTextures(1, &lut_texture); + + // Bind LUT + xf->glBindTexture(GL_TEXTURE_3D, lut_texture); + + // Set texture parameters + xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + // Allocate storage for texture + xf->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB16F_ARB, + OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, + 0, GL_RGB,GL_FLOAT, nullptr); + + // + // SET UP GLSL SHADER + // + + OCIO::GpuShaderDesc shaderDesc; + const char* ocio_func_name = "OCIODisplay"; + shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_0); + shaderDesc.setFunctionName(ocio_func_name); + shaderDesc.setLut3DEdgeLen(OCIO_LUT3D_EDGE_SIZE); + + // + // COMPUTE 3D LUT + // + + GLfloat* ocio_lut_data = new GLfloat[OCIO_NUM_3D_ENTRIES]; + processor->getGpuLut3D(ocio_lut_data, shaderDesc); + + // Upload LUT data to texture + xf->glTexSubImage3D(GL_TEXTURE_3D, 0, + 0, 0, 0, + OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, + GL_RGB, GL_FLOAT, ocio_lut_data); + + delete [] ocio_lut_data; + + // Create OCIO shader code + QString shader_text(processor->getGpuShaderText(shaderDesc)); + + QString shader_call; + + // Enforce alpha association + if (alpha_is_associated) { + + // If alpha is already associated, we'll need to disassociate and reassociate + shader_text.append("\n"); + + QString disassociate_func_name = "disassoc"; + shader_text.append(GetAlphaDisassociateFunction(disassociate_func_name)); + + QString reassociate_func_name = "reassoc"; + shader_text.append(GetAlphaReassociateFunction(reassociate_func_name)); + + // Make OCIO call pass through disassociate and reassociate function + shader_call = QString("%3(%1(%2(col), tex2));").arg(ocio_func_name, + disassociate_func_name, + reassociate_func_name); + + } else { + + // If alpha is not already associated, we can just associate after OCIO + + // Add associate function + QString associate_func_name = "assoc"; + shader_text.append(GetAlphaAssociateFunction(associate_func_name)); + + // Make OCIO call pass through associate function + shader_call = QString("%2(%1(col, tex2));").arg(ocio_func_name, associate_func_name); + + } + + // Add process() function, which GetPipeline() will call if specified + QString process_function_name = "process"; + shader_text.append(QString("\n" + "uniform sampler3D tex2;\n" + "\n" + "vec4 %2(vec4 col) {\n" + " return %1\n" + "}\n").arg(shader_call, process_function_name)); + + + // Get pipeline-based shader to inject OCIO shader into + QOpenGLShaderProgramPtr shader = olive::shader::GetPipeline(process_function_name, shader_text); + + // Release LUT + xf->glBindTexture(GL_TEXTURE_3D, 0); + + return shader; +} diff --git a/rendering/shadergenerators.h b/rendering/shadergenerators.h new file mode 100644 index 000000000..02123afc6 --- /dev/null +++ b/rendering/shadergenerators.h @@ -0,0 +1,26 @@ +#ifndef SHADERGENERATORS_H +#define SHADERGENERATORS_H + +#include "qopenglshaderprogramptr.h" +#include "framebufferobject.h" +#include +namespace OCIO = OCIO_NAMESPACE::v1; + +namespace olive { +namespace shader { + +QOpenGLShaderProgramPtr GetPipeline(const QString &function_name = QString(), const QString &shader_code = QString()); + +QOpenGLShaderProgramPtr SetupOCIO(QOpenGLContext *ctx, + GLuint &lut_texture, + OCIO::ConstProcessorRcPtr processor, + bool alpha_is_associated); + +QString GetAlphaDisassociateFunction(const QString& function_name); +QString GetAlphaReassociateFunction(const QString& function_name); +QString GetAlphaAssociateFunction(const QString& function_name); + +} +} + +#endif // SHADERGENERATORS_H diff --git a/timeline/clip.cpp b/timeline/clip.cpp index b6ecb65e4..e25291ccc 100644 --- a/timeline/clip.cpp +++ b/timeline/clip.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -22,7 +22,7 @@ #include -#include "effects/effect.h" +#include "nodes/node.h" #include "effects/transition.h" #include "project/footage.h" #include "global/config.h" @@ -32,34 +32,31 @@ #include "timeline/sequence.h" #include "panels/timeline.h" #include "project/media.h" -#include "project/clipboard.h" #include "undo/undo.h" +#include "global/clipboard.h" #include "global/debug.h" +#include "global/timing.h" -const int kRGBAComponentCount = 4; - -Clip::Clip(Sequence* s) : - sequence(s), +Clip::Clip(Track *s) : + track_(s), cacher(this), enabled_(true), clip_in_(0), timeline_in_(0), timeline_out_(0), - track_(0), media_(nullptr), reverse_(false), - autoscale_(olive::CurrentConfig.autoscale_by_default), + autoscale_(olive::config.autoscale_by_default), opening_transition(nullptr), closing_transition(nullptr), undeletable(false), replaced(false), - fbo(nullptr), open_(false), - texture(nullptr) + texture(0) { } -ClipPtr Clip::copy(Sequence* s) { +ClipPtr Clip::copy(Track* s) { ClipPtr copy = std::make_shared(s); copy->set_enabled(enabled()); @@ -67,7 +64,6 @@ ClipPtr Clip::copy(Sequence* s) { copy->set_clip_in(clip_in()); copy->set_timeline_in(timeline_in()); copy->set_timeline_out(timeline_out()); - copy->set_track(track()); copy->set_color(color()); copy->set_media(media(), media_stream_index()); copy->set_autoscaled(autoscaled()); @@ -78,7 +74,7 @@ ClipPtr Clip::copy(Sequence* s) { copy->effects.append(effects.at(i)->copy(copy.get())); } - copy->set_cached_frame_rate((this->sequence == nullptr) ? cached_frame_rate() : this->sequence->frame_rate); + copy->set_cached_frame_rate((this->track_ == nullptr) ? cached_frame_rate() : this->track_->sequence()->frame_rate); copy->refresh(); @@ -87,25 +83,42 @@ ClipPtr Clip::copy(Sequence* s) { bool Clip::IsActiveAt(long timecode) { - // these buffers allow clips to be opened and prepared well before they're displayed - // as well as closed a little after they're not needed anymore - int open_buffer = qCeil(this->sequence->frame_rate*2); - int close_buffer = qCeil(this->sequence->frame_rate); - - return enabled() - && timeline_in(true) < timecode + open_buffer - && timeline_out(true) > timecode - close_buffer - && timecode - timeline_in(true) + clip_in(true) < media_length(); + && timeline_in(true) <= timecode + && timeline_out(true) > timecode + && timecode - timeline_in(true) + clip_in(true) < media_length() + && !track()->IsEffectivelyMuted(); } bool Clip::IsSelected(bool containing) { - if (this->sequence == nullptr) { + if (this->track_ == nullptr) { return false; } - return this->sequence->IsClipSelected(this, containing); + return this->track_->IsClipSelected(this, containing); +} + +bool Clip::IsTransitionSelected(TransitionType type) +{ + switch (type) { + case kTransitionOpening: + return track_->IsTransitionSelected(opening_transition.get()); + case kTransitionClosing: + return track_->IsTransitionSelected(closing_transition.get()); + default: + return false; + } +} + +Selection Clip::ToSelection() +{ + return Selection(timeline_in(), timeline_out(), track()); +} + +olive::TrackType Clip::type() +{ + return track()->type(); } const QColor &Clip::color() @@ -134,7 +147,7 @@ FootageStream *Clip::media_stream() { if (media() != nullptr && media()->get_type() == MEDIA_TYPE_FOOTAGE) { - return media()->to_footage()->get_stream_from_file_index(track() < 0, media_stream_index()); + return media()->to_footage()->get_stream_from_file_index(type() == olive::kTypeVideo, media_stream_index()); } return nullptr; @@ -151,6 +164,11 @@ void Clip::set_media(Media *m, int s) media_stream_ = s; } +void Clip::Move(ComboAction *ca, long iin, long iout, long iclip_in, Track *itrack, bool verify_transitions, bool relative) +{ + track()->sequence()->MoveClip(this, ca, iin, iout, iclip_in, itrack, verify_transitions, relative); +} + bool Clip::enabled() { return enabled_; @@ -161,51 +179,18 @@ void Clip::set_enabled(bool e) enabled_ = e; } -void Clip::move(ComboAction* ca, long iin, long iout, long iclip_in, int itrack, bool verify_transitions, bool relative) -{ - ca->append(new MoveClipAction(this, iin, iout, iclip_in, itrack, relative)); - - if (verify_transitions) { - - // if this is a shared transition, and the corresponding clip will be moved away somehow - if (opening_transition != nullptr - && opening_transition->secondary_clip != nullptr - && opening_transition->secondary_clip->timeline_out() != iin) { - // separate transition - ca->append(new SetPointer(reinterpret_cast(&opening_transition->secondary_clip), nullptr)); - ca->append(new AddTransitionCommand(nullptr, - opening_transition->secondary_clip, - opening_transition, - nullptr, - 0)); - } - - if (closing_transition != nullptr - && closing_transition->secondary_clip != nullptr - && closing_transition->parent_clip->timeline_in() != iout) { - // separate transition - ca->append(new SetPointer(reinterpret_cast(&closing_transition->secondary_clip), nullptr)); - ca->append(new AddTransitionCommand(nullptr, - this, - closing_transition, - nullptr, - 0)); - } - } -} - void Clip::reset_audio() { if (UsesCacher()) { cacher.ResetAudio(); } if (media() != nullptr && media()->get_type() == MEDIA_TYPE_SEQUENCE) { - Sequence* nested_sequence = media()->to_sequence().get(); - for (int i=0;iclips.size();i++) { - Clip* c = nested_sequence->clips.at(i).get(); - if (c != nullptr) { - c->reset_audio(); - } + + QVector nested_sequence_clips = media()->to_sequence()->GetAllClips(); + + for (int i=0;ireset_audio(); } + } } @@ -214,9 +199,9 @@ void Clip::refresh() { if (replaced && media() != nullptr && media()->get_type() == MEDIA_TYPE_FOOTAGE) { Footage* m = media()->to_footage(); - if (track() < 0 && m->video_tracks.size() > 0) { + if (type() == olive::kTypeVideo && m->video_tracks.size() > 0) { set_media(media(), m->video_tracks.at(0).file_index); - } else if (track() >= 0 && m->audio_tracks.size() > 0) { + } else if (type() == olive::kTypeAudio && m->audio_tracks.size() > 0) { set_media(media(), m->audio_tracks.at(0).file_index); } } @@ -235,7 +220,7 @@ QVector &Clip::get_markers() { return markers; } -int Clip::IndexOfEffect(Effect *e) +int Clip::IndexOfEffect(Node *e) { for (int i=0;iget_type())); + switch (media()->get_type()) { + case MEDIA_TYPE_FOOTAGE: + stream.writeAttribute("media", QString::number(media()->to_footage()->save_id)); + stream.writeAttribute("stream", QString::number(media_stream_index())); + break; + case MEDIA_TYPE_SEQUENCE: + stream.writeAttribute("sequence", QString::number(media()->to_sequence()->save_id)); + break; + } + } + + // save markers + // only necessary for null media clips, since media has its own markers + if (media() == nullptr) { + for (int k=0;kload_id)); + stream.writeEndElement(); // link + } + stream.writeEndElement(); // linked + + // save opening and closing transitions + for (int t=kTransitionOpening;t<=kTransitionClosing;t++) { + TransitionPtr transition = (t == kTransitionOpening) ? opening_transition : closing_transition; + + if (transition != nullptr) { + stream.writeStartElement((t == kTransitionOpening) ? "opening" : "closing"); + + // check if this is a shared transition + if (this == transition->secondary_clip) { + // if so, just save a reference to the other clip + stream.writeAttribute("shared", + QString::number(transition->parent_clip->load_id)); + } else { + // otherwise save the whole transition + transition->save(stream); + } + + stream.writeEndElement(); // opening/closing + } + } + + for (int k=0;ksave(stream); + stream.writeEndElement(); // effect + } + + stream.writeEndElement(); // clip } long Clip::clip_in(bool with_transition) { if (with_transition && opening_transition != nullptr && opening_transition->secondary_clip != nullptr) { - // we must be the secondary clip, so return (timeline in - length) + // we must be the secondary clip, so return (clip in - length) return clip_in_ - opening_transition->get_true_length(); } return clip_in_; @@ -281,7 +344,7 @@ void Clip::set_timeline_in(long t) long Clip::timeline_out(bool with_transitions) { if (with_transitions && closing_transition != nullptr && closing_transition->secondary_clip != nullptr) { - // we must be the primary clip, so return (timeline out + length2) + // we must be the primary clip, so return (timeline out + length) return timeline_out_ + closing_transition->get_true_length(); } else { return timeline_out_; @@ -348,12 +411,12 @@ AVRational Clip::time_base() return cacher.media_time_base(); } -int Clip::track() +Track *Clip::track() { return track_; } -void Clip::set_track(int t) +void Clip::set_track(Track *t) { track_ = t; } @@ -364,18 +427,18 @@ long Clip::length() { } double Clip::media_frame_rate() { - Q_ASSERT(track_ < 0); + Q_ASSERT(type() == olive::kTypeVideo); if (media_ != nullptr) { double rate = media_->get_frame_rate(media_stream_index()); if (!qIsNaN(rate)) return rate; } - if (sequence != nullptr) return sequence->frame_rate; + if (track() != nullptr) return track()->sequence()->frame_rate; return qSNaN(); } long Clip::media_length() { - if (this->sequence != nullptr) { - double fr = this->sequence->frame_rate; + if (this->track() != nullptr) { + double fr = this->track()->sequence()->frame_rate; fr /= speed_.value; @@ -386,7 +449,7 @@ long Clip::media_length() { case MEDIA_TYPE_FOOTAGE: { Footage* m = media_->to_footage(); - const FootageStream* ms = m->get_stream_from_file_index(track_ < 0, media_stream_index()); + const FootageStream* ms = m->get_stream_from_file_index(type() == olive::kTypeVideo, media_stream_index()); if (ms != nullptr && ms->infinite_length) { return LONG_MAX; } else { @@ -396,7 +459,7 @@ long Clip::media_length() { case MEDIA_TYPE_SEQUENCE: { Sequence* s = media_->to_sequence().get(); - return rescale_frame_number(s->getEndFrame(), s->frame_rate, fr); + return rescale_frame_number(s->GetEndFrame(), s->frame_rate, fr); } } } @@ -405,13 +468,13 @@ long Clip::media_length() { } int Clip::media_width() { - if (media_ == nullptr && sequence != nullptr) return sequence->width; + if (media_ == nullptr && track() != nullptr) return track()->sequence()->width; switch (media_->get_type()) { case MEDIA_TYPE_FOOTAGE: { const FootageStream* ms = media_stream(); if (ms != nullptr) return ms->video_width; - if (sequence != nullptr) return sequence->width; + if (track() != nullptr) return track()->sequence()->width; break; } case MEDIA_TYPE_SEQUENCE: @@ -424,13 +487,13 @@ int Clip::media_width() { } int Clip::media_height() { - if (media_ == nullptr && sequence != nullptr) return sequence->height; + if (media_ == nullptr && track() != nullptr) return track()->sequence()->height; switch (media_->get_type()) { case MEDIA_TYPE_FOOTAGE: { const FootageStream* ms = media_stream(); if (ms != nullptr) return ms->video_height; - if (sequence != nullptr) return sequence->height; + if (track() != nullptr) return track()->sequence()->height; } break; case MEDIA_TYPE_SEQUENCE: @@ -444,16 +507,17 @@ int Clip::media_height() { void Clip::refactor_frame_rate(ComboAction* ca, double multiplier, bool change_timeline_points) { if (change_timeline_points) { - this->move(ca, - qRound(double(timeline_in_) * multiplier), - qRound(double(timeline_out_) * multiplier), - qRound(double(clip_in_) * multiplier), - track_); + track()->sequence()->MoveClip(this, + ca, + qRound(double(timeline_in_) * multiplier), + qRound(double(timeline_out_) * multiplier), + qRound(double(clip_in_) * multiplier), + track_); } // move keyframes for (int i=0;irow_count();j++) { EffectRow* r = e->row(j); for (int l=0;lFieldCount();l++) { @@ -475,7 +539,7 @@ void Clip::Open() { } // reset variable used to optimize uploading frame data - texture_frame = -1; + texture_timestamp = -1; if (UsesCacher()) { // cacher will unlock open_lock @@ -493,12 +557,14 @@ void Clip::Close(bool wait) { open_ = false; if (media() != nullptr && media()->get_type() == MEDIA_TYPE_SEQUENCE) { - close_active_clips(media()->to_sequence().get()); + media()->to_sequence()->Close(); } // destroy opengl texture in main thread - delete texture; - texture = nullptr; + if (texture > 0) { + QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &texture); + texture = 0; + } // close all effects for (int i=0;iget_type() == MEDIA_TYPE_SEQUENCE) ? 3 : 2; + fbo.clear(); - for (int j=0;jcontains(frame)) { + //if (frame->pts != texture_timestamp) { + + bool allocate_data = false; + + QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions(); + // check if the opengl texture exists yet, create it if not - if (texture == nullptr) { - texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + if (texture == 0) { + + // create texture object + f->glGenTextures(1, &texture); + + f->glBindTexture(GL_TEXTURE_2D, texture); + + // set texture filtering to bilinear + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // set texture wrapping to clamp + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // queue an allocation ahead + allocate_data = true; + + } else { + + f->glBindTexture(GL_TEXTURE_2D, texture); + + } + + int video_width = cacher.media_width(); + int video_height = cacher.media_height(); + + const olive::PixelFormatInfo& pix_fmt_info = olive::pixel_formats.at(cacher.media_pixel_format()); + + f->glPixelStorei(GL_UNPACK_ROW_LENGTH, frame->linesize[0]/pix_fmt_info.bytes_per_pixel); + + if (allocate_data) { // the raw frame size may differ from the one we're using (e.g. a lower resolution proxy), so we make sure // the texture is using the correct dimensions, but then treat it as if it's the original resolution in the // composition - texture->setSize(cacher.media_width(), cacher.media_height()); + f->glTexImage2D( + GL_TEXTURE_2D, + 0, + pix_fmt_info.internal_format, + video_width, + video_height, + 0, + pix_fmt_info.pixel_format, + pix_fmt_info.pixel_type, + frame->data[0] + ); + + } else { + + f->glTexSubImage2D(GL_TEXTURE_2D, + 0, + 0, + 0, + video_width, + video_height, + pix_fmt_info.pixel_format, + pix_fmt_info.pixel_type, + frame->data[0] + ); - texture->setFormat(QOpenGLTexture::RGBA8_UNorm); - texture->setMipLevels(texture->maximumMipLevels()); - texture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear); - texture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); } - glPixelStorei(GL_UNPACK_ROW_LENGTH, frame->linesize[0]/kRGBAComponentCount); + f->glBindTexture(GL_TEXTURE_2D, 0); - // 2 data buffers to ping-pong between - bool using_db_1 = true; - uint8_t* data_buffer_1 = frame->data[0]; - uint8_t* data_buffer_2 = nullptr; + f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - int frame_size = frame->linesize[0]*frame->height; - - for (int i=0;iFlags() & Effect::ImageFlag) && e->IsEnabled()) { - if (data_buffer_1 == frame->data[0]) { - data_buffer_1 = new uint8_t[frame_size]; - data_buffer_2 = new uint8_t[frame_size]; - - memcpy(data_buffer_1, frame->data[0], frame_size); - } - - e->process_image(get_timecode(this, cacher_frame), - using_db_1 ? data_buffer_1 : data_buffer_2, - using_db_1 ? data_buffer_2 : data_buffer_1, - frame_size - ); - - using_db_1 = !using_db_1; - } - } - - texture->setData(QOpenGLTexture::RGBA, - QOpenGLTexture::UInt8, - const_cast(using_db_1 ? data_buffer_1 : data_buffer_2)); - - if (data_buffer_1 != frame->data[0]) { - delete [] data_buffer_1; - delete [] data_buffer_2; - } - - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + texture_timestamp = frame->pts; ret = true; } else { @@ -631,7 +710,7 @@ bool Clip::Retrieve() bool Clip::UsesCacher() { - return track() >= 0 || (media() != nullptr && media()->get_type() == MEDIA_TYPE_FOOTAGE); + return type() == olive::kTypeAudio || (media() != nullptr && media()->get_type() == MEDIA_TYPE_FOOTAGE); } ClipSpeed::ClipSpeed() : diff --git a/timeline/clip.h b/timeline/clip.h index 3f29708f1..30ea217ce 100644 --- a/timeline/clip.h +++ b/timeline/clip.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -30,18 +30,17 @@ #include "rendering/cacher.h" -#include "effects/effect.h" +#include "nodes/node.h" #include "effects/transition.h" #include "undo/comboaction.h" #include "project/media.h" #include "project/footage.h" - +#include "rendering/framebufferobject.h" #include "marker.h" +#include "nodes/nodegraph.h" +#include "selection.h" -extern "C" { -#include -#include -} +class Track; struct ClipSpeed { ClipSpeed(); @@ -49,18 +48,21 @@ struct ClipSpeed { bool maintain_audio_pitch; }; -using ClipPtr = std::shared_ptr; - -class Sequence; - class Clip { public: - Clip(Sequence *s); + Clip(Track *s); ~Clip(); - ClipPtr copy(Sequence *s); + ClipPtr copy(Track *s); + + void Save(QXmlStreamWriter& stream); bool IsActiveAt(long timecode); bool IsSelected(bool containing = true); + bool IsTransitionSelected(TransitionType type); + + Selection ToSelection(); + + olive::TrackType type(); const QColor& color(); void set_color(int r, int g, int b); @@ -75,17 +77,17 @@ public: long media_length(); void set_media(Media* m, int s); - bool enabled(); - void set_enabled(bool e); - - void move(ComboAction* ca, + void Move(ComboAction* ca, long iin, long iout, long iclip_in, - int itrack, + Track *itrack, bool verify_transitions = true, bool relative = false); + bool enabled(); + void set_enabled(bool e); + long clip_in(bool with_transition = false); void set_clip_in(long c); @@ -95,8 +97,8 @@ public: long timeline_out(bool with_transition = false); void set_timeline_out(long t); - int track(); - void set_track(int t); + Track* track(); + void set_track(Track* t); bool reversed(); void set_reversed(bool r); @@ -121,15 +123,15 @@ public: long length(); void refactor_frame_rate(ComboAction* ca, double multiplier, bool change_timeline_points); - Sequence* sequence; + Track* parent_; // markers QVector& get_markers(); // other variables (should be deep copied/duplicated in copy()) - int IndexOfEffect(Effect* e); - QList effects; - QVector linked; + int IndexOfEffect(Node* e); + QList effects; + QVector linked; TransitionPtr opening_transition; TransitionPtr closing_transition; @@ -152,17 +154,22 @@ public: QMutex cache_lock; // video playback variables - QOpenGLFramebufferObject** fbo; - QOpenGLTexture* texture; - long texture_frame; + QVector fbo; + GLuint texture; + int64_t texture_timestamp; + +#ifndef NO_OCIO + QOpenGLShaderProgramPtr ocio_shader; + GLuint ocio_lut_texture; +#endif private: // timeline variables (should be copied in copy()) + Track* track_; bool enabled_; long clip_in_; long timeline_in_; long timeline_out_; - int track_; QString name_; Media* media_; int media_stream_; @@ -174,6 +181,8 @@ private: Cacher cacher; long cacher_frame; + NodeGraph pipeline_; + QVector markers; QColor color_; bool open_; diff --git a/timeline/ghost.cpp b/timeline/ghost.cpp new file mode 100644 index 000000000..ec48003c5 --- /dev/null +++ b/timeline/ghost.cpp @@ -0,0 +1,13 @@ +#include "ghost.h" + +Ghost::Ghost() : + transition(nullptr), + track_movement(0), + clip(nullptr) +{ +} + +Selection Ghost::ToSelection() const +{ + return Selection(in, out, track->Sibling(track_movement)); +} diff --git a/timeline/ghost.h b/timeline/ghost.h new file mode 100644 index 000000000..14ee4b131 --- /dev/null +++ b/timeline/ghost.h @@ -0,0 +1,51 @@ +#ifndef GHOST_H +#define GHOST_H + +#include "effects/transition.h" +#include "track.h" +#include "project/media.h" + +namespace olive { +namespace timeline { + +enum TrimType { + TRIM_NONE, + TRIM_IN, + TRIM_OUT +}; + +} +} + +struct Ghost { + Ghost(); + + Clip* clip; + + long in; + long out; + long clip_in; + + long old_in; + long old_out; + long old_clip_in; + + Track* track; + int track_movement; + + // importing variables + Media* media; + int media_stream; + + // other variables + long ghost_length; + long media_length; + olive::timeline::TrimType trim_type; + + // transition trimming + TransitionPtr transition; + + Selection ToSelection() const; +}; + +#endif // GHOST_H diff --git a/timeline/marker.cpp b/timeline/marker.cpp index afcf495a2..f4043da2c 100644 --- a/timeline/marker.cpp +++ b/timeline/marker.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -32,7 +32,7 @@ #include #include -void draw_marker(QPainter &p, int x, int y, int bottom, bool selected) { +void Marker::Draw(QPainter &p, int x, int y, int bottom, bool selected) { const QPoint points[5] = { QPoint(x, bottom), QPoint(x + MARKER_SIZE, bottom - MARKER_SIZE), @@ -49,22 +49,24 @@ void draw_marker(QPainter &p, int x, int y, int bottom, bool selected) { p.drawPolygon(points, 5); } -void set_marker_internal(Sequence* seq, const QVector& clips) { - // if clips is empty, the marker is being added to the sequence +void Marker::SetOnClips(const QVector &clips) +{ + // Don't bother if there are no clips + if (clips.isEmpty()) { + return; + } // add_marker is used to determine whether we're adding a marker, depending on whether the user input a marker name // however if (config.set_name_with_marker) is true, we don't need a marker name so we just add - bool add_marker = !olive::CurrentConfig.set_name_with_marker; + bool add_marker = !olive::config.set_name_with_marker; QString marker_name; - // if (config.set_name_with_marker) is false (set above), ask for a marker name + // if Config::set_name_with_marker is true (set above), ask for a marker name if (!add_marker) { QInputDialog d(olive::MainWindow); d.setWindowTitle(QCoreApplication::translate("Marker", "Set Marker")); - d.setLabelText(clips.size() > 0 - ? QCoreApplication::translate("Marker", "Set clip marker name:") - : QCoreApplication::translate("Marker", "Set sequence marker name:")); + d.setLabelText(QCoreApplication::translate("Marker", "Set clip marker name:")); d.setInputMode(QInputDialog::TextInput); add_marker = (d.exec() == QDialog::Accepted); marker_name = d.textValue(); @@ -75,43 +77,16 @@ void set_marker_internal(Sequence* seq, const QVector& clips) { ComboAction* ca = new ComboAction(); - if (clips.size() > 0) { - - // add a marker action for each clip - foreach (int i, clips) { - ClipPtr c = seq->clips.at(i); - ca->append(new AddMarkerAction(&c->get_markers(), - seq->playhead - c->timeline_in() + c->clip_in(), - marker_name)); - } - - } else { - - // if no clips are selected, we're adding a marker to the sequence - - // kind of hacky, we get the correct marker structure from the viewer panel object that the sequence is attached to - if (seq == panel_footage_viewer->seq.get()) { - - // get correct marker reference from footage viewer - ca->append(new AddMarkerAction(panel_footage_viewer->marker_ref, seq->playhead, marker_name)); - - } else if (seq == panel_sequence_viewer->seq.get()) { - - // get correct marker reference from sequence viewer - ca->append(new AddMarkerAction(panel_sequence_viewer->marker_ref, seq->playhead, marker_name)); - - } else { - - // fallback to using markers from sequence provided - ca->append(new AddMarkerAction(&seq->markers, seq->playhead, marker_name)); - - } - + // add a marker action for each clip + foreach (Clip* c, clips) { + ca->append(new AddMarkerAction(&c->get_markers(), + c->track()->sequence()->playhead - c->timeline_in() + c->clip_in(), + marker_name)); } // push action - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); // redraw UI for new markers update_ui(false); @@ -120,9 +95,71 @@ void set_marker_internal(Sequence* seq, const QVector& clips) { } } -void set_marker_internal(Sequence *seq) { - // create empty clip array - QVector clips; +void Marker::SetOnSequence(Sequence *seq) { + + // Don't bother if there is no sequence + if (seq == nullptr) { + return; + } + + // add_marker is used to determine whether we're adding a marker, depending on whether the user input a marker name + // however if (config.set_name_with_marker) is true, we don't need a marker name so we just add + bool add_marker = !olive::config.set_name_with_marker; + + QString marker_name; + + // if Config::set_name_with_marker is true (set above), ask for a marker name + if (!add_marker) { + QInputDialog d(olive::MainWindow); + d.setWindowTitle(QCoreApplication::translate("Marker", "Set Marker")); + d.setLabelText(QCoreApplication::translate("Marker", "Set sequence marker name:")); + d.setInputMode(QInputDialog::TextInput); + add_marker = (d.exec() == QDialog::Accepted); + marker_name = d.textValue(); + } + + // if we've decided to add a marker + if (add_marker) { + + ComboAction* ca = new ComboAction(); + + // FIXME kind of hacky, we get the correct marker structure from the viewer panel object that the sequence is + // attached to, as the viewers will give us the footage marker set if its footage rather than the sequence marker + // set + + if (seq == panel_footage_viewer->seq.get()) { + + // get correct marker reference from footage viewer + ca->append(new AddMarkerAction(panel_footage_viewer->marker_ref, seq->playhead, marker_name)); + + } else if (seq == panel_sequence_viewer->seq.get()) { + + // get correct marker reference from sequence viewer + ca->append(new AddMarkerAction(panel_sequence_viewer->marker_ref, seq->playhead, marker_name)); + + } else { + + // fallback to using markers from sequence provided + ca->append(new AddMarkerAction(&seq->markers, seq->playhead, marker_name)); + + } + + + // push action + olive::undo_stack.push(ca); + + // redraw UI for new markers + update_ui(false); + panel_footage_viewer->update_viewer(); + + } - set_marker_internal(seq, clips); +} + +void Marker::Save(QXmlStreamWriter &stream) const +{ + stream.writeStartElement("marker"); + stream.writeAttribute("frame", QString::number(frame)); + stream.writeAttribute("name", name); + stream.writeEndElement(); } diff --git a/timeline/marker.h b/timeline/marker.h index abbb27e0a..7d8c9ad7a 100644 --- a/timeline/marker.h +++ b/timeline/marker.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -25,19 +25,22 @@ #include #include +#include #include +class Clip; class Sequence; -using SequencePtr = std::shared_ptr; struct Marker { long frame; QString name; + + void Save(QXmlStreamWriter& stream) const; + static void Draw(QPainter& p, int x, int y, int bottom, bool selected); + + + static void SetOnClips(const QVector& clips); + static void SetOnSequence(Sequence* seq); }; -void draw_marker(QPainter& p, int x, int y, int bottom, bool selected); - -void set_marker_internal(Sequence *seq, const QVector& clips); -void set_marker_internal(Sequence* seq); - #endif // MARKER_H diff --git a/timeline/selection.cpp b/timeline/selection.cpp new file mode 100644 index 000000000..f7c71649a --- /dev/null +++ b/timeline/selection.cpp @@ -0,0 +1,86 @@ +#include "selection.h" + +#include "effects/transition.h" +#include "timeline/clip.h" + +Selection::Selection() +{ +} + +Selection::Selection(long in, long out, Track *track) : + in_(in), + out_(out), + track_(track) +{ +} + +long Selection::in() const +{ + return in_; +} + +long Selection::out() const +{ + return out_; +} + +Track *Selection::track() const +{ + return track_; +} + +void Selection::set_in(long in) +{ + in_ = in; +} + +void Selection::set_out(long out) +{ + out_ = out; +} + +bool Selection::ContainsTransition(Clip* c, int type) const +{ + if (type == kTransitionOpening) { + return c->opening_transition != nullptr + && out_ == c->timeline_in() + c->opening_transition->get_true_length() + && ((c->opening_transition->secondary_clip == nullptr && in_ == c->timeline_in()) + || (c->opening_transition->secondary_clip != nullptr && in_ == c->timeline_in() - c->opening_transition->get_true_length())); + } else { + return c->closing_transition != nullptr + && in_ == c->timeline_out() - c->closing_transition->get_true_length() + && ((c->closing_transition->secondary_clip == nullptr && out_ == c->timeline_out()) + || (c->closing_transition->secondary_clip != nullptr && out_ == c->timeline_out() + c->closing_transition->get_true_length())); + } +} + +void Selection::Tidy(QVector& selections) +{ + for (int i=0;i ss.out()) { + // do nothing + } else if (s.in() >= ss.in() && s.out() <= ss.out()) { + remove = true; + } else if (s.in() <= ss.out() && s.out() > ss.out()) { + ss.set_out(s.out()); + remove = true; + } else if (s.out() >= ss.in() && s.in() < ss.in()) { + ss.set_in(s.in()); + remove = true; + } + if (remove) { + selections.removeAt(i); + i--; + break; + } + } + } + } + } +} diff --git a/timeline/selection.h b/timeline/selection.h index 79c3a79cb..bdf045eac 100644 --- a/timeline/selection.h +++ b/timeline/selection.h @@ -1,36 +1,53 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #ifndef SELECTION_H #define SELECTION_H -struct Selection { - long in; - long out; - int track; +#include - long old_in; - long old_out; - int old_track; +class Clip; +class Track; - bool trim_in; +class Selection { +public: + Selection(); + Selection(long in, long out, Track* track); + + long in() const; + long out() const; + Track* track() const; + + void set_in(long in); + void set_out(long out); + + bool ContainsTransition(Clip* c, int type) const; + + static void Tidy(QVector &selections); + +private: + long in_; + long out_; + Track* track_; + + bool trim_in_; }; #endif // SELECTION_H diff --git a/timeline/sequence.cpp b/timeline/sequence.cpp index d8edc90b6..75340e974 100644 --- a/timeline/sequence.cpp +++ b/timeline/sequence.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -22,7 +22,10 @@ #include +#include "timelinefunctions.h" #include "panels/panels.h" +#include "global/clipboard.h" +#include "global/config.h" #include "global/debug.h" Sequence::Sequence() : @@ -32,9 +35,13 @@ Sequence::Sequence() : workarea_out(0), wrapper_sequence(false) { -} + // Set up tracks + track_lists_.resize(olive::kTypeCount); -Sequence::~Sequence() {} + for (int i=0;i(i)); + } +} SequencePtr Sequence::copy() { SequencePtr s = std::make_shared(); @@ -46,16 +53,8 @@ SequencePtr Sequence::copy() { s->audio_layout = audio_layout; // deep copy all of the sequence's clips - s->clips.resize(clips.size()); - for (int i=0;iclips[i] = nullptr; - } else { - ClipPtr copy = c->copy(s.get()); - copy->linked = c->linked; - s->clips[i] = copy; - } + for (int i=0;itrack_lists_[i] = track_lists_.at(i)->copy(s.get()); } // copy all of the sequence's markers @@ -64,66 +63,858 @@ SequencePtr Sequence::copy() { return s; } -long Sequence::getEndFrame() { - long end = 0; - for (int j=0;jtimeline_out() > end) { - end = c->timeline_out(); - } +void Sequence::Save(QXmlStreamWriter &stream) +{ + // Provide unique IDs for each Clip + QVector all_clips = GetAllClips(); + for (int i=0;iload_id = i; } - return end; + + stream.writeStartElement("sequence"); + stream.writeAttribute("id", QString::number(save_id)); + stream.writeAttribute("name", name); + stream.writeAttribute("width", QString::number(width)); + stream.writeAttribute("height", QString::number(height)); + stream.writeAttribute("framerate", QString::number(frame_rate, 'f', 10)); + stream.writeAttribute("afreq", QString::number(audio_frequency)); + stream.writeAttribute("alayout", QString::number(audio_layout)); + if (this == Timeline::GetTopSequence().get()) { + stream.writeAttribute("open", "1"); + } + stream.writeAttribute("workarea", QString::number(using_workarea)); + stream.writeAttribute("workareaIn", QString::number(workarea_in)); + stream.writeAttribute("workareaOut", QString::number(workarea_out)); + + QVector transition_save_cache; + QVector transition_clip_save_cache; + + for (int j=0;jSave(stream); + } + + for (int j=0;jmedia() == m)) { + for (int j=0;jTrackCount();i++) { + end_frame = qMax(track_list->TrackAt(i)->GetEndFrame(), end_frame); + } + + } + + return end_frame; +} + +QVector Sequence::GetAllClips() +{ + QVector all_clips; + + for (int j=0;jTrackCount();i++) { + all_clips.append(track_list->TrackAt(i)->GetAllClips()); + } + + } + + return all_clips; +} + +TrackList *Sequence::GetTrackList(olive::TrackType type) +{ + return track_lists_.at(type); +} + +void Sequence::Close() +{ + QVector all_clips = GetAllClips(); + + for (int i=0;iClose(true); + } +} + +void Sequence::RefreshClipsUsingMedia(Media *m) { + + QVector all_clips = GetAllClips(); + + for (int i=0;imedia() == m) { c->Close(true); c->refresh(); } } + } QVector Sequence::SelectedClips(bool containing) { QVector selected_clips; - for (int i=0;iTrackCount();j++) { + Track* t = tl->TrackAt(j); + + selected_clips.append(t->GetSelectedClips(containing)); } } return selected_clips; } -QVector Sequence::SelectedClipIndexes() +void Sequence::AddClipsFromGhosts(ComboAction* ca, const QVector& ghosts) { - QVector selected_clips; + // add clips + long earliest_point = LONG_MAX; + QVector added_clips; + for (int i=0;i(g.track->Sibling(g.track_movement)); + c->set_media(g.media, g.media_stream); + c->set_timeline_in(g.in); + c->set_timeline_out(g.out); + c->set_clip_in(g.clip_in); + if (c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* m = c->media()->to_footage(); + if (m->video_tracks.size() == 0) { + // audio only (greenish) + c->set_color(128, 192, 128); + } else if (m->audio_tracks.size() == 0) { + // video only (orangeish) + c->set_color(192, 160, 128); + } else { + // video and audio (blueish) + c->set_color(128, 128, 192); + } + c->set_name(m->name); + } else if (c->media()->get_type() == MEDIA_TYPE_SEQUENCE) { + // sequence (red?ish?) + c->set_color(192, 128, 128); + + c->set_name(c->media()->to_sequence()->name); + } + c->refresh(); + added_clips.append(c); + + } + ca->append(new AddClipCommand(added_clips)); + + // link clips from the same media + for (int i=0;imedia() == cc->media()) { + c->linked.append(cc.get()); + } + } + + if (olive::config.add_default_effects_to_clips) { + if (c->type() == olive::kTypeVideo) { + // add default video effects + c->effects.append(olive::node_library[kTransformEffect]->Create(c.get())); + } else if (c->type() == olive::kTypeAudio) { + // add default audio effects + c->effects.append(olive::node_library[kVolumeEffect]->Create(c.get())); + c->effects.append(olive::node_library[kPanEffect]->Create(c.get())); + } } } - return selected_clips; + if (olive::config.enable_seek_to_import) { + panel_sequence_viewer->seek(earliest_point); + } + + olive::timeline::snapped = false; } -Effect *Sequence::GetSelectedGizmo() +void Sequence::MoveClip(Clip *c, ComboAction *ca, long iin, long iout, long iclip_in, Track *itrack, bool verify_transitions, bool relative) { - Effect* gizmo_ptr = nullptr; + ClipPtr clip_ptr = c->track()->GetClipObjectFromRawPtr(c); + + ca->append(new MoveClipAction(clip_ptr, iin, iout, iclip_in, itrack, relative)); + + if (verify_transitions) { + + // if this is a shared transition, and the corresponding clip will be moved away somehow + if (c->opening_transition != nullptr + && c->opening_transition->secondary_clip != nullptr + && c->opening_transition->secondary_clip->timeline_out() != iin) { + // separate transition + ca->append(new SetPointer(reinterpret_cast(&c->opening_transition->secondary_clip), nullptr)); + ca->append(new AddTransitionCommand(nullptr, + c->opening_transition->secondary_clip, + c->opening_transition, + kInvalidNode, + 0)); + } + + if (c->closing_transition != nullptr + && c->closing_transition->secondary_clip != nullptr + && c->closing_transition->parent_clip->timeline_in() != iout) { + // separate transition + ca->append(new SetPointer(reinterpret_cast(&c->closing_transition->secondary_clip), nullptr)); + ca->append(new AddTransitionCommand(nullptr, + c, + c->closing_transition, + kInvalidNode, + 0)); + } + } +} + +void Sequence::EditToPoint(bool in, bool ripple) +{ + QVector all_clips = GetAllClips(); + + if (all_clips.size() > 0) { + long sequence_end = 0; + + bool playhead_falls_on_in = false; + bool playhead_falls_on_out = false; + long next_cut = LONG_MAX; + long prev_cut = 0; + + // find closest in point to playhead + for (int i=0;itimeline_out(), sequence_end); + + if (c->timeline_in() == playhead) + playhead_falls_on_in = true; + + if (c->timeline_out() == playhead) + playhead_falls_on_out = true; + + if (c->timeline_in() > playhead) + next_cut = qMin(c->timeline_in(), next_cut); + + if (c->timeline_out() > playhead) + next_cut = qMin(c->timeline_out(), next_cut); + + if (c->timeline_in() < playhead) + prev_cut = qMax(c->timeline_in(), prev_cut); + + if (c->timeline_out() < playhead) + prev_cut = qMax(c->timeline_out(), prev_cut); + + } + + next_cut = qMin(sequence_end, next_cut); + + QVector areas; + ComboAction* ca = new ComboAction(); + bool push_undo = true; + long seek = playhead; + + if ((in && (playhead_falls_on_out || (playhead_falls_on_in && playhead == 0))) + || (!in && (playhead_falls_on_in || (playhead_falls_on_out && playhead == sequence_end)))) { // one frame mode + if (ripple) { + // set up deletion areas based on track count + long in_point = playhead; + if (!in) { + in_point--; + seek--; + } + + if (in_point >= 0) { + + for (int i=0;iTrackCount();j++) { + areas.append(Selection(in_point, in_point+1, tl->TrackAt(j))); + } + + } + + // trim and move clips around the in point + DeleteAreas(ca, areas, true); + + if (ripple) { + Ripple(ca, in_point, -1); + } + } else { + push_undo = false; + } + } else { + push_undo = false; + } + } else { + // set up deletion areas based on track count + + long area_in, area_out; + + if (in) { + seek = prev_cut; + area_in = prev_cut; + area_out = playhead; + } else { + area_in = playhead; + area_out = next_cut; + } + + if (area_in == area_out) { + + push_undo = false; + + } else { + + for (int i=0;iTrackCount();j++) { + areas.append(Selection(area_in, area_out, tl->TrackAt(j))); + } + + } + + // trim and move clips around the in point + DeleteAreas(ca, areas, true); + if (ripple) { + Ripple(ca, area_in, area_in - area_out); + } + } + } + + if (push_undo) { + olive::undo_stack.push(ca); + + update_ui(true); + + if (seek != playhead && ripple) { + panel_sequence_viewer->seek(seek); + } + } else { + delete ca; + } + } else { + panel_sequence_viewer->seek(0); + } +} + +bool Sequence::SnapPoint(long *l, double zoom, bool use_playhead, bool use_markers, bool use_workarea) +{ + olive::timeline::snapped = false; + if (olive::timeline::snapping) { + if (use_playhead && !panel_sequence_viewer->playing) { + // snap to playhead + if (olive::timeline::SnapToPoint(playhead, l, zoom)) return true; + } + + // snap to marker + if (use_markers) { + for (int i=0;i all_clips = GetAllClips(); + for (int i=0;itimeline_in(), l, zoom)) { + return true; + } else if (olive::timeline::SnapToPoint(c->timeline_out(), l, zoom)) { + return true; + } else if (c->opening_transition != nullptr + && olive::timeline::SnapToPoint(c->timeline_in() + c->opening_transition->get_true_length(), l, zoom)) { + return true; + } else if (c->closing_transition != nullptr + && olive::timeline::SnapToPoint(c->timeline_out() - c->closing_transition->get_true_length(), l, zoom)) { + return true; + } else { + // try to snap to clip markers + for (int j=0;jget_markers().size();j++) { + if (olive::timeline::SnapToPoint(c->get_markers().at(j).frame + c->timeline_in() - c->clip_in(), l, zoom)) { + return true; + } + } + } + + } + } + + return false; +} + +void Sequence::DeleteInToOut(bool ripple) +{ + if (using_workarea) { + + QVector areas_to_delete; + + for (int i=0;iTrackCount();j++) { + areas_to_delete.append(Selection(workarea_in, workarea_out, tl->TrackAt(j))); + } + + } + + ComboAction* ca = new ComboAction(); + DeleteAreas(ca, areas_to_delete, true); + if (ripple) Ripple(ca, + workarea_in, + workarea_in - workarea_out); + ca->append(new SetTimelineInOutCommand(this, false, 0, 0)); + olive::undo_stack.push(ca); + update_ui(true); + } +} + +void Sequence::DeleteClipsUsingMedia(const QVector& media) +{ + QVector all_clips = GetAllClips(); + + ComboAction* ca = new ComboAction(); + bool deleted = false; + + for (int j=0;jmedia() == media.at(j)) { + ca->append(new DeleteClipAction(c)); + deleted = true; + } + } + } + + if (deleted) { + olive::undo_stack.push(ca); + update_ui(true); + } else { + delete ca; + } +} + +void Sequence::Ripple(ComboAction *ca, long point, long length, const QVector &ignore) +{ + ca->append(new RippleAction(this, point, length, ignore)); +} + +void Sequence::ChangeTrackHeightsRelatively(int diff) +{ + for (int i=0;iTrackCount();j++) { + Track* t = tl->TrackAt(j); + + t->set_height(t->height() + diff); + } + } +} + +void Sequence::ToggleLinksOnSelected() +{ + QVector selected_clips = SelectedClips(); + + bool link = true; + QVector link_clips; + + for (int i=0;ilinked.size() > 0) { + link = false; // prioritize unlinking + + for (int j=0;jlinked.size();j++) { // add links to the command + if (!link_clips.contains(c->linked.at(j))) { + link_clips.append(c->linked.at(j)); + } + } + } + } + + if (!link_clips.isEmpty()) { + olive::undo_stack.push(new LinkCommand(link_clips, link)); + } +} + +void Sequence::Split() +{ + ComboAction* ca = new ComboAction(); + bool split_occurred = false; + + // See if there are any selected clips at the current playhead to split + QVector selected_clips = SelectedClips(true); + if (selected_clips.size() > 0) { + // see if whole clips are selected + QVector pre_clips; + QVector post_clips; + + for (int i=0;iappend(new AddClipCommand(post_clips)); + + } + } + + // If we weren't able to split any selected clips above, see if there are arbitrary selections to split + if (!split_occurred) { + + TrackList* track_list; + Track* track; + + foreach (track_list, track_lists_) { + + QVector tracks = track_list->tracks(); + foreach (track, tracks) { + + QVector track_selections = track->Selections(); + QVector split_positions; + + for (int j=0;jClipCount();i++) { + Clip* c = track->GetClip(i).get(); + + if (SplitClipAtPositions(ca, c, split_positions, false)) { + split_occurred = true; + } + } + } + } + } + + // if nothing was selected or no selections fell within playhead, simply split at playhead + if (!split_occurred) { + split_occurred = SplitAllClipsAtPoint(ca, playhead); + } + + if (split_occurred) { + olive::undo_stack.push(ca); + update_ui(true); + } else { + delete ca; + } +} + +void Sequence::DeleteAreas(ComboAction* ca, QVector areas, bool deselect_areas, bool ripple) +{ + if (areas.isEmpty()) { + return; + } + + Selection::Tidy(areas); + + panel_graph_editor->set_row(nullptr); + panel_effect_controls->Clear(true); + + QVector pre_clips; + QVector post_clips; + + QVector all_clips = GetAllClips(); + + for (int i=0;itrack() == s.track() && !c->undeletable) { + if (s.ContainsTransition(c, kTransitionOpening)) { + // delete opening transition + ca->append(new DeleteTransitionCommand(c->opening_transition)); + } else if (s.ContainsTransition(c, kTransitionClosing)) { + // delete closing transition + ca->append(new DeleteTransitionCommand(c->closing_transition)); + } else if (c->timeline_in() >= s.in() && c->timeline_out() <= s.out()) { + // clips falls entirely within deletion area + ca->append(new DeleteClipAction(c)); + } else if (c->timeline_in() < s.in() && c->timeline_out() > s.out()) { + // middle of clip is within deletion area + + // duplicate clip + ClipPtr post = SplitClip(ca, true, c, s.in(), s.out()); + + pre_clips.append(c); + post_clips.append(post); + } else if (c->timeline_in() < s.in() && c->timeline_out() > s.in()) { + // only out point is in deletion area + MoveClip(c, ca, c->timeline_in(), s.in(), c->clip_in(), c->track()); + + if (c->closing_transition != nullptr) { + if (s.in() < c->timeline_out() - c->closing_transition->get_true_length()) { + ca->append(new DeleteTransitionCommand(c->closing_transition)); + } else { + ca->append(new ModifyTransitionCommand(c->closing_transition, + c->closing_transition->get_true_length() - (c->timeline_out() - s.in()))); + } + } + } else if (c->timeline_in() < s.out() && c->timeline_out() > s.out()) { + // only in point is in deletion area + MoveClip(c, ca, s.out(), c->timeline_out(), c->clip_in() + (s.out() - c->timeline_in()), c->track()); + + if (c->opening_transition != nullptr) { + if (s.out() > c->timeline_in() + c->opening_transition->get_true_length()) { + ca->append(new DeleteTransitionCommand(c->opening_transition)); + } else { + ca->append(new ModifyTransitionCommand(c->opening_transition, + c->opening_transition->get_true_length() - (s.out() - c->timeline_in()))); + } + } + } + } + } + } + + // deselect selected clip areas + long minimum_in = LONG_MAX; + long minimum_length = LONG_MAX; + if (deselect_areas) { + QVector area_copy = areas; + for (int i=0;iDeselectArea(s.in(), s.out()); + + // Get ripple point and ripple length + minimum_in = qMin(minimum_in, s.in()); + minimum_length = qMin(minimum_length, s.in() - s.out()); + } + } + + if (ripple) { + RippleDeleteArea(ca, minimum_in, minimum_length); + } + + olive::timeline::RelinkClips(pre_clips, post_clips); + ca->append(new AddClipCommand(post_clips)); +} + +bool Sequence::SplitAllClipsAtPoint(ComboAction *ca, long point) +{ + bool split = false; + + QVector all_clips = GetAllClips(); + + for (int j=0;jIsActiveAt(point)) { + SplitClipAtPositions(ca, c, {point}, true); + split = true; + } + } + + return split; +} + +bool Sequence::SplitClipAtPositions(ComboAction *ca, Clip* clip, QVector positions, bool also_split_links) +{ + // Add the clip and each of its links to the pre_splits array + + bool split_occurred = false; + + QVector pre_splits; + pre_splits.append(clip); + + if (also_split_links) { + for (int i=0;ilinked.size();i++) { + pre_splits.append(clip->linked.at(i)); + } + } + + std::sort(positions.begin(), positions.end()); + + // Remove any duplicate positions + for (int i=1;i > post_splits(positions.size()); + + for (int i=positions.size()-1;i>=0;i--) { + + post_splits[i].resize(pre_splits.size()); + + for (int j=0;jset_timeline_out(qMin(post_splits[i][j]->timeline_out(), + positions.at(i+1))); + } + } + } + } + + for (int i=0;iappend(new AddClipCommand(post_splits[i])); + } + + return split_occurred; +} + +void Sequence::RippleDeleteEmptySpace(ComboAction* ca, Track* track, long point) +{ + QVector track_clips = track->GetAllClips(); + + long ripple_start = LONG_MAX; + long ripple_end = LONG_MAX; + + for (int i=0;itimeline_in() <= point && c->timeline_out() >= point) { + // This point is not actually empty, so there's nothing to do here + return; + } + + if (c->timeline_out() <= point) { + + ripple_start = qMin(c->timeline_out(), ripple_start); + + } else if (c->timeline_in() >= point) { + + ripple_end = qMin(c->timeline_in(), ripple_end); + + } + } + + // We now know the maximum ripple we could do to clear this empty space, but we need to ensure it won't cause + // overlaps of clips in other tracks + + if (ripple_start == ripple_end) { + return; + } + + RippleDeleteArea(ca, point, ripple_start - ripple_end); +} + +void Sequence::RippleDeleteArea(ComboAction* ca, long ripple_point, long ripple_length) { + + for (int i=0;iTrackCount();j++) { + Track* t = tl->TrackAt(j); + + // We've already tested `track`, so we don't need to test it again + long first_in_point_after_point = LONG_MAX; + long out_point_just_before_first_in_point = LONG_MIN; + + QVector track_clips = t->GetAllClips(); + + // Find the in point of the clip directly after the point + for (int k=0;ktimeline_in() >= ripple_point) { + first_in_point_after_point = qMin(first_in_point_after_point, c->timeline_in()); + } + } + + // Ensure we found a valid in point before proceeding + if (first_in_point_after_point != LONG_MAX) { + + // Find the out point of the clip directly before the clip found above + for (int k=0;ktimeline_out() <= first_in_point_after_point) { + out_point_just_before_first_in_point = qMax(out_point_just_before_first_in_point, c->timeline_out()); + } + } + + long ripple_test = first_in_point_after_point - out_point_just_before_first_in_point + ripple_length; + + if (ripple_test < 0) { + ripple_length -= ripple_test; + } + } + } + } + + if (ripple_length != 0) { + Ripple(ca, ripple_point, ripple_length); + } + +} + +Node *Sequence::GetSelectedGizmo() +{ + Node* gizmo_ptr = nullptr; + + QVector clips = GetAllClips(); for (int i=0;iIsActiveAt(playhead) - && IsClipSelected(c, true)) { + Clip* c = clips.at(i); + if (c->IsActiveAt(playhead) + && c->IsSelected()) { // This clip is selected and currently active - we'll use this for gizmos if (!c->effects.isEmpty()) { @@ -132,7 +923,7 @@ Effect *Sequence::GetSelectedGizmo() // none selected for (int j=0;jeffects.size();j++) { - Effect* e = c->effects.at(j).get(); + Node* e = c->effects.at(j).get(); // retrieve gizmo data from effect if (e->are_gizmos_enabled()) { @@ -156,81 +947,251 @@ Effect *Sequence::GetSelectedGizmo() return gizmo_ptr; } -bool Sequence::IsClipSelected(int clip_index, bool containing) +void Sequence::SelectAll() { - return IsClipSelected(clips.at(clip_index).get(), containing); + for (int j=0;jTrackCount();i++) { + tl->TrackAt(i)->SelectAll(); + } + } } -bool Sequence::IsClipSelected(Clip *clip, bool containing) +void Sequence::SelectAtPlayhead() { + for (int j=0;jTrackCount();i++) { + tl->TrackAt(i)->SelectAtPoint(playhead); + } + } +} + +void Sequence::ClearSelections() +{ + for (int j=0;jTrackCount();i++) { + tl->TrackAt(i)->ClearSelections(); + } + } +} + +void Sequence::AddSelectionsToClipboard(bool delete_originals) +{ + olive::clipboard.Clear(); + olive::clipboard.SetType(Clipboard::CLIPBOARD_TYPE_CLIP); + + QVector original_clips; + QVector copied_clips; + + long min_in = LONG_MAX; + + QVector selections = Selections(); for (int i=0;itrack() == s.track && ((clip->timeline_in() >= s.in && clip->timeline_out() <= s.out) - || (!containing && !(clip->timeline_in() >= s.out || clip->timeline_out() <= s.in)))) { - return true; - } - } - return false; -} -bool Sequence::IsTransitionSelected(Transition *t) -{ - if (t == nullptr) { - return false; - } + // Get the clips contained in the track this selection pertains to + QVector track_clips = s.track()->GetAllClips(); - Clip* c = t->parent_clip; + for (int j=0;jparent_clip->track(); - long transition_in_point; - long transition_out_point; + Clip* c = track_clips.at(j); - // Get positions of the transition on the timeline + // Check if this selection contains this clip + if (!(c->timeline_out() < s.in() || c->timeline_in() > s.out())) { - if (t == c->opening_transition.get()) { - transition_in_point = c->timeline_in(); - transition_out_point = c->timeline_in() + t->get_true_length(); + // If so, we'll be copying this clip + original_clips.append(c); - if (t->secondary_clip != nullptr) { - transition_in_point -= t->get_true_length(); - } - } else { - transition_in_point = c->timeline_out() - t->get_true_length(); - transition_out_point = c->timeline_out(); + ClipPtr copy = c->copy(nullptr); - if (t->secondary_clip != nullptr) { - transition_out_point += t->get_true_length(); - } - } + // If we only copied part of this clip, adjust the copy so it's only that part of the clip + if (copy->timeline_in() < s.in()) { + copy->set_clip_in(copy->clip_in() + (s.in() - copy->timeline_in())); + copy->set_timeline_in(s.in()); + } - // See if there's a selection matching this - for (int i=0;i= transition_out_point - && selections.at(i).track == transition_track) { - return true; - } - } + if (copy->timeline_out() > s.out()) { + copy->set_timeline_out(s.out()); + } - return false; -} + // Store the minimum in point as all copies will be stored offset from 0 + min_in = qMin(min_in, s.in()); + + copied_clips.append(copy); + olive::clipboard.Append(copy); -void Sequence::getTrackLimits(int* video_tracks, int* audio_tracks) { - int vt = 0; - int at = 0; - for (int j=0;jtrack() < 0 && c->track() < vt) { // video clip - vt = c->track(); - } else if (c->track() > at) { - at = c->track(); } } } - if (video_tracks != nullptr) *video_tracks = vt; - if (audio_tracks != nullptr) *audio_tracks = at; + + // Determine whether we actually copied anything + if (min_in < LONG_MAX) { + + // Offset all copied clips to 0 + for (int i=0;iset_timeline_in(copy->timeline_in() - min_in); + copy->set_timeline_out(copy->timeline_out() - min_in); + } + + // Relink the copied clips with each other + olive::timeline::RelinkClips(original_clips, copied_clips); + + // If we're deleting the originals (i.e. cutting), delete them now + if (delete_originals) { + ComboAction* ca = new ComboAction(); + DeleteAreas(ca, selections, true); + olive::undo_stack.push(ca); + } + + } } -// static variable for the currently active sequence -SequencePtr olive::ActiveSequence = nullptr; +QVector Sequence::Selections() +{ + QVector selections; + + for (int j=0;jTrackCount();i++) { + selections.append(tl->TrackAt(i)->Selections()); + } + } + + return selections; +} + +void Sequence::SetSelections(const QVector &selections) +{ + ClearSelections(); + + for (int i=0;iSelectArea(s.in(), s.out()); + } +} + +void Sequence::TidySelections() +{ + QVector selections = Selections(); + Selection::Tidy(selections); + SetSelections(selections); +} + +ClipPtr Sequence::SplitClip(ComboAction *ca, bool transitions, Clip* pre, long frame) +{ + return SplitClip(ca, transitions, pre, frame, frame); +} + +ClipPtr Sequence::SplitClip(ComboAction *ca, bool transitions, Clip* pre, long frame, long post_in) +{ + if (pre == nullptr) { + return nullptr; + } + + if (pre->timeline_in() < frame && pre->timeline_out() > frame) { + // duplicate clip without duplicating its transitions, we'll restore them later + + ClipPtr post = pre->copy(pre->track()); + + long new_clip_length = frame - pre->timeline_in(); + + post->set_timeline_in(post_in); + post->set_clip_in(pre->clip_in() + (post->timeline_in() - pre->timeline_in())); + + MoveClip(pre, ca, pre->timeline_in(), frame, pre->clip_in(), pre->track(), false); + + if (transitions) { + + // check if this clip has a closing transition + if (pre->closing_transition != nullptr) { + + // if so, move closing transition to the post clip + post->closing_transition = pre->closing_transition; + + // and set the original clip's closing transition to nothing + ca->append(new SetPointer(reinterpret_cast(&pre->closing_transition), nullptr)); + + // and set the transition's reference to the post clip + if (post->closing_transition->parent_clip == pre) { + ca->append(new SetPointer(reinterpret_cast(&post->closing_transition->parent_clip), post.get())); + } + if (post->closing_transition->secondary_clip == pre) { + ca->append(new SetPointer(reinterpret_cast(&post->closing_transition->secondary_clip), post.get())); + } + + // and make sure it's at the correct size to the closing clip + if (post->closing_transition != nullptr && post->closing_transition->get_true_length() > post->length()) { + ca->append(new ModifyTransitionCommand(post->closing_transition, post->length())); + post->closing_transition->set_length(post->length()); + } + + } + + // we're keeping the opening clip, so ensure that's a correct size too + if (pre->opening_transition != nullptr && pre->opening_transition->get_true_length() > new_clip_length) { + ca->append(new ModifyTransitionCommand(pre->opening_transition, new_clip_length)); + } + } + + return post; + + } else if (frame == pre->timeline_in() + && pre->opening_transition != nullptr + && pre->opening_transition->secondary_clip != nullptr) { + // special case for shared transitions to split it into two + + // set transition to single-clip mode + ca->append(new SetPointer(reinterpret_cast(&pre->opening_transition->secondary_clip), nullptr)); + + // clone transition for other clip + ca->append(new AddTransitionCommand(nullptr, + pre->opening_transition->secondary_clip, + pre->opening_transition, + kInvalidNode, + 0) + ); + + } + + return nullptr; +} + +bool Sequence::SplitSelection(ComboAction *ca, QVector selections) +{ + bool ret = false; + QVector all_clips = GetAllClips(); + + for (int i=0;i points; + + for (int j=0;jtrack()) { + if (c->timeline_in() < s.in() && c->timeline_out() > s.in()) { + points.append(s.in()); + } + if (c->timeline_in() < s.out() && c->timeline_out() > s.out()) { + points.append(s.out()); + } + } + } + + if (SplitClipAtPositions(ca, c, points, false)) { + ret = true; + } + } + + return ret; +} diff --git a/timeline/sequence.h b/timeline/sequence.h index 559564e3b..94051ae83 100644 --- a/timeline/sequence.h +++ b/timeline/sequence.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -27,32 +27,89 @@ #include "clip.h" #include "marker.h" #include "selection.h" +#include "tracklist.h" +#include "ghost.h" -class Sequence { +class Sequence : public QObject { + Q_OBJECT public: Sequence(); - ~Sequence(); SequencePtr copy(); + + void Save(QXmlStreamWriter& stream); + QString name; - void getTrackLimits(int* video_tracks, int* audio_tracks); - long getEndFrame(); int width; int height; double frame_rate; int audio_frequency; int audio_layout; - void RefreshClips(Media* m = nullptr); + long GetEndFrame(); + QVector GetAllClips(); + TrackList* GetTrackList(olive::TrackType type); + + /** + * @brief Close all open clips in a Sequence + * + * Closes any currently open clips on a Sequence and waits for them to close before returning. This may be slow as a + * result on large Sequence objects. If a Clip is a nested Sequence, this function calls itself recursively on that + * Sequence too. + * + * @param s + * + * The Sequence to close all clips on. + */ + void Close(); + + void RefreshClipsUsingMedia(Media* m = nullptr); QVector SelectedClips(bool containing = true); - QVector SelectedClipIndexes(); - Effect* GetSelectedGizmo(); + void AddClipsFromGhosts(ComboAction *ca, const QVector &ghosts); + + void MoveClip(Clip* c, + ComboAction* ca, + long iin, + long iout, + long iclip_in, + Track *itrack, + bool verify_transitions = true, + bool relative = false); + + void EditToPoint(bool in, bool ripple); + + bool SnapPoint(long* l, double zoom, bool use_playhead, bool use_markers, bool use_workarea); + + void DeleteAreas(ComboAction* ca, QVector areas, bool deselect_areas = false, bool ripple = false); + void DeleteInToOut(bool ripple); + void DeleteClipsUsingMedia(const QVector &media); + + void Ripple(ComboAction *ca, long point, long length, const QVector &ignore = QVector()); + + void ChangeTrackHeightsRelatively(int diff); + + void ToggleLinksOnSelected(); + + void Split(); + bool SplitAllClipsAtPoint(ComboAction *ca, long point); + bool SplitClipAtPositions(ComboAction* ca, Clip *clip, QVector positions, bool also_split_links = true); + + void RippleDeleteEmptySpace(ComboAction *ca, Track *track, long point); + void RippleDeleteArea(ComboAction* ca, long ripple_point, long ripple_length); + + Node* GetSelectedGizmo(); - bool IsClipSelected(int clip_index, bool containing = true); bool IsClipSelected(Clip* clip, bool containing = true); bool IsTransitionSelected(Transition* t); - QVector selections; + void SelectAll(); + void SelectAtPlayhead(); + void ClearSelections(); + void AddSelectionsToClipboard(bool delete_originals); + QVector Selections(); + void SetSelections(const QVector& selections); + void TidySelections(); + long playhead; bool using_workarea; @@ -64,14 +121,16 @@ public: int save_id; QVector markers; - QVector clips; +signals: + void Changed(); +private: + QVector track_lists_; + + ClipPtr SplitClip(ComboAction* ca, bool transitions, Clip *clip, long frame); + ClipPtr SplitClip(ComboAction* ca, bool transitions, Clip *clip, long frame, long post_in); + bool SplitSelection(ComboAction* ca, QVector selections); }; using SequencePtr = std::shared_ptr; -// static variable for the currently active sequence -namespace olive { - extern SequencePtr ActiveSequence; -} - #endif // SEQUENCE_H diff --git a/timeline/timelinefunctions.cpp b/timeline/timelinefunctions.cpp new file mode 100644 index 000000000..fbbd1985b --- /dev/null +++ b/timeline/timelinefunctions.cpp @@ -0,0 +1,172 @@ +#include "timelinefunctions.h" + +#include "global/math.h" +#include "global/config.h" +#include "global/timing.h" +#include "sequence.h" + +// snapping +bool olive::timeline::snapping = true; +bool olive::timeline::snapped = false; +long olive::timeline::snap_point = 0; + +void olive::timeline::RelinkClips(QVector &pre_clips, QVector &post_clips) +{ + // Loop through each "pre" clip + for (int i=0;ilinked.size();j++) { + + // Loop again through the "pre" clips to determine which are linked to each other + for (int l=0;llinked.at(j) == pre_clips.at(l)) { + + // Check if we have an equivalent in the post_clips, and link it if so + if (post_clips.at(l) != nullptr) { + post_clips.at(i)->linked.append(post_clips.at(l).get()); + } + + } + } + } + } +} + +bool olive::timeline::SnapToPoint(long point, long* l, double zoom) { + long limit = getFrameFromScreenPoint(zoom, 10); // FIXME magic number 10 used for on screen pixel threshold to snap to + if (*l > point-limit-1 && *l < point+limit+1) { + olive::timeline::snap_point = point; + *l = point; + olive::timeline::snapped = true; + return true; + } + return false; +} + + +QVector olive::timeline::CreateGhostsFromMedia(Sequence *seq, + long entry_point, + QVector &media_list) +{ + QVector ghosts; + + for (int i=0;iget_type()) { + case MEDIA_TYPE_FOOTAGE: + m = medium->to_footage(); + can_import = m->ready; + if (m->using_inout) { + double source_fr = 30; + if (m->video_tracks.size() > 0 && !qIsNull(m->video_tracks.at(0).video_frame_rate)) { + source_fr = m->video_tracks.at(0).video_frame_rate * m->speed; + } + default_clip_in = rescale_frame_number(m->in, source_fr, seq->frame_rate); + default_clip_out = rescale_frame_number(m->out, source_fr, seq->frame_rate); + } + break; + case MEDIA_TYPE_SEQUENCE: + s = medium->to_sequence().get(); + sequence_length = s->GetEndFrame(); + if (seq != nullptr) sequence_length = rescale_frame_number(sequence_length, s->frame_rate, seq->frame_rate); + can_import = (s != seq && sequence_length != 0); + if (s->using_workarea) { + default_clip_in = rescale_frame_number(s->workarea_in, s->frame_rate, seq->frame_rate); + default_clip_out = rescale_frame_number(s->workarea_out, s->frame_rate, seq->frame_rate); + } + break; + default: + can_import = false; + } + + if (can_import) { + Ghost g; + g.clip = nullptr; + g.trim_type = olive::timeline::TRIM_NONE; + g.old_clip_in = g.clip_in = default_clip_in; + g.media = medium; + g.in = entry_point; + g.transition = nullptr; + + switch (medium->get_type()) { + case MEDIA_TYPE_FOOTAGE: + // is video source a still image? + if (m->video_tracks.size() > 0 && m->video_tracks.at(0).infinite_length && m->audio_tracks.size() == 0) { + g.out = g.in + 100; + } else { + long length = m->get_length_in_frames(seq->frame_rate); + g.out = entry_point + length - default_clip_in; + if (m->using_inout) { + g.out -= (length - default_clip_out); + } + } + + if (import_data.type() == olive::timeline::kImportAudioOnly + || import_data.type() == olive::timeline::kImportBoth) { + for (int j=0;jaudio_tracks.size();j++) { + if (m->audio_tracks.at(j).enabled) { + g.track = seq->GetTrackList(olive::kTypeAudio)->TrackAt(j); + g.media_stream = m->audio_tracks.at(j).file_index; + ghosts.append(g); + } + } + } + + if (import_data.type() == olive::timeline::kImportVideoOnly + || import_data.type() == olive::timeline::kImportBoth) { + for (int j=0;jvideo_tracks.size();j++) { + if (m->video_tracks.at(j).enabled) { + g.track = seq->GetTrackList(olive::kTypeVideo)->TrackAt(j); + g.media_stream = m->video_tracks.at(j).file_index; + ghosts.append(g); + } + } + } + break; + case MEDIA_TYPE_SEQUENCE: + g.out = entry_point + sequence_length - default_clip_in; + + if (s->using_workarea) { + g.out -= (sequence_length - default_clip_out); + } + + if (import_data.type() == olive::timeline::kImportVideoOnly + || import_data.type() == olive::timeline::kImportBoth) { + g.track = seq->GetTrackList(olive::kTypeVideo)->First(); + ghosts.append(g); + } + + if (import_data.type() == olive::timeline::kImportAudioOnly + || import_data.type() == olive::timeline::kImportBoth) { + g.track = seq->GetTrackList(olive::kTypeAudio)->First(); + ghosts.append(g); + } + + break; + } + entry_point = g.out; + } + } + for (int i=0;i + +#include "timeline/clip.h" +#include "timeline/mediaimportdata.h" +#include "ghost.h" + +namespace olive { +namespace timeline { + +enum CreateObjects { + ADD_OBJ_TITLE, + ADD_OBJ_SOLID, + ADD_OBJ_BARS, + ADD_OBJ_TONE, + ADD_OBJ_NOISE, + ADD_OBJ_AUDIO +}; + +enum Alignment { + kAlignmentTop, + kAlignmentBottom, + kAlignmentSingle +}; + +void RelinkClips(QVector& pre_clips, QVector &post_clips); + +bool SnapToPoint(long point, long* l, double zoom); + +QVector CreateGhostsFromMedia(Sequence *seq, long entry_point, QVector &media_list); + +// snapping +extern bool snapping; +extern bool snapped; +extern long snap_point; + +} +} + +#endif // TIMELINEFUNCTIONS_H diff --git a/timeline/timelineshared.cpp b/timeline/timelineshared.cpp new file mode 100644 index 000000000..6f5fd8337 --- /dev/null +++ b/timeline/timelineshared.cpp @@ -0,0 +1,6 @@ +#include "timelineshared.h" + +TimelineShared::TimelineShared() +{ + +} diff --git a/timeline/timelineshared.h b/timeline/timelineshared.h new file mode 100644 index 000000000..0622ac154 --- /dev/null +++ b/timeline/timelineshared.h @@ -0,0 +1,11 @@ +#ifndef TIMELINESHARED_H +#define TIMELINESHARED_H + + +class TimelineShared +{ +public: + TimelineShared(); +}; + +#endif // TIMELINESHARED_H diff --git a/timeline/timelinetools.cpp b/timeline/timelinetools.cpp new file mode 100644 index 000000000..fe3eea243 --- /dev/null +++ b/timeline/timelinetools.cpp @@ -0,0 +1,3 @@ +#include "timelinetools.h" + +olive::timeline::Tool olive::timeline::current_tool = olive::timeline::TIMELINE_TOOL_POINTER; diff --git a/timeline/timelinetools.h b/timeline/timelinetools.h new file mode 100644 index 000000000..4fbe6f704 --- /dev/null +++ b/timeline/timelinetools.h @@ -0,0 +1,47 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef TIMELINETOOLS_H +#define TIMELINETOOLS_H + +namespace olive { +namespace timeline { + +enum Tool { + TIMELINE_TOOL_POINTER, + TIMELINE_TOOL_EDIT, + TIMELINE_TOOL_RAZOR, + TIMELINE_TOOL_RIPPLE, + TIMELINE_TOOL_ROLLING, + TIMELINE_TOOL_SLIP, + TIMELINE_TOOL_SLIDE, + TIMELINE_TOOL_HAND, + TIMELINE_TOOL_ZOOM, + TIMELINE_TOOL_MENU, + TIMELINE_TOOL_TRANSITION, + TIMELINE_TOOL_COUNT +}; + +extern Tool current_tool; + +} +} + +#endif // TIMELINETOOLS_H diff --git a/timeline/track.cpp b/timeline/track.cpp new file mode 100644 index 000000000..2ba9cbbe6 --- /dev/null +++ b/timeline/track.cpp @@ -0,0 +1,436 @@ +#include "track.h" + +#include "timeline/clip.h" +#include "timeline/tracklist.h" +#include "timeline/sequence.h" +#include "global/math.h" + +int olive::timeline::kTrackDefaultHeight = 40; +int olive::timeline::kTrackMinHeight = 30; +int olive::timeline::kTrackHeightIncrement = 10; + +Track::Track(TrackList* parent, olive::TrackType type) : + parent_(parent), + type_(type), + muted_(false), + soloed_(false), + locked_(false), + height_(olive::timeline::kTrackDefaultHeight) +{ +} + +Track *Track::copy(TrackList *parent) +{ + Track* t = new Track(parent, type_); + + t->ResizeClipArray(ClipCount()); + for (int i=0;icopy(t); + copy->linked = c->linked; + t->clips_[i] = copy; + } + + return t; +} + +Sequence *Track::sequence() +{ + return parent_->GetParent(); +} + +TrackList *Track::track_list() +{ + return parent_; +} + +void Track::Save(QXmlStreamWriter &stream) +{ + stream.writeStartElement("track"); + + for (int j=0;jSave(stream); + + } + + stream.writeEndElement(); // track +} + +olive::TrackType Track::type() +{ + return type_; +} + +int Track::height() +{ + return height_; +} + +void Track::set_height(int h) +{ + height_ = qMax(h, olive::timeline::kTrackMinHeight); + emit HeightChanged(h); +} + +QString Track::name() +{ + if (name_.isEmpty()) { + int display_index = Index() + 1; + + switch (type_) { + case olive::kTypeVideo: + return tr("Video %1").arg(display_index); + case olive::kTypeAudio: + return tr("Audio %1").arg(display_index); + case olive::kTypeSubtitle: + return tr("Subtitle %1").arg(display_index); + default: + return tr("Unknown %1").arg(display_index); + } + } + + return name_; +} + +void Track::SetName(const QString &s) +{ + name_ = s; +} + +void Track::AddClip(ClipPtr clip) +{ + if (clips_.contains(clip)) { + return; + } + + clips_.append(clip); + if (clip->track() != nullptr && clip->track() != this) { + clip->track()->RemoveClip(clip.get()); + } + clip->set_track(this); +} + +int Track::ClipCount() +{ + return clips_.size(); +} + +ClipPtr Track::GetClip(int i) +{ + return clips_.at(i); +} + +void Track::RemoveClip(int i) +{ + clips_.removeAt(i); +} + +void Track::RemoveClip(Clip *c) +{ + for (int i=0;i Track::GetAllClips() +{ + QVector clips; + + clips.resize(clips_.size()); + for (int i=0;i Track::GetSelectedClips(bool containing) +{ + QVector selected_clips; + + for (int i=0;itimeline_in() <= point && c->timeline_out() > point) { + return c; + } + } + + return nullptr; +} + +bool Track::ContainsClip(Clip *c) +{ + ClipPtr clip; + foreach (clip, clips_) { + if (clip.get() == c) { + return true; + } + } + return false; +} + +Track *Track::Previous() +{ + int index = Index(); + + if (index == 0) { + return nullptr; + } + + return parent_->TrackAt(index - 1); +} + +Track *Track::Next() +{ + return parent_->TrackAt(Index() + 1); +} + +Track *Track::Sibling(int diff) +{ + if (diff == 0) { + return this; + } + + return track_list()->TrackAt(qMax(0, Index() + diff)); +} + +int Track::Index() +{ + return parent_->IndexOfTrack(this); +} + +bool Track::IsClipSelected(int clip_index, bool containing) +{ + return IsClipSelected(clips_.at(clip_index).get(), containing); +} + +bool Track::IsClipSelected(Clip *clip, bool containing) +{ + for (int i=0;itimeline_in() >= s.in() && clip->timeline_out() <= s.out()) + || (!containing && !(clip->timeline_in() >= s.out() || clip->timeline_out() <= s.in())))) { + return true; + } + } + return false; +} + +bool Track::IsTransitionSelected(Transition *t) +{ + if (t == nullptr) { + return false; + } + + Clip* c = t->parent_clip; + + long transition_in_point; + long transition_out_point; + + // Get positions of the transition on the timeline + + if (t == c->opening_transition.get()) { + transition_in_point = c->timeline_in(); + transition_out_point = c->timeline_in() + t->get_true_length(); + + if (t->secondary_clip != nullptr) { + transition_in_point -= t->get_true_length(); + } + } else { + transition_in_point = c->timeline_out() - t->get_true_length(); + transition_out_point = c->timeline_out(); + + if (t->secondary_clip != nullptr) { + transition_out_point += t->get_true_length(); + } + } + + // See if there's a selection matching this + for (int i=0;i= transition_out_point) { + return true; + } + } + + return false; +} + +void Track::SelectArea(long in, long out) +{ + selections_.append(Selection(in, out, this)); + Selection::Tidy(selections_); +} + +void Track::SelectClip(Clip* c) +{ + SelectArea(c->timeline_in(), c->timeline_out()); +} + +void Track::SelectAll() +{ + ClearSelections(); + + // Select every clip + for (int i=0;itimeline_in() <= point + && c->timeline_out() > point) { + SelectClip(c); + } + } +} + +void Track::ClearSelections() +{ + selections_.clear(); +} + +void Track::DeselectArea(long in, long out) +{ + int selection_count = selections_.size(); + for (int i=0;i= in && s.out() <= out) { + // whole selection is in deselect area + selections_.removeAt(i); + i--; + selection_count--; + } else if (s.in() < in && s.out() > out) { + // middle of selection is in deselect area + Selection new_sel(out, s.out(), s.track()); + selections_.append(new_sel); + + s.set_out(in); + } else if (s.in() < in && s.out() > in) { + // only out point is in deselect area + s.set_out(in); + } else if (s.in() < out && s.out() > out) { + // only in point is in deselect area + s.set_in(out); + } + } +} + +QVector Track::Selections() +{ + return selections_; +} + +long Track::GetEndFrame() +{ + long end_frame = 0; + + for (int i=0;itimeline_out(true)); + } + } + + return end_frame; +} + +bool Track::IsEffectivelyMuted() +{ + // Check if this track is muted + if (muted_) { + return true; + } + + // Check if any tracks are soloed + bool a_track_is_soloed = false; + + for (int i=0;iTrackCount();i++) { + if (track_list()->TrackAt(i)->IsSoloed()) { + a_track_is_soloed = true; + break; + } + } + + // Return if a track is soloed and this track is not soloed + return (a_track_is_soloed && !soloed_); +} + +bool Track::IsMuted() +{ + return muted_; +} + +void Track::SetMuted(bool muted) +{ + muted_ = muted; +} + +bool Track::IsSoloed() +{ + return soloed_; +} + +void Track::SetSoloed(bool soloed) +{ + soloed_ = soloed; +} + +bool Track::IsLocked() +{ + return locked_; +} + +void Track::SetLocked(bool locked) +{ + locked_ = locked; +} diff --git a/timeline/track.h b/timeline/track.h new file mode 100644 index 000000000..310cd69e9 --- /dev/null +++ b/timeline/track.h @@ -0,0 +1,125 @@ +#ifndef TRACK_H +#define TRACK_H + +#include +#include + +#include "nodes/node.h" +#include "tracktypes.h" +#include "undo/comboaction.h" +#include "timeline/selection.h" + +class Sequence; +class Transition; + +namespace olive { + namespace timeline { + const int kGhostThickness = 2; + const int kClipTextPadding = 3; + + /** + * @brief Set default track sizes + * + * Olive has a few default constants used for adjusting track heights in the Timeline. For HiDPI, it makes + * sense to multiply these by the current DPI scale. It uses a variable from QApplication to do this multiplication, + * which means the QApplication instance needs to be instantiated before these are calculated. Therefore, call this + * function ONCE after QApplication is created to multiply the track heights correctly. + */ + void MultiplyTrackSizesByDPI(); + + extern int kTrackDefaultHeight; + extern int kTrackMinHeight; + extern int kTrackHeightIncrement; + + extern int kTimelineLabelFixedWidth; + } +} + +class TrackList; + +class Track : public QObject +{ + Q_OBJECT +public: + Track(TrackList* parent, olive::TrackType type); + Track* copy(TrackList* parent); + + Sequence* sequence(); + TrackList* track_list(); + + void Save(QXmlStreamWriter& stream); + + olive::TrackType type(); + + int height(); + void set_height(int h); + + QString name(); + void SetName(const QString& s); + + void AddClip(ClipPtr clip); + int ClipCount(); + ClipPtr GetClip(int i); + void RemoveClip(int i); + void RemoveClip(Clip* c); + QVector GetAllClips(); + QVector GetSelectedClips(bool containing); + ClipPtr GetClipObjectFromRawPtr(Clip* c); + Clip* GetClipFromPoint(long point); + bool ContainsClip(Clip* c); + + Track* Previous(); + Track* Next(); + Track* Sibling(int diff); + + int Index(); + + bool IsClipSelected(int clip_index, bool containing = true); + bool IsClipSelected(Clip* clip, bool containing = true); + bool IsTransitionSelected(Transition* t); + + void DeleteArea(ComboAction *ca, const Selection& s); + void DeleteArea(ComboAction *ca, long in, long out); + + void SelectArea(long in, long out); + void SelectClip(Clip *c); + void SelectAll(); + void SelectAtPoint(long point); + void TidySelections(); + void ClearSelections(); + void DeselectArea(long in, long out); + QVector Selections(); + + long GetEndFrame(); + + bool IsEffectivelyMuted(); + bool IsMuted(); + bool IsSoloed(); + bool IsLocked(); + +public slots: + void SetMuted(bool muted); + void SetSoloed(bool soloed); + void SetLocked(bool locked); + +signals: + void HeightChanged(int height); + +private: + void ResizeClipArray(int new_size); + + TrackList* parent_; + olive::TrackType type_; + int height_; + QVector clips_; + QVector effects_; + QVector selections_; + + bool muted_; + bool soloed_; + bool locked_; + + QString name_; +}; + +#endif // TRACK_H diff --git a/timeline/tracklist.cpp b/timeline/tracklist.cpp new file mode 100644 index 000000000..a735750b2 --- /dev/null +++ b/timeline/tracklist.cpp @@ -0,0 +1,107 @@ +#include "tracklist.h" + +#include "timeline/sequence.h" + +TrackList::TrackList(Sequence *parent, olive::TrackType type) : + QObject(parent), + type_(type) +{ + // Ensure we have at least one track + AddTrack(); +} + +void TrackList::Save(QXmlStreamWriter &stream) +{ + stream.writeStartElement("Tracks"); + + for (int i=0;iSave(stream); + } + + stream.writeEndElement(); // Tracks +} + +TrackList* TrackList::copy(Sequence *parent) +{ + TrackList* t = new TrackList(parent, type_); + + t->ResizeTrackArray(tracks_.size()); + for (int i=0;itracks_[i] = tracks_.at(i)->copy(t); + } + + return t; +} + +void TrackList::AddTrack() +{ + Track* track = new Track(this, type_); + tracks_.append(track); + + emit TrackCountChanged(); +} + +void TrackList::RemoveTrack(int i) +{ + if (tracks_.size() == 1) { + return; + } + tracks_.removeAt(i); + + emit TrackCountChanged(); +} + +Track *TrackList::First() +{ + return tracks_.first(); +} + +Track *TrackList::Last() +{ + return tracks_.last(); +} + +int TrackList::TrackCount() +{ + return tracks_.size(); +} + +int TrackList::IndexOfTrack(Track *track) +{ + for (int i=0;i= tracks_.size()) { + AddTrack(); + } + + return tracks_.at(i); +} + +QVector TrackList::tracks() +{ + return tracks_; +} + +olive::TrackType TrackList::type() +{ + return type_; +} + +Sequence *TrackList::GetParent() +{ + return static_cast(parent()); +} + +void TrackList::ResizeTrackArray(int i) +{ + tracks_.resize(i); +} diff --git a/timeline/tracklist.h b/timeline/tracklist.h new file mode 100644 index 000000000..5b657aa06 --- /dev/null +++ b/timeline/tracklist.h @@ -0,0 +1,38 @@ +#ifndef TRACKLIST_H +#define TRACKLIST_H + +#include "track.h" + +class TrackList : public QObject +{ + Q_OBJECT +public: + TrackList(Sequence* parent, olive::TrackType type); + TrackList* copy(Sequence* parent); + + void Save(QXmlStreamWriter& stream); + + void AddTrack(); + void RemoveTrack(int i); + Track* First(); + Track* Last(); + int TrackCount(); + int IndexOfTrack(Track* track); + Track* TrackAt(int i); + QVector tracks(); + + olive::TrackType type(); + + Sequence* GetParent(); + +signals: + void TrackCountChanged(); + +private: + void ResizeTrackArray(int i); + + olive::TrackType type_; + QVector tracks_; +}; + +#endif // TRACKLIST_H diff --git a/timeline/tracktypes.h b/timeline/tracktypes.h new file mode 100644 index 000000000..c49b049a7 --- /dev/null +++ b/timeline/tracktypes.h @@ -0,0 +1,13 @@ +#ifndef TRACKTYPES_H +#define TRACKTYPES_H + +namespace olive { + enum TrackType { + kTypeVideo, + kTypeAudio, + kTypeSubtitle, + kTypeCount + }; +}; + +#endif // TRACKTYPES_H diff --git a/ui/audiomonitor.cpp b/ui/audiomonitor.cpp index 239deff00..61e1f07ee 100644 --- a/ui/audiomonitor.cpp +++ b/ui/audiomonitor.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -29,8 +29,6 @@ #include #include -#include - #define AUDIO_MONITOR_PEAK_HEIGHT 15 #define AUDIO_MONITOR_GAP 3 @@ -39,23 +37,33 @@ extern "C" { } AudioMonitor::AudioMonitor(QWidget *parent) : - QWidget(parent) + QWidget(parent), + peaked_(false) { clear_timer.setInterval(500); connect(&clear_timer, SIGNAL(timeout()), this, SLOT(clear())); } -void AudioMonitor::set_value(const QVector &ivalues) { - values = ivalues; - update(); +void AudioMonitor::set_value(const QVector &ivalues) { + if (value_lock.tryLock()) { + values = ivalues; - QMetaObject::invokeMethod(&clear_timer, "start", Qt::QueuedConnection); + if (peaked_.size() != values.size()) { + peaked_.resize(values.size()); + peaked_.fill(false); + } + + value_lock.unlock(); + + QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); + QMetaObject::invokeMethod(&clear_timer, "start", Qt::QueuedConnection); + } } void AudioMonitor::clear() { clear_timer.stop(); - - values.fill(1); + peaked_.fill(false); + values.fill(0.0); update(); } @@ -67,24 +75,39 @@ void AudioMonitor::resizeEvent(QResizeEvent *e) { QWidget::resizeEvent(e); } +void AudioMonitor::mousePressEvent(QMouseEvent *) +{ + peaked_.fill(false); + update(); +} + void AudioMonitor::paintEvent(QPaintEvent *) { - if (olive::ActiveSequence != nullptr && values.size() > 0) { + value_lock.lock(); + if (values.size() > 0) { QPainter p(this); int channel_x = AUDIO_MONITOR_GAP; int channel_count = values.size(); int channel_width = (width()/channel_count) - AUDIO_MONITOR_GAP; - int i; - for (i=0;i 1.0f) { + peaked_[i] = true; + } - r.setHeight(qRound(r.height()*(values.at(i)))); - peak = (r.height() == 0); + // We draw an inverted dark shadow over the gradient to represent to are not lit up + // TODO change to decibel representation + float val = 1.0f - qMin(1.0f, values.at(i)); + r.setHeight(qRound(r.height()*val)); QRect peak_rect(channel_x, 0, channel_width, AUDIO_MONITOR_PEAK_HEIGHT); - if (peak) { + if (peaked_[i]) { + // We're peaked on this channel, so we light up the peak light p.fillRect(peak_rect, QColor(255, 0, 0)); } else { p.fillRect(peak_rect, QColor(64, 0, 0)); @@ -95,4 +118,5 @@ void AudioMonitor::paintEvent(QPaintEvent *) { channel_x += channel_width + AUDIO_MONITOR_GAP; } } + value_lock.unlock(); } diff --git a/ui/audiomonitor.h b/ui/audiomonitor.h index 39a40d39a..f6a4505c2 100644 --- a/ui/audiomonitor.h +++ b/ui/audiomonitor.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -23,6 +23,7 @@ #include #include +#include /** * @brief The AudioMonitor class @@ -52,22 +53,29 @@ public: * An array of doubles between 0.0 and 1.0 to display the amplitude. 0.0 is no audio, 1.0 is full volume. Each array * entry is a channel and the audio monitor will automatically adjust to the channel count in the array. */ - void set_value(const QVector& values); + void set_value(const QVector &values); protected: /** * @brief Internal paint event * - * Paints the + * Paints the audio monitor */ - void paintEvent(QPaintEvent *); + virtual void paintEvent(QPaintEvent *) override; /** * @brief Internal resize event handler * * Triggers a repaint when the widget is resized. */ - void resizeEvent(QResizeEvent *); + virtual void resizeEvent(QResizeEvent *) override; + + /** + * @brief Internal mouse press handler + * + * @param event + */ + virtual void mousePressEvent(QMouseEvent *event) override; signals: @@ -82,7 +90,14 @@ private: /** * @brief Internal value storage */ - QVector values; + QVector values; + + /** + * @brief Value mutex + * + * Audio is often processed and sent to this object from other threads. To keep them synchronized, we lock them here. + */ + QMutex value_lock; /** * @brief Internal timer to clear the audio monitor after a certain amount of time @@ -92,6 +107,11 @@ private: */ QTimer clear_timer; + /** + * @brief Internal variable for whether audio played as peaked or not + */ + QVector peaked_; + private slots: /** * @brief Slot to clear the audio monitor diff --git a/ui/blur.cpp b/ui/blur.cpp index ecdf7442a..c13ede7d6 100644 --- a/ui/blur.cpp +++ b/ui/blur.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "blur.h" void olive::ui::blur(QImage& result, const QRect& rect, int radius, bool alphaOnly) { diff --git a/ui/blur.h b/ui/blur.h index 6e56ea032..ca2b930d8 100644 --- a/ui/blur.h +++ b/ui/blur.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef BLUR_H #define BLUR_H diff --git a/ui/checkboxex.cpp b/ui/checkboxex.cpp new file mode 100644 index 000000000..e76c12985 --- /dev/null +++ b/ui/checkboxex.cpp @@ -0,0 +1,33 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "checkboxex.h" + +#include "undo/undostack.h" +#include "undo/undo.h" + +CheckboxEx::CheckboxEx(QWidget* parent) : QCheckBox(parent) { +// connect(this, SIGNAL(clicked(bool)), this, SLOT(checkbox_command())); +} + +void CheckboxEx::checkbox_command() { + CheckboxCommand* c = new CheckboxCommand(this); + olive::UndoStack.push(c); +} diff --git a/ui/checkboxex.h b/ui/checkboxex.h new file mode 100644 index 000000000..b5d20fd17 --- /dev/null +++ b/ui/checkboxex.h @@ -0,0 +1,35 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef CHECKBOXEX_H +#define CHECKBOXEX_H + +#include + +class CheckboxEx : public QCheckBox +{ + Q_OBJECT +public: + CheckboxEx(QWidget* parent = 0); +private slots: + void checkbox_command(); +}; + +#endif // CHECKBOXEX_H diff --git a/ui/clickablelabel.cpp b/ui/clickablelabel.cpp index 86f7f7de2..dcd2c1c72 100644 --- a/ui/clickablelabel.cpp +++ b/ui/clickablelabel.cpp @@ -1,33 +1,38 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #include "clickablelabel.h" ClickableLabel::ClickableLabel(QWidget *parent, Qt::WindowFlags f) : - QLabel(parent, f) + QLabel(parent, f) {} ClickableLabel::ClickableLabel(const QString &text, QWidget *parent, Qt::WindowFlags f) : - QLabel(text, parent, f) + QLabel(text, parent, f) {} void ClickableLabel::mousePressEvent(QMouseEvent *) { - emit clicked(); + emit clicked(); +} + +void ClickableLabel::mouseDoubleClickEvent(QMouseEvent *ev) +{ + emit double_clicked(); } diff --git a/ui/clickablelabel.h b/ui/clickablelabel.h index a1dd67786..2cf9d6848 100644 --- a/ui/clickablelabel.h +++ b/ui/clickablelabel.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -29,13 +29,15 @@ * Simple QLabel-derived class that emits a clicked() signal when the widget receives a mouse press event. */ class ClickableLabel : public QLabel { - Q_OBJECT + Q_OBJECT public: - ClickableLabel(QWidget * parent = 0, Qt::WindowFlags f = 0); - ClickableLabel(const QString & text, QWidget * parent = 0, Qt::WindowFlags f = 0); - void mousePressEvent(QMouseEvent *ev); + ClickableLabel(QWidget * parent = nullptr, Qt::WindowFlags f = nullptr); + ClickableLabel(const QString & text, QWidget * parent = nullptr, Qt::WindowFlags f = nullptr); + virtual void mousePressEvent(QMouseEvent *ev) override; + virtual void mouseDoubleClickEvent(QMouseEvent *ev) override; signals: - void clicked(); + void clicked(); + void double_clicked(); }; #endif // CLICKABLELABEL_H diff --git a/ui/collapsiblewidget.cpp b/ui/collapsiblewidget.cpp index 22dbab99b..d8136ce91 100644 --- a/ui/collapsiblewidget.cpp +++ b/ui/collapsiblewidget.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -29,13 +29,16 @@ #include #include #include +#include +#include #include "ui/icons.h" #include "global/debug.h" -CollapsibleWidget::CollapsibleWidget(QWidget* parent) : QWidget(parent) { - selected = false; - +CollapsibleWidget::CollapsibleWidget(QWidget* parent) : + QWidget(parent), + contents(nullptr) +{ layout = new QVBoxLayout(this); layout->setMargin(0); layout->setSpacing(0); @@ -58,24 +61,13 @@ CollapsibleWidget::CollapsibleWidget(QWidget* parent) : QWidget(parent) { title_bar_layout->addStretch(); layout->addWidget(title_bar); - connect(title_bar, SIGNAL(select(bool, bool)), this, SLOT(header_click(bool, bool))); + connect(title_bar, SIGNAL(select()), this, SLOT(Selected())); set_button_icon(true); - - contents = nullptr; } -void CollapsibleWidget::header_click(bool s, bool deselect) { - selected = s; - title_bar->selected = s; - if (s) { - QPalette p = title_bar->palette(); - p.setColor(QPalette::Background, QColor(255, 255, 255, 64)); - title_bar->setPalette(p); - } else { - title_bar->setPalette(palette()); - } - if (deselect) emit deselect_others(this); +void CollapsibleWidget::Selected() { + emit deselect_others(this); } bool CollapsibleWidget::IsFocused() { @@ -96,7 +88,17 @@ void CollapsibleWidget::SetExpanded(bool s) bool CollapsibleWidget::IsSelected() { - return selected; + return title_bar->IsSelected(); +} + +void CollapsibleWidget::Deselect() +{ + title_bar->SetSelected(false); +} + +void CollapsibleWidget::SetSelectable(bool s) +{ + title_bar->SetSelectable(s); } void CollapsibleWidget::set_button_icon(bool open) { @@ -108,7 +110,6 @@ void CollapsibleWidget::SetContents(QWidget* c) { contents = c; if (!existing) { layout->addWidget(contents); - connect(enabled_check, SIGNAL(toggled(bool)), contents, SLOT(setEnabled(bool))); connect(collapse_button, SIGNAL(clicked()), this, SLOT(on_visible_change())); } } @@ -126,19 +127,65 @@ void CollapsibleWidget::on_visible_change() { SetExpanded(!IsExpanded()); } -CollapsibleWidgetHeader::CollapsibleWidgetHeader(QWidget* parent) : QWidget(parent), selected(false) { +CollapsibleWidgetHeader::CollapsibleWidgetHeader(QWidget* parent) : + QWidget(parent), + selected_(false), + selectable_(true) +{ setContextMenuPolicy(Qt::CustomContextMenu); } +bool CollapsibleWidgetHeader::IsSelected() +{ + return selected_; +} + +void CollapsibleWidgetHeader::SetSelected(bool s, bool deselect_others) +{ + selected_ = s; + + if (s) { + QPalette p = palette(); + p.setColor(QPalette::Background, QColor(255, 255, 255, 64)); + setPalette(p); + } else { + setPalette(qApp->palette()); + } + + if (deselect_others) { + emit select(); + } +} + +void CollapsibleWidgetHeader::SetSelectable(bool s) +{ + selectable_ = s; + + if (!selectable_ && selected_) { + SetSelected(false); + } +} + +bool CollapsibleWidgetHeader::event(QEvent *event) +{ + if (!selectable_ + && (event->type() == QEvent::MouseButtonPress + || event->type() == QEvent::MouseButtonRelease + || event->type() == QEvent::MouseMove + || event->type() == QEvent::MouseButtonDblClick) + && QApplication::sendEvent(parent(), event)) { + return true; + } + return QWidget::event(event); +} + void CollapsibleWidgetHeader::mousePressEvent(QMouseEvent* event) { - if (selected) { - if ((event->modifiers() & Qt::ShiftModifier)) { - selected = false; - emit select(selected, false); + if (selected_) { + if (event->modifiers() & Qt::ShiftModifier) { + SetSelected(false); } } else { - selected = true; - emit select(selected, !(event->modifiers() & Qt::ShiftModifier)); + SetSelected(true, !(event->modifiers() & Qt::ShiftModifier)); } } diff --git a/ui/collapsiblewidget.h b/ui/collapsiblewidget.h index cd2153832..eca9536d1 100644 --- a/ui/collapsiblewidget.h +++ b/ui/collapsiblewidget.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -34,12 +34,20 @@ class CollapsibleWidgetHeader : public QWidget { Q_OBJECT public: CollapsibleWidgetHeader(QWidget* parent = nullptr); - bool selected; + + bool IsSelected(); + void SetSelected(bool s, bool deselect_others = false); + + void SetSelectable(bool s); protected: - void mousePressEvent(QMouseEvent* event); - void paintEvent(QPaintEvent *event); + virtual bool event(QEvent *event) override; + virtual void mousePressEvent(QMouseEvent* event) override; + virtual void paintEvent(QPaintEvent *event) override; signals: - void select(bool, bool); + void select(); +private: + bool selected_; + bool selectable_; }; class CollapsibleWidget : public QWidget @@ -53,13 +61,15 @@ public: bool IsFocused(); bool IsExpanded(); void SetExpanded(bool s); + bool IsSelected(); + void Deselect(); + void SetSelectable(bool s); protected: QCheckBox* enabled_check; CollapsibleWidgetHeader* title_bar; QWidget* contents; private: - bool selected; QLabel* header; QVBoxLayout* layout; QPushButton* collapse_button; @@ -75,7 +85,7 @@ private slots: void on_visible_change(); public slots: - void header_click(bool s, bool deselect); + void Selected(); }; #endif // COLLAPSIBLEWIDGET_H diff --git a/ui/colorbutton.cpp b/ui/colorbutton.cpp index 9423b616c..39eba687c 100644 --- a/ui/colorbutton.cpp +++ b/ui/colorbutton.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/colorbutton.h b/ui/colorbutton.h index e67ca8cd5..2359cbf9f 100644 --- a/ui/colorbutton.h +++ b/ui/colorbutton.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/comboboxex.cpp b/ui/comboboxex.cpp index 3306ffbc1..8cb994129 100644 --- a/ui/comboboxex.cpp +++ b/ui/comboboxex.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/comboboxex.h b/ui/comboboxex.h index e3b7bb6b3..f045f18ef 100644 --- a/ui/comboboxex.h +++ b/ui/comboboxex.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/cursors.cpp b/ui/cursors.cpp index 5116a1b1b..977c52c63 100644 --- a/ui/cursors.cpp +++ b/ui/cursors.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/cursors.h b/ui/cursors.h index 0e72f8fe7..1c6a3a1cc 100644 --- a/ui/cursors.h +++ b/ui/cursors.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/effectui.cpp b/ui/effectui.cpp index 275f913f5..63e6c2420 100644 --- a/ui/effectui.cpp +++ b/ui/effectui.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "effectui.h" #include @@ -9,7 +29,7 @@ #include "ui/menu.h" #include "panels/panels.h" -EffectUI::EffectUI(Effect* e) : +EffectUI::EffectUI(Node* e) : effect_(e) { Q_ASSERT(e != nullptr); @@ -17,7 +37,7 @@ EffectUI::EffectUI(Effect* e) : QString effect_name; // If this effect is actually a transition - if (e->meta->type == EFFECT_TYPE_TRANSITION) { + if (e->type() == EFFECT_TYPE_TRANSITION) { Transition* t = static_cast(e); @@ -49,17 +69,17 @@ EffectUI::EffectUI(Effect* e) : // See if the transition is the clip's opening or closing transition and label it accordingly if (both_selected) { - effect_name = t->name; + effect_name = t->name(); } else if (selected_clip->opening_transition.get() == t) { - effect_name = tr("%1 (Opening)").arg(t->name); + effect_name = tr("%1 (Opening)").arg(t->name()); } else { - effect_name = tr("%1 (Closing)").arg(t->name); + effect_name = tr("%1 (Closing)").arg(t->name()); } } else { // Otherwise just set the title normally - effect_name = e->name; + effect_name = e->name(); } @@ -69,6 +89,9 @@ EffectUI::EffectUI(Effect* e) : ui->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); SetContents(ui); + title_bar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + SetExpanded(e->IsExpanded()); connect(this, SIGNAL(visibleChanged(bool)), e, SLOT(SetExpanded(bool))); @@ -80,9 +103,8 @@ EffectUI::EffectUI(Effect* e) : this, SLOT(show_context_menu(const QPoint&))); - int maximum_column = 0; - widgets_.resize(e->row_count()); + keyframe_navigators_.resize(e->row_count()); for (int i=0;irow_count();i++) { EffectRow* row = e->row(i); @@ -92,55 +114,50 @@ EffectUI::EffectUI(Effect* e) : labels_.append(row_label); - layout_->addWidget(row_label, i, 0); + if (row->IsNodeOutput()) { - widgets_[i].resize(row->FieldCount()); - - int column = 1; - for (int j=0;jFieldCount();j++) { - EffectField* field = row->Field(j); - - QWidget* widget = field->CreateWidget(); - - widgets_[i][j] = widget; - - layout_->addWidget(widget, i, column, 1, field->GetColumnSpan()); - - column += field->GetColumnSpan(); - } - - // Find maximum column to place keyframe controls - maximum_column = qMax(row->FieldCount(), maximum_column); - } - - // Create keyframe controls - maximum_column++; - - keyframe_navigators_.resize(e->row_count()); - - for (int i=0;irow_count();i++) { - EffectRow* row = e->row(i); - - KeyframeNavigator* nav; - - if (row->IsKeyframable()) { - - nav = new KeyframeNavigator(); - - nav->enable_keyframes(row->IsKeyframing()); - - AttachKeyframeNavigationToRow(row, nav); - - layout_->addWidget(nav, i, maximum_column); + row_label->setAlignment(Qt::AlignRight); + layout_->addWidget(row_label, i, 2); } else { - nav = nullptr; + layout_->addWidget(row_label, i, 0); + + widgets_[i].resize(row->FieldCount()); + + QGridLayout* field_layout = new QGridLayout(); + for (int j=0;jFieldCount();j++) { + EffectField* field = row->Field(j); + + QWidget* widget = field->CreateWidget(); + + widgets_[i][j] = widget; + + field_layout->addWidget(widget, 0, j); + } + layout_->addLayout(field_layout, i, 1); + + KeyframeNavigator* nav; + + if (row->IsKeyframable()) { + + nav = new KeyframeNavigator(); + + nav->enable_keyframes(row->IsKeyframing()); + + AttachKeyframeNavigationToRow(row, nav); + + layout_->addWidget(nav, i, 2); + + } else { + + nav = nullptr; + + } + + keyframe_navigators_[i] = nav; } - - keyframe_navigators_[i] = nav; - } enabled_check->setChecked(e->IsEnabled()); @@ -148,10 +165,10 @@ EffectUI::EffectUI(Effect* e) : connect(enabled_check, SIGNAL(toggled(bool)), e, SLOT(FieldChanged())); } -void EffectUI::AddAdditionalEffect(Effect *e) +void EffectUI::AddAdditionalEffect(Node *e) { // Ensure this is the same kind of effect and will be fully compatible - Q_ASSERT(e->meta == effect_->meta); + Q_ASSERT(e->id() == effect_->id()); // Add 'multiple' modifier to header label (but only once) if (additional_effects_.isEmpty()) { @@ -181,7 +198,7 @@ void EffectUI::AddAdditionalEffect(Effect *e) } } -Effect *EffectUI::GetEffect() +Node *EffectUI::GetEffect() { return effect_; } @@ -193,16 +210,24 @@ int EffectUI::GetRowY(int row, QWidget* mapToWidget) { QLabel* row_label = labels_.at(row); + int mapped_coord; + if (mapToWidget == nullptr) { + mapped_coord = contents->pos().y(); + } else { + // FIXME Problematic now that EffectUIs are used outside of EffectControls + mapped_coord = mapToWidget->mapFrom(panel_effect_controls, contents->mapTo(panel_effect_controls, contents->pos())).y(); + mapped_coord -= title_bar->height(); + } + // Get center point of label (label->rect()->center()->y() - instead of y()+height/2 - produces an inaccurate result) return row_label->y() + row_label->height() / 2 - + mapToWidget->mapFrom(panel_effect_controls, contents->mapTo(panel_effect_controls, contents->pos())).y() - - title_bar->height(); + + mapped_coord; } void EffectUI::UpdateFromEffect() { - Effect* effect = GetEffect(); + Node* effect = GetEffect(); for (int j=0;jrow_count();j++) { @@ -214,7 +239,7 @@ void EffectUI::UpdateFromEffect() // Check if this UI object is attached to one effect or many if (additional_effects_.isEmpty()) { - field->UpdateWidgetValue(Widget(j, k), field->Now()); + field->UpdateWidgetValue(Widget(j, k), effect->Now()); } else { @@ -224,14 +249,14 @@ void EffectUI::UpdateFromEffect() EffectField* previous_field = i > 0 ? additional_effects_.at(i-1)->row(j)->Field(k) : field; EffectField* additional_field = additional_effects_.at(i)->row(j)->Field(k); - if (additional_field->GetValueAt(additional_field->Now()) != previous_field->GetValueAt(previous_field->Now())) { + if (additional_field->GetValueAt(additional_effects_.at(i)->Now()) != previous_field->GetValueAt(additional_effects_.at(i-1)->Now())) { same_value = false; break; } } if (same_value) { - field->UpdateWidgetValue(Widget(j, k), field->Now()); + field->UpdateWidgetValue(Widget(j, k), effect->Now()); } else { field->UpdateWidgetValue(Widget(j, k), qSNaN()); } @@ -276,7 +301,7 @@ void EffectUI::AttachKeyframeNavigationToRow(EffectRow *row, KeyframeNavigator * } void EffectUI::show_context_menu(const QPoint& pos) { - if (effect_->meta->type == EFFECT_TYPE_EFFECT) { + if (effect_->type() == EFFECT_TYPE_EFFECT) { Menu menu; Clip* c = effect_->parent_clip; diff --git a/ui/effectui.h b/ui/effectui.h index e24d0c6ff..45f0a95d9 100644 --- a/ui/effectui.h +++ b/ui/effectui.h @@ -1,8 +1,30 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef EFFECTUI_H #define EFFECTUI_H #include "collapsiblewidget.h" -#include "effects/effect.h" +#include "nodes/node.h" +#include "ui/nodeui.h" +#include "ui/keyframenavigator.h" /** * @brief The EffectUI class @@ -28,7 +50,7 @@ public: * * The Effect to make a UI of. It must be a valid object. */ - EffectUI(Effect* e); + EffectUI(Node* e); /** * @brief Attach additional effects to this UI @@ -41,7 +63,7 @@ public: * * The Effect to add to this UI object. */ - void AddAdditionalEffect(Effect* e); + void AddAdditionalEffect(Node* e); /** * @brief Get the primary Effect that this UI object was created for @@ -50,7 +72,7 @@ public: * * The Effect object passed to the constructor when creating this EffectUI. */ - Effect* GetEffect(); + Node* GetEffect(); /** * @brief Get the Y position of a given row @@ -74,7 +96,7 @@ public: * * The row's Y position. */ - int GetRowY(int row, QWidget *mapToWidget); + int GetRowY(int row, QWidget *mapToWidget = nullptr); /** * @brief Update widgets with the current Effect's values. @@ -111,6 +133,8 @@ public: */ bool IsAttachedToClip(Clip* c); +protected: + signals: /** * @brief Cut signal @@ -148,12 +172,12 @@ private: /** * @brief Internal reference to the Effect this object was constructed around. */ - Effect* effect_; + Node* effect_; /** * @brief Internal array of additional Effect objects attached to this UI. */ - QVector additional_effects_; + QVector additional_effects_; /** * @brief Layout for UI widgets diff --git a/ui/embeddedfilechooser.cpp b/ui/embeddedfilechooser.cpp index 4a5bfbec2..f19505703 100644 --- a/ui/embeddedfilechooser.cpp +++ b/ui/embeddedfilechooser.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/embeddedfilechooser.h b/ui/embeddedfilechooser.h index 8022022ee..f67596325 100644 --- a/ui/embeddedfilechooser.h +++ b/ui/embeddedfilechooser.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/focusfilter.cpp b/ui/focusfilter.cpp index b50ef0ae3..be1736a48 100644 --- a/ui/focusfilter.cpp +++ b/ui/focusfilter.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -93,14 +93,14 @@ void FocusFilter::go_to_end() { void FocusFilter::set_viewer_fullscreen() { if (get_focused_panel() == panel_footage_viewer) { - panel_footage_viewer->viewer_widget->set_fullscreen(); + panel_footage_viewer->viewer_widget()->set_fullscreen(); } else { - panel_sequence_viewer->viewer_widget->set_fullscreen(); + panel_sequence_viewer->viewer_widget()->set_fullscreen(); } } void FocusFilter::set_marker() { - if (olive::ActiveSequence != nullptr) { + if (Timeline::GetTopSequence() != nullptr) { QDockWidget* focused_panel = get_focused_panel(); if (focused_panel == panel_footage_viewer) { @@ -108,7 +108,7 @@ void FocusFilter::set_marker() { } else if (focused_panel == panel_sequence_viewer) { panel_sequence_viewer->set_marker(); } else { - panel_timeline->set_marker(); + panel_timeline.first()->set_marker(); } } } @@ -190,28 +190,34 @@ void FocusFilter::clear_inout() { } void FocusFilter::delete_function() { - if (panel_timeline->headers->hasFocus()) { - panel_timeline->headers->delete_markers(); + if (panel_timeline.first()->headers->hasFocus()) { + panel_timeline.first()->headers->delete_markers(); } else if (panel_footage_viewer->headers->hasFocus()) { panel_footage_viewer->headers->delete_markers(); } else if (panel_sequence_viewer->headers->hasFocus()) { panel_sequence_viewer->headers->delete_markers(); - } else if (panel_effect_controls->is_focused()) { + } else if (panel_effect_controls->focused()) { panel_effect_controls->DeleteSelectedEffects(); - } else if (panel_project->is_focused()) { - panel_project->delete_selected_media(); - } else if (panel_effect_controls->keyframe_focus()) { + } else if (panel_project.first()->focused()) { + panel_project.first()->delete_selected_media(); + } else if (panel_effect_controls->focused()) { panel_effect_controls->delete_selected_keyframes(); - } else if (panel_graph_editor->view_is_focused()) { + } else if (panel_graph_editor->focused()) { panel_graph_editor->delete_selected_keys(); } else { - panel_timeline->delete_selection(olive::ActiveSequence->selections, false); + Sequence* top_sequence = Timeline::GetTopSequence().get(); + if (top_sequence != nullptr) { + ComboAction* ca = new ComboAction(); + top_sequence->DeleteAreas(ca, top_sequence->Selections(), true); + olive::undo_stack.push(ca); + update_ui(false); + } } } void FocusFilter::duplicate() { - if (panel_project->is_focused()) { - panel_project->duplicate_selected(); + if (panel_project.first()->focused()) { + panel_project.first()->duplicate_selected(); } } @@ -220,7 +226,7 @@ void FocusFilter::select_all() { if (focused_panel == panel_graph_editor) { panel_graph_editor->select_all(); } else { - panel_timeline->select_all(); + panel_timeline.first()->select_all(); } } @@ -233,7 +239,7 @@ void FocusFilter::zoom_in() { } else if (focused_panel == panel_sequence_viewer) { panel_sequence_viewer->set_zoom(true); } else { - panel_timeline->zoom_in(); + panel_timeline.first()->zoom_in(); } } @@ -246,28 +252,28 @@ void FocusFilter::zoom_out() { } else if (focused_panel == panel_sequence_viewer) { panel_sequence_viewer->set_zoom(false); } else { - panel_timeline->zoom_out(); + panel_timeline.first()->zoom_out(); } } void FocusFilter::cut() { - if (olive::ActiveSequence != nullptr) { + if (Timeline::GetTopSequence() != nullptr) { QDockWidget* focused_panel = get_focused_panel(); if (panel_effect_controls == focused_panel) { panel_effect_controls->copy(true); } else { - panel_timeline->copy(true); + panel_timeline.first()->copy(true); } } } void FocusFilter::copy() { - if (olive::ActiveSequence != nullptr) { + if (Timeline::GetTopSequence() != nullptr) { QDockWidget* focused_panel = get_focused_panel(); if (panel_effect_controls == focused_panel) { panel_effect_controls->copy(false); } else { - panel_timeline->copy(false); + panel_timeline.first()->copy(false); } } } diff --git a/ui/focusfilter.h b/ui/focusfilter.h index 645553860..1fae39ede 100644 --- a/ui/focusfilter.h +++ b/ui/focusfilter.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/fontcombobox.cpp b/ui/fontcombobox.cpp deleted file mode 100644 index c8e10d920..000000000 --- a/ui/fontcombobox.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "fontcombobox.h" - -#include - -FontCombobox::FontCombobox(QWidget* parent) : QComboBox(parent) { - addItems(QFontDatabase().families()); - - value = currentText(); - - connect(this, SIGNAL(currentTextChanged(QString)), this, SLOT(updateInternals())); -} - -void FontCombobox::updateInternals() { - value = currentText(); -} diff --git a/ui/fontcombobox.h b/ui/fontcombobox.h deleted file mode 100644 index 54a7b0823..000000000 --- a/ui/fontcombobox.h +++ /dev/null @@ -1,36 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef FONTCOMBOBOX_H -#define FONTCOMBOBOX_H - -#include - -class FontCombobox : public QComboBox { - Q_OBJECT -public: - FontCombobox(QWidget* parent = nullptr); -private slots: - void updateInternals(); -private: - QString value; -}; - -#endif // FONTCOMBOBOX_H diff --git a/ui/graphview.cpp b/ui/graphview.cpp index 3e13075cd..637bf3e3e 100644 --- a/ui/graphview.cpp +++ b/ui/graphview.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -33,7 +33,7 @@ #include "ui/keyframedrawing.h" #include "undo/undo.h" #include "undo/undostack.h" -#include "effects/effect.h" +#include "nodes/node.h" #include "timeline/clip.h" #include "ui/rectangleselect.h" #include "ui/menu.h" @@ -347,7 +347,7 @@ void GraphView::paintEvent(QPaintEvent *) { p.drawLine(playhead_x, 0, playhead_x, height()); if (rect_select) { - draw_selection_rectangle(p, QRect(rect_select_x, rect_select_y, rect_select_w, rect_select_h)); + olive::ui::DrawSelectionRectangle(p, QRect(rect_select_x, rect_select_y, rect_select_w, rect_select_h)); p.setBrush(Qt::NoBrush); } } @@ -470,7 +470,7 @@ void GraphView::mousePressEvent(QMouseEvent *event) { void GraphView::mouseMoveEvent(QMouseEvent *event) { if (!mousedown || !click_add) unsetCursor(); if (mousedown) { - if (event->buttons() & Qt::MiddleButton || panel_timeline->tool == TIMELINE_TOOL_HAND) { + if (event->buttons() & Qt::MiddleButton || olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_HAND) { set_scroll_x(x_scroll + start_x - event->pos().x()); set_scroll_y(y_scroll + event->pos().y() - start_y); start_x = event->pos().x(); @@ -701,7 +701,7 @@ void GraphView::mouseMoveEvent(QMouseEvent *event) { void GraphView::mouseReleaseEvent(QMouseEvent *) { if (click_add_proc) { - olive::UndoStack.push(new KeyframeAdd(click_add_field, click_add_key)); + olive::undo_stack.push(new KeyframeAdd(click_add_field, click_add_key)); } else if (moved_keys && selected_keys.size() > 0) { ComboAction* ca = new ComboAction(); switch (current_handle) { @@ -723,7 +723,7 @@ void GraphView::mouseReleaseEvent(QMouseEvent *) { } break; } - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); } moved_keys = false; mousedown = false; @@ -757,7 +757,7 @@ void GraphView::wheelEvent(QWheelEvent *event) { double new_x_zoom = x_zoom; double new_y_zoom = y_zoom; - if (ctrl != olive::CurrentConfig.scroll_zooms) { + if (ctrl != olive::config.scroll_zooms) { zooming = true; } @@ -849,7 +849,7 @@ void GraphView::set_selected_keyframe_type(int type) { EffectKeyframe& key = row->Field(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)]; ca->append(new SetInt(&key.type, type)); } - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); update_ui(false); } } diff --git a/ui/graphview.h b/ui/graphview.h index 285048882..da1bf77ae 100644 --- a/ui/graphview.h +++ b/ui/graphview.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/icons.cpp b/ui/icons.cpp index 60997e8c0..4e2342cfd 100644 --- a/ui/icons.cpp +++ b/ui/icons.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "icons.h" #include diff --git a/ui/icons.h b/ui/icons.h index 3153434dd..e78180dbd 100644 --- a/ui/icons.h +++ b/ui/icons.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef ICONS_H #define ICONS_H diff --git a/ui/keyframedrawing.cpp b/ui/keyframedrawing.cpp index 568ab3533..ce4a49f24 100644 --- a/ui/keyframedrawing.cpp +++ b/ui/keyframedrawing.cpp @@ -1,26 +1,26 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #include "keyframedrawing.h" -#include "effects/effect.h" +#include "nodes/node.h" #include "timeline/clip.h" #define KEYFRAME_POINT_COUNT 4 diff --git a/ui/keyframedrawing.h b/ui/keyframedrawing.h index 2c15e893f..f94103532 100644 --- a/ui/keyframedrawing.h +++ b/ui/keyframedrawing.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/keyframenavigator.cpp b/ui/keyframenavigator.cpp index ec1c1b034..6a50d4aeb 100644 --- a/ui/keyframenavigator.cpp +++ b/ui/keyframenavigator.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/keyframenavigator.h b/ui/keyframenavigator.h index 3811b3906..85d718519 100644 --- a/ui/keyframenavigator.h +++ b/ui/keyframenavigator.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/keyframeview.cpp b/ui/keyframeview.cpp index 7e3b6c679..44c122fc1 100644 --- a/ui/keyframeview.cpp +++ b/ui/keyframeview.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -23,7 +23,7 @@ #include #include -#include "effects/effect.h" +#include "nodes/node.h" #include "ui/collapsiblewidget.h" #include "panels/panels.h" #include "timeline/clip.h" @@ -42,6 +42,7 @@ #include "effects/keyframe.h" #include "ui/graphview.h" #include "ui/menu.h" +#include "global/math.h" KeyframeView::KeyframeView(QWidget *parent) : QWidget(parent), @@ -95,7 +96,7 @@ void KeyframeView::menu_set_key_type(QAction* a) { EffectField* f = selected_fields.at(i); ca->append(new SetInt(&f->keyframes[selected_keyframes.at(i)].type, a->data().toInt())); } - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); update_ui(false); } } @@ -119,7 +120,7 @@ void KeyframeView::paintEvent(QPaintEvent*) { for (int j=0;jGetEffect(); + Node* e = container->GetEffect(); if (container->IsExpanded()) { for (int j=0;jrow_count();j++) { @@ -172,8 +173,9 @@ void KeyframeView::paintEvent(QPaintEvent*) { panel_effect_controls->horizontalScrollBar->setMaximum(qMax(max_width - width(), 0)); header->set_visible_in(visible_in); - int playhead_x = getScreenPointFromFrame(panel_effect_controls->zoom, olive::ActiveSequence->playhead-visible_in) - x_scroll; - if (dragging && panel_timeline->snapped) { + int playhead_x = getScreenPointFromFrame(panel_effect_controls->zoom, + open_effects_.first()->GetEffect()->parent_clip->track()->sequence()->playhead-visible_in) - x_scroll; + if (dragging && olive::timeline::snapped) { p.setPen(Qt::white); } else { p.setPen(Qt::red); @@ -182,7 +184,7 @@ void KeyframeView::paintEvent(QPaintEvent*) { } if (select_rect) { - draw_selection_rectangle(p, QRect(rect_select_x, rect_select_y, rect_select_w, rect_select_h)); + olive::ui::DrawSelectionRectangle(p, QRect(rect_select_x, rect_select_y, rect_select_w, rect_select_h)); } /*if (mouseover && mouseover_row < rowY.size()) { @@ -234,7 +236,7 @@ void KeyframeView::mousePressEvent(QMouseEvent *event) { rect_select_w = 0; rect_select_h = 0; - if (panel_timeline->tool == TIMELINE_TOOL_HAND || event->buttons() & Qt::MiddleButton) { + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_HAND || event->buttons() & Qt::MiddleButton) { scroll_drag = true; return; } @@ -323,7 +325,7 @@ void KeyframeView::mousePressEvent(QMouseEvent *event) { } void KeyframeView::mouseMoveEvent(QMouseEvent* event) { - if (panel_timeline->tool == TIMELINE_TOOL_HAND) { + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_HAND) { setCursor(Qt::OpenHandCursor); } else { unsetCursor(); @@ -376,14 +378,14 @@ void KeyframeView::mouseMoveEvent(QMouseEvent* event) { long frame_diff = current_frame - drag_frame_start; // snapping to playhead - panel_timeline->snapped = false; - if (panel_timeline->snapping) { + olive::timeline::snapped = false; + if (olive::timeline::snapping) { for (int i=0;iGetParentRow()->GetParentEffect()->parent_clip; long key_time = old_key_vals.at(i) + frame_diff - c->clip_in() + c->timeline_in(); long key_eval = key_time; - if (panel_timeline->snap_to_point(olive::ActiveSequence->playhead, &key_eval)) { + if (olive::timeline::SnapToPoint(c->track()->sequence()->playhead, &key_eval, header->get_zoom())) { frame_diff += (key_eval - key_time); break; } @@ -398,10 +400,10 @@ void KeyframeView::mouseMoveEvent(QMouseEvent* event) { while (!keyframeIsSelected(field, j) && field->keyframes.at(j).time == eval_key + frame_diff) { if (last_frame_diff > frame_diff) { frame_diff++; - panel_timeline->snapped = false; + olive::timeline::snapped = false; } else { frame_diff--; - panel_timeline->snapped = false; + olive::timeline::snapped = false; } } } @@ -432,13 +434,13 @@ void KeyframeView::mouseReleaseEvent(QMouseEvent*) { selected_fields.at(i)->keyframes.at(selected_keyframes.at(i)).time )); } - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); } select_rect = false; dragging = false; mousedown = false; scroll_drag = false; - panel_timeline->snapped = false; + olive::timeline::snapped = false; update_ui(false); } diff --git a/ui/keyframeview.h b/ui/keyframeview.h index 645861ec8..918b40ee3 100644 --- a/ui/keyframeview.h +++ b/ui/keyframeview.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -27,7 +27,7 @@ #include "ui/effectui.h" class Clip; -class Effect; +class Node; class EffectRow; class EffectField; class TimelineHeader; diff --git a/ui/labelslider.cpp b/ui/labelslider.cpp index 93fcbf698..9aa622a28 100644 --- a/ui/labelslider.cpp +++ b/ui/labelslider.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -26,7 +26,7 @@ #include #include "undo/undo.h" -#include "panels/viewer.h" +#include "global/timing.h" #include "global/config.h" #include "global/math.h" #include "global/debug.h" @@ -94,7 +94,7 @@ QString LabelSlider::ValueToString() { } else { switch (display_type) { case FrameNumber: - return frame_to_timecode(long(v), olive::CurrentConfig.timecode_view, frame_rate); + return frame_to_timecode(long(v), olive::config.timecode_view, frame_rate); case Percent: return QString::number((v*100), 'f', decimal_places).append("%"); case Decibel: @@ -311,7 +311,7 @@ void LabelSlider::ShowDialog() if (s.isEmpty()) return; // parse string timecode to a frame number - d = timecode_to_frame(s, olive::CurrentConfig.timecode_view, frame_rate); + d = timecode_to_frame(s, olive::config.timecode_view, frame_rate); } else { diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index d88e3ab1a..cbe7acb3a 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -57,6 +57,7 @@ #include "rendering/audio.h" #include "rendering/renderfunctions.h" #include "undo/undostack.h" +#include "effects/effectloaders.h" MainWindow* olive::MainWindow; @@ -145,33 +146,36 @@ void MainWindow::setup_layout(bool reset) { removeDockWidget(olive::panels.at(i)); } - addDockWidget(Qt::TopDockWidgetArea, panel_project); + addDockWidget(Qt::TopDockWidgetArea, panel_project.first()); addDockWidget(Qt::TopDockWidgetArea, panel_graph_editor); + addDockWidget(Qt::TopDockWidgetArea, panel_node_editor); addDockWidget(Qt::TopDockWidgetArea, panel_footage_viewer); tabifyDockWidget(panel_footage_viewer, panel_effect_controls); panel_footage_viewer->raise(); addDockWidget(Qt::TopDockWidgetArea, panel_sequence_viewer); - addDockWidget(Qt::BottomDockWidgetArea, panel_timeline); + addDockWidget(Qt::BottomDockWidgetArea, panel_timeline.first()); - panel_project->show(); + panel_project.first()->show(); panel_effect_controls->show(); panel_footage_viewer->show(); panel_sequence_viewer->show(); - panel_timeline->show(); + panel_timeline.first()->show(); panel_graph_editor->hide(); + panel_node_editor->hide(); - panel_project->setFloating(false); + panel_project.first()->setFloating(false); panel_effect_controls->setFloating(false); panel_footage_viewer->setFloating(false); panel_sequence_viewer->setFloating(false); - panel_timeline->setFloating(false); + panel_timeline.first()->setFloating(false); panel_graph_editor->setFloating(true); + panel_node_editor->setFloating(true); - resizeDocks({panel_project, panel_footage_viewer, panel_sequence_viewer}, + resizeDocks({panel_project.first(), panel_footage_viewer, panel_sequence_viewer}, {width()/3, width()/3, width()/3}, Qt::Horizontal); - resizeDocks({panel_project, panel_timeline}, + resizeDocks({panel_project.first(), panel_timeline.first()}, {height()/2, height()/2}, Qt::Vertical); } @@ -183,6 +187,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), first_show(true) { + EffectInit::StartLoading(); + olive::cursor::Initialize(); open_debug_file(); @@ -239,19 +245,7 @@ MainWindow::MainWindow(QWidget *parent) : } // search for open recents list - QFile f(olive::Global->get_recent_project_list_file()); - if (f.exists() && f.open(QFile::ReadOnly | QFile::Text)) { - QTextStream text_stream(&f); - while (true) { - QString line = text_stream.readLine(); - if (line.isNull()) { - break; - } else { - recent_projects.append(line); - } - } - f.close(); - } + olive::Global->load_recent_projects(); } } QString config_path = get_config_path(); @@ -260,7 +254,7 @@ MainWindow::MainWindow(QWidget *parent) : config_dir.mkpath("."); QString config_fn = config_dir.filePath("config.xml"); if (QFileInfo::exists(config_fn)) { - olive::CurrentConfig.load(config_fn); + olive::config.load(config_fn); } } @@ -268,6 +262,18 @@ MainWindow::MainWindow(QWidget *parent) : olive::icon::Initialize(); + // Load OpenColorIO configuration if set + if (olive::config.enable_color_management && !olive::config.ocio_config_path.isEmpty()) { + try { + OCIO::SetCurrentConfig(OCIO::Config::CreateFromFile(olive::config.ocio_config_path.toUtf8())); + } catch (OCIO::Exception& e) { + QMessageBox::critical(this, + tr("OpenColorIO Config Error"), + tr("Failed to set OpenColorIO configuration: %1").arg(e.what()), + QMessageBox::Ok); + } + } + alloc_panels(this); // populate menu bars @@ -280,7 +286,7 @@ MainWindow::MainWindow(QWidget *parent) : olive::Global->check_for_autorecovery_file(); // lock panels if the config says so - set_panels_locked(olive::CurrentConfig.locked_panels); + set_panels_locked(olive::config.locked_panels); // set up output audio device init_audio(); @@ -397,8 +403,8 @@ void MainWindow::Restyle() qApp->setStyle(QStyleFactory::create("Fusion")); // Set up whether to load custom CSS or default CSS+palette - if (!olive::CurrentConfig.css_path.isEmpty() - && load_css_from_file(olive::CurrentConfig.css_path)) { + if (!olive::config.css_path.isEmpty() + && load_css_from_file(olive::config.css_path)) { qApp->setPalette(qApp->style()->standardPalette()); @@ -407,7 +413,7 @@ void MainWindow::Restyle() // set default palette QPalette palette; - if (olive::CurrentConfig.style == olive::styling::kOliveDefaultLight) { + if (olive::config.style == olive::styling::kOliveDefaultLight) { palette.setColor(QPalette::Window, QColor(208, 208, 208)); palette.setColor(QPalette::WindowText, Qt::black); @@ -463,7 +469,7 @@ void MainWindow::Restyle() // Windows menus have the option of being native, so we may not need this CSS #ifdef Q_OS_WIN - if (!olive::CurrentConfig.use_native_menu_styling) { + if (!olive::config.use_native_menu_styling) { #endif stylesheet.append("QMenu::separator { background: #404040; }"); #ifdef Q_OS_WIN @@ -480,14 +486,14 @@ void MainWindow::Restyle() } void MainWindow::editMenu_About_To_Be_Shown() { - undo_action->setEnabled(olive::UndoStack.canUndo()); - redo_action->setEnabled(olive::UndoStack.canRedo()); + undo_action->setEnabled(olive::undo_stack.canUndo()); + redo_action->setEnabled(olive::undo_stack.canRedo()); } void MainWindow::setup_menus() { QMenuBar* menuBar = new QMenuBar(this); - if (olive::CurrentConfig.use_native_menu_styling) { + if (olive::config.use_native_menu_styling) { OliveGlobal::SetNativeStyling(menuBar); } @@ -506,7 +512,7 @@ void MainWindow::setup_menus() { open_recent = MenuHelper::create_submenu(file_menu); - clear_open_recent_action = MenuHelper::create_menu_action(nullptr, "clearopenrecent", panel_project, SLOT(clear_recent_projects())); + clear_open_recent_action = MenuHelper::create_menu_action(nullptr, "clearopenrecent", olive::Global.get(), SLOT(clear_recent_projects())); save_project = MenuHelper::create_menu_action(file_menu, "saveproj", olive::Global.get(), SLOT(save_project()), QKeySequence("Ctrl+S")); @@ -514,7 +520,7 @@ void MainWindow::setup_menus() { file_menu->addSeparator(); - import_action = MenuHelper::create_menu_action(file_menu, "import", panel_project, SLOT(import_dialog()), QKeySequence("Ctrl+I")); + import_action = MenuHelper::create_menu_action(file_menu, "import", olive::Global.get(), SLOT(open_import_dialog()), QKeySequence("Ctrl+I")); file_menu->addSeparator(); @@ -538,7 +544,7 @@ void MainWindow::setup_menus() { edit_menu->addSeparator(); select_all_action = MenuHelper::create_menu_action(edit_menu, "selectall", &olive::FocusFilter, SLOT(select_all()), QKeySequence("Ctrl+A")); - deselect_all_action = MenuHelper::create_menu_action(edit_menu, "deselectall", panel_timeline, SLOT(deselect()), QKeySequence("Ctrl+Shift+A")); + deselect_all_action = MenuHelper::create_menu_action(edit_menu, "deselectall", panel_timeline.first(), SLOT(deselect()), QKeySequence("Ctrl+Shift+A")); edit_menu->addSeparator(); @@ -546,16 +552,16 @@ void MainWindow::setup_menus() { edit_menu->addSeparator(); - ripple_to_in_point_ = MenuHelper::create_menu_action(edit_menu, "rippletoin", panel_timeline, SLOT(ripple_to_in_point()), QKeySequence("Q")); - ripple_to_out_point_ = MenuHelper::create_menu_action(edit_menu, "rippletoout", panel_timeline, SLOT(ripple_to_out_point()), QKeySequence("W")); - edit_to_in_point_ = MenuHelper::create_menu_action(edit_menu, "edittoin", panel_timeline, SLOT(edit_to_in_point()), QKeySequence("Ctrl+Alt+Q")); - edit_to_out_point_ = MenuHelper::create_menu_action(edit_menu, "edittoout", panel_timeline, SLOT(edit_to_out_point()), QKeySequence("Ctrl+Alt+W")); + ripple_to_in_point_ = MenuHelper::create_menu_action(edit_menu, "rippletoin", panel_timeline.first(), SLOT(ripple_to_in_point()), QKeySequence("Q")); + ripple_to_out_point_ = MenuHelper::create_menu_action(edit_menu, "rippletoout", panel_timeline.first(), SLOT(ripple_to_out_point()), QKeySequence("W")); + edit_to_in_point_ = MenuHelper::create_menu_action(edit_menu, "edittoin", panel_timeline.first(), SLOT(edit_to_in_point()), QKeySequence("Ctrl+Alt+Q")); + edit_to_out_point_ = MenuHelper::create_menu_action(edit_menu, "edittoout", panel_timeline.first(), SLOT(edit_to_out_point()), QKeySequence("Ctrl+Alt+W")); edit_menu->addSeparator(); olive::MenuHelper.make_inout_menu(edit_menu); - delete_inout_point_ = MenuHelper::create_menu_action(edit_menu, "deleteinout", panel_timeline, SLOT(delete_inout()), QKeySequence(";")); - ripple_delete_inout_point_ = MenuHelper::create_menu_action(edit_menu, "rippledeleteinout", panel_timeline, SLOT(ripple_delete_inout()), QKeySequence("'")); + delete_inout_point_ = MenuHelper::create_menu_action(edit_menu, "deleteinout", panel_timeline.first(), SLOT(delete_inout()), QKeySequence(";")); + ripple_delete_inout_point_ = MenuHelper::create_menu_action(edit_menu, "rippledeleteinout", panel_timeline.first(), SLOT(ripple_delete_inout()), QKeySequence("'")); edit_menu->addSeparator(); @@ -567,21 +573,17 @@ void MainWindow::setup_menus() { zoom_in_ = MenuHelper::create_menu_action(view_menu, "zoomin", &olive::FocusFilter, SLOT(zoom_in()), QKeySequence("=")); zoom_out_ = MenuHelper::create_menu_action(view_menu, "zoomout", &olive::FocusFilter, SLOT(zoom_out()), QKeySequence("-")); - increase_track_height_ = MenuHelper::create_menu_action(view_menu, "vzoomin", panel_timeline, SLOT(IncreaseTrackHeight()), QKeySequence("Ctrl+=")); - decrease_track_height_ = MenuHelper::create_menu_action(view_menu, "vzoomout", panel_timeline, SLOT(DecreaseTrackHeight()), QKeySequence("Ctrl+-")); + increase_track_height_ = MenuHelper::create_menu_action(view_menu, "vzoomin", panel_timeline.first(), SLOT(IncreaseTrackHeight()), QKeySequence("Ctrl+=")); + decrease_track_height_ = MenuHelper::create_menu_action(view_menu, "vzoomout", panel_timeline.first(), SLOT(DecreaseTrackHeight()), QKeySequence("Ctrl+-")); - show_all = MenuHelper::create_menu_action(view_menu, "showall", panel_timeline, SLOT(toggle_show_all()), QKeySequence("\\")); + show_all = MenuHelper::create_menu_action(view_menu, "showall", panel_timeline.first(), SLOT(toggle_show_all()), QKeySequence("\\")); show_all->setCheckable(true); view_menu->addSeparator(); - track_lines = MenuHelper::create_menu_action(view_menu, "tracklines", &olive::MenuHelper, SLOT(toggle_bool_action())); - track_lines->setCheckable(true); - track_lines->setData(reinterpret_cast(&olive::CurrentConfig.show_track_lines)); - rectified_waveforms = MenuHelper::create_menu_action(view_menu, "rectifiedwaveforms", &olive::MenuHelper, SLOT(toggle_bool_action())); rectified_waveforms->setCheckable(true); - rectified_waveforms->setData(reinterpret_cast(&olive::CurrentConfig.rectified_waveforms)); + rectified_waveforms->setData(reinterpret_cast(&olive::config.rectified_waveforms)); view_menu->addSeparator(); @@ -658,8 +660,8 @@ void MainWindow::setup_menus() { playback_menu->addSeparator(); - go_to_prev_cut_ = MenuHelper::create_menu_action(playback_menu, "prevcut", panel_timeline, SLOT(previous_cut()), QKeySequence("Up")); - go_to_next_cut_ = MenuHelper::create_menu_action(playback_menu, "nextcut", panel_timeline, SLOT(next_cut()), QKeySequence("Down")); + go_to_prev_cut_ = MenuHelper::create_menu_action(playback_menu, "prevcut", panel_sequence_viewer, SLOT(prev_cut()), QKeySequence("Up")); + go_to_next_cut_ = MenuHelper::create_menu_action(playback_menu, "nextcut", panel_sequence_viewer, SLOT(next_cut()), QKeySequence("Down")); playback_menu->addSeparator(); @@ -676,7 +678,7 @@ void MainWindow::setup_menus() { loop_action_ = MenuHelper::create_menu_action(playback_menu, "loop", &olive::MenuHelper, SLOT(toggle_bool_action())); loop_action_->setCheckable(true); - loop_action_->setData(reinterpret_cast(&olive::CurrentConfig.loop)); + loop_action_->setData(reinterpret_cast(&olive::config.loop)); // INITIALIZE WINDOW MENU @@ -684,7 +686,7 @@ void MainWindow::setup_menus() { window_project_action = MenuHelper::create_menu_action(window_menu, "panelproject", this, SLOT(toggle_panel_visibility())); window_project_action->setCheckable(true); - window_project_action->setData(reinterpret_cast(panel_project)); + window_project_action->setData(reinterpret_cast(panel_project.first())); window_effectcontrols_action = MenuHelper::create_menu_action(window_menu, "paneleffectcontrols", this, SLOT(toggle_panel_visibility())); window_effectcontrols_action->setCheckable(true); @@ -692,12 +694,16 @@ void MainWindow::setup_menus() { window_timeline_action = MenuHelper::create_menu_action(window_menu, "paneltimeline", this, SLOT(toggle_panel_visibility())); window_timeline_action->setCheckable(true); - window_timeline_action->setData(reinterpret_cast(panel_timeline)); + window_timeline_action->setData(reinterpret_cast(panel_timeline.first())); window_graph_editor_action = MenuHelper::create_menu_action(window_menu, "panelgrapheditor", this, SLOT(toggle_panel_visibility())); window_graph_editor_action->setCheckable(true); window_graph_editor_action->setData(reinterpret_cast(panel_graph_editor)); + window_node_editor_action = MenuHelper::create_menu_action(window_menu, "panelnodeeditor", this, SLOT(toggle_panel_visibility())); + window_node_editor_action->setCheckable(true); + window_node_editor_action->setData(reinterpret_cast(panel_node_editor)); + window_footageviewer_action = MenuHelper::create_menu_action(window_menu, "panelfootageviewer", this, SLOT(toggle_panel_visibility())); window_footageviewer_action->setCheckable(true); window_footageviewer_action->setData(reinterpret_cast(panel_footage_viewer)); @@ -726,49 +732,49 @@ void MainWindow::setup_menus() { pointer_tool_action = MenuHelper::create_menu_action(tools_menu, "pointertool", &olive::MenuHelper, SLOT(menu_click_button()), QKeySequence("V")); pointer_tool_action->setCheckable(true); - pointer_tool_action->setData(reinterpret_cast(panel_timeline->toolArrowButton)); + pointer_tool_action->setData(reinterpret_cast(panel_timeline.first()->toolArrowButton)); tools_group->addAction(pointer_tool_action); edit_tool_action = MenuHelper::create_menu_action(tools_menu, "edittool", &olive::MenuHelper, SLOT(menu_click_button()), QKeySequence("X")); edit_tool_action->setCheckable(true); - edit_tool_action->setData(reinterpret_cast(panel_timeline->toolEditButton)); + edit_tool_action->setData(reinterpret_cast(panel_timeline.first()->toolEditButton)); tools_group->addAction(edit_tool_action); ripple_tool_action = MenuHelper::create_menu_action(tools_menu, "rippletool", &olive::MenuHelper, SLOT(menu_click_button()), QKeySequence("B")); ripple_tool_action->setCheckable(true); - ripple_tool_action->setData(reinterpret_cast(panel_timeline->toolRippleButton)); + ripple_tool_action->setData(reinterpret_cast(panel_timeline.first()->toolRippleButton)); tools_group->addAction(ripple_tool_action); razor_tool_action = MenuHelper::create_menu_action(tools_menu, "razortool", &olive::MenuHelper, SLOT(menu_click_button()), QKeySequence("C")); razor_tool_action->setCheckable(true); - razor_tool_action->setData(reinterpret_cast(panel_timeline->toolRazorButton)); + razor_tool_action->setData(reinterpret_cast(panel_timeline.first()->toolRazorButton)); tools_group->addAction(razor_tool_action); slip_tool_action = MenuHelper::create_menu_action(tools_menu, "sliptool", &olive::MenuHelper, SLOT(menu_click_button()), QKeySequence("Y")); slip_tool_action->setCheckable(true); - slip_tool_action->setData(reinterpret_cast(panel_timeline->toolSlipButton)); + slip_tool_action->setData(reinterpret_cast(panel_timeline.first()->toolSlipButton)); tools_group->addAction(slip_tool_action); slide_tool_action = MenuHelper::create_menu_action(tools_menu, "slidetool", &olive::MenuHelper, SLOT(menu_click_button()), QKeySequence("U")); slide_tool_action->setCheckable(true); - slide_tool_action->setData(reinterpret_cast(panel_timeline->toolSlideButton)); + slide_tool_action->setData(reinterpret_cast(panel_timeline.first()->toolSlideButton)); tools_group->addAction(slide_tool_action); hand_tool_action = MenuHelper::create_menu_action(tools_menu, "handtool", &olive::MenuHelper, SLOT(menu_click_button()), QKeySequence("H")); hand_tool_action->setCheckable(true); - hand_tool_action->setData(reinterpret_cast(panel_timeline->toolHandButton)); + hand_tool_action->setData(reinterpret_cast(panel_timeline.first()->toolHandButton)); tools_group->addAction(hand_tool_action); transition_tool_action = MenuHelper::create_menu_action(tools_menu, "transitiontool", &olive::MenuHelper, SLOT(menu_click_button()), QKeySequence("T")); transition_tool_action->setCheckable(true); - transition_tool_action->setData(reinterpret_cast(panel_timeline->toolTransitionButton)); + transition_tool_action->setData(reinterpret_cast(panel_timeline.first()->toolTransitionButton)); tools_group->addAction(transition_tool_action); tools_menu->addSeparator(); snap_toggle = MenuHelper::create_menu_action(tools_menu, "snapping", &olive::MenuHelper, SLOT(menu_click_button()), QKeySequence("S")); snap_toggle->setCheckable(true); - snap_toggle->setData(reinterpret_cast(panel_timeline->snappingButton)); + snap_toggle->setData(reinterpret_cast(panel_timeline.first()->snappingButton)); tools_menu->addSeparator(); @@ -850,7 +856,6 @@ void MainWindow::Retranslate() increase_track_height_->setText(tr("Increase Track Height")); decrease_track_height_->setText(tr("Decrease Track Height")); show_all->setText(tr("Toggle Show All")); - track_lines->setText(tr("Track Lines")); rectified_waveforms->setText(tr("Rectified Waveforms")); frames_action->setText(tr("Frames")); drop_frame_action->setText(tr("Drop Frame")); @@ -892,6 +897,7 @@ void MainWindow::Retranslate() window_effectcontrols_action->setText(tr("Effect Controls")); window_timeline_action->setText(tr("Timeline")); window_graph_editor_action->setText(tr("Graph Editor")); + window_node_editor_action->setText(tr("Node Editor")); window_footageviewer_action->setText(tr("Media Viewer")); window_sequenceviewer_action->setText(tr("Sequence Viewer")); @@ -955,18 +961,19 @@ void MainWindow::closeEvent(QCloseEvent *e) { panel_graph_editor->set_row(nullptr); panel_effect_controls->Clear(true); - olive::Global->set_sequence(nullptr); + panel_footage_viewer->viewer_widget()->close_window(); + panel_sequence_viewer->viewer_widget()->close_window(); - panel_footage_viewer->viewer_widget->close_window(); - panel_sequence_viewer->viewer_widget->close_window(); - - panel_footage_viewer->set_main_sequence(); + olive::undo_stack.clear(); QString data_dir = get_data_path(); QString config_path = get_config_path(); + + const QString& autorecovery_filename = olive::Global->get_autorecovery_filename(); if (!data_dir.isEmpty() && !autorecovery_filename.isEmpty()) { if (QFile::exists(autorecovery_filename)) { - QFile::rename(autorecovery_filename, autorecovery_filename + "." + QDateTime::currentDateTimeUtc().toString("yyyyMMddHHmmss")); + QFile::rename(autorecovery_filename, + autorecovery_filename + "." + QDateTime::currentDateTimeUtc().toString("yyyyMMddHHmmss")); } } if (!config_path.isEmpty()) { @@ -975,7 +982,7 @@ void MainWindow::closeEvent(QCloseEvent *e) { QString config_fn = config_dir.filePath("config.xml"); // save settings - olive::CurrentConfig.save(config_fn); + olive::config.save(config_fn); // save panel layout QFile panel_config(get_config_dir().filePath("layout")); @@ -1118,32 +1125,30 @@ void MainWindow::playbackMenu_About_To_Be_Shown() { } void MainWindow::viewMenu_About_To_Be_Shown() { - olive::MenuHelper.set_bool_action_checked(track_lines); - olive::MenuHelper.set_bool_action_checked(rectified_waveforms); - olive::MenuHelper.set_int_action_checked(frames_action, olive::CurrentConfig.timecode_view); - olive::MenuHelper.set_int_action_checked(drop_frame_action, olive::CurrentConfig.timecode_view); - olive::MenuHelper.set_int_action_checked(nondrop_frame_action, olive::CurrentConfig.timecode_view); - olive::MenuHelper.set_int_action_checked(milliseconds_action, olive::CurrentConfig.timecode_view); + olive::MenuHelper.set_int_action_checked(frames_action, olive::config.timecode_view); + olive::MenuHelper.set_int_action_checked(drop_frame_action, olive::config.timecode_view); + olive::MenuHelper.set_int_action_checked(nondrop_frame_action, olive::config.timecode_view); + olive::MenuHelper.set_int_action_checked(milliseconds_action, olive::config.timecode_view); - title_safe_off->setChecked(!olive::CurrentConfig.show_title_safe_area); - title_safe_default->setChecked(olive::CurrentConfig.show_title_safe_area - && !olive::CurrentConfig.use_custom_title_safe_ratio); - title_safe_43->setChecked(olive::CurrentConfig.show_title_safe_area - && olive::CurrentConfig.use_custom_title_safe_ratio - && qFuzzyCompare(olive::CurrentConfig.custom_title_safe_ratio, title_safe_43->data().toDouble())); - title_safe_169->setChecked(olive::CurrentConfig.show_title_safe_area - && olive::CurrentConfig.use_custom_title_safe_ratio - && qFuzzyCompare(olive::CurrentConfig.custom_title_safe_ratio, title_safe_169->data().toDouble())); - title_safe_custom->setChecked(olive::CurrentConfig.show_title_safe_area - && olive::CurrentConfig.use_custom_title_safe_ratio + title_safe_off->setChecked(!olive::config.show_title_safe_area); + title_safe_default->setChecked(olive::config.show_title_safe_area + && !olive::config.use_custom_title_safe_ratio); + title_safe_43->setChecked(olive::config.show_title_safe_area + && olive::config.use_custom_title_safe_ratio + && qFuzzyCompare(olive::config.custom_title_safe_ratio, title_safe_43->data().toDouble())); + title_safe_169->setChecked(olive::config.show_title_safe_area + && olive::config.use_custom_title_safe_ratio + && qFuzzyCompare(olive::config.custom_title_safe_ratio, title_safe_169->data().toDouble())); + title_safe_custom->setChecked(olive::config.show_title_safe_area + && olive::config.use_custom_title_safe_ratio && !title_safe_43->isChecked() && !title_safe_169->isChecked()); full_screen->setChecked(windowState() == Qt::WindowFullScreen); - show_all->setChecked(panel_timeline->showing_all); + show_all->setChecked(panel_timeline.first()->showing_all); } void MainWindow::toolMenu_About_To_Be_Shown() { @@ -1157,9 +1162,9 @@ void MainWindow::toolMenu_About_To_Be_Shown() { olive::MenuHelper.set_button_action_checked(transition_tool_action); olive::MenuHelper.set_button_action_checked(snap_toggle); - olive::MenuHelper.set_int_action_checked(no_autoscroll, olive::CurrentConfig.autoscroll); - olive::MenuHelper.set_int_action_checked(page_autoscroll, olive::CurrentConfig.autoscroll); - olive::MenuHelper.set_int_action_checked(smooth_autoscroll, olive::CurrentConfig.autoscroll); + olive::MenuHelper.set_int_action_checked(no_autoscroll, olive::config.autoscroll); + olive::MenuHelper.set_int_action_checked(page_autoscroll, olive::config.autoscroll); + olive::MenuHelper.set_int_action_checked(smooth_autoscroll, olive::config.autoscroll); } void MainWindow::toggle_panel_visibility() { @@ -1192,15 +1197,15 @@ void MainWindow::set_panels_locked(bool locked) } } - olive::CurrentConfig.locked_panels = locked; + olive::config.locked_panels = locked; } void MainWindow::fileMenu_About_To_Be_Shown() { - if (recent_projects.size() > 0) { + if (olive::Global->recent_project_count() > 0) { open_recent->clear(); open_recent->setEnabled(true); - for (int i=0;iaddAction(recent_projects.at(i)); + for (int i=0;irecent_project_count();i++) { + QAction* action = open_recent->addAction(olive::Global->recent_project(i)); action->setProperty("keyignore", true); action->setData(i); connect(action, SIGNAL(triggered()), &olive::MenuHelper, SLOT(open_recent_from_menu())); diff --git a/ui/mainwindow.h b/ui/mainwindow.h index e38410291..722e575e5 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -263,7 +263,6 @@ private: QAction* zoom_out_; QAction* increase_track_height_; QAction* decrease_track_height_; - QAction* track_lines; QAction* frames_action; QAction* drop_frame_action; QAction* nondrop_frame_action; @@ -309,6 +308,7 @@ private: QAction* window_effectcontrols_action; QAction* window_timeline_action; QAction* window_graph_editor_action; + QAction* window_node_editor_action; QAction* window_footageviewer_action; QAction* window_sequenceviewer_action; diff --git a/ui/mediaiconservice.cpp b/ui/mediaiconservice.cpp index 9563c54e1..955d31db0 100644 --- a/ui/mediaiconservice.cpp +++ b/ui/mediaiconservice.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -37,12 +37,13 @@ MediaIconService::MediaIconService() { throbber_pixmap_ = QPixmap(":/icons/throbber.png"); } -void MediaIconService::SetMediaIcon(Media *media, int icon_type) { +void MediaIconService::SetMediaIcon(Media *media, IconType icon_type) { // if this icon is already part of the throbber animation loop, remove it if (throbber_items_.contains(media)) { throbber_lock_.lock(); throbber_items_.removeAll(media); + media->disable_thumbnail(false); throbber_lock_.unlock(); @@ -66,6 +67,8 @@ void MediaIconService::SetMediaIcon(Media *media, int icon_type) { case ICON_TYPE_LOADING: throbber_items_.append(media); + media->disable_thumbnail(true); + // if the animation timer isn't running, start it if (!throbber_animator_.isActive()) { // set starting frame to 0 diff --git a/ui/mediaiconservice.h b/ui/mediaiconservice.h index f817696da..70ed586fd 100644 --- a/ui/mediaiconservice.h +++ b/ui/mediaiconservice.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -40,7 +40,7 @@ class MediaIconService : public QObject { public: MediaIconService(); public slots: - void SetMediaIcon(Media* media, int icon_type); + void SetMediaIcon(Media* media, IconType icon_type); signals: void IconChanged(); private slots: diff --git a/ui/menu.cpp b/ui/menu.cpp index 20a6990ca..a77652794 100644 --- a/ui/menu.cpp +++ b/ui/menu.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "menu.h" #include "global/global.h" @@ -6,7 +26,7 @@ Menu::Menu(QWidget *parent) : QMenu(parent) { - if (olive::CurrentConfig.use_native_menu_styling) { + if (olive::config.use_native_menu_styling) { OliveGlobal::SetNativeStyling(this); } } @@ -14,7 +34,7 @@ Menu::Menu(QWidget *parent) : Menu::Menu(const QString &title, QWidget *parent) : QMenu(title, parent) { - if (olive::CurrentConfig.use_native_menu_styling) { + if (olive::config.use_native_menu_styling) { OliveGlobal::SetNativeStyling(this); } } diff --git a/ui/menu.h b/ui/menu.h index 6b33e60e3..a4f36bdc0 100644 --- a/ui/menu.h +++ b/ui/menu.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef MENU_H #define MENU_H diff --git a/ui/menuhelper.cpp b/ui/menuhelper.cpp index 9a637861e..e4a38c280 100644 --- a/ui/menuhelper.cpp +++ b/ui/menuhelper.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -26,7 +26,7 @@ #include #include "global/config.h" -#include "project/clipboard.h" +#include "global/clipboard.h" #include "ui/mainwindow.h" #include "global/global.h" #include "panels/panels.h" @@ -39,10 +39,10 @@ void MenuHelper::InitializeSharedMenus() new_project_ = create_menu_action(nullptr, "newproj", olive::Global.get(), SLOT(new_project()), QKeySequence("Ctrl+N")); new_project_->setParent(this); - new_sequence_ = create_menu_action(nullptr, "newseq", panel_project, SLOT(new_sequence()), QKeySequence("Ctrl+Shift+N")); + new_sequence_ = create_menu_action(nullptr, "newseq", olive::Global.get(), SLOT(open_new_sequence_dialog()), QKeySequence("Ctrl+Shift+N")); new_sequence_->setParent(this); - new_folder_ = create_menu_action(nullptr, "newfolder", panel_project, SLOT(new_folder())); + new_folder_ = create_menu_action(nullptr, "newfolder", panel_project.first(), SLOT(new_folder())); new_folder_->setParent(this); set_in_point_ = create_menu_action(nullptr, "setinpoint", &olive::FocusFilter, SLOT(set_in_point()), QKeySequence("I")); @@ -60,16 +60,16 @@ void MenuHelper::InitializeSharedMenus() clear_inout_point = create_menu_action(nullptr, "clearinout", &olive::FocusFilter, SLOT(clear_inout()), QKeySequence("G")); clear_inout_point->setParent(this); - add_default_transition_ = create_menu_action(nullptr, "deftransition", panel_timeline, SLOT(add_transition()), QKeySequence("Ctrl+Shift+D")); + add_default_transition_ = create_menu_action(nullptr, "deftransition", panel_timeline.first(), SLOT(add_transition()), QKeySequence("Ctrl+Shift+D")); add_default_transition_->setParent(this); - link_unlink_ = create_menu_action(nullptr, "linkunlink", panel_timeline, SLOT(toggle_links()), QKeySequence("Ctrl+L")); + link_unlink_ = create_menu_action(nullptr, "linkunlink", panel_timeline.first(), SLOT(toggle_links()), QKeySequence("Ctrl+L")); link_unlink_->setParent(this); - enable_disable_ = create_menu_action(nullptr, "enabledisable", panel_timeline, SLOT(toggle_enable_on_selected_clips()), QKeySequence("Shift+E")); + enable_disable_ = create_menu_action(nullptr, "enabledisable", panel_timeline.first(), SLOT(toggle_enable_on_selected_clips()), QKeySequence("Shift+E")); enable_disable_->setParent(this); - nest_ = create_menu_action(nullptr, "nest", panel_timeline, SLOT(nest())); + nest_ = create_menu_action(nullptr, "nest", panel_timeline.first(), SLOT(nest())); nest_->setParent(this); cut_ = create_menu_action(nullptr, "cut", &olive::FocusFilter, SLOT(cut()), QKeySequence("Ctrl+X")); @@ -90,10 +90,10 @@ void MenuHelper::InitializeSharedMenus() delete_ = create_menu_action(nullptr, "delete", &olive::FocusFilter, SLOT(delete_function()), QKeySequence("Del")); delete_->setParent(this); - ripple_delete_ = create_menu_action(nullptr, "rippledelete", panel_timeline, SLOT(ripple_delete()), QKeySequence("Shift+Del")); + ripple_delete_ = create_menu_action(nullptr, "rippledelete", panel_timeline.first(), SLOT(ripple_delete()), QKeySequence("Shift+Del")); ripple_delete_->setParent(this); - split_ = create_menu_action(nullptr, "split", panel_timeline, SLOT(split_at_playhead()), QKeySequence("Ctrl+K")); + split_ = create_menu_action(nullptr, "split", panel_timeline.first(), SLOT(split_at_playhead()), QKeySequence("Ctrl+K")); split_->setParent(this); Retranslate(); @@ -193,23 +193,23 @@ void MenuHelper::set_titlesafe_from_menu() { if (qIsNaN(tsa)) { // disable title safe area - olive::CurrentConfig.show_title_safe_area = false; + olive::config.show_title_safe_area = false; } else { // using title safe area - olive::CurrentConfig.show_title_safe_area = true; + olive::config.show_title_safe_area = true; // are we using the default area aspect ratio, or a specific one if (qIsNull(tsa)) { // default title safe area - olive::CurrentConfig.use_custom_title_safe_ratio = false; + olive::config.use_custom_title_safe_ratio = false; } else { // using a specific aspect ratio - olive::CurrentConfig.use_custom_title_safe_ratio = true; + olive::config.use_custom_title_safe_ratio = true; if (tsa < 0.0) { @@ -229,25 +229,25 @@ void MenuHelper::set_titlesafe_from_menu() { if (!input.isEmpty()) { QStringList inputList = input.split(':'); - olive::CurrentConfig.custom_title_safe_ratio = inputList.at(0).toDouble()/inputList.at(1).toDouble(); + olive::config.custom_title_safe_ratio = inputList.at(0).toDouble()/inputList.at(1).toDouble(); } } else { // specified tsa is a specific custom aspect ratio - olive::CurrentConfig.custom_title_safe_ratio = tsa; + olive::config.custom_title_safe_ratio = tsa; } } } - panel_sequence_viewer->viewer_widget->update(); + panel_sequence_viewer->viewer_widget()->update(); } void MenuHelper::set_autoscroll() { QAction* action = static_cast(sender()); - olive::CurrentConfig.autoscroll = action->data().toInt(); + olive::config.autoscroll = action->data().toInt(); } void MenuHelper::menu_click_button() { @@ -256,7 +256,7 @@ void MenuHelper::menu_click_button() { void MenuHelper::set_timecode_view() { QAction* action = static_cast(sender()); - olive::CurrentConfig.timecode_view = action->data().toInt(); + olive::config.timecode_view = action->data().toInt(); update_ui(false); } @@ -267,8 +267,8 @@ void MenuHelper::open_recent_from_menu() { void MenuHelper::create_effect_paste_action(QMenu *menu) { - QAction* paste_action = menu->addAction(tr("&Paste"), panel_timeline, SLOT(paste(bool))); - paste_action->setEnabled(clipboard.size() > 0 && clipboard_type == CLIPBOARD_TYPE_EFFECT); + QAction* paste_action = menu->addAction(tr("&Paste"), olive::Global.get(), SLOT(paste(bool))); + paste_action->setEnabled(olive::clipboard.Count() > 0 && olive::clipboard.type() == Clipboard::CLIPBOARD_TYPE_EFFECT); } Menu* MenuHelper::create_submenu(QMenuBar* parent, diff --git a/ui/menuhelper.h b/ui/menuhelper.h index 24b50db7b..d568c3862 100644 --- a/ui/menuhelper.h +++ b/ui/menuhelper.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/nodeedgeui.cpp b/ui/nodeedgeui.cpp new file mode 100644 index 000000000..a275af92f --- /dev/null +++ b/ui/nodeedgeui.cpp @@ -0,0 +1,67 @@ +#include "nodeedgeui.h" + +#include +#include + +#include "global/math.h" +#include "ui/effectui.h" + +NodeEdgeUI::NodeEdgeUI(NodeEdge* edge) : + edge_(edge), + output_node_(nullptr), + input_node_(nullptr) +{ + setPen(QPen(Qt::white, 2)); +} + +void NodeEdgeUI::adjust() +{ + if (output_node_ == nullptr) { + // Locate the output and input nodes' UIs + + QList all_items = scene()->items(); + + for (int j=0;j(all_items.at(j)); + if (node != nullptr) { + + // Check if this node has the output row + int row_index = node->GetNode()->IndexOfRow(edge_->output()); + + if (row_index > -1) { + output_node_ = node; + output_index_ = row_index; + } + + // Check if this node has the input row + row_index = node->GetNode()->IndexOfRow(edge_->input()); + + if (row_index > -1) { + input_node_ = node; + input_index_ = row_index; + } + } + } + } + + setPath(GetEdgePath(output_node_->pos() + output_node_->GetNodeSocketRects().at(output_index_).center(), + input_node_->pos() + input_node_->GetNodeSocketRects().at(input_index_).center())); +} + +NodeEdge *NodeEdgeUI::edge() +{ + return edge_; +} + +QPainterPath NodeEdgeUI::GetEdgePath(const QPointF &start_pos, const QPointF &end_pos) +{ + double mid_x = double_lerp(start_pos.x(), end_pos.x(), 0.5); + + QPainterPath path_; + path_.moveTo(start_pos); + path_.cubicTo(QPointF(mid_x, start_pos.y()), + QPointF(mid_x, end_pos.y()), + end_pos); + + return path_; +} diff --git a/ui/nodeedgeui.h b/ui/nodeedgeui.h new file mode 100644 index 000000000..3634334d2 --- /dev/null +++ b/ui/nodeedgeui.h @@ -0,0 +1,26 @@ +#ifndef NODEEDGEUI_H +#define NODEEDGEUI_H + +#include + +#include "nodeui.h" + +class NodeEdgeUI : public QGraphicsPathItem { +public: + NodeEdgeUI(NodeEdge* edge); + + static QPainterPath GetEdgePath(const QPointF& start_pos, const QPointF& end_pos); + + void adjust(); + NodeEdge* edge(); + +private: + NodeEdge* edge_; + + NodeUI* output_node_; + int output_index_; + NodeUI* input_node_; + int input_index_; +}; + +#endif // NODEEDGEUI_H diff --git a/ui/nodeui.cpp b/ui/nodeui.cpp new file mode 100644 index 000000000..14808ce3e --- /dev/null +++ b/ui/nodeui.cpp @@ -0,0 +1,323 @@ +#include "nodeui.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ui/effectui.h" +#include "ui/nodeedgeui.h" + +const int kRoundedRectRadius = 5; +const int kTextPadding = 4; +const int kNodePlugSize = 12; + +NodeUI::NodeUI() : + node_(nullptr), + drag_destination_(nullptr), + clicked_socket_(-1) +{ + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsSelectable, true); +} + +void NodeUI::AddToScene(QGraphicsScene *scene) +{ + scene->addItem(this); +} + +void NodeUI::SetNode(Node *n) +{ + node_ = n; + + QRectF rectangle; + + rectangle.setTopLeft(pos()); + rectangle.setSize(QSizeF(200, GetRowY(node_->row_count()))); + + QRectF inner_rect = rectangle; + inner_rect.setX(inner_rect.x() + kNodePlugSize/2); + inner_rect.setWidth(inner_rect.width() - kNodePlugSize/2); + + drag_path_ = QPainterPath(); + drag_path_.addRoundedRect(inner_rect, kRoundedRectRadius, kRoundedRectRadius); + + setRect(rectangle); +} + +Node *NodeUI::GetNode() +{ + return node_; +} + +void NodeUI::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(widget) + + // Draw node sockets + QVector sockets = GetNodeSocketRects(); + if (!sockets.isEmpty()) { + painter->setPen(Qt::black); + painter->setBrush(Qt::gray); + + for (int i=0;idrawEllipse(sockets.at(i)); + } + } + + // Draw main background rounded rectangle + QPalette palette = qApp->palette(); + + if (option->state & QStyle::State_Selected) { + painter->setPen(palette.highlight().color()); + } else { + painter->setPen(palette.base().color()); + } + painter->setBrush(palette.window()); + painter->drawPath(drag_path_); + + // Draw node title + painter->setPen(palette.text().color()); + int left_text_x = rect().x() + kNodePlugSize/2 + kTextPadding; + painter->drawText(left_text_x, + GetRowY(-1) + qApp->fontMetrics().ascent(), + node_->name()); + + // Draw node row names + for (int i=0;irow_count();i++) { + int text_x; + + if (node_->row(i)->IsNodeOutput()) { + // right alignment + text_x = rect().right() - kNodePlugSize/2 - qApp->fontMetrics().width(node_->row(i)->name()) - kTextPadding; + } else { + // left alignment + text_x = left_text_x; + } + + painter->drawText(text_x, + GetRowY(i) + qApp->fontMetrics().ascent(), + node_->row(i)->name()); + } + + // Draw title splitter line + int line_y = GetRowY(-1) + qApp->fontMetrics().height() + kTextPadding/2; + painter->drawLine(rect().x() + kNodePlugSize/2, line_y, rect().right() - kNodePlugSize/2, line_y); +} + +void NodeUI::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + QVector sockets = GetNodeSocketRects(); + + clicked_socket_ = -1; + drag_destination_ = nullptr; + + for (int i=0;ipos())) { + clicked_socket_ = i; + break; + } + } + + if (clicked_socket_ > -1) { + + // See if this socket already has an edge connected + QVector edges = node_->row(clicked_socket_)->edges(); + if (!edges.isEmpty() && edges.last()->input()->GetParentEffect() == node_) { + + NodeEdge* e = edges.last().get(); + + // If this node is the edge's "input", then we'll drag that instead of creating a new edge + + EffectRow* other_row = e->output(); + + Node* other_node = other_row->GetParentEffect(); + + int other_row_index = other_node->IndexOfRow(other_row); + + NodeUI* other_node_ui = FindUIFromNode(other_node); + + // Start an edge at the opposite end + drag_line_start_ = other_node_ui->pos() + other_node_ui->GetNodeSocketRects().at(other_row_index).center(); + + drag_source_ = other_row; + + // Disconnect the existing edge, and treat our dynamic one as an edit of that one + EffectRow::DisconnectEdge(edges.last()); + + + + } else { + + // Start a new edge here + drag_line_start_ = pos() + sockets.at(clicked_socket_).center(); + + drag_source_ = node_->row(clicked_socket_); + + } + + drag_line_ = scene()->addPath(NodeEdgeUI::GetEdgePath(drag_line_start_, event->scenePos())); + + if (!edges.isEmpty()) { + drag_line_->setPen(QPen(Qt::white, 2)); + } else { + drag_line_->setPen(QPen(Qt::gray, 2)); + } + + event->accept(); + } else { + QGraphicsItem::mousePressEvent(event); + } +} + +void NodeUI::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (clicked_socket_ > -1) { + + bool line_is_touching_node = false; + drag_destination_ = nullptr; + + QPointF drag_line_end = event->scenePos(); + + // Get all the graphics items at this mouse position + QList mouse_items = scene()->items(event->scenePos()); + for (int j=0;j(mouse_items.at(j)); + if (mouse_node != nullptr) { + + // If so, get this node's sockets and loop through them + QVector mouse_nodes_sockets = mouse_node->GetNodeSocketRects(); + for (int i=0;iscenePos() - mouse_node->pos())) { + + // If so, see if the two rows can be connected + + EffectRow* local_row = drag_source_; + EffectRow* remote_row = mouse_node->GetRowFromIndex(i); + + // Ensure one is an input and one is an output and whether their data types are compatible + if (local_row->IsNodeInput() != remote_row->IsNodeInput()) { + + // Determine which row is the input and which row is the output + EffectRow* input_row = (local_row->IsNodeInput()) ? local_row : remote_row; + EffectRow* output_row = (local_row->IsNodeInput()) ? remote_row : local_row; + + if (input_row->CanAcceptDataType(output_row->OutputDataType())) { + // This is a valid connection + line_is_touching_node = true; + + // Snap drag line to this socket + drag_line_end = mouse_node->pos() + mouse_nodes_sockets.at(i).center(); + + // Cancel loop since we have a connection now + j = mouse_items.size(); + + // Cache destination row + drag_destination_ = remote_row; + } + } + + break; + } + } + } + } + + if (line_is_touching_node) { + drag_line_->setPen(QPen(Qt::white, 2)); + } else { + drag_line_->setPen(QPen(Qt::gray, 2)); + } + + drag_line_->setPath(NodeEdgeUI::GetEdgePath(drag_line_start_, drag_line_end)); + + event->accept(); + + } else { + QGraphicsItem::mouseMoveEvent(event); + } +} + +void NodeUI::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (clicked_socket_ > -1) { + scene()->removeItem(drag_line_); + delete drag_line_; + + event->accept(); + + if (drag_destination_ != nullptr) { + EffectRow::ConnectEdge(drag_source_, drag_destination_); + } + } else { + QGraphicsItem::mouseReleaseEvent(event); + } +} + +QVector NodeUI::GetNodeSocketRects() +{ + QVector rects; + + if (node_ != nullptr) { + Node* e = node_; + + for (int i=0;irow_count();i++) { + + EffectRow* row = e->row(i); + + if (row->IsNodeInput() || row->IsNodeOutput()) { + qreal x = (row->IsNodeOutput()) ? rect().right() - kNodePlugSize : rect().x(); + int y = GetRowY(i) + qApp->fontMetrics().height()/2; + + rects.append(QRectF(x, + rect().y() + y - kNodePlugSize/2, + kNodePlugSize, + kNodePlugSize)); + } + } + } + + return rects; +} + +EffectRow *NodeUI::GetRowFromIndex(int i) +{ + if (node_ != nullptr && i < node_->row_count()) { + return node_->row(i); + } + return nullptr; +} + +NodeUI *NodeUI::FindUIFromNode(Node* n) +{ + QList all_items = scene()->items(); + + for (int j=0;j(all_items.at(j)); + if (node != nullptr) { + + // Check if this node has the specified + + if (node->node_ == n) { + return node; + } + } + } + + return nullptr; +} + +int NodeUI::GetRowY(int index) +{ + return (qApp->fontMetrics().height() + kTextPadding) * (index + 1) + kTextPadding; +} diff --git a/ui/nodeui.h b/ui/nodeui.h new file mode 100644 index 000000000..0558bbe01 --- /dev/null +++ b/ui/nodeui.h @@ -0,0 +1,49 @@ +#ifndef NODEUI_H +#define NODEUI_H + +#include +#include + +class EffectUI; +class EffectRow; +class NodeEdge; +class Node; + +class NodeUI : public QGraphicsRectItem { +public: + NodeUI(); + + void AddToScene(QGraphicsScene* scene); + //void Resize(const QSize& s); + void SetNode(Node* n); + Node* GetNode(); + + QVector GetNodeSocketRects(); + +protected: + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; + + virtual void mousePressEvent(QGraphicsSceneMouseEvent * event) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + +private: + EffectRow *GetRowFromIndex(int i); + NodeUI* FindUIFromNode(Node* n); + int GetRowY(int index); + + Node* node_; + + QGraphicsPathItem* drag_line_; + QPointF drag_line_start_; + EffectRow* drag_source_; + EffectRow* drag_destination_; + QPainterPath drag_path_; + + int clicked_socket_; + +}; + + + +#endif // NODEUI_H diff --git a/ui/nodeview.cpp b/ui/nodeview.cpp new file mode 100644 index 000000000..b3c33f413 --- /dev/null +++ b/ui/nodeview.cpp @@ -0,0 +1,69 @@ +#include "nodeview.h" + +#include +#include +#include + +#include "ui/menu.h" +#include "nodes/node.h" + +NodeView::NodeView(QGraphicsScene *scene, QWidget *parent) : + QGraphicsView(scene, parent), + hand_moving_(false) +{ + setMouseTracking(true); + setWindowTitle(tr("Node Editor")); + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); +} + +void NodeView::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::MidButton) { + hand_moving_ = true; + drag_start_ = event->pos(); + } else if (event->button() == Qt::RightButton && scene()->itemAt(mapToScene(event->pos()), QTransform()) == nullptr) { + + // If the user clicked with the right button on empty space, show the context menu + emit RequestContextMenu(); + + } else { + QGraphicsView::mousePressEvent(event); + } +} + +void NodeView::mouseMoveEvent(QMouseEvent *event) +{ + if (hand_moving_) { + QPoint delta = event->pos() - drag_start_; + + horizontalScrollBar()->setValue(horizontalScrollBar()->value() - delta.x()); + verticalScrollBar()->setValue(verticalScrollBar()->value() - delta.y()); + + drag_start_ = event->pos(); + } else { + QGraphicsView::mouseMoveEvent(event); + } +} + +void NodeView::mouseReleaseEvent(QMouseEvent *event) +{ + if (!hand_moving_) { + QGraphicsView::mouseReleaseEvent(event); + } + hand_moving_ = false; +} + +void NodeView::wheelEvent(QWheelEvent *event) +{ + if (event->modifiers() & Qt::ControlModifier) { + if (event->angleDelta().y() < 0) { + scale(0.9, 0.9); + } else { + scale(1.1, 1.1); + } + } else { + QGraphicsView::wheelEvent(event); + } +} diff --git a/ui/nodeview.h b/ui/nodeview.h new file mode 100644 index 000000000..648aef23d --- /dev/null +++ b/ui/nodeview.h @@ -0,0 +1,28 @@ +#ifndef NODEVIEW_H +#define NODEVIEW_H + +#include + +#include "timeline/tracktypes.h" + +class NodeView : public QGraphicsView { + Q_OBJECT +public: + NodeView(QGraphicsScene *scene, QWidget* parent = nullptr); + +signals: + void RequestContextMenu(); + +protected: + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent *event) override; + virtual void wheelEvent(QWheelEvent *event) override; + +private: + bool hand_moving_; + QPoint drag_start_; + +}; + +#endif // NODEVIEW_H diff --git a/ui/panel.cpp b/ui/panel.cpp index ed54ea43d..27d80afdf 100644 --- a/ui/panel.cpp +++ b/ui/panel.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -26,9 +26,6 @@ QVector olive::panels; Panel::Panel(QWidget *parent) : QDockWidget (parent) { -// setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); -// setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - olive::panels.append(this); } @@ -37,6 +34,11 @@ Panel::~Panel() olive::panels.removeAll(this); } +bool Panel::focused() +{ + return hasFocus(); +} + void Panel::LoadLayoutState(const QByteArray &) {} QByteArray Panel::SaveLayoutState() diff --git a/ui/panel.h b/ui/panel.h index c4c9f2cbe..4c97ee178 100644 --- a/ui/panel.h +++ b/ui/panel.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -31,6 +31,8 @@ public: virtual void Retranslate() = 0; + virtual bool focused(); + virtual void LoadLayoutState(const QByteArray& data); virtual QByteArray SaveLayoutState(); protected: diff --git a/ui/rectangleselect.cpp b/ui/rectangleselect.cpp index 033c9c47d..8c865ebf2 100644 --- a/ui/rectangleselect.cpp +++ b/ui/rectangleselect.cpp @@ -1,27 +1,27 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ #include "rectangleselect.h" -void draw_selection_rectangle(QPainter& painter, const QRect& rect) { - painter.setPen(QColor(204, 204, 204)); - painter.setBrush(QColor(0, 0, 0, 32)); - painter.drawRect(rect); +void olive::ui::DrawSelectionRectangle(QPainter& painter, const QRect& rect) { + painter.setPen(QColor(204, 204, 204)); + painter.setBrush(QColor(0, 0, 0, 32)); + painter.drawRect(rect); } diff --git a/ui/rectangleselect.h b/ui/rectangleselect.h index 9cc11f078..10764ffa9 100644 --- a/ui/rectangleselect.h +++ b/ui/rectangleselect.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -23,6 +23,9 @@ #include +namespace olive { +namespace ui { + /** * @brief Routine for drawing a drag selection rectangle for any given QPainter * @@ -34,6 +37,9 @@ * * Rectangle to draw */ -void draw_selection_rectangle(QPainter& painter, const QRect& rect); +void DrawSelectionRectangle(QPainter& painter, const QRect& rect); + +} +} #endif // RECTANGLESELECT_H diff --git a/ui/resizablescrollbar.cpp b/ui/resizablescrollbar.cpp index 785f2eba8..a339a0c9f 100644 --- a/ui/resizablescrollbar.cpp +++ b/ui/resizablescrollbar.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/resizablescrollbar.h b/ui/resizablescrollbar.h index ef1453bc2..571ada493 100644 --- a/ui/resizablescrollbar.h +++ b/ui/resizablescrollbar.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -25,25 +25,25 @@ class ResizableScrollBar : public QScrollBar { - Q_OBJECT + Q_OBJECT public: - ResizableScrollBar(QWidget * parent = 0); - bool is_resizing(); + ResizableScrollBar(QWidget * parent = nullptr); + bool is_resizing(); signals: - void resize_move(double i); + void resize_move(double i); protected: - void resizeEvent(QResizeEvent *event) override; - void mousePressEvent(QMouseEvent *) override; - void mouseMoveEvent(QMouseEvent *) override; - void mouseReleaseEvent(QMouseEvent *) override; + void resizeEvent(QResizeEvent *event) override; + void mousePressEvent(QMouseEvent *) override; + void mouseMoveEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *) override; private: - bool resize_init; - bool resize_proc; - int resize_start; - bool resize_top; + bool resize_init; + bool resize_proc; + int resize_start; + bool resize_top; - int resize_start_max; - int resize_start_width; + int resize_start_max; + int resize_start_width; }; #endif // RESIZABLESCROLLBAR_H diff --git a/ui/scrollarea.cpp b/ui/scrollarea.cpp deleted file mode 100644 index 4249a39f3..000000000 --- a/ui/scrollarea.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "scrollarea.h" - -#include -#include - -#include "global/config.h" -#include "panels/panels.h" -#include "panels/timeline.h" - -ScrollArea::ScrollArea(QWidget* parent) : QScrollArea(parent) {} - -void ScrollArea::wheelEvent(QWheelEvent *e) { - if (olive::CurrentConfig.scroll_zooms) { - e->ignore(); - - if (e->angleDelta().y() > 0) { - panel_timeline->zoom_in(); - } else if (e->angleDelta().y() < 0) { - panel_timeline->zoom_out(); - } - } else { - QScrollArea::wheelEvent(e); - } -} diff --git a/ui/scrollarea.h b/ui/scrollarea.h deleted file mode 100644 index 667fe53d1..000000000 --- a/ui/scrollarea.h +++ /dev/null @@ -1,33 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef SCROLLAREA_H -#define SCROLLAREA_H - -#include - -class ScrollArea : public QScrollArea -{ -public: - ScrollArea(QWidget* parent = 0); - void wheelEvent(QWheelEvent *); -}; - -#endif // SCROLLAREA_H diff --git a/ui/sourceiconview.cpp b/ui/sourceiconview.cpp index 60bd06184..d4f1ecceb 100644 --- a/ui/sourceiconview.cpp +++ b/ui/sourceiconview.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/sourceiconview.h b/ui/sourceiconview.h index e615068f0..bbd7e3236 100644 --- a/ui/sourceiconview.h +++ b/ui/sourceiconview.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/sourcetable.cpp b/ui/sourcetable.cpp index 5a0d67ac0..d3f74f449 100644 --- a/ui/sourcetable.cpp +++ b/ui/sourcetable.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/sourcetable.h b/ui/sourcetable.h index cc28ea48f..aeea9dda3 100644 --- a/ui/sourcetable.h +++ b/ui/sourcetable.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/styling.cpp b/ui/styling.cpp index 2c890a3ef..5d8dfd08f 100644 --- a/ui/styling.cpp +++ b/ui/styling.cpp @@ -1,10 +1,30 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "styling.h" #include "global/config.h" bool olive::styling::UseDarkIcons() { - return olive::CurrentConfig.style == kOliveDefaultLight || olive::CurrentConfig.style == kNativeDarkIcons; + return olive::config.style == kOliveDefaultLight || olive::config.style == kNativeDarkIcons; } QColor olive::styling::GetIconColor() @@ -20,5 +40,5 @@ QColor olive::styling::GetIconColor() bool olive::styling::UseNativeUI() { - return olive::CurrentConfig.style == kNativeLightIcons || olive::CurrentConfig.style == kNativeDarkIcons; + return olive::config.style == kNativeLightIcons || olive::config.style == kNativeDarkIcons; } diff --git a/ui/styling.h b/ui/styling.h index c8a1b7343..e41a9df9b 100644 --- a/ui/styling.h +++ b/ui/styling.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef STYLING_H #define STYLING_H diff --git a/ui/texteditex.cpp b/ui/texteditex.cpp index 5b66c45a1..28f32a8b0 100644 --- a/ui/texteditex.cpp +++ b/ui/texteditex.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -35,6 +35,9 @@ TextEditEx::TextEditEx(QWidget *parent, bool enable_rich_text) : layout->setMargin(0); layout->setSpacing(0); + layout->setSpacing(0); + layout->setMargin(0); + text_editor_ = new QTextEdit(); text_editor_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding); connect(text_editor_, SIGNAL(textChanged()), this, SLOT(queue_text_modified())); diff --git a/ui/texteditex.h b/ui/texteditex.h index f9f8700a2..46e12bb66 100644 --- a/ui/texteditex.h +++ b/ui/texteditex.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/timelinearea.cpp b/ui/timelinearea.cpp new file mode 100644 index 000000000..e0c795ab8 --- /dev/null +++ b/ui/timelinearea.cpp @@ -0,0 +1,196 @@ +#include "timelinearea.h" + +#include + +#include "panels/timeline.h" +#include "global/config.h" + +int olive::timeline::kTimelineLabelFixedWidth = 200; + +TimelineArea::TimelineArea(Timeline* timeline, olive::timeline::Alignment alignment) : + timeline_(timeline), + track_list_(nullptr), + alignment_(alignment) +{ + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + + // LABELS + label_container_ = new QWidget(); + label_container_layout_ = new QVBoxLayout(label_container_); + label_container_layout_->setMargin(0); + label_container_layout_->setSpacing(0); + label_container_layout_->addStretch(); + + // LABEL SCROLLAREA + QScrollArea* label_area = new QScrollArea(); + label_area->setFrameShape(QFrame::NoFrame); + label_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + label_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + label_area->verticalScrollBar()->setMaximum(INT_MAX); + label_area->setWidgetResizable(true); + label_area->setFixedWidth(olive::timeline::kTimelineLabelFixedWidth); + label_area->setWidget(label_container_); + layout->addWidget(label_area); + + // VIEW + view_ = new TimelineView(timeline_); + view_->SetAlignment(alignment_); + layout->addWidget(view_); + + // SCROLLBAR + scrollbar_ = new QScrollBar(Qt::Vertical); + layout->addWidget(scrollbar_); + + connect(scrollbar_, SIGNAL(valueChanged(int)), view_, SLOT(setScroll(int))); + connect(scrollbar_, SIGNAL(valueChanged(int)), label_area->verticalScrollBar(), SLOT(setValue(int))); + connect(view_, SIGNAL(setScrollMaximum(int)), this, SLOT(setScrollMaximum(int))); + connect(view_, SIGNAL(requestScrollChange(int)), scrollbar_, SLOT(setValue(int))); +} + +void TimelineArea::SetTrackList(Sequence *sequence, olive::TrackType track_list) +{ + if (track_list_ != nullptr) { + disconnect(track_list_, SIGNAL(TrackCountChanged()), this, SLOT(RefreshLabels())); + } + + if (sequence == nullptr) { + + track_list_ = nullptr; + + } else { + + track_list_ = sequence->GetTrackList(track_list); + connect(track_list_, SIGNAL(TrackCountChanged()), this, SLOT(RefreshLabels())); + + } + + RefreshLabels(); + + view_->SetTrackList(track_list_); + +} + +void TimelineArea::SetAlignment(olive::timeline::Alignment alignment) +{ + view_->SetAlignment(alignment); + alignment_ = alignment; + + RefreshLabels(); +} + +TrackList *TimelineArea::track_list() +{ + return track_list_; +} + +TimelineView *TimelineArea::view() +{ + return view_; +} + +void TimelineArea::wheelEvent(QWheelEvent *event) +{ + + // TODO: implement pixel scrolling + + bool shift = (event->modifiers() & Qt::ShiftModifier); + bool ctrl = (event->modifiers() & Qt::ControlModifier); + bool alt = (event->modifiers() & Qt::AltModifier); + + // "Scroll Zooms" false + Control up : not zooming + // "Scroll Zooms" false + Control down: zooming + // "Scroll Zooms" true + Control up : zooming + // "Scroll Zooms" true + Control down: not zooming + bool zooming = (olive::config.scroll_zooms != ctrl); + + // Allow shift for axis swap, but don't swap on zoom... Unless + // we need to override Qt's axis swap via Alt + bool swap_hv = ((shift != olive::config.invert_timeline_scroll_axes) & + !zooming) | (alt & !shift & zooming); + + int delta_h = swap_hv ? event->angleDelta().y() : event->angleDelta().x(); + int delta_v = swap_hv ? event->angleDelta().x() : event->angleDelta().y(); + + if (zooming) { + + // Zoom only uses vertical scrolling, to avoid glitches on touchpads. + // Don't do anything if not scrolling vertically. + + if (delta_v != 0) { + + // delta_v == 120 for one click of a mousewheel. Less or more for a + // touchpad gesture. Calculate speed to compensate. + // 120 = ratio of 4/3 (1.33), -120 = ratio of 3/4 (.75) + + double zoom_ratio = 1.0 + (abs(delta_v) * 0.33 / 120); + + if (delta_v < 0) { + zoom_ratio = 1.0 / zoom_ratio; + } + + timeline_->multiply_zoom(zoom_ratio); + } + + } else { + + // Use the Timeline's main scrollbar for horizontal scrolling, and this + // widget's scrollbar for vertical scrolling. + + QScrollBar* bar_v = scrollbar_; + QScrollBar* bar_h = timeline_->horizontalScrollBar; + + // Match the wheel events to the size of a step as per + // https://doc.qt.io/qt-5/qwheelevent.html#angleDelta + + int step_h = bar_h->singleStep() * delta_h / -120; + int step_v = bar_v->singleStep() * delta_v / -120; + + // Apply to appropriate scrollbars + + bar_h->setValue(bar_h->value() + step_h); + bar_v->setValue(bar_v->value() + step_v); + } +} + +void TimelineArea::RefreshLabels() +{ + if (track_list_ == nullptr) { + + labels_.clear(); + + } else { + + labels_.resize(track_list_->TrackCount()); + for (int i=0;i(); + labels_[i]->SetTrack(track_list_->TrackAt(i)); + labels_[i]->SetAlignment(alignment_); + + switch (alignment_) { + case olive::timeline::kAlignmentTop: + label_container_layout_->insertWidget(label_container_layout_->count()-1, labels_[i].get()); + break; + case olive::timeline::kAlignmentBottom: + label_container_layout_->insertWidget(1, labels_[i].get()); + break; + case olive::timeline::kAlignmentSingle: + break; + } + } + + } +} + +void TimelineArea::resizeEvent(QResizeEvent *) +{ + scrollbar_->setPageStep(view_->height()); +} + +void TimelineArea::setScrollMaximum(int maximum) +{ + scrollbar_->setMaximum(qMax(0, maximum - view_->height())); + label_container_->setMinimumHeight(maximum); +// label_container_->setFixedHeight(maximum); +} diff --git a/ui/timelinearea.h b/ui/timelinearea.h new file mode 100644 index 000000000..c4c929bf8 --- /dev/null +++ b/ui/timelinearea.h @@ -0,0 +1,41 @@ +#ifndef TIMELINEAREA_H +#define TIMELINEAREA_H + +#include "timeline/sequence.h" +#include "timeline/track.h" +#include "timeline/tracklist.h" +#include "timeline/timelinefunctions.h" +#include "ui/timelineview.h" +#include "ui/timelinelabel.h" + +class TimelineArea : public QWidget +{ + Q_OBJECT +public: + TimelineArea(Timeline *timeline, olive::timeline::Alignment alignment = olive::timeline::kAlignmentTop); + + void SetTrackList(Sequence* sequence, olive::TrackType track_list); + void SetAlignment(olive::timeline::Alignment alignment); + + TrackList* track_list(); + TimelineView* view(); + + virtual void wheelEvent(QWheelEvent *event) override; +public slots: + void RefreshLabels(); +protected: + virtual void resizeEvent(QResizeEvent *event) override; +private: + Timeline* timeline_; + TrackList* track_list_; + TimelineView* view_; + QVector labels_; + olive::timeline::Alignment alignment_; + QVBoxLayout* label_container_layout_; + QScrollBar* scrollbar_; + QWidget* label_container_; +private slots: + void setScrollMaximum(int); +}; + +#endif // TIMELINEAREA_H diff --git a/ui/timelineheader.cpp b/ui/timelineheader.cpp index f39065b4f..996f6e6a5 100644 --- a/ui/timelineheader.cpp +++ b/ui/timelineheader.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -28,11 +28,11 @@ #include "mainwindow.h" #include "panels/panels.h" -#include "panels/timeline.h" +#include "global/math.h" #include "timeline/sequence.h" #include "undo/undo.h" #include "project/media.h" -#include "panels/viewer.h" +#include "global/timing.h" #include "global/config.h" #include "global/global.h" #include "ui/menu.h" @@ -94,7 +94,7 @@ int TimelineHeader::getHeaderScreenPointFromFrame(long frame) { void TimelineHeader::set_playhead(int mouse_x) { long frame = getHeaderFrameFromScreenPoint(mouse_x); - if (snapping) panel_timeline->snap_to_timeline(&frame, false, true, true); + if (snapping) viewer->seq->SnapPoint(&frame, zoom, false, true, true); if (frame != viewer->seq->playhead) { viewer->seek(frame); } @@ -114,10 +114,10 @@ void TimelineHeader::set_in_point(long new_in) { if (new_out == new_in) { new_in--; } else if (new_out < new_in) { - new_out = viewer->seq->getEndFrame(); + new_out = viewer->seq->GetEndFrame(); } - olive::UndoStack.push(new SetTimelineInOutCommand(viewer->seq.get(), true, new_in, new_out)); + olive::undo_stack.push(new SetTimelineInOutCommand(viewer->seq.get(), true, new_in, new_out)); update_parents(); } @@ -129,7 +129,7 @@ void TimelineHeader::set_out_point(long new_out) { new_in = 0; } - olive::UndoStack.push(new SetTimelineInOutCommand(viewer->seq.get(), true, new_in, new_out)); + olive::undo_stack.push(new SetTimelineInOutCommand(viewer->seq.get(), true, new_in, new_out)); update_parents(); } @@ -150,7 +150,7 @@ void TimelineHeader::show_text(bool enable) { void TimelineHeader::mousePressEvent(QMouseEvent* event) { if (viewer->seq != nullptr && event->buttons() & Qt::LeftButton) { if (resizing_workarea) { - sequence_end = viewer->seq->getEndFrame(); + sequence_end = viewer->seq->GetEndFrame(); } else { /*int QPoint start(in_x, height()+2); @@ -216,7 +216,7 @@ void TimelineHeader::mouseMoveEvent(QMouseEvent* event) { if (dragging) { if (resizing_workarea) { long frame = getHeaderFrameFromScreenPoint(event->pos().x()); - if (snapping) panel_timeline->snap_to_timeline(&frame, true, true, false); + if (snapping) viewer->seq->SnapPoint(&frame, zoom, true, true, false); if (resizing_workarea_in) { temp_workarea_in = qMax(qMin(temp_workarea_out-1, frame), 0L); @@ -231,7 +231,7 @@ void TimelineHeader::mouseMoveEvent(QMouseEvent* event) { // snap markers for (int i=0;isnap_to_timeline(&fm, true, false, true)) { + if (snapping && viewer->seq->SnapPoint(&fm, zoom, true, false, true)) { frame_movement = fm - selected_marker_original_times.at(i); break; } @@ -282,7 +282,7 @@ void TimelineHeader::mouseReleaseEvent(QMouseEvent*) { if (viewer->seq != nullptr) { dragging = false; if (resizing_workarea) { - olive::UndoStack.push(new SetTimelineInOutCommand(viewer->seq.get(), true, temp_workarea_in, temp_workarea_out)); + olive::undo_stack.push(new SetTimelineInOutCommand(viewer->seq.get(), true, temp_workarea_in, temp_workarea_out)); } else if (dragging_markers && selected_markers.size() > 0) { bool moved = false; ComboAction* ca = new ComboAction(); @@ -294,7 +294,7 @@ void TimelineHeader::mouseReleaseEvent(QMouseEvent*) { } } if (moved) { - olive::UndoStack.push(ca); + olive::undo_stack.push(ca); } else { delete ca; } @@ -303,7 +303,7 @@ void TimelineHeader::mouseReleaseEvent(QMouseEvent*) { resizing_workarea = false; dragging = false; dragging_markers = false; - panel_timeline->snapped = false; + olive::timeline::snapped = false; update_parents(); } } @@ -332,7 +332,7 @@ void TimelineHeader::delete_markers() { // Send command to delete selected markers DeleteMarkerAction* dma = new DeleteMarkerAction(viewer->marker_ref); dma->markers.append(selected_markers); - olive::UndoStack.push(dma); + olive::undo_stack.push(dma); // remove any indices for the selected markers that no longer exist for (int i=0;i lastTextBoundary) { - timecode = frame_to_timecode(frame + in_visible, olive::CurrentConfig.timecode_view, viewer->seq->frame_rate); + timecode = frame_to_timecode(frame + in_visible, olive::config.timecode_view, viewer->seq->frame_rate); fullTextWidth = fm.width(timecode); textWidth = fullTextWidth>>1; text_x = lineX; // centers the text to that point on the timeline, LEFT aligns it if not - if (olive::CurrentConfig.center_timeline_timecodes) { + if (olive::config.center_timeline_timecodes) { text_x -= textWidth; } else { text_x += TEXT_PADDING_FROM_LINE; @@ -416,7 +416,7 @@ void TimelineHeader::paintEvent(QPaintEvent*) { // draw line markers p.setPen(Qt::gray); - p.drawLine(lineX, (!olive::CurrentConfig.center_timeline_timecodes && draw_text) ? 0 : yoff, lineX, height()); + p.drawLine(lineX, (!olive::config.center_timeline_timecodes && draw_text) ? 0 : yoff, lineX, height()); // draw sub-line markers for (int j=1;jsetCheckable(true); - center_timecodes->setChecked(olive::CurrentConfig.center_timeline_timecodes); - center_timecodes->setData(reinterpret_cast(&olive::CurrentConfig.center_timeline_timecodes)); + center_timecodes->setChecked(olive::config.center_timeline_timecodes); + center_timecodes->setData(reinterpret_cast(&olive::config.center_timeline_timecodes)); menu.exec(mapToGlobal(pos)); } diff --git a/ui/timelineheader.h b/ui/timelineheader.h index 780a05d97..596365d02 100644 --- a/ui/timelineheader.h +++ b/ui/timelineheader.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -30,68 +30,68 @@ bool center_scroll_to_playhead(QScrollBar* bar, double zoom, long playhead); class TimelineHeader : public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit TimelineHeader(QWidget *parent = 0); - void set_in_point(long p); - void set_out_point(long p); + explicit TimelineHeader(QWidget *parent = 0); + void set_in_point(long p); + void set_out_point(long p); - Viewer* viewer; + Viewer* viewer; - bool snapping; + bool snapping; - void show_text(bool enable); - double get_zoom(); - void delete_markers(); - void set_scrollbar_max(QScrollBar* bar, long sequence_end_frame, int offset); + void show_text(bool enable); + double get_zoom(); + void delete_markers(); + void set_scrollbar_max(QScrollBar* bar, long sequence_end_frame, int offset); public slots: - void update_zoom(double z); - void set_scroll(int); - void set_visible_in(long i); - void show_context_menu(const QPoint &pos); - void resized_scroll_listener(double d); + void update_zoom(double z); + void set_scroll(int); + void set_visible_in(long i); + void show_context_menu(const QPoint &pos); + void resized_scroll_listener(double d); protected: - void paintEvent(QPaintEvent*); - void mousePressEvent(QMouseEvent*); - void mouseMoveEvent(QMouseEvent*); - void mouseReleaseEvent(QMouseEvent*); - void focusOutEvent(QFocusEvent*); + void paintEvent(QPaintEvent*); + void mousePressEvent(QMouseEvent*); + void mouseMoveEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + void focusOutEvent(QFocusEvent*); private: - void update_parents(); + void update_parents(); - bool dragging; + bool dragging; - bool resizing_workarea; - bool resizing_workarea_in; - long temp_workarea_in; - long temp_workarea_out; - long sequence_end; + bool resizing_workarea; + bool resizing_workarea_in; + long temp_workarea_in; + long temp_workarea_out; + long sequence_end; - double zoom; + double zoom; - long in_visible; + long in_visible; - void set_playhead(int mouse_x); + void set_playhead(int mouse_x); - int get_marker_offset(); + int get_marker_offset(); - QFontMetrics fm; + QFontMetrics fm; - int drag_start; - bool dragging_markers; - QVector selected_markers; - QVector selected_marker_original_times; + int drag_start; + bool dragging_markers; + QVector selected_markers; + QVector selected_marker_original_times; - long getHeaderFrameFromScreenPoint(int x); - int getHeaderScreenPointFromFrame(long frame); + long getHeaderFrameFromScreenPoint(int x); + int getHeaderScreenPointFromFrame(long frame); - int scroll; + int scroll; - int height_actual; - bool text_enabled; + int height_actual; + bool text_enabled; signals: }; diff --git a/ui/timelinelabel.cpp b/ui/timelinelabel.cpp new file mode 100644 index 000000000..37d7fd0b8 --- /dev/null +++ b/ui/timelinelabel.cpp @@ -0,0 +1,112 @@ +#include "timelinelabel.h" + +#include +#include +#include + +TimelineLabel::TimelineLabel() : + track_(nullptr), + alignment_(olive::timeline::kAlignmentTop) +{ + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(layout->spacing()/2); + + label_ = new ClickableLabel(); + layout->addWidget(label_); + connect(label_, SIGNAL(double_clicked()), this, SLOT(RenameTrack())); + + mute_button_ = new QPushButton("M"); + QSize fixed_size(mute_button_->sizeHint().height(), mute_button_->sizeHint().height()); + fixed_size *= 0.75; + mute_button_->setFixedSize(fixed_size); + mute_button_->setStyleSheet("QPushButton::checked { background: red; }"); + mute_button_->setCheckable(true); + layout->addWidget(mute_button_); + + solo_button_ = new QPushButton("S"); + solo_button_->setFixedSize(fixed_size); + solo_button_->setStyleSheet("QPushButton::checked { background: yellow; }"); + solo_button_->setCheckable(true); + layout->addWidget(solo_button_); + + lock_button_ = new QPushButton("L"); + lock_button_->setFixedSize(fixed_size); + lock_button_->setStyleSheet("QPushButton::checked { background: gray; }"); + lock_button_->setCheckable(true); + layout->addWidget(lock_button_); +} + +void TimelineLabel::SetTrack(Track *track) +{ + if (track_ != nullptr) { + disconnect(mute_button_, SIGNAL(toggled(bool)), track_, SLOT(SetMuted(bool))); + disconnect(solo_button_, SIGNAL(toggled(bool)), track_, SLOT(SetSoloed(bool))); + disconnect(lock_button_, SIGNAL(toggled(bool)), track_, SLOT(SetLocked(bool))); + } + + track_ = track; + + if (track_ != nullptr) { + UpdateState(); + + connect(mute_button_, SIGNAL(toggled(bool)), track_, SLOT(SetMuted(bool))); + connect(solo_button_, SIGNAL(toggled(bool)), track_, SLOT(SetSoloed(bool))); + connect(lock_button_, SIGNAL(toggled(bool)), track_, SLOT(SetLocked(bool))); + + connect(track_, SIGNAL(HeightChanged(int)), this, SLOT(UpdateHeight(int))); + } +} + +void TimelineLabel::UpdateState() +{ + label_->setText(track_->name()); + + UpdateHeight(track_->height()); + + mute_button_->setChecked(track_->IsMuted()); + solo_button_->setChecked(track_->IsSoloed()); + lock_button_->setChecked(track_->IsLocked()); +} + +void TimelineLabel::SetAlignment(olive::timeline::Alignment alignment) +{ + alignment_ = alignment; + update(); +} + +void TimelineLabel::paintEvent(QPaintEvent *) +{ + if (alignment_ != olive::timeline::kAlignmentTop && alignment_ != olive::timeline::kAlignmentBottom) { + return; + } + + QPainter p(this); + + p.setPen(QColor(0, 0, 0, 96)); + + int line_y = (alignment_ == olive::timeline::kAlignmentTop) ? height() - 1 : 0; + + p.drawLine(0, line_y, width(), line_y); +} + +void TimelineLabel::RenameTrack() +{ + bool ok; + QString new_name = QInputDialog::getText(this, + tr("Rename Track"), + tr("Enter the new name for this track"), + QLineEdit::Normal, + track_->name(), + &ok); + if (ok) { + track_->SetName(new_name); + + UpdateState(); + } +} + +void TimelineLabel::UpdateHeight(int h) +{ + setFixedHeight(h+1); +} diff --git a/ui/timelinelabel.h b/ui/timelinelabel.h new file mode 100644 index 000000000..348c59ceb --- /dev/null +++ b/ui/timelinelabel.h @@ -0,0 +1,39 @@ +#ifndef TIMELINELABEL_H +#define TIMELINELABEL_H + +#include +#include + +#include "ui/clickablelabel.h" +#include "timeline/track.h" +#include "timeline/timelinefunctions.h" + +class TimelineLabel : public QWidget +{ + Q_OBJECT +public: + TimelineLabel(); + + void SetTrack(Track* track); + void UpdateState(); + void SetAlignment(olive::timeline::Alignment alignment); +protected: + virtual void paintEvent(QPaintEvent *event) override; +private: + QPushButton* mute_button_; + QPushButton* solo_button_; + QPushButton* lock_button_; + + ClickableLabel* label_; + + Track* track_; + + olive::timeline::Alignment alignment_; +private slots: + void RenameTrack(); + void UpdateHeight(int h); +}; + +using TimelineLabelPtr = std::shared_ptr; + +#endif // TIMELINELABEL_H diff --git a/ui/timelinetools.h b/ui/timelinetools.h deleted file mode 100644 index de54fa8a2..000000000 --- a/ui/timelinetools.h +++ /dev/null @@ -1,39 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef TIMELINETOOLS_H -#define TIMELINETOOLS_H - -enum TimelineTool { - TIMELINE_TOOL_POINTER, - TIMELINE_TOOL_EDIT, - TIMELINE_TOOL_RAZOR, - TIMELINE_TOOL_RIPPLE, - TIMELINE_TOOL_ROLLING, - TIMELINE_TOOL_SLIP, - TIMELINE_TOOL_SLIDE, - TIMELINE_TOOL_HAND, - TIMELINE_TOOL_ZOOM, - TIMELINE_TOOL_MENU, - TIMELINE_TOOL_TRANSITION, - TIMELINE_TOOL_COUNT -}; - -#endif // TIMELINETOOLS_H diff --git a/ui/timelineview.cpp b/ui/timelineview.cpp new file mode 100644 index 000000000..25351529d --- /dev/null +++ b/ui/timelineview.cpp @@ -0,0 +1,3381 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "timelineview.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "global/global.h" +#include "panels/panels.h" +#include "project/projectelements.h" +#include "rendering/audio.h" +#include "global/config.h" +#include "global/timing.h" +#include "ui/sourcetable.h" +#include "ui/sourceiconview.h" +#include "undo/undo.h" +#include "undo/undostack.h" +#include "ui/viewerwidget.h" +#include "ui/resizablescrollbar.h" +#include "dialogs/newsequencedialog.h" +#include "mainwindow.h" +#include "ui/rectangleselect.h" +#include "rendering/renderfunctions.h" +#include "ui/cursors.h" +#include "ui/menuhelper.h" +#include "ui/menu.h" +#include "ui/focusfilter.h" +#include "dialogs/clippropertiesdialog.h" +#include "global/debug.h" +#include "nodes/node.h" +#include "effects/internal/solideffect.h" +#include "timeline/track.h" +#include "global/math.h" +#include "project/projectfunctions.h" +#include "ui/waveform.h" + +#define MAX_TEXT_WIDTH 20 +#define TRANSITION_BETWEEN_RANGE 40 + +TimelineView::TimelineView(Timeline *parent) : + timeline_(parent), + self_created_sequence(nullptr), + track_list_(nullptr), + scroll(0), + alignment_(olive::timeline::kAlignmentTop), + track_resizing(false) +{ + setMouseTracking(true); + + setFocusPolicy(Qt::ClickFocus); + + setAcceptDrops(true); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); + + tooltip_timer.setInterval(500); + connect(&tooltip_timer, SIGNAL(timeout()), this, SLOT(tooltip_timer_timeout())); +} + +void TimelineView::SetAlignment(olive::timeline::Alignment alignment) +{ + alignment_ = alignment; +} + +void TimelineView::SetTrackList(TrackList *tl) +{ + track_list_ = tl; + + update(); +} + +void TimelineView::show_context_menu(const QPoint& pos) { + if (sequence() != nullptr) { + // hack because sometimes right clicking doesn't trigger mouse release event + ParentTimeline()->rect_select_init = false; + ParentTimeline()->rect_select_proc = false; + + Menu menu(this); + + QAction* undoAction = menu.addAction(tr("&Undo")); + QAction* redoAction = menu.addAction(tr("&Redo")); + connect(undoAction, SIGNAL(triggered(bool)), olive::Global.get(), SLOT(undo())); + connect(redoAction, SIGNAL(triggered(bool)), olive::Global.get(), SLOT(redo())); + undoAction->setEnabled(olive::undo_stack.canUndo()); + redoAction->setEnabled(olive::undo_stack.canRedo()); + menu.addSeparator(); + + // collect all the selected clips + QVector selected_clips = sequence()->SelectedClips(); + + olive::MenuHelper.make_edit_functions_menu(&menu, !selected_clips.isEmpty()); + + if (selected_clips.isEmpty()) { + // no clips are selected + + // determine if we can perform a ripple empty space + ParentTimeline()->cursor_frame = ParentTimeline()->getTimelineFrameFromScreenPoint(pos.x()); + ParentTimeline()->cursor_track = getTrackFromScreenPoint(pos.y()); + + // check if the space the cursor is currently at is empty + if (ParentTimeline()->cursor_track->GetClipFromPoint(ParentTimeline()->cursor_frame) == nullptr) { + QAction* ripple_delete_action = menu.addAction(tr("R&ipple Delete Empty Space")); + connect(ripple_delete_action, SIGNAL(triggered(bool)), ParentTimeline(), SLOT(ripple_delete_empty_space())); + } + + QAction* seq_settings = menu.addAction(tr("Sequence Settings")); + connect(seq_settings, SIGNAL(triggered(bool)), this, SLOT(open_sequence_properties())); + } + + if (!selected_clips.isEmpty()) { + + bool video_clips_are_selected = false; + bool audio_clips_are_selected = false; + + for (int i=0;itype() == olive::kTypeVideo) { + video_clips_are_selected = true; + } else { + audio_clips_are_selected = true; + } + } + + menu.addSeparator(); + + menu.addAction(tr("&Speed/Duration"), olive::Global.get(), SLOT(open_speed_dialog())); + + if (audio_clips_are_selected) { + menu.addAction(tr("Auto-Cut Silence"), olive::Global.get(), SLOT(open_autocut_silence_dialog())); + } + + QAction* autoscaleAction = menu.addAction(tr("Auto-S&cale"), this, SLOT(toggle_autoscale())); + autoscaleAction->setCheckable(true); + // set autoscale to the first selected clip + autoscaleAction->setChecked(selected_clips.at(0)->autoscaled()); + + olive::MenuHelper.make_clip_functions_menu(&menu); + + // check if all selected clips have the same media for a "Reveal In Project" + bool same_media = true; + rc_reveal_media = selected_clips.at(0)->media(); + for (int i=1;imedia() != rc_reveal_media) { + same_media = false; + break; + } + } + + if (same_media) { + QAction* revealInProjectAction = menu.addAction(tr("&Reveal in Project")); + connect(revealInProjectAction, SIGNAL(triggered(bool)), this, SLOT(reveal_media())); + } + + menu.addAction(tr("Properties"), this, SLOT(show_clip_properties())); + } + + menu.exec(mapToGlobal(pos)); + } +} + +void TimelineView::toggle_autoscale() { + QVector selected_clips = sequence()->SelectedClips(); + + if (!selected_clips.isEmpty()) { + SetClipProperty* action = new SetClipProperty(kSetClipPropertyAutoscale); + + for (int i=0;iAddSetting(c, !c->autoscaled()); + } + + olive::undo_stack.push(action); + } +} + +void TimelineView::tooltip_timer_timeout() { + if (tooltip_clip != nullptr) { + QToolTip::showText(QCursor::pos(), + tr("%1\nStart: %2\nEnd: %3\nDuration: %4").arg( + tooltip_clip->name(), + frame_to_timecode(tooltip_clip->timeline_in(), olive::config.timecode_view, sequence()->frame_rate), + frame_to_timecode(tooltip_clip->timeline_out(), olive::config.timecode_view, sequence()->frame_rate), + frame_to_timecode(tooltip_clip->length(), olive::config.timecode_view, sequence()->frame_rate) + )); + } + + tooltip_timer.stop(); +} + +void TimelineView::open_sequence_properties() { + QVector sequence_items = olive::project_model.GetAllSequences(); + + for (int i=0;ito_sequence().get() == sequence()) { + NewSequenceDialog nsd(this, sequence_items.at(i)); + nsd.exec(); + return; + } + } + + QMessageBox::critical(this, tr("Error"), tr("Couldn't locate media wrapper for sequence.")); +} + +void TimelineView::show_clip_properties() +{ + // get list of selected clips + QVector selected_clips = sequence()->SelectedClips(); + + // if clips are selected, open the clip properties dialog + if (!selected_clips.isEmpty()) { + ClipPropertiesDialog cpd(this, selected_clips); + cpd.exec(); + } +} + +void TimelineView::dragEnterEvent(QDragEnterEvent *event) { + bool import_init = false; + + QVector media_list; + ParentTimeline()->importing_files = false; + + for (int i=0;iIsProjectWidget(event->source())) { + + QModelIndexList items = panel_project.at(i)->get_current_selected(); + + media_list.resize(items.size()); + for (int i=0;iitem_to_media(items.at(i)); + } + import_init = true; + + break; + + } + } + + if (event->source() == panel_footage_viewer) { + if (panel_footage_viewer->seq.get() != sequence()) { // don't allow nesting the same sequence + + media_list.append(olive::timeline::MediaImportData(panel_footage_viewer->media, + static_cast(event->mimeData()->text().toInt()))); + import_init = true; + + } + } + + if (olive::config.enable_drag_files_to_timeline && event->mimeData()->hasUrls()) { + QList urls = event->mimeData()->urls(); + if (!urls.isEmpty()) { + QStringList file_list; + + for (int i=0;i last_imported_media = olive::project_model.GetLastImportedMedia(); + for (int i=0;ito_footage(); + + // waits for media to have a duration + // TODO would be much nicer if this was multithreaded + f->ready_lock.lock(); + f->ready_lock.unlock(); + + if (f->ready) { + media_list.append(last_imported_media.at(i)); + } + } + + if (media_list.isEmpty()) { + olive::undo_stack.undo(); + } else { + import_init = true; + ParentTimeline()->importing_files = true; + } + } + } + + if (import_init) { + event->acceptProposedAction(); + + long entry_point; + Sequence* seq = sequence(); + + if (seq == nullptr) { + // if no sequence, we're going to create a new one using the clips as a reference + entry_point = 0; + + self_created_sequence = olive::project::CreateSequenceFromMedia(media_list); + seq = self_created_sequence.get(); + } else { + entry_point = ParentTimeline()->getTimelineFrameFromScreenPoint(event->pos().x()); + ParentTimeline()->drag_frame_start = entry_point + getFrameFromScreenPoint(ParentTimeline()->zoom, 50); + ParentTimeline()->drag_track_start = track_list_->First(); + } + + ParentTimeline()->ghosts = olive::timeline::CreateGhostsFromMedia(seq, entry_point, media_list); + + ParentTimeline()->importing = true; + } +} + +void TimelineView::dragMoveEvent(QDragMoveEvent *event) { + if (ParentTimeline()->importing) { + event->acceptProposedAction(); + + if (sequence() != nullptr) { + QPoint pos = event->pos(); + ParentTimeline()->scroll_to_frame(ParentTimeline()->getTimelineFrameFromScreenPoint(event->pos().x())); + update_ghosts(pos, event->keyboardModifiers() & Qt::ShiftModifier); + ParentTimeline()->move_insert = ((event->keyboardModifiers() & Qt::ControlModifier) && (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_POINTER || ParentTimeline()->importing)); + update_ui(false); + } + } +} + +void TimelineView::wheelEvent(QWheelEvent *event) { + static_cast(parent())->wheelEvent(event); +} + +void TimelineView::dragLeaveEvent(QDragLeaveEvent* event) { + event->accept(); + if (ParentTimeline()->importing) { + if (ParentTimeline()->importing_files) { + olive::undo_stack.undo(); + } + ParentTimeline()->importing_files = false; + ParentTimeline()->ghosts.clear(); + ParentTimeline()->importing = false; + update_ui(false); + } + if (self_created_sequence != nullptr) { + self_created_sequence.reset(); + self_created_sequence = nullptr; + } +} + +void TimelineView::delete_area_under_ghosts(ComboAction* ca, Sequence* s) { + // delete areas before adding + QVector delete_areas; + for (int i=0;ighosts.size();i++) { + delete_areas.append(ParentTimeline()->ghosts.at(i).ToSelection()); + } + s->DeleteAreas(ca, delete_areas, false); +} + +void TimelineView::insert_clips(ComboAction* ca, Sequence* s) { + bool ripple_old_point = true; + + long earliest_old_point = LONG_MAX; + long latest_old_point = LONG_MIN; + + long earliest_new_point = LONG_MAX; + long latest_new_point = LONG_MIN; + + QVector ignore_clips; + for (int i=0;ighosts.size();i++) { + const Ghost& g = ParentTimeline()->ghosts.at(i); + + earliest_old_point = qMin(earliest_old_point, g.old_in); + latest_old_point = qMax(latest_old_point, g.old_out); + earliest_new_point = qMin(earliest_new_point, g.in); + latest_new_point = qMax(latest_new_point, g.out); + + if (g.clip != nullptr) { + ignore_clips.append(g.clip); + } else { + // don't try to close old gap if importing + ripple_old_point = false; + } + } + + QVector sequence_clips = s->GetAllClips(); + for (int i=0;ighosts.size();j++) { + if (ParentTimeline()->ghosts.at(j).clip == c) { + found = true; + break; + } + } + if (!found) { + if (c->timeline_in() < earliest_new_point && c->timeline_out() > earliest_new_point) { + s->SplitClipAtPositions(ca, c, {earliest_new_point}, true); + } + + // determine if we should close the gap the old clips left behind + if (ripple_old_point + && !((c->timeline_in() < earliest_old_point && c->timeline_out() <= earliest_old_point) || (c->timeline_in() >= latest_old_point && c->timeline_out() > latest_old_point)) + && !ignore_clips.contains(c)) { + ripple_old_point = false; + } + } + } + + long ripple_length = (latest_new_point - earliest_new_point); + + s->Ripple(ca, earliest_new_point, ripple_length, ignore_clips); + + if (ripple_old_point) { + // works for moving later clips earlier but not earlier to later + long second_ripple_length = (earliest_old_point - latest_old_point); + + s->Ripple(ca, latest_old_point, second_ripple_length, ignore_clips); + + if (earliest_old_point < earliest_new_point) { + for (int i=0;ighosts.size();i++) { + Ghost& g = ParentTimeline()->ghosts[i]; + g.in += second_ripple_length; + g.out += second_ripple_length; + } + + QVector sequence_selections = s->Selections(); + for (int i=0;iSetSelections(sequence_selections); + } + } +} + +void TimelineView::dropEvent(QDropEvent* event) { + if (ParentTimeline()->importing && ParentTimeline()->ghosts.size() > 0) { + event->acceptProposedAction(); + + ComboAction* ca = new ComboAction(); + + Sequence* s = sequence(); + + // if we're dropping into nothing, create a new sequences based on the clip being dragged + if (s == nullptr) { + s = self_created_sequence.get(); + olive::project_model.CreateSequence(ca, self_created_sequence, true, nullptr); + self_created_sequence = nullptr; + } else if (event->keyboardModifiers() & Qt::ControlModifier) { + insert_clips(ca, s); + } else { + delete_area_under_ghosts(ca, s); + } + + s->AddClipsFromGhosts(ca, ParentTimeline()->ghosts); + + ParentTimeline()->ghosts.clear(); + + ParentTimeline()->importing = false; + + olive::undo_stack.push(ca); + + setFocus(); + + update_ui(true); + } +} + +void TimelineView::mouseDoubleClickEvent(QMouseEvent *event) { + if (sequence() != nullptr) { + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_EDIT) { + Clip* clip = GetClipAtCursor(); + if (clip != nullptr) { + if (!(event->modifiers() & Qt::ShiftModifier)) { + sequence()->ClearSelections(); + } + clip->track()->SelectClip(clip); + update_ui(false); + } + } else if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_POINTER) { + Clip* c = GetClipAtCursor(); + if (c != nullptr) { + if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) { + Timeline::OpenSequence(c->media()->to_sequence()); + } + } + } + } +} + +bool TimelineView::current_tool_shows_cursor() { + return (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_EDIT + || olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_RAZOR + || ParentTimeline()->creating); +} + +Clip *TimelineView::GetClipAtCursor() +{ + if (ParentTimeline()->cursor_track == nullptr) { + return nullptr; + } + + return ParentTimeline()->cursor_track->GetClipFromPoint(ParentTimeline()->cursor_frame); +} + +QVector TimelineView::GetSplitTracksFromMouseCoords(bool also_split_links, long frame, int top, int bottom) +{ + // Convert top and bottom coords to global coordinates used by GetTracksInRectangle() + int global_top = mapToGlobal(QPoint(0, top)).y(); + int global_bottom = mapToGlobal(QPoint(0, bottom)).y(); + + // Get the current tracks in this mouse range + QVector split_tracks = ParentTimeline()->GetTracksInRectangle(global_top, global_bottom); + + // If we're also splitting links, loop through each track and search for clips that will be split at this point + if (also_split_links) { + + // Cache array size because we'll be adding to it and don't want to cause an infinite loop + int split_track_size = split_tracks.size(); + for (int i=0;iClipCount();j++) { + Clip* c = track->GetClip(j).get(); + + // Check if this clip is going to be split at this frame + if (c->timeline_in() < frame && c->timeline_out() > frame) { + + // Loop through clip's links for more tracks to split + for (int k=0;klinked.size();k++) { + Track* link_track = c->linked.at(k)->track(); + if (!split_tracks.contains(link_track)) { + split_tracks.append(link_track); + } + } + + // Break because there will only be one clip active at this frame per track + break; + } + } + } + } + + return split_tracks; +} + +void TimelineView::mousePressEvent(QMouseEvent *event) { + if (sequence() != nullptr) { + + int effective_tool = olive::timeline::current_tool; + + // some user actions will override which tool we'll be using + if (event->button() == Qt::MiddleButton) { + effective_tool = olive::timeline::TIMELINE_TOOL_HAND; + ParentTimeline()->creating = false; + } else if (event->button() == Qt::RightButton) { + effective_tool = olive::timeline::TIMELINE_TOOL_MENU; + ParentTimeline()->creating = false; + } + + // ensure cursor_frame and cursor_track are up to date + mouseMoveEvent(event); + + // store current cursor positions + ParentTimeline()->drag_x_start = event->pos().x(); + ParentTimeline()->drag_y_start = event->pos().y(); + + // store current frame/tracks as the values to start dragging from + ParentTimeline()->drag_frame_start = ParentTimeline()->cursor_frame; + ParentTimeline()->drag_track_start = ParentTimeline()->cursor_track; + + // get the clip the user is currently hovering over, priority to trim_target set from mouseMoveEvent + Clip* hovered_clip = ParentTimeline()->trim_target == nullptr ? + GetClipAtCursor() + : ParentTimeline()->trim_target; + + bool shift = (event->modifiers() & Qt::ShiftModifier); + bool alt = (event->modifiers() & Qt::AltModifier); + + // Normal behavior is to reset selections to zero when clicking, but if Shift is held, we add selections + // to the existing selections. `selection_offset` is the index to change selections from (and we don't touch + // any prior to that) + if (shift) { + ParentTimeline()->selection_cache = sequence()->Selections(); + } else { + ParentTimeline()->selection_cache.clear(); + } + + // if the user is creating an object + if (ParentTimeline()->creating) { + olive::TrackType create_type = olive::kTypeVideo; + switch (ParentTimeline()->creating_object) { + case olive::timeline::ADD_OBJ_TITLE: + case olive::timeline::ADD_OBJ_SOLID: + case olive::timeline::ADD_OBJ_BARS: + break; + case olive::timeline::ADD_OBJ_TONE: + case olive::timeline::ADD_OBJ_NOISE: + case olive::timeline::ADD_OBJ_AUDIO: + create_type = olive::kTypeAudio; + break; + } + + // if the track the user clicked is correct for the type of object we're adding + + if (track_list_->type() == create_type) { + Ghost g; + g.in = g.old_in = g.out = g.old_out = ParentTimeline()->drag_frame_start; + + g.track = ParentTimeline()->drag_track_start; + if (g.track == nullptr) { + g.track = track_list_->Last(); + ParentTimeline()->drag_track_start = track_list_->Last(); + g.track_movement = getTrackIndexFromScreenPoint(event->pos().y()) - g.track->Index(); + } + + g.trim_type = olive::timeline::TRIM_OUT; + ParentTimeline()->ghosts.append(g); + + ParentTimeline()->moving_init = true; + ParentTimeline()->moving_proc = true; + } + } else { + + // pass through tools to determine what action we'll be starting + switch (effective_tool) { + + // many tools share pointer-esque behavior + case olive::timeline::TIMELINE_TOOL_POINTER: + case olive::timeline::TIMELINE_TOOL_RIPPLE: + case olive::timeline::TIMELINE_TOOL_SLIP: + case olive::timeline::TIMELINE_TOOL_ROLLING: + case olive::timeline::TIMELINE_TOOL_SLIDE: + case olive::timeline::TIMELINE_TOOL_MENU: + { + if (track_resizing && effective_tool != olive::timeline::TIMELINE_TOOL_MENU) { + + // if the cursor is currently hovering over a track, init track resizing + ParentTimeline()->moving_init = true; + + } else { + + // check if we're currently hovering over a clip or not + if (hovered_clip != nullptr) { + + if (hovered_clip->IsSelected()) { + + if (shift) { + + // if the user clicks a selected clip while holding shift, deselect the clip + hovered_clip->track()->DeselectArea(hovered_clip->timeline_in(), hovered_clip->timeline_out()); + + // if the user isn't holding alt, also deselect all of its links as well + if (!alt) { + for (int i=0;ilinked.size();i++) { + Clip* link = hovered_clip->linked.at(i); + link->track()->DeselectArea(link->timeline_in(), link->timeline_out()); + } + } + + } else if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_POINTER + && ParentTimeline()->transition_select != kTransitionNone) { + + // if the clip was selected by then the user clicked a transition, de-select the clip and its links + // and select the transition only + + hovered_clip->track()->DeselectArea(hovered_clip->timeline_in(), hovered_clip->timeline_out()); + + for (int i=0;ilinked.size();i++) { + Clip* link = hovered_clip->linked.at(i); + link->track()->DeselectArea(link->timeline_in(), link->timeline_out()); + } + + long s_in = 0; + long s_out = 0; + + // select the transition only + if (ParentTimeline()->transition_select == kTransitionOpening + && hovered_clip->opening_transition != nullptr) { + s_in = hovered_clip->timeline_in(); + + if (hovered_clip->opening_transition->secondary_clip != nullptr) { + s_in -= hovered_clip->opening_transition->get_true_length(); + } + + s_out = hovered_clip->timeline_in() + hovered_clip->opening_transition->get_true_length(); + + } else if (ParentTimeline()->transition_select == kTransitionClosing + && hovered_clip->closing_transition != nullptr) { + + s_in = hovered_clip->timeline_out() - hovered_clip->closing_transition->get_true_length(); + s_out = hovered_clip->timeline_out(); + + if (hovered_clip->closing_transition->secondary_clip != nullptr) { + s_out += hovered_clip->closing_transition->get_true_length(); + } + } + hovered_clip->track()->SelectArea(s_in, s_out); + } + + } else { + + // if the clip is not already selected + + // if shift is NOT down, we change clear all current selections + if (!shift) { + sequence()->ClearSelections(); + } + + long s_in = hovered_clip->timeline_in(); + long s_out = hovered_clip->timeline_out(); + + // if user is using the pointer tool, they may be trying to select a transition + // check if the use is hovering over a transition + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_POINTER) { + if (ParentTimeline()->transition_select == kTransitionOpening) { + // move the selection to only select the transitoin + s_out = hovered_clip->timeline_in() + hovered_clip->opening_transition->get_true_length(); + + // if the transition is a "shared" transition, adjust the selection to select both sides + if (hovered_clip->opening_transition->secondary_clip != nullptr) { + s_in -= hovered_clip->opening_transition->get_true_length(); + } + } else if (ParentTimeline()->transition_select == kTransitionClosing) { + // move the selection to only select the transitoin + s_in = hovered_clip->timeline_out() - hovered_clip->closing_transition->get_true_length(); + + // if the transition is a "shared" transition, adjust the selection to select both sides + if (hovered_clip->closing_transition->secondary_clip != nullptr) { + s_out += hovered_clip->closing_transition->get_true_length(); + } + } + } + + // add the selection to the array + hovered_clip->track()->SelectArea(s_in, s_out); + + // if the config is set to also seek with selections, do so now + if (olive::config.select_also_seeks) { + panel_sequence_viewer->seek(hovered_clip->timeline_in()); + } + + // if alt is not down, select links (provided we're not selecting transitions) + if (!alt && ParentTimeline()->transition_select == kTransitionNone) { + + for (int i=0;ilinked.size();i++) { + + Clip* link = hovered_clip->linked.at(i); + + // check if the clip is already selected + if (!link->IsSelected()) { + link->track()->SelectClip(link); + } + + } + + } + } + + // authorize the starting of a move action if the mouse moves after this + if (effective_tool != olive::timeline::TIMELINE_TOOL_MENU) { + ParentTimeline()->moving_init = true; + } + + } else { + + // if the user did not click a clip at all, we start a rectangle selection + + if (!shift) { + sequence()->ClearSelections(); + } + + ParentTimeline()->rect_select_init = true; + } + + // update everything + update_ui(false); + } + } + break; + case olive::timeline::TIMELINE_TOOL_HAND: + + // initiate moving with the hand tool + ParentTimeline()->hand_moving = true; + + break; + case olive::timeline::TIMELINE_TOOL_EDIT: + + // if the config is set to seek with the edit tool, do so now + if (olive::config.edit_tool_also_seeks) { + panel_sequence_viewer->seek(ParentTimeline()->drag_frame_start); + } + + // initiate selecting + ParentTimeline()->selecting = true; + + break; + case olive::timeline::TIMELINE_TOOL_RAZOR: + { + + // initiate razor tool + ParentTimeline()->splitting = true; + + ParentTimeline()->split_tracks = GetSplitTracksFromMouseCoords(!alt, + ParentTimeline()->drag_frame_start, + event->pos().y(), + event->pos().y()); + + update_ui(false); + } + break; + case olive::timeline::TIMELINE_TOOL_TRANSITION: + { + + // if there is a clip to run the transition tool on, initiate the transition tool + if (ParentTimeline()->transition_tool_open_clip != nullptr + || ParentTimeline()->transition_tool_close_clip != nullptr) { + ParentTimeline()->transition_tool_init = true; + } + + } + break; + } + } + } +} + +void make_room_for_transition(ComboAction* ca, + Clip* c, + int type, + long transition_start, + long transition_end, + bool delete_old_transitions, + long timeline_in = -1, + long timeline_out = -1) { + // it's possible to specify other in/out points for the clip, but default behavior is to use the ones existing + if (timeline_in < 0) { + timeline_in = c->timeline_in(); + } + if (timeline_out < 0) { + timeline_out = c->timeline_out(); + } + + // make room for transition + if (type == kTransitionOpening) { + if (delete_old_transitions && c->opening_transition != nullptr) { + ca->append(new DeleteTransitionCommand(c->opening_transition)); + } + if (c->closing_transition != nullptr) { + if (transition_end >= c->timeline_out()) { + ca->append(new DeleteTransitionCommand(c->closing_transition)); + } else if (transition_end > c->timeline_out() - c->closing_transition->get_true_length()) { + ca->append(new ModifyTransitionCommand(c->closing_transition, c->timeline_out() - transition_end)); + } + } + } else { + if (delete_old_transitions && c->closing_transition != nullptr) { + ca->append(new DeleteTransitionCommand(c->closing_transition)); + } + if (c->opening_transition != nullptr) { + if (transition_start <= c->timeline_in()) { + ca->append(new DeleteTransitionCommand(c->opening_transition)); + } else if (transition_start < c->timeline_in() + c->opening_transition->get_true_length()) { + ca->append(new ModifyTransitionCommand(c->opening_transition, transition_start - c->timeline_in())); + } + } + } +} + +void TimelineView::VerifyTransitionsAfterCreating(ComboAction* ca, Clip* open, Clip* close, long transition_start, long transition_end) { + // in case the user made the transition larger than the clips, we're going to delete everything under + // the transition ghost and extend the clips to the transition's coordinates as necessary + + if (open == nullptr && close == nullptr) { + qWarning() << "VerifyTransitionsAfterCreating() called with two null clips"; + return; + } + + // determine whether this is a "shared" transition between to clips or not + bool shared_transition = (open != nullptr && close != nullptr); + + Track* track = nullptr; + + // first we set the clips to "undeletable" so they aren't affected by delete_areas_and_relink() + if (open != nullptr) { + open->undeletable = true; + track = open->track(); + } + if (close != nullptr) { + close->undeletable = true; + track = close->track(); + } + + // set the area to delete to the transition's coordinates and clear it + QVector areas; + areas.append(Selection(transition_start, transition_end, track)); + sequence()->DeleteAreas(ca, areas, false); + + // set the clips back to undeletable now that we're done + if (open != nullptr) { + open->undeletable = false; + } + if (close != nullptr) { + close->undeletable = false; + } + + // loop through both kinds of transition + for (int t=kTransitionOpening;t<=kTransitionClosing;t++) { + + Clip* clip_ref = (t == kTransitionOpening) ? open : close; + + // if we have an opening transition: + if (clip_ref != nullptr) { + + // make_room_for_transition will adjust the opposite transition to make space for this one, + // for example if the user makes an opening transition that overlaps the closing transition, it'll resize + // or even delete the closing transition if necessary (and vice versa) + + make_room_for_transition(ca, clip_ref, t, transition_start, transition_end, true); + + // check if the transition coordinates require the clip to be resized + if (transition_start < clip_ref->timeline_in() || transition_end > clip_ref->timeline_out()) { + + long new_in, new_out; + + if (t == kTransitionOpening) { + + // if the transition is shared, it doesn't matter if the transition extend beyond the in point since + // that'll be "absorbed" by the other clip + new_in = (shared_transition) ? open->timeline_in() : qMin(transition_start, open->timeline_in()); + + new_out = qMax(transition_end, open->timeline_out()); + + } else { + + new_in = qMin(transition_start, close->timeline_in()); + + // if the transition is shared, it doesn't matter if the transition extend beyond the out point since + // that'll be "absorbed" by the other clip + new_out = (shared_transition) ? close->timeline_out() : qMax(transition_end, close->timeline_out()); + + } + + + + clip_ref->Move(ca, + new_in, + new_out, + clip_ref->clip_in() - (clip_ref->timeline_in() - new_in), + clip_ref->track()); + } + } + } +} + +int TimelineView::GetTotalAreaHeight() +{ + // start by adding a track height worth of padding + int panel_height = olive::timeline::kTrackDefaultHeight; + + for (int i=0;iTrackCount();i++) { + panel_height += track_list_->TrackAt(i)->height(); + } + + return panel_height; +} + +void TimelineView::mouseReleaseEvent(QMouseEvent *event) { + QToolTip::hideText(); + if (sequence() != nullptr) { + bool alt = (event->modifiers() & Qt::AltModifier); + bool shift = (event->modifiers() & Qt::ShiftModifier); + bool ctrl = (event->modifiers() & Qt::ControlModifier); + + if (event->button() == Qt::LeftButton) { + ComboAction* ca = new ComboAction(); + bool push_undo = false; + + if (ParentTimeline()->creating) { + if (ParentTimeline()->ghosts.size() > 0) { + const Ghost& g = ParentTimeline()->ghosts.at(0); + + if (ParentTimeline()->creating_object == olive::timeline::ADD_OBJ_AUDIO) { + olive::MainWindow->statusBar()->clearMessage(); + panel_sequence_viewer->cue_recording(qMin(g.in, g.out), qMax(g.in, g.out), g.track); + ParentTimeline()->creating = false; + } else if (g.in != g.out) { + ClipPtr c = std::make_shared(g.track->Sibling(g.track_movement)); + c->set_media(nullptr, 0); + c->set_timeline_in(qMin(g.in, g.out)); + c->set_timeline_out(qMax(g.in, g.out)); + c->set_clip_in(0); + c->set_color(192, 192, 64); + + if (ctrl) { + insert_clips(ca, sequence()); + } else { + sequence()->DeleteAreas(ca, {c->ToSelection()}, false); + } + + QVector add; + add.append(c); + ca->append(new AddClipCommand(add)); + + if (c->type() == olive::kTypeVideo && olive::config.add_default_effects_to_clips) { + // default video effects (before custom effects) + c->effects.append(olive::node_library[kTransformEffect]->Create(c.get())); + } + + switch (ParentTimeline()->creating_object) { + case olive::timeline::ADD_OBJ_TITLE: + c->set_name(tr("Title")); + c->effects.append(olive::node_library[kRichTextInput]->Create(c.get())); + break; + case olive::timeline::ADD_OBJ_SOLID: + c->set_name(tr("Solid Color")); + c->effects.append(olive::node_library[kSolidInput]->Create(c.get())); + break; + case olive::timeline::ADD_OBJ_BARS: + { + c->set_name(tr("Bars")); + NodePtr e = olive::node_library[kSolidInput]->Create(c.get()); + + // Auto-select bars + SolidEffect* solid_effect = static_cast(e.get()); + solid_effect->SetType(SolidEffect::SOLID_TYPE_BARS); + + c->effects.append(e); + } + break; + case olive::timeline::ADD_OBJ_TONE: + c->set_name(tr("Tone")); + c->effects.append(olive::node_library[kToneInput]->Create(c.get())); + break; + case olive::timeline::ADD_OBJ_NOISE: + c->set_name(tr("Noise")); + c->effects.append(olive::node_library[kNoiseInput]->Create(c.get())); + break; + default: + break; + } + + if (c->type() == olive::kTypeAudio && olive::config.add_default_effects_to_clips) { + // default audio effects (after custom effects) + c->effects.append(olive::node_library[kVolumeEffect]->Create(c.get())); + c->effects.append(olive::node_library[kPanEffect]->Create(c.get())); + } + + push_undo = true; + + if (!shift) { + ParentTimeline()->creating = false; + } + } + } + } else if (ParentTimeline()->moving_proc) { + + // see if any clips actually moved, otherwise we don't need to do any processing + // (perhaps this could be moved further up to cover more actions?) + + bool process_moving = false; + + for (int i=0;ighosts.size();i++) { + const Ghost& g = ParentTimeline()->ghosts.at(i); + if (g.in != g.old_in + || g.out != g.old_out + || g.clip_in != g.old_clip_in + || g.track_movement != 0) { + process_moving = true; + break; + } + } + + if (process_moving) { + + const Ghost& first_ghost = ParentTimeline()->ghosts.at(0); + + // start a ripple movement + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_RIPPLE) { + + // ripple_length becomes the length/number of frames we trimmed + // ripple_point is the "axis" around which we move all the clips, any clips after it get moved + long ripple_length; + long ripple_point = LONG_MAX; + + if (ParentTimeline()->trim_type == olive::timeline::TRIM_IN) { + + // it's assumed that all the ghosts rippled by the same length, so we just take the difference of the + // first ghost here + ripple_length = first_ghost.old_in - first_ghost.in; + + // for in trimming movements we also move the selections forward (unnecessary for out trimming since + // the selected clips more or less stay in the same place) + /* + for (int i=0;iselections.size();i++) { + sequence()->selections[i].in += ripple_length; + sequence()->selections[i].out += ripple_length; + } + */ + } else { + + // use the out points for length if the user trimmed the out point + ripple_length = first_ghost.old_out - ParentTimeline()->ghosts.at(0).out; + + } + + // build a list of "ignore clips" that won't get affected by ripple_clips() below + QVector ignore_clips; + for (int i=0;ighosts.size();i++) { + const Ghost& g = ParentTimeline()->ghosts.at(i); + + // for the same reason that we pushed selections forward above, for in trimming, + // we push the ghosts forward here + if (ParentTimeline()->trim_type == olive::timeline::TRIM_IN) { + ignore_clips.append(g.clip); + ParentTimeline()->ghosts[i].in += ripple_length; + ParentTimeline()->ghosts[i].out += ripple_length; + } + + // find the earliest ripple point + long comp_point = (ParentTimeline()->trim_type == olive::timeline::TRIM_IN) ? g.old_in : g.old_out; + ripple_point = qMin(ripple_point, comp_point); + } + + // if this was out trimming, flip the direction of the ripple + if (ParentTimeline()->trim_type == olive::timeline::TRIM_OUT) ripple_length = -ripple_length; + + // finally, ripple everything + sequence()->Ripple(ca, ripple_point, ripple_length, ignore_clips); + } + + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_POINTER + && (event->modifiers() & Qt::AltModifier) + && ParentTimeline()->trim_target == nullptr) { + + // if the user was holding alt (and not trimming), we duplicate clips rather than move them + QVector old_clips; + QVector new_clips; + QVector delete_areas; + for (int i=0;ighosts.size();i++) { + const Ghost& g = ParentTimeline()->ghosts.at(i); + if (g.old_in != g.in || g.old_out != g.out || g.track_movement != 0 || g.clip_in != g.old_clip_in) { + + // create copy of clip + ClipPtr c = g.clip->copy(g.track->Sibling(g.track_movement)); + + c->set_timeline_in(g.in); + c->set_timeline_out(g.out); + + delete_areas.append(g.ToSelection()); + + old_clips.append(g.clip); + new_clips.append(c); + + } + } + + if (new_clips.size() > 0) { + + // delete anything under the new clips + sequence()->DeleteAreas(ca, delete_areas, false); + + // relink duplicated clips + olive::timeline::RelinkClips(old_clips, new_clips); + + // add them + ca->append(new AddClipCommand(new_clips)); + + } + + } else { + + // if we're not holding alt, this will just be a move + + // if the user is holding ctrl, perform an insert rather than an overwrite + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_POINTER && ctrl) { + + insert_clips(ca, sequence()); + + } else if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_POINTER || olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_SLIDE) { + + // if the user is not holding ctrl, we start standard clip movement + + // delete everything under the new clips + QVector delete_areas; + for (int i=0;ighosts.size();i++) { + // step 1 - set clips that are moving to "undeletable" (to avoid step 2 deleting any part of them) + const Ghost& g = ParentTimeline()->ghosts.at(i); + + // set clip to undeletable so it's unaffected by delete_areas_and_relink() below + g.clip->undeletable = true; + + // if the user was moving a transition make sure they're undeletable too + if (g.transition != nullptr) { + g.transition->parent_clip->undeletable = true; + if (g.transition->secondary_clip != nullptr) { + g.transition->secondary_clip->undeletable = true; + } + } + + // set area to delete + delete_areas.append(g.ToSelection()); + } + + sequence()->DeleteAreas(ca, delete_areas, false); + + // clean up, i.e. make everything not undeletable again + for (int i=0;ighosts.size();i++) { + const Ghost& g = ParentTimeline()->ghosts.at(i); + g.clip->undeletable = false; + + if (g.transition != nullptr) { + g.transition->parent_clip->undeletable = false; + if (g.transition->secondary_clip != nullptr) { + g.transition->secondary_clip->undeletable = false; + } + } + } + } + + // finally, perform actual movement of clips + for (int i=0;ighosts.size();i++) { + Ghost& g = ParentTimeline()->ghosts[i]; + + Clip* c = g.clip; + + if (g.transition == nullptr) { + + // if this was a clip rather than a transition + + c->Move(ca, + (g.in - g.old_in), + (g.out - g.old_out), + (g.clip_in - g.old_clip_in), + g.track->Sibling(g.track_movement), + false, + true); + + } else { + + // if the user was moving a transition + + bool is_opening_transition = (g.transition == c->opening_transition); + long new_transition_length = g.out - g.in; + if (g.transition->secondary_clip != nullptr) new_transition_length >>= 1; + ca->append( + new ModifyTransitionCommand(is_opening_transition ? c->opening_transition : c->closing_transition, + new_transition_length) + ); + + long clip_length = c->length(); + + if (g.transition->secondary_clip != nullptr) { + + // if this is a shared transition + if (g.in != g.old_in && g.trim_type == olive::timeline::TRIM_NONE) { + long movement = g.in - g.old_in; + + // check if the transition is going to extend the out point (opening clip) + long timeline_out_movement = 0; + if (g.out > g.transition->parent_clip->timeline_out()) { + timeline_out_movement = g.out - g.transition->parent_clip->timeline_out(); + } + + // check if the transition is going to extend the in point (closing clip) + long timeline_in_movement = 0; + if (g.in < g.transition->secondary_clip->timeline_in()) { + timeline_in_movement = g.in - g.transition->secondary_clip->timeline_in(); + } + + g.transition->parent_clip->Move(ca, movement, timeline_out_movement, movement, g.transition->parent_clip->track(), false, true); + g.transition->secondary_clip->Move(ca, timeline_in_movement, movement, timeline_in_movement, g.transition->secondary_clip->track(), false, true); + + make_room_for_transition(ca, g.transition->parent_clip, kTransitionOpening, g.in, g.out, false); + make_room_for_transition(ca, g.transition->secondary_clip, kTransitionClosing, g.in, g.out, false); + + } + + } else if (is_opening_transition) { + + if (g.in != g.old_in) { + // if transition is going to make the clip bigger, make the clip bigger + + // check if the transition is going to extend the out point + long timeline_out_movement = 0; + if (g.out > g.transition->parent_clip->timeline_out()) { + timeline_out_movement = g.out - g.transition->parent_clip->timeline_out(); + } + + c->Move(ca, + (g.in - g.old_in), + timeline_out_movement, + (g.clip_in - g.old_clip_in), + g.track, + false, + true); + clip_length -= (g.in - g.old_in); + } + + make_room_for_transition(ca, c, kTransitionOpening, g.in, g.out, false); + + } else { + + if (g.out != g.old_out) { + + // check if the transition is going to extend the in point + long timeline_in_movement = 0; + if (g.in < g.transition->parent_clip->timeline_in()) { + timeline_in_movement = g.in - g.transition->parent_clip->timeline_in(); + } + + // if transition is going to make the clip bigger, make the clip bigger + c->Move(ca, timeline_in_movement, (g.out - g.old_out), timeline_in_movement, c->track(), false, true); + clip_length += (g.out - g.old_out); + } + + make_room_for_transition(ca, c, kTransitionClosing, g.in, g.out, false); + + } + } + } + + // time to verify the transitions of moved clips + for (int i=0;ighosts.size();i++) { + const Ghost& g = ParentTimeline()->ghosts.at(i); + + // only applies to moving clips, transitions are verified above instead + if (g.transition == nullptr) { + Clip* c = g.clip; + + long new_clip_length = g.out - g.in; + + // using a for loop between constants to repeat the same steps for the opening and closing transitions + for (int t=kTransitionOpening;t<=kTransitionClosing;t++) { + + TransitionPtr transition = (t == kTransitionOpening) ? c->opening_transition : c->closing_transition; + + // check the whether the clip has a transition here + if (transition != nullptr) { + + // if the new clip size exceeds the opening transition's length, resize the transition + if (new_clip_length < transition->get_true_length()) { + ca->append(new ModifyTransitionCommand(transition, new_clip_length)); + } + + // check if the transition is a shared transition (it'll never have a secondary clip if it isn't) + if (transition->secondary_clip != nullptr) { + + // check if the transition's "edge" is going to move + if ((t == kTransitionOpening && g.in != g.old_in) + || (t == kTransitionClosing && g.out != g.old_out) + || (g.track_movement != 0)) { + + // if we're here, this clip shares its opening transition as the closing transition of another + // clip (or vice versa), and the in point is moving, so we may have to account for this + + // the other clip sharing this transition may be moving as well, meaning we don't have to do + // anything + + bool split = true; + + // loop through ghosts to find out + + // for a shared transition, the secondary_clip will always be the closing transition side and + // the parent_clip will always be the opening transition side + Clip* search_clip = (t == kTransitionOpening) + ? transition->secondary_clip : transition->parent_clip; + + for (int j=0;jghosts.size();j++) { + const Ghost& other_clip_ghost = ParentTimeline()->ghosts.at(j); + + if (other_clip_ghost.clip == search_clip) { + + // we found the other clip in the current ghosts/selections + + // see if it's destination edge will be equal to this ghost's edge (in which case the + // transition doesn't need to change) + // + // also only do this if j is less than i, because it only needs to happen once and chances are + // the other clip already + + bool edges_still_touch = (other_clip_ghost.track_movement == g.track_movement); + + if (edges_still_touch) { + if (t == kTransitionOpening) { + edges_still_touch = (other_clip_ghost.out == g.in); + } else { + edges_still_touch = (other_clip_ghost.in == g.out); + } + } + + if (edges_still_touch || j < i) { + split = false; + } + + break; + } + } + + if (split) { + // separate shared transition into one transition for each clip + + if (t == kTransitionOpening) { + + // set transition to single-clip mode + ca->append(new SetPointer(reinterpret_cast(&transition->secondary_clip), + nullptr)); + + // create duplicate transition for other clip + ca->append(new AddTransitionCommand(nullptr, + transition->secondary_clip, + transition, + kInvalidNode, + 0)); + + } else { + + // set transition to single-clip mode + ca->append(new SetPointer(reinterpret_cast(&transition->secondary_clip), + nullptr)); + + // that transition will now attach to the other clip, so we duplicate it for this one + + // create duplicate transition for this clip + ca->append(new AddTransitionCommand(nullptr, + transition->secondary_clip, + transition, + kInvalidNode, + 0)); + + } + } + + } + } + } + } + } + } + } + + // move selections to match new ghosts + QVector new_selections; + for (int i=0;ighosts.size();i++) { + new_selections.append(ParentTimeline()->ghosts.at(i).ToSelection()); + } + ca->append(new SetSelectionsCommand(sequence(), sequence()->Selections(), new_selections)); + + push_undo = true; + + } + } else if (ParentTimeline()->selecting || ParentTimeline()->rect_select_proc) { + } else if (ParentTimeline()->transition_tool_proc) { + const Ghost& g = ParentTimeline()->ghosts.at(0); + + // if the transition is greater than 0 length (if it is 0, we make nothing) + if (g.in != g.out) { + + // get transition coordinates on the timeline + long transition_start = qMin(g.in, g.out); + long transition_end = qMax(g.in, g.out); + + // get clip references from tool's cached data + Clip* open = ParentTimeline()->transition_tool_open_clip; + + Clip* close = ParentTimeline()->transition_tool_close_clip; + + + + // if it's shared, the transition length is halved (one half for each clip will result in the full length) + long transition_length = transition_end - transition_start; + if (open != nullptr && close != nullptr) { + transition_length /= 2; + } + + VerifyTransitionsAfterCreating(ca, open, close, transition_start, transition_end); + + // finally, add the transition to these clips + ca->append(new AddTransitionCommand(open, + close, + nullptr, + ParentTimeline()->transition_tool_meta, + transition_length)); + + push_undo = true; + } + } else if (ParentTimeline()->splitting) { + + QVector split_tracks = GetSplitTracksFromMouseCoords(false, + ParentTimeline()->drag_frame_start, + ParentTimeline()->drag_y_start, + event->pos().y()); + + for (int i=0;iGetClipFromPoint(ParentTimeline()->drag_frame_start); + if (split_index != nullptr + && sequence()->SplitClipAtPositions(ca, split_index, {ParentTimeline()->drag_frame_start}, !alt)) { + push_undo = true; + } + } + } + + // remove duplicate selections + sequence()->TidySelections(); + + if (push_undo) { + olive::undo_stack.push(ca); + } else { + delete ca; + } + + // destroy all ghosts + ParentTimeline()->ghosts.clear(); + + // clear split tracks + ParentTimeline()->split_tracks.clear(); + + ParentTimeline()->selecting = false; + ParentTimeline()->moving_proc = false; + ParentTimeline()->moving_init = false; + ParentTimeline()->splitting = false; + olive::timeline::snapped = false; + ParentTimeline()->rect_select_init = false; + ParentTimeline()->rect_select_proc = false; + ParentTimeline()->transition_tool_init = false; + ParentTimeline()->transition_tool_proc = false; + pre_clips.clear(); + post_clips.clear(); + + update_ui(true); + } + ParentTimeline()->hand_moving = false; + } +} + +void TimelineView::init_ghosts() { + for (int i=0;ighosts.size();i++) { + Ghost& g = ParentTimeline()->ghosts[i]; + Clip* c = g.clip; + + g.track = c->track(); + g.clip_in = g.old_clip_in = c->clip_in(); + + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_SLIP) { + g.clip_in = g.old_clip_in = c->clip_in(true); + g.in = g.old_in = c->timeline_in(true); + g.out = g.old_out = c->timeline_out(true); + g.ghost_length = g.old_out - g.old_in; + } else if (g.transition == nullptr) { + // this ghost is for a clip + g.in = g.old_in = c->timeline_in(); + g.out = g.old_out = c->timeline_out(); + g.ghost_length = g.old_out - g.old_in; + } else if (g.transition == c->opening_transition) { + g.in = g.old_in = c->timeline_in(true); + g.ghost_length = c->opening_transition->get_length(); + g.out = g.old_out = g.in + g.ghost_length; + } else if (g.transition == c->closing_transition) { + g.out = g.old_out = c->timeline_out(true); + g.ghost_length = c->closing_transition->get_length(); + g.in = g.old_in = g.out - g.ghost_length; + g.clip_in = g.old_clip_in = c->clip_in() + c->length() - c->closing_transition->get_true_length(); + } + + // used for trim ops + g.media_length = c->media_length(); + } + /* + for (int i=0;iselections.size();i++) { + Selection& s = sequence()->selections[i]; + s.old_in = s.in; + s.old_out = s.out; + s.old_track = s.track; + } + */ +} + +void validate_transitions(Clip* c, int transition_type, long& frame_diff) { + long validator; + + if (transition_type == kTransitionOpening) { + // prevent from going below 0 on the timeline + validator = c->timeline_in() + frame_diff; + if (validator < 0) frame_diff -= validator; + + // prevent from going below 0 for the media + validator = c->clip_in() + frame_diff; + if (validator < 0) frame_diff -= validator; + + // prevent transition from exceeding media length + validator -= c->media_length(); + if (validator > 0) frame_diff -= validator; + } else { + // prevent from going below 0 on the timeline + validator = c->timeline_out() + frame_diff; + if (validator < 0) frame_diff -= validator; + + // prevent from going below 0 for the media + validator = c->clip_in() + c->length() + frame_diff; + if (validator < 0) frame_diff -= validator; + + // prevent transition from exceeding media length + validator -= c->media_length(); + if (validator > 0) frame_diff -= validator; + } +} + +void TimelineView::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { + int effective_tool = olive::timeline::current_tool; + if (ParentTimeline()->importing || ParentTimeline()->creating) effective_tool = olive::timeline::TIMELINE_TOOL_POINTER; + + long frame_diff = (lock_frame) ? 0 : ParentTimeline()->getTimelineFrameFromScreenPoint(mouse_pos.x()) - ParentTimeline()->drag_frame_start; + long validator; + long earliest_in_point = LONG_MAX; + int track_diff = getTrackIndexFromScreenPoint(mouse_pos.y()) - ParentTimeline()->drag_track_start->Index(); + + // first try to snap + long fm; + + if (effective_tool != olive::timeline::TIMELINE_TOOL_SLIP) { + // slipping doesn't move the clips so we don't bother snapping for it + for (int i=0;ighosts.size();i++) { + const Ghost& g = ParentTimeline()->ghosts.at(i); + + // snap ghost's in point + if ((olive::timeline::current_tool != olive::timeline::TIMELINE_TOOL_TRANSITION && ParentTimeline()->trim_target == nullptr) + || g.trim_type == olive::timeline::TRIM_IN + || ParentTimeline()->transition_tool_open_clip != nullptr) { + fm = g.old_in + frame_diff; + if (sequence()->SnapPoint(&fm, ParentTimeline()->zoom, true, true, true)) { + frame_diff = fm - g.old_in; + break; + } + } + + // snap ghost's out point + if ((olive::timeline::current_tool != olive::timeline::TIMELINE_TOOL_TRANSITION && ParentTimeline()->trim_target == nullptr) + || g.trim_type == olive::timeline::TRIM_OUT + || ParentTimeline()->transition_tool_close_clip != nullptr) { + fm = g.old_out + frame_diff; + if (sequence()->SnapPoint(&fm, ParentTimeline()->zoom, true, true, true)) { + frame_diff = fm - g.old_out; + break; + } + } + + // if the ghost is attached to a clip, snap its markers too + if (ParentTimeline()->trim_target == nullptr + && g.clip != nullptr + && olive::timeline::current_tool != olive::timeline::TIMELINE_TOOL_TRANSITION) { + Clip* c = g.clip; + for (int j=0;jget_markers().size();j++) { + long marker_real_time = c->get_markers().at(j).frame + c->timeline_in() - c->clip_in(); + fm = marker_real_time + frame_diff; + if (sequence()->SnapPoint(&fm, ParentTimeline()->zoom, true, true, true)) { + frame_diff = fm - marker_real_time; + break; + } + } + } + } + } + + bool clips_are_movable = (effective_tool == olive::timeline::TIMELINE_TOOL_POINTER || effective_tool == olive::timeline::TIMELINE_TOOL_SLIDE); + + // validate ghosts + long temp_frame_diff = frame_diff; // cache to see if we change it (thus cancelling any snap) + for (int i=0;ighosts.size();i++) { + const Ghost& g = ParentTimeline()->ghosts.at(i); + Clip* c = nullptr; + if (g.clip != nullptr) { + c = g.clip; + } + + const FootageStream* ms = nullptr; + if (g.clip != nullptr && c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { + ms = c->media_stream(); + } + + // validate ghosts for trimming + if (ParentTimeline()->creating) { + // i feel like we might need something here but we haven't so far? + } else if (effective_tool == olive::timeline::TIMELINE_TOOL_SLIP) { + if ((c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) + || (ms != nullptr && !ms->infinite_length)) { + // prevent slip moving a clip below 0 clip_in + validator = g.old_clip_in - frame_diff; + if (validator < 0) frame_diff += validator; + + // prevent slip moving clip beyond media length + validator += g.ghost_length; + if (validator > g.media_length) frame_diff += validator - g.media_length; + } + } else if (g.trim_type != olive::timeline::TRIM_NONE) { + if (g.trim_type == olive::timeline::TRIM_IN) { + // prevent clip/transition length from being less than 1 frame long + validator = g.ghost_length - frame_diff; + if (validator < 1) frame_diff -= (1 - validator); + + // prevent timeline in from going below 0 + if (effective_tool != olive::timeline::TIMELINE_TOOL_RIPPLE) { + validator = g.old_in + frame_diff; + if (validator < 0) frame_diff -= validator; + } + + // prevent clip_in from going below 0 + if ((c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) + || (ms != nullptr && !ms->infinite_length)) { + validator = g.old_clip_in + frame_diff; + if (validator < 0) frame_diff -= validator; + } + } else { + // prevent clip length from being less than 1 frame long + validator = g.ghost_length + frame_diff; + if (validator < 1) frame_diff += (1 - validator); + + // prevent clip length exceeding media length + if ((c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) + || (ms != nullptr && !ms->infinite_length)) { + validator = g.old_clip_in + g.ghost_length + frame_diff; + if (validator > g.media_length) frame_diff -= validator - g.media_length; + } + } + + // prevent dual transition from going below 0 on the primary or media length on the secondary + if (g.transition != nullptr && g.transition->secondary_clip != nullptr) { + Clip* otc = g.transition->parent_clip; + Clip* ctc = g.transition->secondary_clip; + + if (g.trim_type == olive::timeline::TRIM_IN) { + frame_diff -= g.transition->get_true_length(); + } else { + frame_diff += g.transition->get_true_length(); + } + + validate_transitions(otc, kTransitionOpening, frame_diff); + validate_transitions(ctc, kTransitionClosing, frame_diff); + + frame_diff = -frame_diff; + validate_transitions(otc, kTransitionOpening, frame_diff); + validate_transitions(ctc, kTransitionClosing, frame_diff); + frame_diff = -frame_diff; + + if (g.trim_type == olive::timeline::TRIM_IN) { + frame_diff += g.transition->get_true_length(); + } else { + frame_diff -= g.transition->get_true_length(); + } + } + + // ripple ops + if (effective_tool == olive::timeline::TIMELINE_TOOL_RIPPLE) { + for (int j=0;jtrim_type == olive::timeline::TRIM_IN) { + validator = post->timeline_in() - frame_diff; + if (validator < 0) frame_diff += validator; + } + + // prevent any post-clips colliding with pre-clips + for (int k=0;ktrack() == post->track()) { + if (ParentTimeline()->trim_type == olive::timeline::TRIM_IN) { + validator = post->timeline_in() - frame_diff - pre->timeline_out(); + if (validator < 0) frame_diff += validator; + } else { + validator = post->timeline_in() + frame_diff - pre->timeline_out(); + if (validator < 0) frame_diff -= validator; + } + } + } + } + } + } else if (clips_are_movable) { // validate ghosts for moving + // prevent clips from moving below 0 on the timeline + validator = g.old_in + frame_diff; + if (validator < 0) frame_diff -= validator; + + if (g.transition != nullptr) { + if (g.transition->secondary_clip != nullptr) { + // prevent dual transitions from going below 0 on the primary or above media length on the secondary + + validator = g.transition->parent_clip->clip_in(true) + frame_diff; + if (validator < 0) frame_diff -= validator; + + validator = g.transition->secondary_clip->timeline_out(true) - g.transition->secondary_clip->timeline_in(true) - g.transition->get_length() + g.transition->secondary_clip->clip_in(true) + frame_diff; + if (validator < 0) frame_diff -= validator; + + validator = g.transition->parent_clip->clip_in() + frame_diff - g.transition->parent_clip->media_length() + g.transition->get_true_length(); + if (validator > 0) frame_diff -= validator; + + validator = g.transition->secondary_clip->timeline_out(true) - g.transition->secondary_clip->timeline_in(true) + g.transition->secondary_clip->clip_in(true) + frame_diff - g.transition->secondary_clip->media_length(); + if (validator > 0) frame_diff -= validator; + } else { + // prevent clip_in from going below 0 + if ((c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) + || (ms != nullptr && !ms->infinite_length)) { + validator = g.old_clip_in + frame_diff; + if (validator < 0) frame_diff -= validator; + } + + // prevent clip length exceeding media length + if ((c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) + || (ms != nullptr && !ms->infinite_length)) { + validator = g.old_clip_in + g.ghost_length + frame_diff; + if (validator > g.media_length) frame_diff -= validator - g.media_length; + } + } + } + + // Prevent any clips from going below the "zeroeth" track + + if (ParentTimeline()->importing || g.track->type() == track_list_->type()) { + + int track_validator = g.track->Index() + track_diff; + if (track_validator < 0) { + track_diff -= track_validator; + } + + } + + } else if (effective_tool == olive::timeline::TIMELINE_TOOL_TRANSITION) { + if (ParentTimeline()->transition_tool_open_clip == nullptr + || ParentTimeline()->transition_tool_close_clip == nullptr) { + validate_transitions(c, g.media_stream, frame_diff); + } else { + // open transition clip + Clip* otc = ParentTimeline()->transition_tool_open_clip; + + // close transition clip + Clip* ctc = ParentTimeline()->transition_tool_close_clip; + + if (g.media_stream == kTransitionClosing) { + // swap + Clip* temp = otc; + otc = ctc; + ctc = temp; + } + + // always gets a positive frame_diff + validate_transitions(otc, kTransitionOpening, frame_diff); + validate_transitions(ctc, kTransitionClosing, frame_diff); + + // always gets a negative frame_diff + frame_diff = -frame_diff; + validate_transitions(otc, kTransitionOpening, frame_diff); + validate_transitions(ctc, kTransitionClosing, frame_diff); + frame_diff = -frame_diff; + } + } + } + + // if the above validation changed the frame movement, it's unlikely we're still snapped + if (temp_frame_diff != frame_diff) { + olive::timeline::snapped = false; + } + + // apply changes to ghosts + for (int i=0;ighosts.size();i++) { + Ghost& g = ParentTimeline()->ghosts[i]; + + if (effective_tool == olive::timeline::TIMELINE_TOOL_SLIP) { + g.clip_in = g.old_clip_in - frame_diff; + } else if (g.trim_type != olive::timeline::TRIM_NONE) { + long ghost_diff = frame_diff; + + // prevent trimming clips from overlapping each other + for (int j=0;jghosts.size();j++) { + const Ghost& comp = ParentTimeline()->ghosts.at(j); + if (i != j && g.track == comp.track) { + long validator; + if (g.trim_type == olive::timeline::TRIM_IN && comp.out < g.out) { + validator = (g.old_in + ghost_diff) - comp.out; + if (validator < 0) ghost_diff -= validator; + } else if (comp.in > g.in) { + validator = (g.old_out + ghost_diff) - comp.in; + if (validator > 0) ghost_diff -= validator; + } + } + } + + // apply changes + if (g.transition != nullptr && g.transition->secondary_clip != nullptr) { + if (g.trim_type == olive::timeline::TRIM_IN) ghost_diff = -ghost_diff; + g.in = g.old_in - ghost_diff; + g.out = g.old_out + ghost_diff; + } else if (g.trim_type == olive::timeline::TRIM_IN) { + g.in = g.old_in + ghost_diff; + g.clip_in = g.old_clip_in + ghost_diff; + } else { + g.out = g.old_out + ghost_diff; + } + } else if (clips_are_movable) { + g.in = g.old_in + frame_diff; + g.out = g.old_out + frame_diff; + + if (g.transition != nullptr + && g.transition == g.clip->opening_transition) { + g.clip_in = g.old_clip_in + frame_diff; + } + + if (ParentTimeline()->importing) { + + g.track_movement = getTrackIndexFromScreenPoint(mouse_pos.y()); + + } else if (g.track->type() == track_list_->type() && g.transition == nullptr) { + + g.track_movement = track_diff; + + } + } else if (effective_tool == olive::timeline::TIMELINE_TOOL_TRANSITION) { + if (ParentTimeline()->transition_tool_open_clip != nullptr + && ParentTimeline()->transition_tool_close_clip != nullptr) { + g.in = g.old_in - frame_diff; + g.out = g.old_out + frame_diff; + } else if (ParentTimeline()->transition_tool_open_clip == g.clip) { + g.out = g.old_out + frame_diff; + } else { + g.in = g.old_in + frame_diff; + } + } + + earliest_in_point = qMin(earliest_in_point, g.in); + } + + // apply changes to selections + /* + if (effective_tool != olive::timeline::TIMELINE_TOOL_SLIP && !ParentTimeline()->importing && !ParentTimeline()->creating) { + for (int i=0;iselections.size();i++) { + Selection& s = sequence()->selections[i]; + if (ParentTimeline()->trim_target > -1) { + if (ParentTimeline()->trim_type == olive::timeline::TRIM_IN) { + s.in = s.old_in + frame_diff; + } else { + s.out = s.old_out + frame_diff; + } + } else if (clips_are_movable) { + for (int i=0;iselections.size();i++) { + Selection& s = sequence()->selections[i]; + s.in = s.old_in + frame_diff; + s.out = s.old_out + frame_diff; + s.track = s.old_track; + + if (ParentTimeline()->importing) { + int abs_track_diff = abs(track_diff); + if (s.old_track < 0) { + s.track -= abs_track_diff; + } else { + s.track += abs_track_diff; + } + } else { + if (same_sign(s.track, ParentTimeline()->drag_track_start)) s.track += track_diff; + } + } + } + } + } + */ + + if (ParentTimeline()->importing) { + QToolTip::showText(mapToGlobal(mouse_pos), frame_to_timecode(earliest_in_point, olive::config.timecode_view, sequence()->frame_rate)); + } else { + QString tip = ((frame_diff < 0) ? "-" : "+") + frame_to_timecode(qAbs(frame_diff), olive::config.timecode_view, sequence()->frame_rate); + + if (ParentTimeline()->trim_target != nullptr) { + // find which clip is being moved + const Ghost* g = nullptr; + for (int i=0;ighosts.size();i++) { + if (ParentTimeline()->ghosts.at(i).clip == ParentTimeline()->trim_target) { + g = &ParentTimeline()->ghosts.at(i); + break; + } + } + + if (g != nullptr) { + tip += " " + tr("Duration:") + " "; + long len = (g->old_out-g->old_in); + if (ParentTimeline()->trim_type == olive::timeline::TRIM_IN) { + len -= frame_diff; + } else { + len += frame_diff; + } + tip += frame_to_timecode(len, olive::config.timecode_view, sequence()->frame_rate); + } + } + + QToolTip::showText(mapToGlobal(mouse_pos), tip); + } +} + +void TimelineView::mouseMoveEvent(QMouseEvent *event) { + + // interrupt any potential tooltip about to show + tooltip_timer.stop(); + + if (sequence() != nullptr) { + bool alt = (event->modifiers() & Qt::AltModifier); + + // store current frame/track corresponding to the cursor + ParentTimeline()->cursor_frame = ParentTimeline()->getTimelineFrameFromScreenPoint(event->pos().x()); + ParentTimeline()->cursor_track = getTrackFromScreenPoint(event->pos().y()); + + // if holding the mouse button down, let's scroll to that location + if (event->buttons() != 0 && olive::timeline::current_tool != olive::timeline::TIMELINE_TOOL_HAND) { + ParentTimeline()->scroll_to_frame(ParentTimeline()->cursor_frame); + } + + // determine if the action should be "inserting" rather than "overwriting" + // Default behavior is to replace/overwrite clips under any clips we're dropping over them. Inserting will + // split and move existing clips at the drop point to make space for the drop + ParentTimeline()->move_insert = ((event->modifiers() & Qt::ControlModifier) + && (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_POINTER + || ParentTimeline()->importing + || ParentTimeline()->creating)); + + // if we're not currently resizing already, default track resizing to false (we'll set it to true later if + // the user is still hovering over a track line) + if (!ParentTimeline()->moving_init) { + track_resizing = false; + } + + // if the current tool uses an on-screen visible cursor, we snap the cursor to the timeline + if (current_tool_shows_cursor()) { + sequence()->SnapPoint(&ParentTimeline()->cursor_frame, + ParentTimeline()->zoom, + + // only snap to the playhead if the edit tool doesn't force the playhead to + // follow it (or if we're not selecting since that means the playhead is + // static at the moment) + !olive::config.edit_tool_also_seeks || !ParentTimeline()->selecting, + + true, + true); + } + + if (ParentTimeline()->selecting) { + + QVector selections = ParentTimeline()->selection_cache; + + if (ParentTimeline()->drag_track_start != nullptr || ParentTimeline()->cursor_track != nullptr) { + + long selection_in = qMin(ParentTimeline()->drag_frame_start, ParentTimeline()->cursor_frame); + long selection_out = qMax(ParentTimeline()->drag_frame_start, ParentTimeline()->cursor_frame); + + int selection_top = mapToGlobal(QPoint(0, ParentTimeline()->drag_y_start)).y(); + int selection_bottom = mapToGlobal(event->pos()).y(); + + QVector selected_tracks = ParentTimeline()->GetTracksInRectangle(selection_top, + selection_bottom); + + Track* track; + + foreach (track, selected_tracks) { + + selections.append(Selection(selection_in, selection_out, track)); + + // If the config is set to select links as well with the edit tool + if (olive::config.edit_tool_selects_links) { + + for (int j=0;jClipCount();j++) { + + Clip* c = track->GetClip(j).get(); + + // See if this selection contains this clip + if (!(c->timeline_in() > selection_out || c->timeline_out() < selection_in)) { + + // If so, select its links as well + for (int k=0;klinked.size();k++) { + Clip* link = c->linked.at(k); + + // Make sure there isn't already a selection for this link + bool found = false; + for (int l=0;ltrack()) { + found = true; + break; + } + } + // If not, make one now + if (!found) { + selections.append(Selection(selection_in, selection_out, link->track())); + } + } + } + } + } + } + } + + sequence()->SetSelections(selections); + + /* + // get number of selections based on tracks in selection area + int selection_tool_count = 1 + + qMax(ParentTimeline()->cursor_track->Index(), ParentTimeline()->drag_track_start->Index()) + - qMin(ParentTimeline()->cursor_track->Index(), ParentTimeline()->drag_track_start->Index()); + + // add count to selection offset for the total number of selection objects + // (offset is usually 0, unless the user is holding shift in which case we add to existing selections) + int selection_count = selection_tool_count + ParentTimeline()->selection_offset; + + // resize selection object array to new count + if (sequence()->selections.size() != selection_count) { + sequence()->selections.resize(selection_count); + } + + // loop through tracks in selection area and adjust them accordingly + int minimum_selection_track = qMin(ParentTimeline()->cursor_track, ParentTimeline()->drag_track_start); + int maximum_selection_track = qMax(ParentTimeline()->cursor_track, ParentTimeline()->drag_track_start); + long selection_in = qMin(ParentTimeline()->drag_frame_start, ParentTimeline()->cursor_frame); + long selection_out = qMax(ParentTimeline()->drag_frame_start, ParentTimeline()->cursor_frame); + for (int i=ParentTimeline()->selection_offset;iselections[i]; + s.track = minimum_selection_track + i - ParentTimeline()->selection_offset; + s.in = selection_in; + s.out = selection_out; + } + + // If the config is set to select links as well with the edit tool + if (olive::config.edit_tool_selects_links) { + + // find which clips are selected + for (int j=0;jclips.size();j++) { + + Clip* c = sequence()->clips.at(j).get(); + + if (c != nullptr && c->IsSelected(false)) { + + // loop through linked clips + for (int k=0;klinked.size();k++) { + + ClipPtr link = sequence()->clips.at(c->linked.at(k)); + + // see if one of the selections is already covering this track + if (!(link->track() >= minimum_selection_track + && link->track() <= maximum_selection_track)) { + + // clip is not in selectin area, time to select it + Selection link_sel; + link_sel.in = selection_in; + link_sel.out = selection_out; + link_sel.track = link->track(); + sequence()->selections.append(link_sel); + + } + + } + + } + } + } + */ + + // if the config is set to seek with the edit too, do so now + if (olive::config.edit_tool_also_seeks) { + panel_sequence_viewer->seek(qMin(ParentTimeline()->drag_frame_start, ParentTimeline()->cursor_frame)); + } else { + // if not, repaint (seeking will trigger a repaint) + ParentTimeline()->repaint_timeline(); + } + + } else if (ParentTimeline()->hand_moving) { + + // if we're hand moving, we'll be adding values directly to the scrollbars + + // the scrollbars trigger repaints when they scroll, which is unnecessary here so we block them + ParentTimeline()->block_repaints = true; + ParentTimeline()->horizontalScrollBar->setValue(ParentTimeline()->horizontalScrollBar->value() + ParentTimeline()->drag_x_start - event->pos().x()); + emit requestScrollChange(scroll + ParentTimeline()->drag_y_start - event->pos().y()); + ParentTimeline()->block_repaints = false; + + // finally repaint + ParentTimeline()->repaint_timeline(); + + // store current cursor position for next hand move event + ParentTimeline()->drag_x_start = event->pos().x(); + ParentTimeline()->drag_y_start = event->pos().y(); + + } else if (ParentTimeline()->moving_init) { + + if (track_resizing) { + + // get cursor movement + int diff = (event->pos().y() - ParentTimeline()->drag_y_start); + + if (alignment_ == olive::timeline::kAlignmentBottom) { + diff = -diff; + } + + // add it to the current track height + int new_height = track_target->height() + diff; + + // limit track height to track minimum height constant + new_height = qMax(new_height, olive::timeline::kTrackMinHeight); + + // set the track height + track_target->set_height(new_height); + + // store current cursor position for next track resize event + ParentTimeline()->drag_y_start = event->pos().y(); + + update(); + + } else if (ParentTimeline()->moving_proc) { + + // we're currently dragging ghosts + update_ghosts(event->pos(), event->modifiers() & Qt::ShiftModifier); + + } else { + + // Prepare to start moving clips in some capacity. We create Ghost objects to store movement data before we + // actually apply it to the clips (in mouseReleaseEvent) + + // loop through clips for any currently selected + QVector partially_selected_clips = sequence()->SelectedClips(false); + for (int i=0;iIsSelected(); + + if (!add) { + // check if a transition is selected + // (only the pointer tool supports moving transitions) + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_POINTER + && (c->opening_transition != nullptr || c->closing_transition != nullptr)) { + + // check if any selections contain a whole transition + if (c->IsTransitionSelected(kTransitionOpening)) { + g.transition = c->opening_transition; + add = true; + } else if (c->IsTransitionSelected(kTransitionClosing)) { + g.transition = c->closing_transition; + add = true; + } + + } + } + + if (add && g.transition != nullptr) { + + // transition may be a shared transition, check if it's already been added elsewhere + for (int j=0;jghosts.size();j++) { + if (ParentTimeline()->ghosts.at(j).transition == g.transition) { + add = false; + break; + } + } + } + + if (add) { + g.clip = c; + g.trim_type = ParentTimeline()->trim_type; + ParentTimeline()->ghosts.append(g); + } + } + } + + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_SLIDE) { + + // for the slide tool, we add the surrounding clips as ghosts that are getting trimmed the opposite way + + // store original array size since we'll be adding to it + int ghost_arr_size = ParentTimeline()->ghosts.size(); + + // loop through clips for any that are "touching" the selected clips + for (int i=0;ighosts.at(i).clip; + + Clip* pre_clip = ghost_clip->track()->GetClipFromPoint(ghost_clip->timeline_in() - 1); + Clip* post_clip = ghost_clip->track()->GetClipFromPoint(ghost_clip->timeline_out() + 1); + + // Check if this clip is already in the ghosts, in which case don't add it + for (int j=0;jghosts.at(j).clip == pre_clip) { + pre_clip = nullptr; + } else if (ParentTimeline()->ghosts.at(j).clip == post_clip) { + post_clip = nullptr; + } + } + + Ghost gh; + gh.transition = nullptr; + + if (pre_clip != nullptr) { + gh.clip = pre_clip; + gh.trim_type = olive::timeline::TRIM_OUT; + ParentTimeline()->ghosts.append(gh); + } + + if (post_clip != nullptr) { + gh.clip = post_clip; + gh.trim_type = olive::timeline::TRIM_IN; + ParentTimeline()->ghosts.append(gh); + } + } + } + + // set up ghost defaults + init_ghosts(); + + // if the ripple tool is selected, prepare to ripple + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_RIPPLE) { + + long axis = LONG_MAX; + + // find the earliest point within the selected clips which is the point we'll ripple around + // also store the currently selected clips so we don't have to do it later + QVector ghost_clips; + ghost_clips.resize(ParentTimeline()->ghosts.size()); + + for (int i=0;ighosts.size();i++) { + Clip* c = ParentTimeline()->ghosts.at(i).clip; + if (ParentTimeline()->trim_type == olive::timeline::TRIM_IN) { + axis = qMin(axis, c->timeline_in()); + } else { + axis = qMin(axis, c->timeline_out()); + } + + // store clip reference + ghost_clips[i] = c; + } + + // loop through clips and cache which are earlier than the axis and which after after + QVector sequence_clips = sequence()->GetAllClips(); + for (int i=0;itimeline_in() >= axis); + + // construct the list of pre and post clips + QVector& clip_list = (clip_is_post) ? post_clips : pre_clips; + + // check if there's already a clip in this list on this track, and if this clip is closer or not + bool found = false; + for (int j=0;jtrack() == c->track()) { + + // if the clip is closer, use this one instead of the current one in the list + if ((!clip_is_post && compare->timeline_out() < c->timeline_out()) + || (clip_is_post && compare->timeline_in() > c->timeline_in())) { + clip_list[j] = c; + } + + found = true; + break; + } + + } + + // if there is no clip on this track in the list, add it + if (!found) { + clip_list.append(c); + } + } + } + } + + // store selections + /* + selection_command = new SetSelectionsCommand(sequence().get()); + selection_command->old_data = sequence()->selections; + */ + + // ready to start moving clips + ParentTimeline()->moving_proc = true; + } + + update_ui(false); + + } else if (ParentTimeline()->splitting) { + + ParentTimeline()->split_tracks = GetSplitTracksFromMouseCoords(!alt, + ParentTimeline()->drag_frame_start, + ParentTimeline()->drag_y_start, + event->pos().y()); + update_ui(false); + + } else if (ParentTimeline()->rect_select_init) { + + // set if the user started dragging at point where there was no clip + + if (ParentTimeline()->rect_select_proc) { + + // we're currently rectangle selecting + + QVector selections = ParentTimeline()->selection_cache; + + // set the right/bottom coords to the current mouse position + // (left/top were set to the starting drag position earlier) + ParentTimeline()->rect_select_rect.setBottomRight(mapToGlobal(event->pos())); + + QVector selected_clips; + + QVector selected_tracks = ParentTimeline()->GetTracksInRectangle(ParentTimeline()->rect_select_rect.top(), + ParentTimeline()->rect_select_rect.bottom()); + + long frame_min = qMin(ParentTimeline()->drag_frame_start, ParentTimeline()->cursor_frame); + long frame_max = qMax(ParentTimeline()->drag_frame_start, ParentTimeline()->cursor_frame); + + Track* track; + + foreach (track, selected_tracks) { + // Loop through track's clips for clips touching this rectangle + for (int i=0;iClipCount();i++) { + Clip* clip = track->GetClip(i).get(); + if (!(clip->timeline_out() < frame_min || clip->timeline_in() > frame_max) ) { + + // create a group of the clip (and its links if alt is not pressed) + QVector session_clips; + session_clips.append(clip); + + if (!alt) { + session_clips.append(clip->linked); + } + + // for each of these clips, see if clip has already been added - + // this can easily happen due to adding linked clips + for (int j=0;jToSelection()); + } + + sequence()->SetSelections(selections); + + ParentTimeline()->repaint_timeline(); + } else { + + // set up rectangle selecting + ParentTimeline()->rect_select_rect.setTopLeft(mapToGlobal(event->pos())); + ParentTimeline()->rect_select_rect.setSize(QSize(0, 0)); + + ParentTimeline()->rect_select_proc = true; + + } + } else if (current_tool_shows_cursor()) { + + // we're not currently performing an action (click is not pressed), but redraw because we have an on-screen cursor + ParentTimeline()->repaint_timeline(); + + } else if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_POINTER || + olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_RIPPLE || + olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_ROLLING) { + + // hide any tooltip that may be currently showing + QToolTip::hideText(); + + // cache cursor position + QPoint pos = event->pos(); + + // + // check to see if the cursor is on a clip edge + // + + // threshold around a trim point that the cursor can be within and still considered "trimming" + int lim = 10; // FIXME Magic number for the clip trimming threshold + int mouse_frame_lower = pos.x() - lim; + int mouse_frame_upper = pos.x() + lim; + + // used to determine whether we the cursor found a trim point or not + bool found = false; + + // used to determine how close the cursor is to a trim point + // (and more specifically, whether another point is closer or not) + long closeness = LONG_MAX; + + // we default to selecting no transition, but set this accordingly if the cursor is on a transition + ParentTimeline()->transition_select = kTransitionNone; + + // we also default to no trimming which may be changed later in this function + ParentTimeline()->trim_type = olive::timeline::TRIM_NONE; + + // set currently trimming clip to -1 (aka null) + ParentTimeline()->trim_target = nullptr; + + // loop through current clips in the sequence + QVector sequence_clips = sequence()->GetAllClips(); + for (int i=0;itrack() == ParentTimeline()->cursor_track) { + + // if this cursor is inside the boundaries of this clip (hovering over the clip) + if (ParentTimeline()->cursor_frame >= c->timeline_in() && + ParentTimeline()->cursor_frame <= c->timeline_out()) { + + // start a timer to show a tooltip about this clip + tooltip_timer.start(); + tooltip_clip = c; + + // check if the cursor is specifically hovering over one of the clip's transitions + if (c->opening_transition != nullptr + && ParentTimeline()->cursor_frame <= c->timeline_in() + c->opening_transition->get_true_length()) { + + ParentTimeline()->transition_select = kTransitionOpening; + + } else if (c->closing_transition != nullptr + && ParentTimeline()->cursor_frame >= c->timeline_out() - c->closing_transition->get_true_length()) { + + ParentTimeline()->transition_select = kTransitionClosing; + + } + } + + int visual_in_point = ParentTimeline()->getTimelineScreenPointFromFrame(c->timeline_in()); + int visual_out_point = ParentTimeline()->getTimelineScreenPointFromFrame(c->timeline_out()); + + // is the cursor hovering around the clip's IN point? + if (visual_in_point > mouse_frame_lower && visual_in_point < mouse_frame_upper) { + + // test how close this IN point is to the cursor + int nc = qAbs(visual_in_point + 1 - pos.x()); + + // and test whether it's closer than the last in/out point we found + if (nc < closeness) { + + // if so, this is the point we'll make active for now (unless we find a closer one later) + ParentTimeline()->trim_target = c; + ParentTimeline()->trim_type = olive::timeline::TRIM_IN; + closeness = nc; + found = true; + + } + } + + // is the cursor hovering around the clip's OUT point? + if (visual_out_point > mouse_frame_lower && visual_out_point < mouse_frame_upper) { + + // test how close this OUT point is to the cursor + int nc = qAbs(visual_out_point - 1 - pos.x()); + + // and test whether it's closer than the last in/out point we found + if (nc < closeness) { + + // if so, this is the point we'll make active for now (unless we find a closer one later) + ParentTimeline()->trim_target = c; + ParentTimeline()->trim_type = olive::timeline::TRIM_OUT; + closeness = nc; + found = true; + + } + } + + // the pointer can be used to resize/trim transitions, here we test if the + // cursor is within the trim point of one of the clip's transitions + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_POINTER) { + + // if the clip has an opening transition + if (c->opening_transition != nullptr) { + + // cache the timeline frame where the transition ends + int transition_point = ParentTimeline()->getTimelineScreenPointFromFrame(c->timeline_in() + + c->opening_transition->get_true_length()); + + // check if the cursor is hovering around it (within the threshold) + if (transition_point > mouse_frame_lower && transition_point < mouse_frame_upper) { + + // similar to above, test how close it is and if it's closer, make this active + int nc = qAbs(transition_point - 1 - pos.x()); + if (nc < closeness) { + ParentTimeline()->trim_target = c; + ParentTimeline()->trim_type = olive::timeline::TRIM_OUT; + ParentTimeline()->transition_select = kTransitionOpening; + closeness = nc; + found = true; + } + } + } + + // if the clip has a closing transition + if (c->closing_transition != nullptr) { + + // cache the timeline frame where the transition starts + int transition_point = ParentTimeline()->getTimelineScreenPointFromFrame(c->timeline_out() + - c->closing_transition->get_true_length()); + + // check if the cursor is hovering around it (within the threshold) + if (transition_point > mouse_frame_lower && transition_point < mouse_frame_upper) { + + // similar to above, test how close it is and if it's closer, make this active + int nc = qAbs(transition_point + 1 - pos.x()); + if (nc < closeness) { + ParentTimeline()->trim_target = c; + ParentTimeline()->trim_type = olive::timeline::TRIM_IN; + ParentTimeline()->transition_select = kTransitionClosing; + closeness = nc; + found = true; + } + } + } + } + } + } + + // if the cursor is indeed on a clip edge, we set the cursor accordingly + if (found) { + + if (ParentTimeline()->trim_type == olive::timeline::TRIM_IN) { // if we're trimming an IN point + setCursor(olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_RIPPLE ? olive::cursor::LeftRipple : olive::cursor::LeftTrim); + } else { // if we're trimming an OUT point + setCursor(olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_RIPPLE ? olive::cursor::RightRipple : olive::cursor::RightTrim); + } + + } else { + // we didn't find a trim target, so we must be doing something else + // (e.g. dragging a clip or resizing the track heights) + + unsetCursor(); + + // check to see if we're resizing a track height + int mouse_pos = event->pos().y(); + + // cursor range for resizing a track + int test_range = 10; // FIXME magic number + + for (int i=0;iTrackCount();i++) { + Track* track = track_list_->TrackAt(i); + + int effective_track_index = i; + + // If alignment is top, use the next track's screen point as the resize anchor so we resize the bottom + // of the track + if (alignment_ == olive::timeline::kAlignmentTop) { + effective_track_index++; + } + + int resize_point = getScreenPointFromTrackIndex(effective_track_index); + + if (mouse_pos > resize_point - test_range + && mouse_pos < resize_point + test_range) { + track_resizing = true; + track_target = track; + setCursor(Qt::SizeVerCursor); + break; + } + } + + } + } else if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_SLIP) { + + // we're not currently performing any slipping, all we do here is set the cursor if mouse is hovering over a + // cursor + if (GetClipAtCursor() != nullptr) { + setCursor(olive::cursor::Slip); + } else { + unsetCursor(); + } + + } else if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_TRANSITION) { + + if (ParentTimeline()->transition_tool_init) { + + // the transition tool has started + + if (ParentTimeline()->transition_tool_proc) { + + // ghosts have been set up, so just run update + update_ghosts(event->pos(), event->modifiers() & Qt::ShiftModifier); + + } else { + + // transition tool is being used but ghosts haven't been set up yet, set them up now + TransitionType primary_type = kTransitionOpening; + Clip* primary = ParentTimeline()->transition_tool_open_clip; + if (primary == nullptr) { + primary_type = kTransitionClosing; + primary = ParentTimeline()->transition_tool_close_clip; + } + + Ghost g; + + g.in = g.old_in = g.out = g.old_out = (primary_type == kTransitionOpening) ? + primary->timeline_in() + : primary->timeline_out(); + + g.track = primary->track(); + g.clip = primary; + g.media_stream = primary_type; + g.trim_type = olive::timeline::TRIM_NONE; + + ParentTimeline()->ghosts.append(g); + + ParentTimeline()->transition_tool_proc = true; + + } + + } else { + + // transition tool has been selected but is not yet active, so we show screen feedback to the user on + // possible transitions + + Clip* mouse_clip = GetClipAtCursor(); + + // set default transition tool references to no clip + ParentTimeline()->transition_tool_open_clip = nullptr; + ParentTimeline()->transition_tool_close_clip = nullptr; + + if (mouse_clip != nullptr) { + + // cursor is hovering over a clip + + // check if the clip and transition are both the same sign (meaning video/audio are the same) + if (track_list_->type() == olive::node_library[ParentTimeline()->transition_tool_meta]->subtype()) { + + // the range within which the transition tool will assume the user wants to make a shared transition + // between two clips rather than just one transition on one clip + long between_range = getFrameFromScreenPoint(ParentTimeline()->zoom, TRANSITION_BETWEEN_RANGE) + 1; + + // set whether the transition is opening or closing based on whether the cursor is on the left half + // or right half of the clip + if (ParentTimeline()->cursor_frame > (mouse_clip->timeline_in() + (mouse_clip->length()/2))) { + ParentTimeline()->transition_tool_close_clip = mouse_clip; + + // if the cursor is within this range, set the post_clip to be the next clip touching + // + // getClipIndexFromCoords() will automatically set to -1 if there's no clip there which means the + // end result will be the same as not setting a clip here at all + if (ParentTimeline()->cursor_frame > mouse_clip->timeline_out() - between_range) { + ParentTimeline()->transition_tool_open_clip = mouse_clip->track()->GetClipFromPoint(mouse_clip->timeline_out()+1); + } + } else { + ParentTimeline()->transition_tool_open_clip = mouse_clip; + + if (ParentTimeline()->cursor_frame < mouse_clip->timeline_in() + between_range) { + ParentTimeline()->transition_tool_close_clip = mouse_clip->track()->GetClipFromPoint(mouse_clip->timeline_in()-1); + } + } + + } + } + } + + ParentTimeline()->repaint_timeline(); + } + } +} + +void TimelineView::leaveEvent(QEvent*) { + tooltip_timer.stop(); +} + +void TimelineView::draw_transition(QPainter& p, Clip* c, const QRect& clip_rect, QRect& text_rect, int transition_type) { + TransitionPtr t = (transition_type == kTransitionOpening) ? c->opening_transition : c->closing_transition; + if (t != nullptr) { + QColor transition_color(255, 0, 0, 16); + int transition_width = getScreenPointFromFrame(ParentTimeline()->zoom, t->get_true_length()); + int transition_height = clip_rect.height(); + int tr_y = clip_rect.y(); + int tr_x = 0; + if (transition_type == kTransitionOpening) { + tr_x = clip_rect.x(); + text_rect.setX(text_rect.x()+transition_width); + } else { + tr_x = clip_rect.right()-transition_width; + text_rect.setWidth(text_rect.width()-transition_width); + } + QRect transition_rect = QRect(tr_x, tr_y, transition_width, transition_height); + p.fillRect(transition_rect, transition_color); + QRect transition_text_rect(transition_rect.x() + olive::timeline::kClipTextPadding, transition_rect.y() + olive::timeline::kClipTextPadding, transition_rect.width() - olive::timeline::kClipTextPadding, transition_rect.height() - olive::timeline::kClipTextPadding); + if (transition_text_rect.width() > MAX_TEXT_WIDTH) { + bool draw_text = true; + + p.setPen(QColor(0, 0, 0, 96)); + if (t->secondary_clip == nullptr) { + if (transition_type == kTransitionOpening) { + p.drawLine(transition_rect.bottomLeft(), transition_rect.topRight()); + } else { + p.drawLine(transition_rect.topLeft(), transition_rect.bottomRight()); + } + } else { + if (transition_type == kTransitionOpening) { + p.drawLine(QPoint(transition_rect.left(), transition_rect.center().y()), transition_rect.topRight()); + p.drawLine(QPoint(transition_rect.left(), transition_rect.center().y()), transition_rect.bottomRight()); + draw_text = false; + } else { + p.drawLine(QPoint(transition_rect.right(), transition_rect.center().y()), transition_rect.topLeft()); + p.drawLine(QPoint(transition_rect.right(), transition_rect.center().y()), transition_rect.bottomLeft()); + } + } + + if (draw_text) { + p.setPen(Qt::white); + p.drawText(transition_text_rect, 0, t->name(), &transition_text_rect); + } + } + p.setPen(Qt::black); + p.drawRect(transition_rect); + } + +} + +void TimelineView::paintEvent(QPaintEvent*) { + // Draw clips + if (track_list_ != nullptr) { + QPainter p(this); + + // get widget width and height + emit setScrollMaximum(GetTotalAreaHeight()); + + for (int i=0;iTrackCount();i++) { + + Track* track = track_list_->TrackAt(i); + + int track_top = getScreenPointFromTrack(track); + int track_bottom = track_top + track->height(); + + if (track_bottom > 0 && track_top < height()) { + for (int j=0;jClipCount();j++) { + + Clip* clip = track->GetClip(j).get(); + + QRect clip_rect(ParentTimeline()->getTimelineScreenPointFromFrame(clip->timeline_in()), + track_top, + getScreenPointFromFrame(ParentTimeline()->zoom, clip->length()), + track->height()); + + if (alignment_ == olive::timeline::kAlignmentTop) { + clip_rect.setHeight(track->height() - 1); + } else if (alignment_ == olive::timeline::kAlignmentBottom) { + clip_rect.setTop(track_top + 1); + } + + QRect text_rect(clip_rect.left() + olive::timeline::kClipTextPadding, + clip_rect.top() + olive::timeline::kClipTextPadding, + clip_rect.width() - olive::timeline::kClipTextPadding - 1, + clip_rect.height() - olive::timeline::kClipTextPadding - 1); + if (clip_rect.left() < width() && clip_rect.right() >= 0 && clip_rect.top() < height() && clip_rect.bottom() >= 0) { + QRect actual_clip_rect = clip_rect; + if (actual_clip_rect.x() < 0) actual_clip_rect.setX(0); + if (actual_clip_rect.right() > width()) actual_clip_rect.setRight(width()); + if (actual_clip_rect.y() < 0) actual_clip_rect.setY(0); + if (actual_clip_rect.bottom() > height()) actual_clip_rect.setBottom(height()); + p.fillRect(actual_clip_rect, (clip->enabled()) ? clip->color() : QColor(96, 96, 96)); + + int thumb_x = clip_rect.x() + 1; + + if (clip->media() != nullptr && clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) { + bool draw_checkerboard = false; + QRect checkerboard_rect(clip_rect); + FootageStream* ms = clip->media_stream(); + if (ms == nullptr) { + draw_checkerboard = true; + } else if (ms->preview_done) { + // draw top and tail triangles + int triangle_size = olive::timeline::kTrackMinHeight >> 2; + if (!ms->infinite_length && clip_rect.width() > triangle_size) { + p.setPen(Qt::NoPen); + p.setBrush(QColor(80, 80, 80)); + if (clip->clip_in() == 0 + && clip_rect.x() + triangle_size > 0 + && clip_rect.y() + triangle_size > 0 + && clip_rect.x() < width() + && clip_rect.y() < height()) { + const QPoint points[3] = { + QPoint(clip_rect.x(), clip_rect.y()), + QPoint(clip_rect.x() + triangle_size, clip_rect.y()), + QPoint(clip_rect.x(), clip_rect.y() + triangle_size) + }; + p.drawPolygon(points, 3); + text_rect.setLeft(text_rect.left() + (triangle_size >> 2)); + } + if (clip->timeline_out() - clip->timeline_in() + clip->clip_in() == clip->media_length() + && clip_rect.right() - triangle_size < width() + && clip_rect.y() + triangle_size > 0 + && clip_rect.right() > 0 + && clip_rect.y() < height()) { + const QPoint points[3] = { + QPoint(clip_rect.right(), clip_rect.y()), + QPoint(clip_rect.right() - triangle_size, clip_rect.y()), + QPoint(clip_rect.right(), clip_rect.y() + triangle_size) + }; + p.drawPolygon(points, 3); + text_rect.setRight(text_rect.right() - (triangle_size >> 2)); + } + } + + p.setBrush(Qt::NoBrush); + + // draw thumbnail/waveform + long media_length = clip->media_length(); + + if (clip->type() == olive::kTypeVideo) { + // draw thumbnail + int thumb_y = p.fontMetrics().height()+olive::timeline::kClipTextPadding+olive::timeline::kClipTextPadding; + if (thumb_x < width() && thumb_y < height()) { + int space_for_thumb = clip_rect.width()-1; + if (clip->opening_transition != nullptr) { + int ot_width = getScreenPointFromFrame(ParentTimeline()->zoom, clip->opening_transition->get_true_length()); + thumb_x += ot_width; + space_for_thumb -= ot_width; + } + if (clip->closing_transition != nullptr) { + space_for_thumb -= getScreenPointFromFrame(ParentTimeline()->zoom, clip->closing_transition->get_true_length()); + } + int thumb_height = clip_rect.height()-thumb_y; + int thumb_width = qRound(thumb_height*(double(ms->video_preview.width())/double(ms->video_preview.height()))); + if (thumb_x + thumb_width >= 0 + && thumb_height > thumb_y + && thumb_y + thumb_height >= 0 + && space_for_thumb > MAX_TEXT_WIDTH) { + int thumb_clip_width = qMin(thumb_width, space_for_thumb); + p.drawImage(QRect(thumb_x, + clip_rect.y()+thumb_y, + thumb_clip_width, + thumb_height), + ms->video_preview, + QRect(0, + 0, + qRound(thumb_clip_width*(double(ms->video_preview.width())/double(thumb_width))), + ms->video_preview.height() + ) + ); + } + } + if (clip->timeline_out() - clip->timeline_in() + clip->clip_in() > clip->media_length()) { + draw_checkerboard = true; + checkerboard_rect.setLeft(ParentTimeline()->getTimelineScreenPointFromFrame(clip->media_length() + clip->timeline_in() - clip->clip_in())); + } + } else if (clip_rect.height() > olive::timeline::kTrackMinHeight) { + // draw waveform + p.setPen(QColor(80, 80, 80)); + + int waveform_start = -qMin(clip_rect.x(), 0); + int waveform_limit = qMin(clip_rect.width(), getScreenPointFromFrame(ParentTimeline()->zoom, media_length - clip->clip_in())); + + if ((clip_rect.x() + waveform_limit) > width()) { + waveform_limit -= (clip_rect.x() + waveform_limit - width()); + } else if (waveform_limit < clip_rect.width()) { + draw_checkerboard = true; + if (waveform_limit > 0) checkerboard_rect.setLeft(checkerboard_rect.left() + waveform_limit); + } + + olive::ui::DrawWaveform(clip, ms, media_length, &p, clip_rect, waveform_start, waveform_limit, ParentTimeline()->zoom); + } + } + if (draw_checkerboard) { + checkerboard_rect.setLeft(qMax(checkerboard_rect.left(), 0)); + checkerboard_rect.setRight(qMin(checkerboard_rect.right(), width())); + checkerboard_rect.setTop(qMax(checkerboard_rect.top(), 0)); + checkerboard_rect.setBottom(qMin(checkerboard_rect.bottom(), height())); + + if (checkerboard_rect.left() < width() + && checkerboard_rect.right() >= 0 + && checkerboard_rect.top() < height() + && checkerboard_rect.bottom() >= 0) { + // draw "error lines" if media stream is missing + p.setPen(QPen(QColor(64, 64, 64), 2)); + int limit = checkerboard_rect.width(); + int clip_height = checkerboard_rect.height(); + for (int j=-clip_height;j checkerboard_rect.right()) { + lines_end_y -= (checkerboard_rect.right() - lines_end_x); + lines_end_x = checkerboard_rect.right(); + } + p.drawLine(lines_start_x, lines_start_y, lines_end_x, lines_end_y); + } + } + } + } + + // draw clip markers + for (int j=0;jget_markers().size();j++) { + const Marker& m = clip->get_markers().at(j); + + // convert marker time (in clip time) to sequence time + long marker_time = m.frame + clip->timeline_in() - clip->clip_in(); + int marker_x = ParentTimeline()->getTimelineScreenPointFromFrame(marker_time); + if (marker_x > clip_rect.x() && marker_x < clip_rect.right()) { + Marker::Draw(p, marker_x, clip_rect.bottom()-p.fontMetrics().height(), clip_rect.bottom(), false); + } + } + p.setBrush(Qt::NoBrush); + + // draw clip transitions + draw_transition(p, clip, clip_rect, text_rect, kTransitionOpening); + draw_transition(p, clip, clip_rect, text_rect, kTransitionClosing); + + // top left bevel + p.setPen(Qt::white); + if (clip_rect.x() >= 0 && clip_rect.x() < width()) p.drawLine(clip_rect.bottomLeft(), clip_rect.topLeft()); + if (clip_rect.y() >= 0 && clip_rect.y() < height()) p.drawLine(QPoint(qMax(0, clip_rect.left()), clip_rect.top()), QPoint(qMin(width(), clip_rect.right()), clip_rect.top())); + + // draw text + if (text_rect.width() > MAX_TEXT_WIDTH && text_rect.right() > 0 && text_rect.left() < width()) { + if (!clip->enabled()) { + p.setPen(Qt::gray); + } else if (clip->color().lightness() > 160) { + // set to black if color is bright + p.setPen(Qt::black); + } + if (clip->linked.size() > 0) { + int underline_y = olive::timeline::kClipTextPadding + p.fontMetrics().height() + clip_rect.top(); + int underline_width = qMin(text_rect.width() - 1, p.fontMetrics().width(clip->name())); + p.drawLine(text_rect.x(), underline_y, text_rect.x() + underline_width, underline_y); + } + QString name = clip->name(); + if (clip->speed().value != 1.0 || clip->reversed()) { + name += " ("; + if (clip->reversed()) name += "-"; + name += QString::number(clip->speed().value*100) + "%)"; + } + p.drawText(text_rect, 0, name, &text_rect); + } + + // bottom right gray + p.setPen(QColor(0, 0, 0, 128)); + if (clip_rect.right() >= 0 && clip_rect.right() < width()) p.drawLine(clip_rect.bottomRight(), clip_rect.topRight()); + if (clip_rect.bottom() >= 0 && clip_rect.bottom() < height()) p.drawLine(QPoint(qMax(0, clip_rect.left()), clip_rect.bottom()), QPoint(qMin(width(), clip_rect.right()), clip_rect.bottom())); + + // draw transition tool + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_TRANSITION) { + + bool shared_transition = (ParentTimeline()->transition_tool_open_clip != nullptr + && ParentTimeline()->transition_tool_close_clip != nullptr); + + QRect transition_tool_rect = clip_rect; + bool draw_transition_tool_rect = false; + + if (ParentTimeline()->transition_tool_open_clip == clip) { + if (shared_transition) { + transition_tool_rect.setWidth(TRANSITION_BETWEEN_RANGE); + } else { + transition_tool_rect.setWidth(transition_tool_rect.width()>>2); + } + draw_transition_tool_rect = true; + } else if (ParentTimeline()->transition_tool_close_clip == clip) { + if (shared_transition) { + transition_tool_rect.setLeft(transition_tool_rect.right() - TRANSITION_BETWEEN_RANGE); + } else { + transition_tool_rect.setLeft(transition_tool_rect.left() + (3*(transition_tool_rect.width()>>2))); + } + draw_transition_tool_rect = true; + } + + if (draw_transition_tool_rect + && transition_tool_rect.left() < width() + && transition_tool_rect.right() > 0) { + if (transition_tool_rect.left() < 0) { + transition_tool_rect.setLeft(0); + } + if (transition_tool_rect.right() > width()) { + transition_tool_rect.setRight(width()); + } + p.fillRect(transition_tool_rect, QColor(0, 0, 0, 128)); + } + } + } + } + + // Draw recording clip if recording if valid + if (panel_sequence_viewer->is_recording_cued() && panel_sequence_viewer->recording_track == track) { + int rec_track_x = ParentTimeline()->getTimelineScreenPointFromFrame(panel_sequence_viewer->recording_start); + int rec_track_y = getScreenPointFromTrack(panel_sequence_viewer->recording_track); + int rec_track_height = track->height(); + if (panel_sequence_viewer->recording_start != panel_sequence_viewer->recording_end) { + QRect rec_rect( + rec_track_x, + rec_track_y, + getScreenPointFromFrame(ParentTimeline()->zoom, panel_sequence_viewer->recording_end - panel_sequence_viewer->recording_start), + rec_track_height + ); + p.setPen(QPen(QColor(96, 96, 96), 2)); + p.fillRect(rec_rect, QColor(192, 192, 192)); + p.drawRect(rec_rect); + } + QRect active_rec_rect( + rec_track_x, + rec_track_y, + getScreenPointFromFrame(ParentTimeline()->zoom, panel_sequence_viewer->seq->playhead - panel_sequence_viewer->recording_start), + rec_track_height + ); + p.setPen(QPen(QColor(192, 0, 0), 2)); + p.fillRect(active_rec_rect, QColor(255, 96, 96)); + p.drawRect(active_rec_rect); + + p.setPen(Qt::NoPen); + + if (!panel_sequence_viewer->playing) { + int rec_marker_size = 6; + int rec_track_midY = rec_track_y + (rec_track_height >> 1); + p.setBrush(Qt::white); + QPoint cue_marker[3] = { + QPoint(rec_track_x, rec_track_midY - rec_marker_size), + QPoint(rec_track_x + rec_marker_size, rec_track_midY), + QPoint(rec_track_x, rec_track_midY + rec_marker_size) + }; + p.drawPolygon(cue_marker, 3); + } + } + + // Draw selections + QVector selections = track->Selections(); + for (int j=0;jgetTimelineScreenPointFromFrame(s.in()); + p.setPen(Qt::NoPen); + p.setBrush(Qt::NoBrush); + p.fillRect(selection_x, + track_top, + ParentTimeline()->getTimelineScreenPointFromFrame(s.out()) - selection_x, + track->height(), + QColor(0, 0, 0, 64)); + } + + // Draw splitting cursor + if (ParentTimeline()->splitting && ParentTimeline()->split_tracks.contains(track)) { + int cursor_x = ParentTimeline()->getTimelineScreenPointFromFrame(ParentTimeline()->drag_frame_start); + + p.setPen(QColor(64, 64, 64)); + p.drawLine(cursor_x, + track_top, + cursor_x, + track_top + track->height()); + } + + // Draw edit cursor + if (current_tool_shows_cursor() && ParentTimeline()->cursor_track == track) { + int cursor_x = ParentTimeline()->getTimelineScreenPointFromFrame(ParentTimeline()->cursor_frame); + + p.setPen(Qt::gray); + p.drawLine(cursor_x, + track_top, + cursor_x, + track_top + track->height()); + } + + // Draw track line + p.setPen(QColor(0, 0, 0, 96)); + if (alignment_ == olive::timeline::kAlignmentTop) { + p.drawLine(0, track_bottom, rect().width(), track_bottom); + } else if (alignment_ == olive::timeline::kAlignmentBottom) { + p.drawLine(0, track_top, rect().width(), track_top); + } + } + } + + // draw rectangle select + if (ParentTimeline()->rect_select_proc) { + QRect relative_rect = QRect(mapFromGlobal(ParentTimeline()->rect_select_rect.topLeft()), + mapFromGlobal(ParentTimeline()->rect_select_rect.bottomRight())); + + olive::ui::DrawSelectionRectangle(p, relative_rect); + } + + // Draw ghosts + if (!ParentTimeline()->ghosts.isEmpty()) { + QVector insert_points; + long first_ghost = LONG_MAX; + for (int i=0;ighosts.size();i++) { + const Ghost& g = ParentTimeline()->ghosts.at(i); + first_ghost = qMin(first_ghost, g.in); + if (g.track->type() == track_list_->type()) { + + int ghost_x = ParentTimeline()->getTimelineScreenPointFromFrame(g.in); + int ghost_y = getScreenPointFromTrackIndex(g.track->Index() + g.track_movement); + int ghost_width = ParentTimeline()->getTimelineScreenPointFromFrame(g.out) - ghost_x - 1; + int ghost_height = getTrackHeightFromTrackIndex(g.track->Index() + g.track_movement) - 1; + + insert_points.append(ghost_y + (ghost_height>>1)); + + p.setPen(QColor(255, 255, 0)); + for (int j=0;jmove_insert && !insert_points.isEmpty()) { + p.setBrush(Qt::white); + p.setPen(Qt::NoPen); + int insert_x = ParentTimeline()->getTimelineScreenPointFromFrame(first_ghost); + int tri_size = olive::timeline::kTrackMinHeight>>2; + + for (int i=0;igetTimelineScreenPointFromFrame(sequence()->playhead); + p.drawLine(playhead_x, rect().top(), playhead_x, rect().bottom()); + + // Draw single frame highlight + int playhead_frame_width = ParentTimeline()->getTimelineScreenPointFromFrame(sequence()->playhead+1) - playhead_x; + if (playhead_frame_width > 5){ //hardcoded for now, maybe better way to do this? + QRectF singleFrameRect(playhead_x, rect().top(), playhead_frame_width, rect().bottom()); + p.fillRect(singleFrameRect, QColor(255,255,255,15)); + } + + // draw border + /* + p.setPen(QColor(0, 0, 0, 64)); + int edge_y = 0; + p.drawLine(0, edge_y, rect().width(), edge_y); + edge_y = rect().height()-1; + p.drawLine(0, edge_y, rect().width(), edge_y); + */ + + // draw snap point + if (olive::timeline::snapped) { + p.setPen(Qt::white); + int snap_x = ParentTimeline()->getTimelineScreenPointFromFrame(olive::timeline::snap_point); + p.drawLine(snap_x, 0, snap_x, height()); + } + } +} + +// ************************************** +// screen point <-> frame/track functions +// ************************************** + +Track *TimelineView::getTrackFromScreenPoint(int y) { + + int index = getTrackIndexFromScreenPoint(y); + + if (index < track_list_->TrackCount()) { + return track_list_->TrackAt(index); + } + + return nullptr; + +} + +int TimelineView::getScreenPointFromTrack(Track *track) { + return getScreenPointFromTrackIndex(track_list_->IndexOfTrack(track)); +} + +int TimelineView::getTrackIndexFromScreenPoint(int y) +{ + if (alignment_ == olive::timeline::kAlignmentSingle) { + return 0; + } + + if (alignment_ == olive::timeline::kAlignmentBottom) { + y = -(y + 1 + scroll - qMax(height(), GetTotalAreaHeight())); + } else { + y += scroll; + } + + if (y < 0) { + return 0; + } + + int heights = 0; + + int i = 0; + while (true) { + + int new_heights = heights; + + if (i < track_list_->TrackCount()) { + new_heights += track_list_->TrackAt(i)->height(); + } else { + new_heights += olive::timeline::kTrackDefaultHeight; + } + + if (y >= heights && y < new_heights) { + return i; + } + + heights = new_heights; + + i++; + } + +} + +int TimelineView::getScreenPointFromTrackIndex(int track) +{ + if (alignment_ == olive::timeline::kAlignmentSingle) { + return 0; + } + + int point = 0; + + for (int i=0;iFirst()->height() - 1; + } + + return point - scroll; +} + +int TimelineView::getTrackHeightFromTrackIndex(int track) +{ + if (track < track_list_->TrackCount()) { + return track_list_->TrackAt(track)->height(); + } else { + return olive::timeline::kTrackDefaultHeight; + } +} + +Timeline *TimelineView::ParentTimeline() +{ + return timeline_; +} + +Sequence *TimelineView::sequence() +{ + if (track_list_ == nullptr) { + return nullptr; + } + + return track_list_->GetParent(); +} + +void TimelineView::setScroll(int s) { + scroll = s; + update(); +} + +void TimelineView::reveal_media() { + panel_project.first()->reveal_media(rc_reveal_media); +} diff --git a/ui/timelineview.h b/ui/timelineview.h new file mode 100644 index 000000000..6a9ab16c8 --- /dev/null +++ b/ui/timelineview.h @@ -0,0 +1,127 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef TIMELINEWIDGET_H +#define TIMELINEWIDGET_H + +#include +#include +#include +#include +#include +#include + +#include "timeline/sequence.h" +#include "timeline/clip.h" +#include "timeline/timelinetools.h" +#include "timeline/timelinefunctions.h" +#include "project/footage.h" +#include "project/media.h" +#include "undo/undo.h" + +class Timeline; + +class TimelineView : public QWidget { + Q_OBJECT +public: + explicit TimelineView(Timeline *parent); + + void SetAlignment(olive::timeline::Alignment alignment); + void SetTrackList(TrackList* tl); + + Track* getTrackFromScreenPoint(int y); + int getScreenPointFromTrack(Track* track); + + int getTrackIndexFromScreenPoint(int y); + int getScreenPointFromTrackIndex(int track); + + int getTrackHeightFromTrackIndex(int track); + +protected: + void paintEvent(QPaintEvent*); + + void mouseDoubleClickEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void leaveEvent(QEvent *event); + + void dragEnterEvent(QDragEnterEvent *event); + void dragLeaveEvent(QDragLeaveEvent *event); + void dropEvent(QDropEvent* event); + void dragMoveEvent(QDragMoveEvent *event); + + void wheelEvent(QWheelEvent *event); +private: + void init_ghosts(); + void update_ghosts(const QPoint& mouse_pos, bool lock_frame); + + Timeline* ParentTimeline(); + Sequence* sequence(); + void delete_area_under_ghosts(ComboAction* ca, Sequence *s); + void insert_clips(ComboAction* ca, Sequence *s); + bool current_tool_shows_cursor(); + void draw_transition(QPainter& p, Clip *c, const QRect& clip_rect, QRect& text_rect, int transition_type); + Clip* GetClipAtCursor(); + + QVector GetSplitTracksFromMouseCoords(bool also_split_links, long frame, int top, int bottom); + + void VerifyTransitionsAfterCreating(ComboAction* ca, Clip* open, Clip* close, long transition_start, long transition_end); + void VerifyTransitionHelper(); + + int GetTotalAreaHeight(); + + Timeline* timeline_; + + olive::timeline::Alignment alignment_; + TrackList* track_list_; + + bool track_resizing; + Track* track_target; + + QVector pre_clips; + QVector post_clips; + + Media* rc_reveal_media; + + SequencePtr self_created_sequence; + + QTimer tooltip_timer; + Clip* tooltip_clip; + + int scroll; + +signals: + void setScrollMaximum(int); + void requestScrollChange(int); + +public slots: + void setScroll(int); + +private slots: + void reveal_media(); + void show_context_menu(const QPoint& pos); + void toggle_autoscale(); + void tooltip_timer_timeout(); + void open_sequence_properties(); + void show_clip_properties(); +}; + +#endif // TIMELINEWIDGET_H diff --git a/ui/timelinewidget.h b/ui/timelinewidget.h deleted file mode 100644 index 2965dd37c..000000000 --- a/ui/timelinewidget.h +++ /dev/null @@ -1,114 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef TIMELINEWIDGET_H -#define TIMELINEWIDGET_H - -#include -#include -#include -#include -#include -#include - -#include "timeline/sequence.h" -#include "timeline/clip.h" -#include "project/footage.h" -#include "project/media.h" -#include "undo/undo.h" -#include "timelinetools.h" - -class Timeline; - -struct TimelineTrackHeight { - int index; - int height; -}; - -bool same_sign(int a, int b); -void draw_waveform(ClipPtr clip, const FootageStream *ms, long media_length, QPainter* p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom); - -class TimelineWidget : public QWidget { - Q_OBJECT -public: - explicit TimelineWidget(QWidget *parent = 0); - QScrollBar* scrollBar; - bool bottom_align; - -public slots: - -protected: - void paintEvent(QPaintEvent*); - - void resizeEvent(QResizeEvent *event); - - void mouseDoubleClickEvent(QMouseEvent *event); - void mousePressEvent(QMouseEvent *event); - void mouseReleaseEvent(QMouseEvent *event); - void mouseMoveEvent(QMouseEvent *event); - void leaveEvent(QEvent *event); - - void dragEnterEvent(QDragEnterEvent *event); - void dragLeaveEvent(QDragLeaveEvent *event); - void dropEvent(QDropEvent* event); - void dragMoveEvent(QDragMoveEvent *event); - - void wheelEvent(QWheelEvent *event); -private: - void init_ghosts(); - void update_ghosts(const QPoint& mouse_pos, bool lock_frame); - bool is_track_visible(int track); - int getTrackFromScreenPoint(int y); - int getScreenPointFromTrack(int track); - int getClipIndexFromCoords(long frame, int track); - - void VerifyTransitionHelper(); - - bool track_resizing; - int track_target; - - QVector pre_clips; - QVector post_clips; - - Media* rc_reveal_media; - - SequencePtr self_created_sequence; - - QTimer tooltip_timer; - int tooltip_clip; - - int scroll; - - SetSelectionsCommand* selection_command; -signals: - -public slots: - void setScroll(int); - -private slots: - void reveal_media(); - void show_context_menu(const QPoint& pos); - void toggle_autoscale(); - void tooltip_timer_timeout(); - void open_sequence_properties(); - void show_clip_properties(); -}; - -#endif // TIMELINEWIDGET_H diff --git a/ui/updatenotification.cpp b/ui/updatenotification.cpp index 595a6b51f..a2fdc5275 100644 --- a/ui/updatenotification.cpp +++ b/ui/updatenotification.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "updatenotification.h" #include diff --git a/ui/updatenotification.h b/ui/updatenotification.h index 8a8f87e78..16c4c2f74 100644 --- a/ui/updatenotification.h +++ b/ui/updatenotification.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef UPDATENOTIFICATION_H #define UPDATENOTIFICATION_H diff --git a/ui/viewercontainer.cpp b/ui/viewercontainer.cpp index 9f9d62367..ea7320131 100644 --- a/ui/viewercontainer.cpp +++ b/ui/viewercontainer.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ diff --git a/ui/viewercontainer.h b/ui/viewercontainer.h index 1724c1d16..8a5da99fe 100644 --- a/ui/viewercontainer.h +++ b/ui/viewercontainer.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -28,42 +28,42 @@ class QScrollBar; class ViewerContainer : public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit ViewerContainer(QWidget *parent = 0); - ~ViewerContainer(); + explicit ViewerContainer(QWidget *parent = 0); + ~ViewerContainer(); - bool fit; - double zoom; + bool fit; + double zoom; - void dragScrollPress(const QPoint&); - void dragScrollMove(const QPoint&); - void parseWheelEvent(QWheelEvent* event); + void dragScrollPress(const QPoint&); + void dragScrollMove(const QPoint&); + void parseWheelEvent(QWheelEvent* event); - Viewer* viewer; - ViewerWidget* child; - void adjust(); + Viewer* viewer; + ViewerWidget* child; + void adjust(); - // manually moves scrollbars into the correct position - void adjust_scrollbars(); + // manually moves scrollbars into the correct position + void adjust_scrollbars(); protected: - void resizeEvent(QResizeEvent *event); + void resizeEvent(QResizeEvent *event); signals: public slots: private slots: - void scroll_changed(); + void scroll_changed(); private: - int drag_start_x; - int drag_start_y; - int horiz_start; - int vert_start; - QScrollBar* horizontal_scrollbar; - QScrollBar* vertical_scrollbar; + int drag_start_x; + int drag_start_y; + int horiz_start; + int vert_start; + QScrollBar* horizontal_scrollbar; + QScrollBar* vertical_scrollbar; }; #endif // VIEWERCONTAINER_H diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 05d43e50e..f667f53bb 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -27,8 +27,8 @@ extern "C" { #include #include #include +#include #include -#include #include #include #include @@ -40,6 +40,7 @@ extern "C" { #include #include #include +#include #include "panels/panels.h" #include "project/projectelements.h" @@ -48,17 +49,23 @@ extern "C" { #include "global/config.h" #include "global/debug.h" #include "global/math.h" +#include "global/timing.h" #include "ui/collapsiblewidget.h" #include "undo/undo.h" #include "project/media.h" #include "ui/viewercontainer.h" #include "rendering/cacher.h" -#include "ui/timelinewidget.h" +#include "ui/timelineview.h" #include "rendering/renderfunctions.h" #include "rendering/renderthread.h" +#include "rendering/shadergenerators.h" #include "ui/viewerwindow.h" #include "ui/menu.h" +#include "ui/waveform.h" #include "mainwindow.h" +#include "effects/effectgizmo.h" + +const int kTitleActionSafeVertexSize = 84; ViewerWidget::ViewerWidget(QWidget *parent) : QOpenGLWidget(parent), @@ -77,17 +84,14 @@ ViewerWidget::ViewerWidget(QWidget *parent) : setContextMenuPolicy(Qt::CustomContextMenu); connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); - renderer = new RenderThread(); - renderer->start(QThread::HighestPriority); - connect(renderer, SIGNAL(ready()), this, SLOT(queue_repaint())); - connect(renderer, SIGNAL(finished()), renderer, SLOT(deleteLater())); + renderer.start(QThread::HighestPriority); + connect(&renderer, SIGNAL(ready()), this, SLOT(queue_repaint())); window = new ViewerWindow(this); } ViewerWidget::~ViewerWidget() { - renderer->cancel(); - delete renderer; + renderer.cancel(); } void ViewerWidget::set_waveform_scroll(int s) { @@ -144,7 +148,7 @@ void ViewerWidget::show_context_menu() { connect(&zoom_menu, SIGNAL(triggered(QAction*)), this, SLOT(set_menu_zoom(QAction*))); menu.addMenu(&zoom_menu); - if (!viewer->is_main_sequence()) { + if (viewer->mode() != Viewer::kTimelineMode) { menu.addAction(tr("Close Media"), viewer, SLOT(close_media())); } @@ -165,7 +169,7 @@ void ViewerWidget::save_frame() { fn += selected_ext; } - renderer->start_render(context(), viewer->seq.get(), 1, fn); + renderer.start_render(context(), viewer->seq.get(), 1, fn); } } @@ -213,9 +217,20 @@ void ViewerWidget::retry() { } void ViewerWidget::initializeGL() { - initializeOpenGLFunctions(); + context()->functions()->initializeOpenGLFunctions(); connect(context(), SIGNAL(aboutToBeDestroyed()), this, SLOT(context_destroy()), Qt::DirectConnection); + + pipeline_ = olive::shader::GetPipeline(); + + vao_.create(); + + title_safe_area_buffer_.create(); + title_safe_area_buffer_.bind(); + title_safe_area_buffer_.allocate(nullptr, kTitleActionSafeVertexSize * sizeof(GLfloat)); + title_safe_area_buffer_.release(); + + gizmo_buffer_.create(); } void ViewerWidget::frame_update() { @@ -225,7 +240,7 @@ void ViewerWidget::frame_update() { update(); } else { doneCurrent(); - renderer->start_render(context(), viewer->seq.get(), viewer->get_playback_speed()); + renderer.start_render(context(), viewer->seq.get(), viewer->get_playback_speed()); } // render the audio @@ -234,7 +249,7 @@ void ViewerWidget::frame_update() { } RenderThread *ViewerWidget::get_renderer() { - return renderer; + return &renderer; } void ViewerWidget::set_scroll(double x, double y) { @@ -247,12 +262,37 @@ void ViewerWidget::seek_from_click(int x) { viewer->seek(getFrameFromScreenPoint(waveform_zoom, x+waveform_scroll)); } +QMatrix4x4 ViewerWidget::get_matrix() +{ + QMatrix4x4 matrix; + + double zoom_factor = container->zoom/(double(width())/double(viewer->seq->width)); + + if (zoom_factor > 1.0) { + double zoom_size = (zoom_factor*2.0) - 2.0; + + matrix.translate((-(x_scroll-0.5))*zoom_size, (y_scroll-0.5)*zoom_size); + matrix.scale(zoom_factor); + } + + return matrix; +} + void ViewerWidget::context_destroy() { makeCurrent(); - if (viewer->seq != nullptr) { - close_active_clips(viewer->seq.get()); + + renderer.delete_ctx(); + + title_safe_area_buffer_.destroy(); + + if (gizmo_buffer_.isCreated()) { + gizmo_buffer_.destroy(); } - renderer->delete_ctx(); + + vao_.destroy(); + + pipeline_ = nullptr; + doneCurrent(); } @@ -300,7 +340,12 @@ void ViewerWidget::move_gizmos(QMouseEvent *event, bool done) { int x_movement = qRound((event->pos().x() - drag_start_x)*multiplier); int y_movement = qRound((event->pos().y() - drag_start_y)*multiplier); - gizmos->gizmo_move(selected_gizmo, x_movement, y_movement, get_timecode(gizmos->parent_clip, gizmos->parent_clip->sequence->playhead), done); + gizmos->gizmo_move(selected_gizmo, + x_movement, + y_movement, + get_timecode(gizmos->parent_clip, + gizmos->parent_clip->track()->sequence()->playhead), + done); gizmo_x_mvmt += x_movement; gizmo_y_mvmt += y_movement; @@ -313,7 +358,7 @@ void ViewerWidget::move_gizmos(QMouseEvent *event, bool done) { void ViewerWidget::mousePressEvent(QMouseEvent* event) { if (waveform) { seek_from_click(event->x()); - } else if (event->buttons() & Qt::MiddleButton || panel_timeline->tool == TIMELINE_TOOL_HAND) { + } else if (event->buttons() & Qt::MiddleButton || olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_HAND) { container->dragScrollPress(event->pos()*container->zoom); } else if (event->buttons() & Qt::LeftButton) { drag_start_x = event->pos().x(); @@ -329,13 +374,13 @@ void ViewerWidget::mousePressEvent(QMouseEvent* event) { void ViewerWidget::mouseMoveEvent(QMouseEvent* event) { unsetCursor(); - if (panel_timeline->tool == TIMELINE_TOOL_HAND) { + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_HAND) { setCursor(Qt::OpenHandCursor); } if (dragging) { if (waveform) { seek_from_click(event->x()); - } else if (event->buttons() & Qt::MiddleButton || panel_timeline->tool == TIMELINE_TOOL_HAND) { + } else if (event->buttons() & Qt::MiddleButton || olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_HAND) { container->dragScrollMove(event->pos()*container->zoom); } else if (event->buttons() & Qt::LeftButton) { if (gizmos == nullptr) { @@ -359,7 +404,7 @@ void ViewerWidget::mouseReleaseEvent(QMouseEvent *event) { if (dragging && gizmos != nullptr && event->button() == Qt::LeftButton - && panel_timeline->tool != TIMELINE_TOOL_HAND) { + && olive::timeline::current_tool != olive::timeline::TIMELINE_TOOL_HAND) { move_gizmos(event, true); } dragging = false; @@ -375,7 +420,7 @@ void ViewerWidget::close_window() { void ViewerWidget::wait_until_render_is_paused() { - renderer->wait_until_paused(); + renderer.wait_until_paused(); } void ViewerWidget::draw_waveform_func() { @@ -393,224 +438,337 @@ void ViewerWidget::draw_waveform_func() { wr.setX(wr.x() - waveform_scroll); p.setPen(Qt::green); - draw_waveform(waveform_clip, waveform_ms, waveform_clip->timeline_out(), &p, wr, waveform_scroll, width()+waveform_scroll, waveform_zoom); + olive::ui::DrawWaveform(waveform_clip.get(), waveform_ms, waveform_clip->timeline_out(), &p, wr, waveform_scroll, width()+waveform_scroll, waveform_zoom); p.setPen(Qt::red); int playhead_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->playhead) - waveform_scroll; p.drawLine(playhead_x, 0, playhead_x, height()); } void ViewerWidget::draw_title_safe_area() { - double halfWidth = 0.5; - double halfHeight = 0.5; - double viewportAr = (double) width() / (double) height(); - double halfAr = viewportAr*0.5; + QOpenGLFunctions* func = context()->functions(); - if (olive::CurrentConfig.use_custom_title_safe_ratio && olive::CurrentConfig.custom_title_safe_ratio > 0) { - if (olive::CurrentConfig.custom_title_safe_ratio > viewportAr) { - halfHeight = (olive::CurrentConfig.custom_title_safe_ratio/viewportAr)*0.5; + pipeline_->bind(); + + + float ar = float(width()) / float(height()); + + float horizontal_cross_size = 0.05f / ar; + + // Set matrix to 0.0 -> 1.0 on both axes + QMatrix4x4 matrix; + matrix.ortho(0.0f, 1.0f, 0.0f, 1.0f, -1.0f, 1.0f); + + // adjust the horizontal center cross by the aspect ratio to appear "square" + if (olive::config.use_custom_title_safe_ratio && olive::config.custom_title_safe_ratio > 0) { + if (ar > olive::config.custom_title_safe_ratio) { + matrix.translate(((ar - olive::config.custom_title_safe_ratio) / 2.0) / ar, 0.0f); + matrix.scale(olive::config.custom_title_safe_ratio / ar, 1.0f); } else { - halfWidth = (viewportAr/olive::CurrentConfig.custom_title_safe_ratio)*0.5; + matrix.translate(0.0f, (((olive::config.custom_title_safe_ratio - ar) / 2.0) / olive::config.custom_title_safe_ratio)); + matrix.scale(1.0f, ar / olive::config.custom_title_safe_ratio); } + + horizontal_cross_size *= ar/olive::config.custom_title_safe_ratio; } - glLoadIdentity(); - glOrtho(-halfWidth, halfWidth, halfHeight, -halfHeight, 0, 1); + float adjusted_cross_x1 = 0.5f - horizontal_cross_size; + float adjusted_cross_x2 = 0.5f + horizontal_cross_size; - glColor4f(0.66f, 0.66f, 0.66f, 1.0f); - glBegin(GL_LINES); + pipeline_->setUniformValue("mvp_matrix", matrix); + pipeline_->setUniformValue("color_only", true); + pipeline_->setUniformValue("color_only_color", QColor(192, 192, 192, 255)); - // action safe rectangle - glVertex2d(-0.45, -0.45); - glVertex2d(0.45, -0.45); - glVertex2d(0.45, -0.45); - glVertex2d(0.45, 0.45); - glVertex2d(0.45, 0.45); - glVertex2d(-0.45, 0.45); - glVertex2d(-0.45, 0.45); - glVertex2d(-0.45, -0.45); - // title safe rectangle - glVertex2d(-0.4, -0.4); - glVertex2d(0.4, -0.4); - glVertex2d(0.4, -0.4); - glVertex2d(0.4, 0.4); - glVertex2d(0.4, 0.4); - glVertex2d(-0.4, 0.4); - glVertex2d(-0.4, 0.4); - glVertex2d(-0.4, -0.4); - // horizontal centers - glVertex2d(-0.45, 0); - glVertex2d(-0.375, 0); - glVertex2d(0.45, 0); - glVertex2d(0.375, 0); + GLfloat vertices[] = { + // action safe lines + 0.05f, 0.05f, 0.0f, + 0.95f, 0.05f, 0.0f, - // vertical centers - glVertex2d(0, -0.45); - glVertex2d(0, -0.375); - glVertex2d(0, 0.45); - glVertex2d(0, 0.375); + 0.95f, 0.05f, 0.0f, + 0.95f, 0.95f, 0.0f, - glEnd(); + 0.95f, 0.95f, 0.0f, + 0.05f, 0.95f, 0.0f, - // center cross - glLoadIdentity(); - glOrtho(-halfAr, halfAr, 0.5, -0.5, -1, 1); + 0.05f, 0.95f, 0.0f, + 0.05f, 0.05f, 0.0f, - glBegin(GL_LINES); + // title safe lines + 0.1f, 0.1f, 0.0f, + 0.9f, 0.1f, 0.0f, - glVertex2d(-0.05, 0); - glVertex2d(0.05, 0); - glVertex2d(0, -0.05); - glVertex2d(0, 0.05); + 0.9f, 0.1f, 0.0f, + 0.9f, 0.9f, 0.0f, + + 0.9f, 0.9f, 0.0f, + 0.1f, 0.9f, 0.0f, + + 0.1f, 0.9f, 0.0f, + 0.1f, 0.1f, 0.0f, + + // side-center markers + 0.05f, 0.5f, 0.0f, + 0.125f, 0.5f, 0.0f, + + 0.95f, 0.5f, 0.0f, + 0.875f, 0.5f, 0.0f, + + 0.5f, 0.05f, 0.0f, + 0.5f, 0.125f, 0.0f, + + 0.5f, 0.95f, 0.0f, + 0.5f, 0.875f, 0.0f, + + // horizontal center cross marker + adjusted_cross_x1, 0.5f, 0.0f, + adjusted_cross_x2, 0.5f, 0.0f, + + // vertical center cross marker + 0.5f, 0.45f, 0.0f, + 0.5f, 0.55f, 0.0f + }; + + vao_.bind(); + + title_safe_area_buffer_.bind(); + title_safe_area_buffer_.write(0, vertices, kTitleActionSafeVertexSize * sizeof(GLfloat)); + + GLuint vertex_location = pipeline_->attributeLocation("a_position"); + func->glEnableVertexAttribArray(vertex_location); + func->glVertexAttribPointer(vertex_location, 3, GL_FLOAT, GL_FALSE, 0, 0); + + func->glDrawArrays(GL_LINES, 0, 28); + + pipeline_->setUniformValue("color_only", false); + + title_safe_area_buffer_.release(); + + vao_.release(); + + pipeline_->release(); - glEnd(); } void ViewerWidget::draw_gizmos() { - float color[4]; - glGetFloatv(GL_CURRENT_COLOR, color); + QOpenGLFunctions* func = context()->functions(); - double dot_size = GIZMO_DOT_SIZE / double(width()) * viewer->seq->width; - double target_size = GIZMO_TARGET_SIZE / double(width()) * viewer->seq->width; + pipeline_->bind(); double zoom_factor = container->zoom/(double(width())/double(viewer->seq->width)); - glPushMatrix(); - glLoadIdentity(); + QMatrix4x4 matrix; + matrix.ortho(0, viewer->seq->width, 0, viewer->seq->height, -1, 1); + matrix.scale(zoom_factor, zoom_factor); + matrix.translate(-(viewer->seq->width-(width()/container->zoom))*x_scroll, + -((viewer->seq->height-(height()/container->zoom))*(1.0-y_scroll))); - glOrtho(0, viewer->seq->width, 0, viewer->seq->height, -1, 10); - glScaled(zoom_factor, zoom_factor, 0.0); - glTranslated(-(viewer->seq->width-(width()/container->zoom))*x_scroll, - -((viewer->seq->height-(height()/container->zoom))*(1.0-y_scroll)), - 0); + // Set transformation matrix + pipeline_->setUniformValue("mvp_matrix", matrix); + + // Set pipeline shader to draw full white + pipeline_->setUniformValue("color_only", true); + pipeline_->setUniformValue("color_only_color", QColor(255, 255, 255, 255)); + + // Set up constants for gizmo sizes + float size_diff = float(viewer->seq->width) / float(width()); + float dot_size = GIZMO_DOT_SIZE * size_diff; + float target_size = GIZMO_TARGET_SIZE * size_diff; + + QVector vertices; - float gizmo_z = 0.0f; for (int j=0;jgizmo_count();j++) { + EffectGizmo* g = gizmos->gizmo(j); - glColor4d(g->color.redF(), g->color.greenF(), g->color.blueF(), 1.0); + switch (g->get_type()) { - case GIZMO_TYPE_DOT: // draw dot - glBegin(GL_QUADS); - glVertex3f(g->screen_pos[0].x()-dot_size, g->screen_pos[0].y()-dot_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()+dot_size, g->screen_pos[0].y()-dot_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()+dot_size, g->screen_pos[0].y()+dot_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()-dot_size, g->screen_pos[0].y()+dot_size, gizmo_z); - glEnd(); + case GIZMO_TYPE_DOT: + + // Draw standard square dot + + vertices.append(g->screen_pos[0].x()-dot_size); + vertices.append(g->screen_pos[0].y()-dot_size); + vertices.append(0.0f); + + vertices.append(g->screen_pos[0].x()+dot_size); + vertices.append(g->screen_pos[0].y()-dot_size); + vertices.append(0.0f); + + vertices.append(g->screen_pos[0].x()+dot_size); + vertices.append(g->screen_pos[0].y()+dot_size); + vertices.append(0.0f); + + vertices.append(g->screen_pos[0].x()-dot_size); + vertices.append(g->screen_pos[0].y()+dot_size); + vertices.append(0.0f); + break; - case GIZMO_TYPE_POLY: // draw lines - glBegin(GL_LINES); + case GIZMO_TYPE_POLY: + + // Draw an arbitrary polygon with lines + for (int k=1;kget_point_count();k++) { - glVertex3f(g->screen_pos[k-1].x(), g->screen_pos[k-1].y(), gizmo_z); - glVertex3f(g->screen_pos[k].x(), g->screen_pos[k].y(), gizmo_z); + + vertices.append(g->screen_pos[k-1].x()); + vertices.append(g->screen_pos[k-1].y()); + vertices.append(0.0f); + + vertices.append(g->screen_pos[k].x()); + vertices.append(g->screen_pos[k].y()); + vertices.append(0.0f); + } - glVertex3f(g->screen_pos[g->get_point_count()-1].x(), g->screen_pos[g->get_point_count()-1].y(), gizmo_z); - glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y(), gizmo_z); - glEnd(); + + vertices.append(g->screen_pos[g->get_point_count()-1].x()); + vertices.append(g->screen_pos[g->get_point_count()-1].y()); + vertices.append(0.0f); + + vertices.append(g->screen_pos[0].x()); + vertices.append(g->screen_pos[0].y()); + vertices.append(0.0f); + break; - case GIZMO_TYPE_TARGET: // draw target - glBegin(GL_LINES); - glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()-target_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()-target_size, gizmo_z); + case GIZMO_TYPE_TARGET: - glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()-target_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()+target_size, gizmo_z); + // Draw "target" gizmo (square with two lines through the middle) - glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()+target_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()+target_size, gizmo_z); + vertices.append(g->screen_pos[0].x()-target_size); + vertices.append(g->screen_pos[0].y()-target_size); + vertices.append(0.0f); - glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()+target_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()-target_size, gizmo_z); + vertices.append(g->screen_pos[0].x()+target_size); + vertices.append(g->screen_pos[0].y()-target_size); + vertices.append(0.0f); - glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y(), gizmo_z); - glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y(), gizmo_z); + vertices.append(g->screen_pos[0].x()+target_size); + vertices.append(g->screen_pos[0].y()-target_size); + vertices.append(0.0f); + + vertices.append(g->screen_pos[0].x()+target_size); + vertices.append(g->screen_pos[0].y()+target_size); + vertices.append(0.0f); + + vertices.append(g->screen_pos[0].x()+target_size); + vertices.append(g->screen_pos[0].y()+target_size); + vertices.append(0.0f); + + vertices.append(g->screen_pos[0].x()-target_size); + vertices.append(g->screen_pos[0].y()+target_size); + vertices.append(0.0f); + + vertices.append(g->screen_pos[0].x()-target_size); + vertices.append(g->screen_pos[0].y()+target_size); + vertices.append(0.0f); + + vertices.append(g->screen_pos[0].x()-target_size); + vertices.append(g->screen_pos[0].y()-target_size); + vertices.append(0.0f); + + + + vertices.append(g->screen_pos[0].x()-target_size); + vertices.append(g->screen_pos[0].y()); + vertices.append(0.0f); + + vertices.append(g->screen_pos[0].x()+target_size); + vertices.append(g->screen_pos[0].y()); + vertices.append(0.0f); + + vertices.append(g->screen_pos[0].x()); + vertices.append(g->screen_pos[0].y()-target_size); + vertices.append(0.0f); + + vertices.append(g->screen_pos[0].x()); + vertices.append(g->screen_pos[0].y()+target_size); + vertices.append(0.0f); - glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y()-target_size, gizmo_z); - glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y()+target_size, gizmo_z); - glEnd(); break; } } - glPopMatrix(); - glColor4f(color[0], color[1], color[2], color[3]); + vao_.bind(); + + // The gizmo buffer may have been destroyed or not created yet, ensure it's created here + if (!gizmo_buffer_.isCreated() && !gizmo_buffer_.create()) { + return; + } + + gizmo_buffer_.bind(); + + // Get the total byte size of the vertex array + int gizmo_buffer_desired_size = vertices.size() * sizeof(GLfloat); + + // Determine if the gizmo count has changed, and if so reallocate the buffer + if (gizmo_buffer_.size() != gizmo_buffer_desired_size) { + gizmo_buffer_.allocate(vertices.constData(), gizmo_buffer_desired_size); + } else { + gizmo_buffer_.write(0, vertices.constData(), gizmo_buffer_desired_size); + } + + + GLuint vertex_location = pipeline_->attributeLocation("a_position"); + func->glEnableVertexAttribArray(vertex_location); + func->glVertexAttribPointer(vertex_location, 3, GL_FLOAT, GL_FALSE, 0, 0); + + gizmo_buffer_.release(); + + func->glDrawArrays(GL_LINES, 0, vertices.size() / 3); + + pipeline_->setUniformValue("color_only", false); + + vao_.release(); + + pipeline_->release(); + } void ViewerWidget::paintGL() { if (waveform) { draw_waveform_func(); } else { - const GLuint tex = renderer->get_texture(); - QMutex* tex_lock = renderer->get_texture_mutex(); + const GLuint tex = renderer.get_texture(); + QMutex* tex_lock = renderer.get_texture_mutex(); tex_lock->lock(); + QOpenGLFunctions* f = context()->functions(); + + makeCurrent(); + // clear to solid black - glClearColor(0.0, 0.0, 0.0, 0.0); - glClear(GL_COLOR_BUFFER_BIT); + f->glClearColor(0.0, 0.0, 0.0, 0.0); + f->glClear(GL_COLOR_BUFFER_BIT); - // set color multipler to straight white - glColor4f(1.0, 1.0, 1.0, 1.0); - - glEnable(GL_TEXTURE_2D); - - // set screen coords to widget size - glLoadIdentity(); - glOrtho(-1, 1, -1, 1, -1, 1); // draw texture from render thread - glBindTexture(GL_TEXTURE_2D, tex); + f->glViewport(0, 0, width(), height()); - context()->functions()->glGenerateMipmap(GL_TEXTURE_2D); + f->glBindTexture(GL_TEXTURE_2D, tex); - glBegin(GL_QUADS); + olive::rendering::Blit(pipeline_.get(), true, get_matrix()); - double zoom_factor = container->zoom/(double(width())/double(viewer->seq->width)); - double zoom_size = (zoom_factor*2.0) - 2.0; - double zoom_left = -zoom_size*x_scroll - 1.0; - double zoom_right = zoom_size*(1.0-x_scroll) + 1.0; - double zoom_bottom = -zoom_size*(1.0-y_scroll) - 1.0; - double zoom_top = zoom_size*(y_scroll) + 1.0; - - //zoom_left *= ar_diff; - //zoom_right *= ar_diff; - - glVertex2d(zoom_left, zoom_bottom); - glTexCoord2d(0, 0); - glVertex2d(zoom_left, zoom_top); - glTexCoord2d(1, 0); - glVertex2d(zoom_right, zoom_top); - glTexCoord2d(1, 1); - glVertex2d(zoom_right, zoom_bottom); - glTexCoord2d(0, 1); - - glEnd(); - - glBindTexture(GL_TEXTURE_2D, 0); + f->glBindTexture(GL_TEXTURE_2D, 0); // draw title/action safe area - if (olive::CurrentConfig.show_title_safe_area) { + if (olive::config.show_title_safe_area) { draw_title_safe_area(); } - gizmos = renderer->gizmos; + gizmos = renderer.gizmos; if (gizmos != nullptr) { draw_gizmos(); } - glDisable(GL_TEXTURE_2D); - glFinish(); - if (window->isVisible()) { window->set_texture(tex, double(viewer->seq->width)/double(viewer->seq->height), tex_lock); } tex_lock->unlock(); - if (renderer->did_texture_fail() && !viewer->playing) { + if (renderer.did_texture_fail() && !viewer->playing) { doneCurrent(); - renderer->start_render(context(), viewer->seq.get(), viewer->get_playback_speed()); + renderer.start_render(context(), viewer->seq.get(), viewer->get_playback_speed()); } } } diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 0a5fdbbbd..f2d0fc7f5 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -29,10 +29,12 @@ #include #include #include +#include +#include #include "timeline/clip.h" #include "project/footage.h" -#include "effects/effect.h" +#include "nodes/node.h" #include "ui/viewerwindow.h" #include "ui/viewercontainer.h" #include "rendering/renderthread.h" @@ -41,7 +43,7 @@ class Viewer; class QOpenGLFramebufferObject; struct GLTextureCoords; -class ViewerWidget : public QOpenGLWidget, QOpenGLFunctions +class ViewerWidget : public QOpenGLWidget { Q_OBJECT public: @@ -81,16 +83,23 @@ private: void move_gizmos(QMouseEvent *event, bool done); bool dragging; void seek_from_click(int x); - Effect* gizmos; + QMatrix4x4 get_matrix(); + Node* gizmos; int drag_start_x; int drag_start_y; int gizmo_x_mvmt; int gizmo_y_mvmt; EffectGizmo* selected_gizmo; - RenderThread* renderer; + RenderThread renderer; ViewerWindow* window; double x_scroll; double y_scroll; + + QOpenGLShaderProgramPtr pipeline_; + QOpenGLVertexArrayObject vao_; + QOpenGLBuffer gizmo_buffer_; + QOpenGLBuffer title_safe_area_buffer_; + private slots: void context_destroy(); void retry(); diff --git a/ui/viewerwindow.cpp b/ui/viewerwindow.cpp index a73b4cb76..5fb5edb69 100644 --- a/ui/viewerwindow.cpp +++ b/ui/viewerwindow.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -28,27 +28,28 @@ #include #include #include - #include -#include "mainwindow.h" +#include "rendering/renderfunctions.h" +#include "rendering/shadergenerators.h" +#include "ui/mainwindow.h" ViewerWindow::ViewerWindow(QWidget *parent) : QOpenGLWidget(parent, Qt::Window), - texture(0), - mutex(nullptr), - show_fullscreen_msg(false) + texture_(0), + mutex_(nullptr), + show_fullscreen_msg_(false) { setMouseTracking(true); - fullscreen_msg_timer.setInterval(2000); - connect(&fullscreen_msg_timer, SIGNAL(timeout()), this, SLOT(fullscreen_msg_timeout())); + fullscreen_msg_timer_.setInterval(2000); + connect(&fullscreen_msg_timer_, SIGNAL(timeout()), this, SLOT(fullscreen_msg_timeout())); } void ViewerWindow::set_texture(GLuint t, double iar, QMutex* imutex) { - texture = t; - ar = iar; - mutex = imutex; + texture_ = t; + ar_ = iar; + mutex_ = imutex; update(); } @@ -94,71 +95,65 @@ void ViewerWindow::keyPressEvent(QKeyEvent *e) { } void ViewerWindow::mousePressEvent(QMouseEvent *e) { - if (show_fullscreen_msg && fullscreen_msg_rect.contains(e->pos())) { + if (show_fullscreen_msg_ && fullscreen_msg_rect_.contains(e->pos())) { hide(); } } void ViewerWindow::mouseMoveEvent(QMouseEvent *) { - fullscreen_msg_timer.start(); - if (!show_fullscreen_msg) { - show_fullscreen_msg = true; + fullscreen_msg_timer_.start(); + if (!show_fullscreen_msg_) { + show_fullscreen_msg_ = true; update(); } } +void ViewerWindow::initializeGL() +{ + pipeline_ = olive::shader::GetPipeline(); +} + void ViewerWindow::paintGL() { - if (texture > 0) { - if (mutex != nullptr) mutex->lock(); + if (texture_ > 0) { + if (mutex_ != nullptr) mutex_->lock(); - glClearColor(0.0, 0.0, 0.0, 0.0); - glClear(GL_COLOR_BUFFER_BIT); + QOpenGLFunctions* f = context()->functions(); + //QOpenGLExtraFunctions* xf = context()->extraFunctions(); - glEnable(GL_TEXTURE_2D); + makeCurrent(); - glBindTexture(GL_TEXTURE_2D, texture); + // clear to solid black + f->glClearColor(0.0, 0.0, 0.0, 0.0); + f->glClear(GL_COLOR_BUFFER_BIT); - glLoadIdentity(); - glOrtho(0, 1, 0, 1, -1, 1); - glBegin(GL_QUADS); + // draw texture from render thread - double top = 0; - double left = 0; - double right = 1; - double bottom = 1; - double widget_ar = double(width()) / double(height()); + QMatrix4x4 matrix; - if (widget_ar > ar) { - double width = 1.0 * ar / widget_ar; - left = (1.0 - width)*0.5; - right = left + width; + double widget_ar = (double(width()) / double(height())); + if (widget_ar > ar_) { + matrix.scale(ar_ / widget_ar, 1.0); } else { - double height = 1.0 / ar * widget_ar; - top = (1.0 - height)*0.5; - bottom = top + height; + matrix.scale(1.0f, widget_ar / ar_); } - glVertex2d(left, top); - glTexCoord2d(0, 0); - glVertex2d(left, bottom); - glTexCoord2d(1, 0); - glVertex2d(right, bottom); - glTexCoord2d(1, 1); - glVertex2d(right, top); - glTexCoord2d(0, 1); - glEnd(); + f->glViewport(0, 0, width(), height()); - glBindTexture(GL_TEXTURE_2D, 0); + f->glBindTexture(GL_TEXTURE_2D, texture_); - glDisable(GL_TEXTURE_2D); + olive::rendering::Blit(pipeline_.get(), true, matrix); - if (mutex != nullptr) mutex->unlock(); + f->glBindTexture(GL_TEXTURE_2D, 0); + + + + if (mutex_ != nullptr) mutex_->unlock(); } - if (show_fullscreen_msg) { + if (show_fullscreen_msg_) { QPainter p(this); QFont f = p.font(); @@ -178,21 +173,21 @@ void ViewerWindow::paintGL() { int rect_padding = 8; - fullscreen_msg_rect = QRect(text_x-rect_padding, + fullscreen_msg_rect_ = QRect(text_x-rect_padding, fm.height()-rect_padding, text_width+rect_padding+rect_padding, fm.height()+rect_padding+rect_padding); - p.drawRect(fullscreen_msg_rect); + p.drawRect(fullscreen_msg_rect_); p.drawText(text_x, text_y, fs_str); } } void ViewerWindow::fullscreen_msg_timeout() { - fullscreen_msg_timer.stop(); - if (show_fullscreen_msg) { - show_fullscreen_msg = false; + fullscreen_msg_timer_.stop(); + if (show_fullscreen_msg_) { + show_fullscreen_msg_ = false; update(); } } diff --git a/ui/viewerwindow.h b/ui/viewerwindow.h index 1257fa13e..2f5dec7f7 100644 --- a/ui/viewerwindow.h +++ b/ui/viewerwindow.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -23,10 +23,11 @@ #include #include +#include +#include +#include -class QMutex; -class QMenu; -class QShortcut; +#include "rendering/qopenglshaderprogramptr.h" class ViewerWindow : public QOpenGLWidget { Q_OBJECT @@ -39,20 +40,22 @@ protected: virtual void mousePressEvent(QMouseEvent*) override; virtual void mouseMoveEvent(QMouseEvent*) override; + virtual void initializeGL() override; virtual void paintGL() override; private: - GLuint texture; - double ar; - QMutex* mutex; + GLuint texture_; + double ar_; + QMutex* mutex_; + QOpenGLShaderProgramPtr pipeline_; // shortcuts void shortcut_copier(QVector& shortcuts, QMenu* menu); QVector shortcuts_; // exit full screen message - QTimer fullscreen_msg_timer; - bool show_fullscreen_msg; - QRect fullscreen_msg_rect; + QTimer fullscreen_msg_timer_; + bool show_fullscreen_msg_; + QRect fullscreen_msg_rect_; private slots: void fullscreen_msg_timeout(); }; diff --git a/ui/waveform.cpp b/ui/waveform.cpp new file mode 100644 index 000000000..40933453b --- /dev/null +++ b/ui/waveform.cpp @@ -0,0 +1,70 @@ +#include "waveform.h" + +#include + +#include "global/config.h" + +int ConvertSampleToHeight(qint8 signed_sample, int channel_height) { + int half_channel_height = channel_height >> 1; + return (half_channel_height) + qRound(((double(signed_sample) / 128.0)) * half_channel_height); +} + +void olive::ui::DrawWaveform(Clip* clip, + const FootageStream* ms, + long media_length, + QPainter *p, + const QRect& clip_rect, + int waveform_start, + int waveform_limit, + double zoom) { + + int divider = ms->audio_channels; + + int channel_height = clip_rect.height()/ms->audio_channels; + + int last_waveform_index = -1; + + for (int i=waveform_start;iclip_in() + (double(i)/zoom))/media_length) * ms->audio_preview.size())/divider)*divider; + + if (clip->reversed()) { + waveform_index = ms->audio_preview.size() - waveform_index - (ms->audio_channels * 2); + } + + if (last_waveform_index < 0) last_waveform_index = waveform_index; + + for (int j=0;jaudio_channels;j++) { + int bottom = clip_rect.top()+channel_height*(j+1); + + int offset_range_start = last_waveform_index+(j*2); + int offset_range_end = waveform_index+(j*2); + int offset_range_min = qMin(offset_range_start, offset_range_end); + int offset_range_max = qMax(offset_range_start, offset_range_end); + + // Break if we're about to draw from an index that doesn't exist + if (offset_range_min+1 >= ms->audio_preview.size()) { + break; + } + + int min = ConvertSampleToHeight(ms->audio_preview.at(offset_range_min), channel_height); + int max = ConvertSampleToHeight(ms->audio_preview.at(offset_range_min+1), channel_height); + + if ((offset_range_max + 1) < ms->audio_preview.size()) { + + // for waveform drawings, we get the maximum below 0 and maximum above 0 for this waveform range + for (int k=offset_range_min+2;k<=offset_range_max;k+=2) { + min = ConvertSampleToHeight(ms->audio_preview.at(k), channel_height); + max = ConvertSampleToHeight(ms->audio_preview.at(k+1), channel_height); + } + + // draw waveforms + if (olive::config.rectified_waveforms) { + p->drawLine(clip_rect.left()+i, bottom, clip_rect.left()+i, bottom - (max - min)); + } else { + p->drawLine(clip_rect.left()+i, bottom - min, clip_rect.left()+i, bottom - max); + } + } + } + last_waveform_index = waveform_index; + } +} diff --git a/ui/waveform.h b/ui/waveform.h new file mode 100644 index 000000000..d7dc92040 --- /dev/null +++ b/ui/waveform.h @@ -0,0 +1,25 @@ +#ifndef WAVEFORM_H +#define WAVEFORM_H + +#include +#include + +#include "timeline/clip.h" +#include "project/footage.h" + +namespace olive { +namespace ui { + +void DrawWaveform(Clip* clip, + const FootageStream *ms, + long media_length, + QPainter* p, + const QRect& clip_rect, + int waveform_start, + int waveform_limit, + double zoom); + +} +} + +#endif // WAVEFORM_H diff --git a/undo/comboaction.cpp b/undo/comboaction.cpp index e0d9be038..16035fb01 100644 --- a/undo/comboaction.cpp +++ b/undo/comboaction.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "comboaction.h" ComboAction::ComboAction() {} diff --git a/undo/comboaction.h b/undo/comboaction.h index 083e82b6d..d5ed2e7af 100644 --- a/undo/comboaction.h +++ b/undo/comboaction.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef COMBOACTION_H #define COMBOACTION_H diff --git a/undo/undo.cpp b/undo/undo.cpp index 895ec49a9..f9140cc5b 100644 --- a/undo/undo.cpp +++ b/undo/undo.cpp @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -38,24 +38,24 @@ #include "ui/labelslider.h" #include "ui/viewerwidget.h" #include "project/media.h" -#include "project/clipboard.h" +#include "global/clipboard.h" #include "project/previewgenerator.h" #include "ui/mainwindow.h" -MoveClipAction::MoveClipAction(Clip *c, long iin, long iout, long iclip_in, int itrack, bool irelative) { - clip = c; - - old_in = c->timeline_in(); - old_out = c->timeline_out(); - old_clip_in = c->clip_in(); - old_track = c->track(); - - new_in = iin; - new_out = iout; - new_clip_in = iclip_in; - new_track = itrack; - - relative = irelative; +MoveClipAction::MoveClipAction(ClipPtr c, long iin, long iout, long iclip_in, Track* itrack, bool irelative) : + clip(c), + old_in(c->timeline_in()), + old_out(c->timeline_out()), + old_clip_in(c->clip_in()), + old_track(c->track()), + new_in(iin), + new_out(iout), + new_clip_in(iclip_in), + new_track(itrack), + relative(irelative), + done(false) +{ + doRedo(); } void MoveClipAction::doUndo() { @@ -63,88 +63,93 @@ void MoveClipAction::doUndo() { clip->set_timeline_in (clip->timeline_in() - new_in); clip->set_timeline_out (clip->timeline_out() - new_out); clip->set_clip_in (clip->clip_in() - new_clip_in); - clip->set_track (clip->track() - new_track); } else { clip->set_timeline_in(old_in); clip->set_timeline_out(old_out); clip->set_clip_in(old_clip_in); - clip->set_track(old_track); } + + // Move clip to the new track ONLY IF the old track currently contains this clip - a workaround to ensure this + // action doesn't accidentaly add a clip that it's not supposed to + if (new_track->ContainsClip(clip.get())) { + old_track->AddClip(clip); + } + + done = false; } void MoveClipAction::doRedo() { - if (relative) { - clip->set_timeline_in(clip->timeline_in() + new_in); - clip->set_timeline_out(clip->timeline_out() + new_out); - clip->set_clip_in(clip->clip_in() + new_clip_in); - clip->set_track(clip->track() + new_track); - } else { - clip->set_timeline_in(new_in); - clip->set_timeline_out(new_out); - clip->set_clip_in(new_clip_in); - clip->set_track(new_track); + if (!done) { + if (relative) { + clip->set_timeline_in(clip->timeline_in() + new_in); + clip->set_timeline_out(clip->timeline_out() + new_out); + clip->set_clip_in(clip->clip_in() + new_clip_in); + } else { + clip->set_timeline_in(new_in); + clip->set_timeline_out(new_out); + clip->set_clip_in(new_clip_in); + } + + // Move clip to the new track ONLY IF the old track currently contains this clip - a workaround to ensure this + // action doesn't accidentaly add a clip that it's not supposed to + if (old_track->ContainsClip(clip.get())) { + new_track->AddClip(clip); + } + + done = true; } } -DeleteClipAction::DeleteClipAction(Sequence *s, int clip) { - seq = s; - index = clip; - opening_transition = -1; - closing_transition = -1; -} +DeleteClipAction::DeleteClipAction(Clip *clip) : + done_(false) +{ + // Get shared_ptr object to take ownership of this Clip -DeleteClipAction::~DeleteClipAction() {} + clip_ = clip->track()->GetClipObjectFromRawPtr(clip); + doRedo(); +} void DeleteClipAction::doUndo() { - // restore ref to clip - seq->clips[index] = ref; + // restore clip to this track + clip_->track()->AddClip(clip_); // restore links to this clip - for (int i=linkClipIndex.size()-1;i>=0;i--) { - seq->clips.at(linkClipIndex.at(i))->linked.insert(linkLinkIndex.at(i), index); + for (int i=0;ilinked.append(clip_.get()); } - ref = nullptr; + done_ = false; } void DeleteClipAction::doRedo() { - // remove ref to clip - ref = seq->clips.at(index); - if (ref->IsOpen()) { - ref->Close(true); - } - seq->clips[index] = nullptr; + if (!done_) { + + // remove ref to clip + if (clip_->IsOpen()) { + clip_->Close(true); + } + + clip_->track()->RemoveClip(clip_.get()); + + // delete link to this clip + QVector clips = clip_->track()->sequence()->GetAllClips(); + for (int i=0;iclips.size();i++) { - ClipPtr c = seq->clips.at(i); - if (c != nullptr) { for (int j=0;jlinked.size();j++) { - if (c->linked.at(j) == index) { - linkClipIndex.append(i); - linkLinkIndex.append(j); + if (c->linked.at(j) == clip_.get()) { c->linked.removeAt(j); + clips_linked_to_this_one_.append(c); + break; } } } + + done_ = true; + } } -ChangeSequenceAction::ChangeSequenceAction(SequencePtr s) { - new_sequence = s; -} - -void ChangeSequenceAction::doUndo() { - olive::Global->set_sequence(old_sequence); -} - -void ChangeSequenceAction::doRedo() { - old_sequence = olive::ActiveSequence; - olive::Global->set_sequence(new_sequence); -} - SetTimelineInOutCommand::SetTimelineInOutCommand(Sequence* s, bool enabled, long in, long out) { seq = s; new_enabled = enabled; @@ -159,7 +164,7 @@ void SetTimelineInOutCommand::doUndo() { // footage viewer functions if (seq->wrapper_sequence) { - Footage* m = seq->clips.at(0)->media()->to_footage(); + Footage* m = seq->GetAllClips().first()->media()->to_footage(); m->using_inout = old_enabled; m->in = old_in; m->out = old_out; @@ -177,14 +182,14 @@ void SetTimelineInOutCommand::doRedo() { // footage viewer functions if (seq->wrapper_sequence) { - Footage* m = seq->clips.at(0)->media()->to_footage(); + Footage* m = seq->GetAllClips().first()->media()->to_footage(); m->using_inout = new_enabled; m->in = new_in; m->out = new_out; } } -AddEffectCommand::AddEffectCommand(Clip* c, EffectPtr e, const EffectMeta *m, int insert_pos) { +AddEffectCommand::AddEffectCommand(Clip* c, NodePtr e, NodeType m, int insert_pos) { clip = c; ref = e; meta = m; @@ -202,7 +207,7 @@ void AddEffectCommand::doUndo() { void AddEffectCommand::doRedo() { if (ref == nullptr) { - ref = Effect::Create(clip, meta); + ref = olive::node_library[meta]->Create(clip); } if (pos < 0) { clip->effects.append(ref); @@ -214,7 +219,7 @@ void AddEffectCommand::doRedo() { AddTransitionCommand::AddTransitionCommand(Clip* iopen, Clip* iclose, TransitionPtr copy, - const EffectMeta *itransition, + NodeType itransition, int ilength) { open_ = iopen; close_ = iclose; @@ -246,9 +251,10 @@ void AddTransitionCommand::doRedo() { // create new transition object if (new_transition_ref_ == nullptr) { if (transition_to_copy_ == nullptr) { - new_transition_ref_ = Transition::CreateFromMeta(primary, secondary, transition_meta_); + new_transition_ref_ = std::static_pointer_cast(olive::node_library[transition_meta_]->Create(primary)); + new_transition_ref_->secondary_clip = secondary; } else { - new_transition_ref_ = transition_to_copy_->copy(primary, nullptr); + new_transition_ref_ = std::static_pointer_cast(transition_to_copy_->copy(primary)); } } @@ -287,8 +293,9 @@ void ModifyTransitionCommand::doRedo() { transition_ref_->set_length(new_length_); } -DeleteTransitionCommand::DeleteTransitionCommand(TransitionPtr t) { - transition_ref_ = t; +DeleteTransitionCommand::DeleteTransitionCommand(TransitionPtr t) : + transition_ref_(t) +{ } void DeleteTransitionCommand::doUndo() { @@ -348,90 +355,86 @@ void DeleteMediaCommand::doRedo() { olive::project_model.removeChild(parent, item.get()); } -AddClipCommand::AddClipCommand(Sequence *s, QVector& add) { - link_offset_ = 0; - seq = s; - clips = add; +AddClipCommand::AddClipCommand(const QVector &add) : + clips_(add), + done_(false) +{ + doRedo(); } -AddClipCommand::~AddClipCommand() {} - void AddClipCommand::doUndo() { // clear effects panel panel_graph_editor->set_row(nullptr); panel_effect_controls->Clear(true); - for (int i=0;iclips.last(); + for (int i=0;ilinked.size();j++) { - c->linked[j] -= link_offset_; - } + + c->track()->RemoveClip(c.get()); // deselect the area occupied by this clip - panel_timeline->deselect_area(c->timeline_in(), c->timeline_out(), c->track()); + c->track()->DeselectArea(c->timeline_in(), c->timeline_out()); // if the clip is open, close it if (c->IsOpen()) { c->Close(true); } } - - // remove it from the sequence - seq->clips.removeLast(); } + done_ = false; } void AddClipCommand::doRedo() { - link_offset_ = seq->clips.size(); - for (int i=0;ilinked.size();j++) { - original->linked[j] += link_offset_; - } + if (!done_) { + for (int i=0;itrack()->AddClip(original); + } } - - seq->clips.append(original); + done_ = true; } } -LinkCommand::LinkCommand() { - link = true; +LinkCommand::LinkCommand(const QVector& clips, bool link) : + clips_(clips), + link_(link) +{ } void LinkCommand::doUndo() { - for (int i=0;iclips.at(clips.at(i)); - if (link) { + for (int i=0;ilinked.clear(); } else { - c->linked = old_links.at(i); + c->linked = old_links_.at(i); } } } void LinkCommand::doRedo() { - old_links.clear(); - for (int i=0;iclips.at(clips.at(i)); - if (link) { - for (int j=0;jlinked.append(clips.at(j)); + c->linked.append(clips_.at(j)); } } + } else { - old_links.append(c->linked); + + old_links_.append(c->linked); c->linked.clear(); + } } } @@ -464,12 +467,14 @@ ReplaceMediaCommand::ReplaceMediaCommand(MediaPtr i, QString s) { void ReplaceMediaCommand::replace(QString& filename) { // close any clips currently using this media - QVector all_sequences = panel_project->list_all_project_sequences(); + QVector all_sequences = olive::project_model.GetAllSequences(); for (int i=0;ito_sequence().get(); - for (int j=0;jclips.size();j++) { - ClipPtr c = s->clips.at(j); - if (c != nullptr && c->media() == item.get() && c->IsOpen()) { + + QVector sequence_clips = all_sequences.at(i)->to_sequence()->GetAllClips(); + + for (int j=0;jmedia() == item.get() && c->IsOpen()) { c->Close(true); c->replaced = true; } @@ -479,7 +484,7 @@ void ReplaceMediaCommand::replace(QString& filename) { // replace media QStringList files; files.append(filename); - panel_project->process_file_list(files, false, item, nullptr); + olive::project_model.process_file_list(files, false, item, nullptr); PreviewGenerator::AnalyzeMedia(item.get()); } @@ -505,7 +510,7 @@ void ReplaceClipMediaCommand::replace(bool undo) { } for (int i=0;iIsOpen()) { c->Close(true); } @@ -540,7 +545,7 @@ void ReplaceClipMediaCommand::doRedo() { update_ui(true); } -EffectDeleteCommand::EffectDeleteCommand(Effect *e) : +EffectDeleteCommand::EffectDeleteCommand(Node *e) : effect_(e) {} @@ -675,13 +680,13 @@ void SetClipProperty::MainLoop(bool undo) void SetClipProperty::doUndo() { MainLoop(true); - panel_sequence_viewer->viewer_widget->frame_update(); + panel_sequence_viewer->viewer_widget()->frame_update(); } void SetClipProperty::doRedo() { MainLoop(false); - panel_sequence_viewer->viewer_widget->frame_update(); + panel_sequence_viewer->viewer_widget()->frame_update(); } AddMarkerAction::AddMarkerAction(QVector* m, long t, QString n) { @@ -798,21 +803,22 @@ void SetBool::doRedo() { *boolean = new_setting; } -SetSelectionsCommand::SetSelectionsCommand(Sequence *s) { - seq = s; - done = true; +SetSelectionsCommand::SetSelectionsCommand(Sequence *s, + const QVector &old_data, + const QVector &new_data) : + seq_(s), + old_data_(old_data), + new_data_(new_data) +{ + } void SetSelectionsCommand::doUndo() { - seq->selections = old_data; - done = false; + seq_->SetSelections(old_data_); } void SetSelectionsCommand::doRedo() { - if (!done) { - seq->selections = new_data; - done = true; - } + seq_->SetSelections(new_data_); } EditSequenceCommand::EditSequenceCommand(Media* i, SequencePtr s) { @@ -850,15 +856,12 @@ void EditSequenceCommand::update() { // Update sequence's tooltip item->update_tooltip(); - for (int i=0;iclips.size();i++) { - if (seq->clips.at(i) != nullptr) { - seq->clips.at(i)->refresh(); + QVector all_clips = seq->GetAllClips(); + for (int i=0;irefresh(); } } - - if (olive::ActiveSequence == seq) { - olive::Global->set_sequence(seq); - } } SetInt::SetInt(int* pointer, int new_value) { @@ -896,7 +899,11 @@ void CloseAllClipsCommand::doUndo() { } void CloseAllClipsCommand::doRedo() { - close_active_clips(olive::ActiveSequence.get()); + QVector sequences = olive::project_model.GetAllSequences(); + + for (int i=0;ito_sequence()->Close(); + } } UpdateFootageTooltip::UpdateFootageTooltip(Media *i) { @@ -930,13 +937,13 @@ RemoveClipsFromClipboard::RemoveClipsFromClipboard(int index) { RemoveClipsFromClipboard::~RemoveClipsFromClipboard() {} void RemoveClipsFromClipboard::doUndo() { - clipboard.insert(pos, clip); + olive::clipboard.Insert(pos, clip); done = false; } void RemoveClipsFromClipboard::doRedo() { - clip = std::static_pointer_cast(clipboard.at(pos)); - clipboard.removeAt(pos); + clip = std::static_pointer_cast(olive::clipboard.Get(pos)); + olive::clipboard.RemoveAt(pos); done = true; } @@ -977,7 +984,7 @@ void ReloadEffectsCommand::doRedo() { panel_effect_controls->Reload(); } -RippleAction::RippleAction(Sequence *is, long ipoint, long ilength, const QVector &iignore) : +RippleAction::RippleAction(Sequence *is, long ipoint, long ilength, const QVector &iignore) : s(is), point(ipoint), length(ilength), @@ -992,13 +999,21 @@ void RippleAction::doUndo() { void RippleAction::doRedo() { ca = new ComboAction(); - for (int i=0;iclips.size();i++) { - if (!ignore.contains(i)) { - ClipPtr c = s->clips.at(i); - if (c != nullptr) { - if (c->timeline_in() >= point) { - c->move(ca, length, length, 0, 0, true, true); - } + + QVector all_clips = s->GetAllClips(); + + for (int i=0;itimeline_in() >= point) { + s->MoveClip(c, + ca, + length, + length, + 0, + c->track(), + true, + true); } } } @@ -1084,8 +1099,9 @@ void SetIsKeyframing::doRedo() { row->SetKeyframingInternal(b); } -RefreshClips::RefreshClips(Media *m) { - media = m; +RefreshClips::RefreshClips(Media *m) : + media(m) +{ } void RefreshClips::doUndo() { @@ -1094,12 +1110,14 @@ void RefreshClips::doUndo() { void RefreshClips::doRedo() { // close any clips currently using this media - QVector all_sequences = panel_project->list_all_project_sequences(); + QVector all_sequences = olive::project_model.GetAllSequences(); for (int i=0;ito_sequence().get(); - for (int j=0;jclips.size();j++) { - Clip* c = s->clips.at(j).get(); - if (c != nullptr && c->media() == media) { + + QVector sequence_clips = all_sequences.at(i)->to_sequence().get()->GetAllClips(); + + for (int j=0;jmedia() == media || media == nullptr) { c->replaced = true; c->refresh(); } @@ -1112,10 +1130,10 @@ void UpdateViewer::doUndo() { } void UpdateViewer::doRedo() { - panel_sequence_viewer->viewer_widget->frame_update(); + panel_sequence_viewer->viewer_widget()->frame_update(); } -SetEffectData::SetEffectData(Effect *e, const QByteArray &s) { +SetEffectData::SetEffectData(Node *e, const QByteArray &s) { effect = e; data = s; } diff --git a/undo/undo.h b/undo/undo.h index b98ce7ea6..9ce89d493 100644 --- a/undo/undo.h +++ b/undo/undo.h @@ -1,20 +1,20 @@ /*** - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . ***/ @@ -30,6 +30,7 @@ #include "comboaction.h" +#include "nodes/node.h" #include "timeline/marker.h" #include "timeline/selection.h" #include "effects/keyframe.h" @@ -43,13 +44,12 @@ using SequencePtr = std::shared_ptr; class Media; using MediaPtr = std::shared_ptr; -class Effect; -using EffectPtr = std::shared_ptr; +class Node; +using NodePtr = std::shared_ptr; class Transition; using TransitionPtr = std::shared_ptr; -struct EffectMeta; class EffectRow; class EffectField; @@ -77,89 +77,74 @@ private: class MoveClipAction : public OliveAction { public: - MoveClipAction(Clip* c, long iin, long iout, long iclip_in, int itrack, bool irelative); + MoveClipAction(ClipPtr c, long iin, long iout, long iclip_in, Track* itrack, bool irelative); virtual void doUndo() override; virtual void doRedo() override; private: - Clip* clip; + ClipPtr clip; long old_in; long old_out; long old_clip_in; - int old_track; + Track* old_track; long new_in; long new_out; long new_clip_in; - int new_track; + Track* new_track; bool relative; + + bool done; }; class RippleAction : public OliveAction { public: - RippleAction(Sequence* is, long ipoint, long ilength, const QVector& iignore); + RippleAction(Sequence* is, long ipoint, long ilength, const QVector &iignore); virtual void doUndo() override; virtual void doRedo() override; private: Sequence* s; long point; long length; - QVector ignore; + QVector ignore; ComboAction* ca; }; class DeleteClipAction : public OliveAction { public: - DeleteClipAction(Sequence* s, int clip); - virtual ~DeleteClipAction() override; + DeleteClipAction(Clip* clip); virtual void doUndo() override; virtual void doRedo() override; private: - Sequence* seq; - ClipPtr ref; - int index; - - int opening_transition; - int closing_transition; - - QVector linkClipIndex; - QVector linkLinkIndex; -}; - -class ChangeSequenceAction : public OliveAction { -public: - ChangeSequenceAction(SequencePtr s); - virtual void doUndo() override; - virtual void doRedo() override; -private: - SequencePtr old_sequence; - SequencePtr new_sequence; + ClipPtr clip_; + QVector clips_linked_to_this_one_; + bool done_; }; class AddEffectCommand : public OliveAction { public: - AddEffectCommand(Clip* c, EffectPtr e, const EffectMeta* m, int insert_pos = -1); + AddEffectCommand(Clip* c, NodePtr e, NodeType m, int insert_pos = -1); virtual void doUndo() override; virtual void doRedo() override; private: Clip* clip; - const EffectMeta* meta; - EffectPtr ref; + NodeType meta; + NodePtr ref; int pos; bool done; }; class AddTransitionCommand : public OliveAction { public: - AddTransitionCommand(Clip* iopen, Clip* iclose, TransitionPtr copy, const EffectMeta* itransition, int ilength); + AddTransitionCommand(Clip* iopen, Clip* iclose, TransitionPtr copy, NodeType itransition, int ilength); virtual void doUndo() override; virtual void doRedo() override; private: Clip* open_; Clip* close_; TransitionPtr transition_to_copy_; - const EffectMeta* transition_meta_; + NodeType transition_meta_; int length_; TransitionPtr old_open_transition_; TransitionPtr old_close_transition_; @@ -228,26 +213,24 @@ private: class AddClipCommand : public OliveAction { public: - AddClipCommand(Sequence* s, QVector& add); - virtual ~AddClipCommand() override; + AddClipCommand(const QVector& add); virtual void doUndo() override; virtual void doRedo() override; private: - Sequence* seq; - QVector clips; - int link_offset_; + Track* track_; + QVector clips_; + bool done_; }; class LinkCommand : public OliveAction { public: - LinkCommand(); + LinkCommand(const QVector &clips, bool link); virtual void doUndo() override; virtual void doRedo() override; - Sequence* s; - QVector clips; - bool link; private: - QVector< QVector > old_links; + QVector clips_; + bool link_; + QVector< QVector > old_links_; }; class CheckboxCommand : public OliveAction { @@ -279,7 +262,7 @@ public: ReplaceClipMediaCommand(Media *, Media *, bool); virtual void doUndo() override; virtual void doRedo() override; - QVector clips; + QVector clips; private: Media* old_media; Media* new_media; @@ -290,12 +273,12 @@ private: class EffectDeleteCommand : public OliveAction { public: - EffectDeleteCommand(Effect* e); + EffectDeleteCommand(Node* e); virtual void doUndo() override; virtual void doRedo() override; private: - Effect* effect_; - EffectPtr deleted_obj_; + Node* effect_; + NodePtr deleted_obj_; Clip* parent_clip_; int index_; }; @@ -428,14 +411,13 @@ private: class SetSelectionsCommand : public OliveAction { public: - SetSelectionsCommand(Sequence* s); + SetSelectionsCommand(Sequence* s, const QVector& old_data, const QVector& new_data); virtual void doUndo() override; virtual void doRedo() override; - QVector old_data; - QVector new_data; private: - Sequence* seq; - bool done; + QVector old_data_; + QVector new_data_; + Sequence* seq_; }; class EditSequenceCommand : public OliveAction { @@ -611,11 +593,11 @@ public: class SetEffectData : public OliveAction { public: - SetEffectData(Effect* e, const QByteArray &s); + SetEffectData(Node* e, const QByteArray &s); virtual void doUndo() override; virtual void doRedo() override; private: - Effect* effect; + Node* effect; QByteArray data; QByteArray old_data; }; diff --git a/undo/undostack.cpp b/undo/undostack.cpp index 2edc0f0b1..7fa229777 100644 --- a/undo/undostack.cpp +++ b/undo/undostack.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "undostack.h" -QUndoStack olive::UndoStack; +QUndoStack olive::undo_stack; diff --git a/undo/undostack.h b/undo/undostack.h index 9d42c4583..35bc92584 100644 --- a/undo/undostack.h +++ b/undo/undostack.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef UNDOSTACK_H #define UNDOSTACK_H @@ -7,7 +27,7 @@ namespace olive { /** * @brief Global undo stack object */ -extern QUndoStack UndoStack; +extern QUndoStack undo_stack; } #endif // UNDOSTACK_H